The aggregate-return refusal cannot fire, and the reason is in check.ml

Item 17 left this as a loose end: flan_vec_as_slice returns a slice by value
and should hit the "aggregate return" refusal, bounds-condition.flan exercises
it in and out of bounds and matches, and nobody traced why.

It is the first of the two possibilities that report named — the refusal is
narrower than it reads, and nothing is going right by accident.
flan_vec_as_slice's Flan-level return type is Unit. check.ml builds it as
[rt loc Types.Unit "flan_vec_as_slice"] and flan_rt.c writes the two words
through a [void *out] parameter, so [is_void rty] answers first and the
[is_agg rty] test below it is never reached.

That is not one symbol's accident, it is the convention. Every aggregate-valued
runtime result crosses through an out-pointer the checker allocates; every
other [rt] builder in check.ml answers Unit, an Int, a Ptr, an Alloc or a
Handle. And the other user of this path, a [declare]d C function, is covered by
[crossable], which admits String and Slice only as a parameter and refuses an
aggregate return outright.

So there is no sret convention to build for Rt, and building one would be worse
than the refusal: the C boundary wants SysV classification — a 16-byte slice
comes back in rax:rdx — and not the hidden-pointer convention this backend uses
internally. There is no classifier in the file and nothing to test one against.
The line stays as a guard against those two rules changing, and now says which
rules and what the work would actually be.

With it goes the rest of item 16's claim that the container runtime is
unexercised. It is: Vec and Map through vec.flan, vec-of-vec.flan, maps.flan
and map-iter.flan, and Pool through registry.flan, handles.flan,
generics.flan and pool-stale-region.flan. All match.
This commit is contained in:
Joseph Ferano 2026-09-13 20:36:56 +07:00
parent bdecd2b8f2
commit 8ff8a71de8

View File

@ -1776,7 +1776,29 @@ and call_native f ~sym ?(chan = false) ~(args : Tast.expr list) ~rty dst =
call_sym f.b sym;
if chan then guard f;
if not (is_void rty) then begin
if is_agg rty then unsupported "aggregate return from %s" sym;
(* Unreachable, and it is worth saying why rather than leaving it reading
like a gap in the backend. Nothing that crosses this boundary returns an
aggregate, by two rules that both live in [check.ml]:
- every aggregate-valued runtime result comes back through an
*out-pointer* the checker allocates, so the Flan-level return type is
[Unit] or a scalar. [flan_vec_as_slice] is the one that looks like a
counter-example and is not: [check.ml] builds it as [rt loc
Types.Unit] and [flan_rt.c] writes the two words through [void *out].
Every other [rt] builder in the file answers [Unit], an [Int], a
[Ptr], an [Alloc] or a [Handle].
- [crossable], which admits [String] and [Slice _] only as "a
parameter" and refuses an aggregate return from a [declare] outright.
So this is a guard against those two rules changing, and not a feature
waiting to be written. If one ever does change, the work it names is
*SysV classification* and not the internal convention in the header: C
returns a 16-byte slice in rax:rdx, and there is no classifier in this
file. Refusing is the honest answer until there is. *)
if is_agg rty then
unsupported
"%s returns %s by value, which needs SysV return classification this \
backend does not have" sym (Types.to_string rty);
store_loc f ~reg:(if is_float rty then xmm0 else rax) dst rty
end