flan/test/programs/dyn-boundary.flan
Joseph Ferano 7c7586ebc6 --no-gc is a pass, not a flag the emitter can see
The promise is that this program carries no collector, and the way to keep it is
to refuse every dyn rather than to emit a different program: a dyn value is one
the runtime allocates and the collector owns, and there is no smaller version to
fall back to. So it runs between checking and emission, answers unit or raises,
and hands the very same program on. Emit has no field to branch on and is told
nothing.

That is what makes the byte-identity claim true rather than approximate, and it
is tested by compiling three annotated programs twice and comparing the text. A
field, a mode, or a comment that mentioned the flag would break it on something
incidental, a long way from anything to do with dyn.

Every site is named, the way the global cycle refusal names the whole ring: a
reader who has to annotate their program wants the list, not the first one and
then another compile. Globals and signatures as well as body values -- the two
files it is tested against report nine sites each, and the floors are set under
that so an added line does not fail the test and a pass that named one site and
stopped would.

The four programs run at -O2 and -O0. dyn-boundary is asserted on its exit
status as well as its output, because the boundary is only interesting in that
it can fail and a test that showed it working would be testing the easy half.
The x86 survey skips them by name: a REFUSED there means a node that backend has
stopped lowering, which is a regression, and this is the opposite -- a lane that
has not started. Take a name off llvmonly when the lowering arrives and the
survey will say whether it works. 128 match, 0 differ, 0 refused.

Checked while writing these: a dyn function with an early return pops its roots
on both paths, and one with a defer pops on the transfer path too.
2026-09-19 06:33:28 +07:00

43 lines
1.4 KiB
Plaintext

;;;; The boundary in both directions, and the trap when a claim is wrong.
;;;;
;;;; Typed to dyn is implicit: take-dyn is called with an i64 and the boxing is
;;;; written nowhere. Dyn to typed is not: take-i64's parameter says i64, and
;;;; that annotation is the whole of why the unboxing is allowed to happen —
;;;; and the whole of why it may fail, which the last line of main proves by
;;;; handing it a float.
;;;;
;;;; A let carries no type in this language, so the annotation sites a dyn can
;;;; be unboxed at are the ones that do: a parameter, a return type, and a
;;;; global's declared type. All three are here.
(defvar seven i64 7)
(defvar boxed dyn 21)
;; The other direction at a global: a dyn initialiser meeting a written type.
(defvar unboxed i64 boxed)
(defn take-dyn [d dyn] dyn
(+ d 100))
(defn take-i64 [n i64] i64
(* n 2))
(defn identity-dyn [d] dyn d)
;; A dyn value answered at a written return type, which is the third site.
(defn as-i64 [d] i64 d)
(defn main [] ()
;; Typed in: the i64 is boxed at the call with nothing written.
(print (take-dyn seven))
(print "\n")
;; Dyn out: the parameter is typed, so the word is unboxed at the call.
(print (take-i64 boxed))
(print "\n")
(print unboxed)
(print "\n")
(print (as-i64 (identity-dyn 5)))
(print "\n")
;; And the claim that is wrong. The runtime owns the message.
(print (take-i64 (identity-dyn 1.5)))
(print "\n"))