What happened
#630 widened OP_LINE's operand from 16 to 32 bits — a correct fix (line
numbers wrapped at 65536). Its commit message says "no opcode number or
on-disk format changed", which is true and also the wrong frame: the operand
width is the bytecode ABI for anything that produces a chunk and hands it
to vm_run_bytecode.
Two consumers do exactly that, and both broke silently at rc=0:
- ouroboros — the self-hosted codegen emits
OP_LINE + a 2-byte operand.
Under v0.33.0 the VM reads 4 bytes, so it consumes the next instruction's
first two bytes as the high half of the line number and resumes misaligned.
Every behavioral-parity program produced empty output with exit 0. The
bootstrap fixed point still PASSED, because self-host-compiling itself is
byte-comparison, not execution — so the suite's loudest signal stayed green
while every executed program produced nothing.
- iLambdaAi — its grading ladder compiles a candidate with the vendored
ouroboros codegen and runs it through sandbox_run. Every "runs" rung
scored 1 instead of 2: a grader that quietly lost the ability to award its
top grade, which is the exact failure mode that research loop cannot
self-detect.
Minimal repro (from an ouroboros checkout, pre-fix):
load_file of "src/frontend.eigs"
load_file of "src/codegen.eigs"
r is vm_run_bytecode of (ouro_compile of "print of (2 + 3)")
v0.32.0 prints 5. v0.33.0 prints nothing, returns null, exits 0.
Why CI could not catch it
Consumers pin a release. A mid-release operand-width change therefore cannot
fail any consumer's CI until the version bump — by which time the release is
already cut. The pre-bump consumer sweep caught this one; that sweep is a
manual ritual, not a gate.
What would catch it
Options, cheapest first:
- An in-repo external-producer canary. A test that hand-assembles a tiny
chunk (including OP_LINE) and runs it through vm_run_bytecode, asserting
the output. Any operand-width or opcode-numbering change fails it in the
same PR. This is the one that scales — it needs no sibling checkout.
- A bytecode ABI version. Stamp an ABI revision the VM checks when it is
handed a chunk, so a stale producer gets a loud error instead of executing
garbage. Turns a silent-wrong into a named failure even when the canary is
not updated.
- Document the seam. The
eigenscript-extend-vm checklist covers adding
an opcode; it should also cover changing an existing opcode's operand
width, and name vm_run_bytecode's external producers as a site to update.
(1) + (3) are small and I would do both; (2) is the real fix if we intend
vm_run_bytecode to be a supported public seam rather than an ouroboros
private door.
Found during the v0.33.0 pre-bump sweep. Downstream emitter fixes are landing
in ouroboros and iLambdaAi.
What happened
#630 widened
OP_LINE's operand from 16 to 32 bits — a correct fix (linenumbers wrapped at 65536). Its commit message says "no opcode number or
on-disk format changed", which is true and also the wrong frame: the operand
width is the bytecode ABI for anything that produces a chunk and hands it
to
vm_run_bytecode.Two consumers do exactly that, and both broke silently at rc=0:
OP_LINE+ a 2-byte operand.Under v0.33.0 the VM reads 4 bytes, so it consumes the next instruction's
first two bytes as the high half of the line number and resumes misaligned.
Every behavioral-parity program produced empty output with exit 0. The
bootstrap fixed point still PASSED, because self-host-compiling itself is
byte-comparison, not execution — so the suite's loudest signal stayed green
while every executed program produced nothing.
ouroboros codegen and runs it through
sandbox_run. Every "runs" rungscored 1 instead of 2: a grader that quietly lost the ability to award its
top grade, which is the exact failure mode that research loop cannot
self-detect.
Minimal repro (from an ouroboros checkout, pre-fix):
v0.32.0 prints
5. v0.33.0 prints nothing, returns null, exits 0.Why CI could not catch it
Consumers pin a release. A mid-release operand-width change therefore cannot
fail any consumer's CI until the version bump — by which time the release is
already cut. The pre-bump consumer sweep caught this one; that sweep is a
manual ritual, not a gate.
What would catch it
Options, cheapest first:
chunk (including
OP_LINE) and runs it throughvm_run_bytecode, assertingthe output. Any operand-width or opcode-numbering change fails it in the
same PR. This is the one that scales — it needs no sibling checkout.
handed a chunk, so a stale producer gets a loud error instead of executing
garbage. Turns a silent-wrong into a named failure even when the canary is
not updated.
eigenscript-extend-vmchecklist covers addingan opcode; it should also cover changing an existing opcode's operand
width, and name
vm_run_bytecode's external producers as a site to update.(1) + (3) are small and I would do both; (2) is the real fix if we intend
vm_run_bytecodeto be a supported public seam rather than an ouroborosprivate door.
Found during the v0.33.0 pre-bump sweep. Downstream emitter fixes are landing
in ouroboros and iLambdaAi.