The tagged sum is Tast.data now and Tast.unions is C's untagged union, which
arrives as a structure whose every field sits at offset zero. That is a
layout, and a layout is the one thing an object has none of here -- two
properties cannot share a run of bytes -- so it is refused with the type's own
name rather than met as "not a struct".
Two hardenings the sweep could not have found. $str built a view onto node's
Buffer pool, where small Buffers share one ArrayBuffer, so a write through one
string literal's slice could have reached another's bytes; native puts a
literal in .rodata where that write is a fault. And copy_of's Option arm named
its argument three times, which evaluates it three times.
SURVEY_FLAGS reaches both sides now, as it does in the x86 sweep, so the
unchecked indexing path -- a bare a[i] with no $at around it, which nothing
had ever run -- is swept too: 23 match, 0 differ.
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.
lib/js.ml lowers the same checked Tast the other two backends take to one
CommonJS file, by object mapping rather than linear memory: docs/DISCUSS.md
item 5 settled that fork before this was written, and item 5's consequence is
the whole shape of the file. Object mapping means the host's collector owns
every value, so there is no (Ptr T), no free, no arena and no allocator, and a
program that uses one is refused by name with a location rather than compiled
badly.
flan build --target=js leaves Build.executable through its own two lines,
before anything that assumes a clang: there is no object to compile and no
linker to run. --dev, --debug, --sanitize and --x86 are refused there rather
than swallowed. Js.Unsupported exits 3 beside X86.Unsupported, so a sweep can
count refused-by-name apart from did-not-compile.
What runs end to end: integer and float arithmetic with the normalisation each
width needs, let, if, while with break and continue, calls, function values,
structs, fixed arrays, slices, unions, options, match, and println through the
same structural printer the other backends walk.
Value semantics is the trap the object mapping sets and the reason the header
carries a section on it. A Flan struct and a fixed array copy on assignment and
a JS object does not, so every site emit.ml memcpys emits a generated
Point$copy here. Fable's JS backend faces the same question for F# structs and
answers it the other way -- it inserts no clone, and its Rust backend does --
so the divergence is deliberate and the survey pins it.