spike/js/survey.sh is the x86 sweep's shape with one deliberate difference in what it counts. That backend is behind, so a refusal there is a regression and its strict mode fails on one. This is a dialect, so a refusal is the design working -- a pointer, an allocator, a Map, the FFI and conditions are refused permanently and correctly. What fails the @js alias is a DIFFER, which is a wrong answer, and a CRASH, which is JS this backend emitted and node would not run. Two probes carry the decisions the corpus does not reach. p1-int-semantics prints wrapping at all eight widths, a multiply past 2^53, truncating division with a negative operand, shifts whose count is out of range, bitwise over a u32, f32 that is not a double, and the conversions both ways -- 35 lines, all identical to the LLVM build. It found two real bugs: >>> binds tighter than & in JavaScript, so a bit-and on a u32 answered -1; and a 64-bit value through Number() rounds to 53 bits before it can be truncated, so (i32 i64hi) answered 0 where it must answer -1. p2-value-copies goes past values.flan to the cases a shallow copy would pass: a struct inside a struct, a struct returned out of a function, an element read out of an array of structs, and a global. Also fixed, and all three were found by the sweep rather than by reading: a unit-typed call in statement position was compiled to an expression nobody emitted, so (load-xs) silently did not happen; an arrow body that starts with a brace is a block, so a zeroed array of structs was a syntax error; and a bounds message must carry the index expression's location, not the form's, because that is the one emit.ml passes to check_at. Render reads an Option's tag as field 0 and a union's as field 0, which is the LLVM layout and not this one, so both are answered here rather than refused. fdefers is dropped rather than refused: nothing in the dialect can start a transfer, so the transfer exit path is unreachable, and refusing it would have refused every program that writes a plain defer.
88 lines
3.7 KiB
Plaintext
88 lines
3.7 KiB
Plaintext
;;;; The evidence for lib/js.ml's value-semantics decision.
|
|
;;;;
|
|
;;;; A Flan struct and a fixed array are values: binding one copies it, and
|
|
;;;; emit.ml spends a memcpy at every such site. A JS object assigns by
|
|
;;;; reference, so an object mapping that does nothing aliases where the LLVM
|
|
;;;; build copied — and the divergence is invisible until something mutates a
|
|
;;;; copy, which is what every line below does.
|
|
;;;;
|
|
;;;; test/programs/values.flan already pins the two simplest cases. This goes
|
|
;;;; past them, to the ones a shallow copy would pass and a wrong one would
|
|
;;;; not: a struct inside a struct, a struct returned out of a function, an
|
|
;;;; element read out of an array of structs, and a global.
|
|
;;;;
|
|
;;;; Fable answers the same question the other way for F# structs on its JS
|
|
;;;; backend — it inserts no clone at all, and its Rust backend does — so this
|
|
;;;; is the property that says which of the two this dialect chose.
|
|
|
|
(defstruct Point [x i32 y i32])
|
|
(defstruct Box [lo Point hi Point n i32])
|
|
|
|
(defvar gp Point)
|
|
|
|
;; A parameter is a copy: writing to it must not reach the caller's value.
|
|
(defn bump [p Point] Point
|
|
(set (.x p) (+ (.x p) 100))
|
|
p)
|
|
|
|
;; A returned struct is a copy of whatever it names, not a second name for it.
|
|
(defn origin-of [b Box] Point
|
|
(.lo b))
|
|
|
|
(defn show [p Point] ()
|
|
(print (.x p)) (print " ") (println (.y p)))
|
|
|
|
(defn main [] i32
|
|
;; ── A let binding copies ────────────────────────────────────────
|
|
(let [a (Point {.x 1 .y 2})
|
|
b a]
|
|
(set (.x b) 99)
|
|
(show a) ; 1 2 — a must not have moved
|
|
(show b)) ; 99 2
|
|
|
|
;; ── A parameter copies ──────────────────────────────────────────
|
|
(let [a (Point {.x 1 .y 2})
|
|
c (bump a)]
|
|
(show a) ; 1 2
|
|
(show c)) ; 101 2
|
|
|
|
;; ── A nested struct copies with its container ───────────────────
|
|
(let [bx (Box {.lo (Point {.x 1 .y 1}) .hi (Point {.x 9 .y 9}) .n 3})
|
|
cy bx]
|
|
(set (.x (.lo cy)) 42)
|
|
(set (.n cy) 7)
|
|
(print (.x (.lo bx))) (print " ") (println (.n bx)) ; 1 3
|
|
(print (.x (.lo cy))) (print " ") (println (.n cy))) ; 42 7
|
|
|
|
;; ── A field read is a copy, not a view ──────────────────────────
|
|
(let [bx (Box {.lo (Point {.x 1 .y 1}) .hi (Point {.x 9 .y 9}) .n 3})
|
|
p (origin-of bx)]
|
|
(set (.x p) 55)
|
|
(print (.x (.lo bx))) (print " ") (println (.x p))) ; 1 55
|
|
|
|
;; ── A global copies both ways ───────────────────────────────────
|
|
(set gp (Point {.x 4 .y 5}))
|
|
(let [g gp]
|
|
(set (.x g) 0)
|
|
(show gp) ; 4 5
|
|
(show g)) ; 0 5
|
|
|
|
;; ── A fixed array is a value too ────────────────────────────────
|
|
(let [xs [1 2 3]
|
|
ys xs]
|
|
(set (at ys 0) 77)
|
|
(print (at xs 0)) (print " ") (println (at ys 0))) ; 1 77
|
|
|
|
;; ── An array of structs copies its elements ─────────────────────
|
|
;; An element read is a value too, so mutating what came out of one must
|
|
;; reach neither array. A shallow copy of the outer array would leave both
|
|
;; naming the same element object, and this would print 8 8 8.
|
|
(let [ps [(Point {.x 1 .y 1}) (Point {.x 2 .y 2})]
|
|
qs ps
|
|
e (at qs 0)]
|
|
(set (.x e) 8)
|
|
(print (.x (at ps 0))) (print " ")
|
|
(print (.x (at qs 0))) (print " ")
|
|
(println (.x e))) ; 1 1 8
|
|
0)
|