From 8ff8a71de87fdc32c48894e40058d3554acc2678 Mon Sep 17 00:00:00 2001 From: Joseph Ferano Date: Sun, 13 Sep 2026 20:36:56 +0700 Subject: [PATCH] The aggregate-return refusal cannot fire, and the reason is in check.ml MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- lib/x86.ml | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/lib/x86.ml b/lib/x86.ml index 07eba99..2a0a5a7 100644 --- a/lib/x86.ml +++ b/lib/x86.ml @@ -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