flan/test/programs/fill.flan
Joseph Ferano 7bd2c99353 sentinel-filled is now dead-beef, and takes the pattern
The author's revision. The name says what it writes, and the pattern is the
program's to choose: (dead-beef) is DEADBEEF, (dead-beef 0xBAADF00D) is
BA AD F0 0D. One byte-order rule covers both — a pattern's ascending bytes
are its big-endian bytes, which is how the hex literal reads left to right —
so every candidate DISCUSS.org listed is now spellable without the compiler
naming any of them.

The bare form is not a case a backend knows about: the checker writes
Tast.dead_beef_default in where the argument would have been, so
(dead-beef) and (dead-beef 0xDEADBEEF) are the same node and an acceptance
row prints both to say so.

The operand is an ordinary u32 expression, which is what the byte arm
already accepts for its byte. A literal is byte-reversed at compile time and
still reaches the loop as an immediate; a computed one is reversed at run
time, by llvm.bswap.i32 on one backend and bswap on the other, after which
the tail shifts its bytes out of the word rather than folding them. The
program runs a computed pattern over lengths 6 and 7 deliberately: that is
the case a constant-only implementation would pass by accident.

filled is untouched, and so is the fill boundary.
2026-09-20 18:33:05 +07:00

145 lines
6.2 KiB
Plaintext

;;;; (filled b) and (dead-beef), the two byte fills, read back as bytes.
;;;;
;;;; The whole point of this program is that every row is a *byte* and not a
;;;; value: the pattern fill's contract is "a hex dump reads the pattern left
;;;; to right", which is a claim about which byte lands at which address, and
;;;; only reading the bytes back in address order can check it. The u32 rows
;;;; are the same claim from the other side — a little-endian load of
;;;; DE AD BE EF is 0xEFBEADDE, which is 4022250974, so a backend that wrote
;;;; the word the other way round would print 3735928559 here and be caught.
;;;;
;;;; The lengths are chosen for the tail. 8 is a whole number of patterns, 9
;;;; ends on DE, 6 ends on DE AD, and 7 ends on DE AD BE — the three truncated
;;;; endings and the one that is not truncated at all.
;;;;
;;;; The rows that matter most are the last group: a pattern that is *not* a
;;;; literal, over a length that is not a multiple of four. That is the case a
;;;; constant-only implementation would pass by accident — the word reaches
;;;; the loop in a register and the tail bytes have to be shifted out of it
;;;; rather than folded, on both backends.
(defstruct Words [a u32 b u32])
;;;; A computed global initialiser: a fill is never a constant the linker can
;;;; write, so this one goes through the startup function on both backends.
(defvar gfill [4 u8] (filled 0x41))
(defvar gsent [5 u8])
(defn bytes4 [b [4 u8]] ()
(dotimes [i 4] (print (at b i)) (print " ")))
;;;; A u32 that no literal rule can see through: (u32 0xBAADF00D) will not do,
;;;; because the cast checks its operand as an i32 first and 0xBAADF00D is not
;;;; one. A parameter's declared type is what makes the literal a u32, and it
;;;; is also what stops the checker folding it at the fill.
(defn u32of [x u32] u32 x)
;;;; The pattern crosses a call boundary, so nothing can fold it at the fill.
(defn beef7 [pat u32] ()
(let [a (array 7 u8)]
(set a (dead-beef pat))
(dotimes [i 7] (print (at a i)) (print " "))
(println "")))
(defn main [] i32
;; One byte, repeated. 0xFF is the -1 fill a debug allocator wants.
(let [a (array 4 u8)]
(set a (filled 0xFF))
(bytes4 a) (println "")) ; 255 255 255 255
;; The byte is an expression, not a literal: this is a memset with the
;; operand in a register, and the register path is the one a constant would
;; otherwise hide.
(let [v (u8 7)
a (array 4 u8)]
(set a (filled v))
(bytes4 a) (println "")) ; 7 7 7 7
;; (filled 0) is (zeroed), byte for byte, and saying so here is what keeps
;; the two from drifting.
(let [a (array 4 u8)]
(set a (filled 0))
(bytes4 a) (println "")) ; 0 0 0 0
;; Four bytes, ascending, with nothing truncated.
(let [a (array 8 u8)]
(set a (dead-beef))
(dotimes [i 8] (print (at a i)) (print " "))
(println "")) ; 222 173 190 239 x2
;; The bare form is the spelled-out default, and this is the row that says
;; so: the same eight bytes from (dead-beef 0xDEADBEEF).
(let [a (array 8 u8)]
(set a (dead-beef 0xDEADBEEF))
(dotimes [i 8] (print (at a i)) (print " "))
(println "")) ; identical to the row above
;; The three truncated tails.
(let [a (array 9 u8)]
(set a (dead-beef))
(dotimes [i 9] (print (at a i)) (print " "))
(println "")) ; ... ends on 222
(let [a (array 6 u8)]
(set a (dead-beef))
(dotimes [i 6] (print (at a i)) (print " "))
(println "")) ; ... ends on 222 173
(let [a (array 7 u8)]
(set a (dead-beef))
(dotimes [i 7] (print (at a i)) (print " "))
(println "")) ; ... ends on 222 173 190
;; A pattern of the program's own choosing, laid down left to right the same
;; way: 0xBAADF00D is BA AD F0 0D, which is 186 173 240 13.
(let [a (array 6 u8)]
(set a (dead-beef 0xBAADF00D))
(dotimes [i 6] (print (at a i)) (print " "))
(println "")) ; 186 173 240 13 186 173
;; The same pattern read as words rather than as bytes, which is what says
;; which end the DE is at.
(let [w (Words {})]
(set w (dead-beef))
(print (.a w)) (print " ") (print (.b w)) (println ""))
(let [w (Words {})]
(set w (dead-beef 0xBAADF00D))
(print (.a w)) (print " ") (print (.b w)) (println ""))
;; A nested aggregate: a fixed array of structs is plain data all the way
;; down, so the fill reaches every byte of it.
(let [g (array 2 Words)]
(set g (filled 0xFF))
(print (.a (at g 0))) (print " ") (print (.b (at g 1))) (println ""))
;; The globals. [gfill] was filled before main ran; [gsent] is filled here.
(dotimes [i 4] (print (at gfill i)) (print " "))
(println "") ; 65 65 65 65
(set gsent (dead-beef))
(dotimes [i 5] (print (at gsent i)) (print " "))
(println "") ; 222 173 190 239 222
;; A fill in value position rather than as the value of a [set]: the same
;; bytes, reached through a temporary instead of through the place.
(bytes4 (filled 0xFF)) (println "") ; 255 255 255 255
;; ── The computed pattern ──────────────────────────────────────────
;; None of these can be folded: the pattern is a parameter, or arithmetic
;; the checker does not evaluate. Seven bytes is one whole pattern and a
;; three-byte tail, so the tail bytes have to come out of the register the
;; word is in rather than out of a constant.
(beef7 0xDEADBEEF) ; 222 173 190 239 222 173 190
(beef7 0xBAADF00D) ; 186 173 240 13 186 173 240
(let [p (u32of 0xBAADF00D)
a (array 6 u8)]
(set a (dead-beef p))
(dotimes [i 6] (print (at a i)) (print " "))
(println "")) ; 186 173 240 13 186 173
;; A pattern that is genuinely computed, not merely held in a slot, and read
;; back as a word so the byte order of the runtime path is pinned too.
(let [p (u32of 0xBAAD0000)
w (Words {})]
(set w (dead-beef (bit-or p (u32of 0xF00D))))
(print (.a w)) (print " ") (print (.b w)) (println ""))
0)