flan/spike/x86/p7-slice-from-ptr.flan
Joseph Ferano bdecd2b8f2 slice-from-ptr on the x86 backend, and the check that has to be signed
Another lane landed (slice-from-ptr p n) while this backend was not looking,
and it arrived as two refusals rather than one: slice-from-ptr.flan and
bounds.flan both stopped building through --x86. Neither is a new obstacle —
a Slice _ is {ptr, i64} here exactly as it is in emit.ml, so the form is one
store of the pointer and one of the length and no new representation at all.

The half worth writing down is the check. There is nothing to compare the
length against — only the caller knows how many elements live behind that
pointer — so what is checked is that the promise is not absurd, and that test
is *signed*. check_slice's own compares are unsigned, and a negative i32
sign-extended to 64 bits is a huge unsigned value that an unsigned "hi <= len"
waves through; the result would be a slice about 2^64 long that reads as a
pass and faults somewhere else entirely.

Nothing in the corpus walks that path: every length in slice-from-ptr.flan is
a literal, and a negative literal is refused by check.ml before any code is
emitted. So spike/x86/p7-slice-from-ptr.flan takes the length as a parameter
and runs it through a restart-case, which puts the condition's low/high/length
on stdout and compares them against the LLVM build.
2026-09-13 20:27:44 +07:00

47 lines
1.7 KiB
Plaintext

;;;; (slice-from-ptr p n) with a length the checker cannot see.
;;;;
;;;; test/programs/slice-from-ptr.flan covers the form itself, but every length
;;;; in it is a literal, and a negative literal is refused by check.ml before
;;;; any code is emitted. So the run-time half of the check -- the one both
;;;; backends plant beside the form -- is walked by nothing in the corpus.
;;;;
;;;; The half that matters here is that the test is *signed*. check_slice's own
;;;; compares are unsigned, and a negative i32 sign-extended to 64 bits is a
;;;; huge unsigned value that an unsigned "hi <= len" waves straight through.
;;;; Getting that wrong yields a slice whose length is about 2^64, which reads
;;;; as a pass and segfaults somewhere else entirely.
;;;;
;;;; Both cases go through a restart-case, so what is compared is the message
;;;; on stderr as well as the fact that something was signalled.
(defvar a [4 i32])
(defn promised [n i32] i32
;; n is a parameter, so the checker has no literal to look at.
(restart-case
(let [s (slice-from-ptr (addr (at a 0)) n)]
(len s))
(give-up [] -1)))
(defn show [name string n i64] ()
(print name)
(print " ")
(println n))
(defn main [] ()
(dotimes [i 4]
(set (at a i) (* (+ i 1) 10)))
(handler-bind
[(BoundsError [c]
(show "low" (.low c))
(show "high" (.high c))
(show "length" (.length c))
(invoke-restart 'give-up))]
(println (promised 4)) ; 4 -- the truth
(println (promised 0)) ; 0 -- empty is not an error
(println (promised 2)) ; 2 -- shorter than the truth is legal
(println (promised -1)) ; -1 -- signalled, and the restart answered
(println (promised -1000000))))