Skip to content

Commit de546e2

Browse files
migrate(C2 wave 2a): VmMulDiv scalar multiply/divide, 4-gate green (#539)
## C2 wave 2a — scalar multiply/divide `Mul` and `Div` turned out to be pure **scalar value-transforms**, not memory opcodes, so they need **no array/linear-memory ABI** — split out of "C2 wave 2" and landed ahead of the memory-bound wave 2b. **`VmMulDiv`** (11 exports) re-decomposed per the brain/senses split — the register-name dict stays host-side, the brain is the scalar-int value transform. Reversibility migrated as headline exports: - `mul_roundtrip` — Bennett ancilla (`c := c + a*b`, inverse `c := c - a*b`), `invert∘execute == id`. - `div_reconstruct` — `q*b + r == a` for **all** `b` (incl. divide-by-zero, where `r` holds the original `a`). The intentional-flaw in-place/simple variants are migrated as value transforms with **no** roundtrip==id claim (the source ships them to demonstrate where in-place multiply / simple divide break reversibility). ### Four gates green | Gate | Result | |---|---| | G1 compile | 1/1 (WASM, validates; 11 exports) | | G2 parity | **3322/3322** (mul reversibility + div reconstruction + every guard) | | G3 boundary | n/a (numeric transform — no value↔integer encoding table) | | G4 assail | 1/1 clean | ### Two new i32 ABI facts surfaced and handled 1. **JS `a*b` loses precision above 2^53** → the parity oracle uses `Math.imul` to stay i32-exact against `i32.mul` (a naive `(a*b)|0` oracle would mismatch at the domain extremes, e.g. `INT_MAX*INT_MAX`). 2. **`i32.div_s` TRAPS on `b==0` and `INT_MIN/-1`** (ReScript wraps the latter), and `i32.rem_s` TRAPS on `b==0` → the brain guards both (nested-`if`, avoiding the unverified `&&` codegen path) so the wasm is a **total** function matching ReScript's observable output — never a trap. Ledger + `migration-map.json` updated: **C2 wave 2a DONE**; **wave 2b** (memory/control opcodes + structural VM) correctly scoped as needing the array/linear-memory ABI (the next real unblocker, also feeds the string wall). Staged-only under `proposals/idaptik/migrated/` — idaptik write-gate honoured. https://claude.ai/code/session_01WoKhFQePiRsAj7aqnxbG8s --- _Generated by [Claude Code](https://claude.ai/code/session_01WoKhFQePiRsAj7aqnxbG8s)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent 956323f commit de546e2

5 files changed

Lines changed: 231 additions & 2 deletions

File tree

proposals/MIGRATION-PLAN.adoc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,8 @@ Heuristic:
192192
| D (C2 wave 1) | DONE | Deep wave 2 (2026-06-05, Opus). Cluster C2 *wave 1* — the reversible VM value-transform opcodes — re-decomposed into *4 kernels* under `proposals/idaptik/migrated/` (VmArith, VmBitwise, VmAncilla, VmInstruction), covering 11 opcodes (Add/Sub/Negate/Noop/Swap/Flip/Xor/Rol/Ror/And/Or) + the 23-opcode taxonomy. Brain = the reversible scalar-int value transform per opcode; the register-name dict stays host-side. *Reversibility (`invert∘execute = id`) pinned as `*_roundtrip` exports.* *Four gates green:* 4/4 compile, 2100/2100 parity (incl. every round-trip, over i32 extremes), 1 echo-boundary LOSSLESS (23-opcode encoding, agda exit 0), 4/4 assail-clean. Evidence: `migrated/EVIDENCE-C2.adoc`. Surfaced 3 compiler facts: unary `~` codegen bug (workaround `-a-1`), arithmetic `>>` + no `>>>` (logical-shift-right emulated). NEXT: C2 wave 2.
193193
| C6+C8 (fan-out) | DONE | Parallel deep wave (2026-06-05): two Sonnet agents migrated clusters C6 (combat/enemy) + C8 (device/network); parent re-verified + consolidated. *16 kernels* staged under `proposals/idaptik/migrated/` — C6: CombatFx, Detection, DifficultyScale, Distraction, DualAlert, HitboxGeom, PlayerHp, SecurityAi; C8: GlobalNetworkData, NetworkManager, SecurityRank, DeviceCaps, LaptopState, NetworkTransfer, PowerManager, CovertLink. *Four gates green (re-run by parent, not just agent-reported):* 16/16 compile, *34280/34280 parity* (C6 8185 + C8 26095, independent oracles), 7 echo-boundary LOSSLESS proofs, 16/16 assail-clean. Re-verification CAUGHT 3 PA-AFF-001 findings the agent reports missed (SecurityAi, SecurityRank, NetworkManager) — fixed with the established guard-helper clamp declaration; NetworkManager parity held at 2645/2645 after dropping the dead `Cat` enum, confirming semantics preserved. *4th compiler finding:* Float→wasm codegen broadly incomplete (pub-fn exports always type i32; float-literal operands mis-emit; `trunc()`/`float()` absent) — drives the `*Int.affine` parity subsets, keeps floats host-side. Evidence: `migrated/EVIDENCE-C6.adoc` + `EVIDENCE-C8.adoc`. NEXT: complete C7, then C2 wave 2.
194194
| C7 | DONE | Player cluster complete (2026-06-05): two scoped agents (5 verify + 3 float-re-decompose) + parent re-verification. *8 kernels* — CriticalRoll, PlayerAttributes, QCertifications, SkillRank, SkillAbilities, QPrograms, JessicaLoadout, JessicaBackground. *Four gates green (every gate re-run by parent):* 8/8 compile, *4348/4348 parity* (independent oracles), *6 echo-boundary LOSSLESS proofs* across 4 kernels (QCertifications, SkillRank, JessicaLoadout×3, JessicaBackground — agda re-typechecked by parent, exit 0); the other 4 transform/classifier kernels G3-n/a, 8/8 assail-clean. 3 kernels (CriticalRoll/PlayerAttributes/QPrograms) hit the Float→wasm wall (`min_float`/`max_float`/`trunc`) and were re-decomposed Int-native (milli-unit convention, floats host-side) per the C6 `*Int` pattern; parity caught + fixed an off-by-100 in PlayerAttributes. Evidence: `migrated/EVIDENCE-C7.adoc`. NEXT: C2 wave 2.
195-
| C2b+ | TODO | C2 wave 2 (needs an array/linear-memory ABI): Mul/Div, the stack/memory opcodes (Push/Pop/Load/Store), control flow (Call/Loop/IfPos/IfZero), I/O (Send/Recv/CoprocessorCall), and the structural VM files (State, VM, SubroutineRegistry, *Coprocessor, bindings). Then C3..C12. The unary-`~` codegen bug is a candidate Phase-F compiler fix.
195+
| C2 wave 2a | DONE | Scalar multiply/divide (2026-06-05, Opus). `Mul`/`Div` turned out to be pure *scalar* value-transforms (not memory ops), so they need *no* array ABI — split out of "wave 2" and landed now. *1 kernel* `VmMulDiv` (11 exports) under `proposals/idaptik/migrated/`. Brain = the reversible ancilla multiply (`c := c + a*b`, inverse `c := c - a*b`) + the quotient/remainder divide whose dividend is reconstructable (`q*b + r == a` for all b incl. 0); the intentional-flaw in-place/simple variants migrated as value transforms with no rt==id claim. *Four gates green:* 1/1 compile, *3322/3322 parity* (incl. mul reversibility roundtrip + div reconstruction), G3 n/a (numeric transform), 1/1 assail-clean. *2 new i32 ABI facts:* (a) JS `a*b` loses precision >2^53 → oracle must use `Math.imul` to match `i32.mul`; (b) `i32.div_s` TRAPS on `b==0` and `INT_MIN/-1` (ReScript wraps the latter) → brain guards both (nested-`if`, avoiding the unverified `&&` codegen path) so the wasm is total. Evidence: `migrated/EVIDENCE-C2-wave2a.adoc`. NEXT: C2 wave 2b.
196+
| C2 wave 2b | TODO | The memory/control opcodes (Push/Pop/Load/Store, Call/Loop/IfPos/IfZero, Recv/Send/CoprocessorCall/InstructionCoprocessor) + structural VM files (State, VM, SubroutineRegistry, VmStateCoprocessor, bindings). *Genuinely needs an array/linear-memory ABI* (`[len:i32 LE][bytes]` + `__affine_alloc`) — the next real unblocker, also feeds the string wall. Opus-level ABI design. Then C3..C12. The unary-`~` codegen bug is a candidate Phase-F compiler fix.
196197
| F+ | TODO | Compiler walls (string backend, then effects).
197198
| Ω | TODO (access-gated) | Cutover + ReScript extinction.
198199
|===
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
// SPDX-License-Identifier: AGPL-3.0-or-later
2+
// SPDX-FileCopyrightText: 2025-2026 Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
3+
= Cluster C2 wave 2a (VM multiply/divide) — four-gate evidence (captured 2026-06-05)
4+
:toc: macro
5+
6+
[IMPORTANT]
7+
====
8+
*Captured run of the 4-gate recipe over the scalar multiply/divide opcodes.*
9+
Wave 1 (#537-era) covered the value-transform opcodes that need only `+`/`-`/
10+
bitwise ops. This wave 2a covers `Mul` and `Div` — also pure scalar transforms,
11+
so they need *no* array/linear-memory ABI. (The memory opcodes — Push/Pop/Load/
12+
Store/Call/Loop/IfPos/IfZero and the structural VM files — are wave 2b and *do*
13+
need the array ABI; they are deliberately not in this wave.) Toolchain:
14+
AffineScript compiler `_build/default/bin/main.exe`, Deno 2.8.2.
15+
====
16+
17+
toc::[]
18+
19+
== Summary
20+
21+
[cols="2,1,1,2,1",options="header"]
22+
|===
23+
| Kernel | G1 compile | G2 parity | G3 boundary | G4 assail
24+
| `VmMulDiv` | OK | 3322/3322 | n/a (numeric transform) | clean
25+
| *Total* | *1/1* | *3322/3322* | *n/a* | *1/1 clean*
26+
|===
27+
28+
Source: `vm/lib/ocaml/Mul.res` + `vm/lib/ocaml/Div.res`. Eleven exports —
29+
`mul_fwd`, `mul_inv`, `mul_roundtrip`, `div_q`, `div_r`, `div_reconstruct`,
30+
`mul_inplace_fwd`, `mul_inplace_inv`, `div_simple_q`, `div_simple_invert`.
31+
32+
== Re-decomposition (brain/senses)
33+
34+
The ReScript VM stores state in a `dict<int>` keyed by register name; each
35+
opcode is an `execute`/`invert` closure pair over that dict. Per the brain/
36+
senses split the register-name strings + dict stay host-side; the brain is the
37+
scalar-int value transform. "The integer IS the register."
38+
39+
*Reversibility is migrated as explicit headline exports:*
40+
41+
* `mul_roundtrip` — `mul_inv(a, b, mul_fwd(a,b,c)) == c` (Bennett ancilla:
42+
`c := c + a*b`, inverse `c := c - a*b`). 729/729.
43+
* `div_reconstruct` — `q*b + r == a` for *all* `b` including 0 (the source
44+
stashes the original `a` into `r` on the divide-by-zero branch, so the
45+
identity holds universally). 81/81.
46+
47+
The intentional-flaw educational variants (`Mul.makeInPlace`, `Div.makeSimple`)
48+
are migrated faithfully as value transforms but carry *no* roundtrip==id claim
49+
(the source ships them precisely to demonstrate where in-place multiply / simple
50+
divide break reversibility).
51+
52+
== Two real i32 ABI facts surfaced by this opcode pair
53+
54+
. *JS multiplication is not i32-exact.* `a * b` in JS is exact only below 2^53;
55+
for two i32 operands the product can exceed that and lose precision before
56+
`| 0`. The parity oracle therefore uses `Math.imul`, which matches wasm
57+
`i32.mul` exactly (e.g. `2147483647 * 2147483647 -> 1`). A naive `(a*b)|0`
58+
oracle would have produced false mismatches at the domain extremes. This is a
59+
new fact beyond wave 1 (which used only `+`/`-`, exact in JS over the domain).
60+
. *`i32.div_s` / `i32.rem_s` trap where ReScript wraps/guards.* `i32.div_s`
61+
TRAPS on `b == 0` and on `INT_MIN / -1`; `i32.rem_s` TRAPS on `b == 0`
62+
(but `INT_MIN % -1 == 0`, no trap). ReScript's int `/` truncates through
63+
`| 0` so `INT_MIN / -1` wraps to `INT_MIN`, and the source guards `b == 0`
64+
explicitly. The `.affine` brain reproduces both guards (nested-`if` form, to
65+
stay clear of the unverified `&&` codegen path) so the wasm is a *total*
66+
function matching ReScript's observable output — never a trap. The oracle
67+
re-derives the original `.res` output independently and the two agree across
68+
the full sweep, including every `(x, 0)` and the `(INT_MIN, -1)` pair.
69+
70+
== Gate detail
71+
72+
* *G1 compile* — `main.exe compile VmMulDiv.affine -o VmMulDiv.wasm` → WASM,
73+
validates via `WebAssembly.compile`; 11 exports + `memory`.
74+
* *G2 parity* — `parity.mjs vmmuldiv.config.mjs` → 3322/3322 over the i32
75+
domain `{INT_MIN, -1e6, -7, -1, 0, 1, 7, 1e6, INT_MAX}` (Cartesian per arity).
76+
* *G3 boundary* — n/a. `Mul`/`Div` are numeric transforms with no discrete
77+
value↔integer encoding table, so injectivity is not the relevant property;
78+
the G2 round-trip against the independent oracle is the faithfulness check
79+
(same disposition as wave-1 `VmArith`/`VmBitwise`/`VmAncilla`).
80+
* *G4 assail* — `assail.mjs VmMulDiv.affine` → 0 findings. The numeric guards
81+
return out-of-band-safe values (`0` on divide-by-zero per source; `INT_MIN`
82+
on the wrap); there is no enum decoder, so PA-AFF-001 does not apply.
83+
84+
== Remaining in cluster C2
85+
86+
Wave 2b — the memory/control opcodes (Push, Pop, Load, Store, Call, Loop,
87+
IfPos, IfZero, Recv, Send, CoprocessorCall, InstructionCoprocessor) and the
88+
structural VM files (State, VM, SubroutineRegistry, VmStateCoprocessor,
89+
bindings). These need an *array / linear-memory ABI* (`[len:i32 LE][bytes]` +
90+
`__affine_alloc`), the next real unblocker — it also feeds the string wall.
91+
That is Opus-level ABI design and is the next C2 task.
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// SPDX-License-Identifier: AGPL-3.0-or-later
2+
//
3+
// VmMulDiv -- the reversible integer multiply/divide opcodes of the idaptik VM,
4+
// the pure-integer core extracted from vm/lib/ocaml/{Mul,Div}.res. Per the
5+
// brain/senses split (DESIGN-VISION), the register-name strings and the state
6+
// dict live host-side; the brain is the reversible VALUE transform each opcode
7+
// performs, as scalar-int functions. "The integer IS the register."
8+
//
9+
// Reversibility is the defining invariant of this VM, but MUL and DIV are NOT
10+
// reversible in place -- multiply-by-zero and integer division both erase
11+
// information (Landauer). The source therefore uses Bennett's ancilla trick:
12+
// * MUL.make accumulates the product into a THIRD register: c := c + a*b,
13+
// inverse c := c - a*b -- reversible as long as a,b are untouched.
14+
// * DIV.make keeps BOTH quotient and remainder (q = a/b, r = a mod b), so the
15+
// dividend is reconstructable: a == q*b + r (true even for b == 0, since the
16+
// source stashes the original a into r on the divide-by-zero branch).
17+
// The educational in-place / simple variants (intentionally NOT reversible) are
18+
// migrated faithfully as value transforms, but carry no roundtrip==id claim.
19+
//
20+
//## Two wasm i32 hazards the ReScript source does not share (guarded here)
21+
// i32.div_s TRAPS on b == 0 and on INT_MIN / -1; i32.rem_s TRAPS on b == 0.
22+
// ReScript wraps INT_MIN / -1 to INT_MIN (its `/` truncates through `| 0`) and
23+
// the source guards b == 0 explicitly. We reproduce both guards so the wasm is
24+
// a TOTAL function matching ReScript's observable output -- never a trap.
25+
// (Separately, JS `a*b` loses precision above 2^53, so the parity oracle uses
26+
// Math.imul to stay i32-exact against i32.mul -- see vmmuldiv.config.mjs.)
27+
28+
// --- MUL.make: ancilla multiply, a reversible inverse-pair on the c register ---
29+
// execute c := c + a*b invert c := c - a*b
30+
pub fn mul_fwd(a: Int, b: Int, c: Int) -> Int { c + a * b }
31+
pub fn mul_inv(a: Int, b: Int, c: Int) -> Int { c - a * b }
32+
// reversibility headline: invert undoes execute (oracle = identity on c).
33+
// Holds over i32 wraparound: (c + a*b) - a*b == c even when a*b overflows.
34+
pub fn mul_roundtrip(a: Int, b: Int, c: Int) -> Int { mul_inv(a, b, mul_fwd(a, b, c)) } // == c
35+
36+
// --- DIV.make: quotient + remainder, reconstructable dividend ---
37+
// Quotient. b == 0 guard mirrors the source (q := 0). The INT_MIN/-1 guard
38+
// returns ReScript's wrapped value and avoids the i32.div_s trap.
39+
fn div_q_guarded(a: Int, b: Int) -> Int {
40+
if b == 0 { return 0; }
41+
if a == -2147483648 {
42+
if b == -1 { return -2147483648; }
43+
}
44+
a / b
45+
}
46+
pub fn div_q(a: Int, b: Int) -> Int { div_q_guarded(a, b) }
47+
// Remainder. b == 0 stores the original a (the source's divide-by-zero branch).
48+
// i32.rem_s(INT_MIN, -1) == 0 in wasm, matching ReScript's 0 -- no guard needed.
49+
pub fn div_r(a: Int, b: Int) -> Int {
50+
if b == 0 { return a; }
51+
a % b
52+
}
53+
// reversibility headline: a == q*b + r -- the whole reason DIV keeps r.
54+
// Holds for ALL b (b == 0: q=0, r=a, so 0*0 + a == a).
55+
pub fn div_reconstruct(a: Int, b: Int) -> Int { div_q(a, b) * b + div_r(a, b) } // == a
56+
57+
// --- MUL.makeInPlace: a := a*b (execute), a := a/b (invert) -- INTENTIONAL FLAW
58+
// The source ships this to demonstrate why in-place multiply breaks
59+
// reversibility (b == 0 erases the product; a*b can overflow). We migrate the
60+
// value transforms faithfully; NO roundtrip == id is claimed for them.
61+
pub fn mul_inplace_fwd(a: Int, b: Int) -> Int { a * b }
62+
pub fn mul_inplace_inv(a: Int, b: Int) -> Int {
63+
if b == 0 { return a; } // source leaves a unchanged when b == 0
64+
if a == -2147483648 {
65+
if b == -1 { return -2147483648; }
66+
}
67+
a / b
68+
}
69+
70+
// --- DIV.makeSimple: q := a/b (execute; q unchanged if b == 0), q := 0 (invert)
71+
// execute reads the prior quotient register so the b == 0 no-write branch is a
72+
// faithful identity on it.
73+
pub fn div_simple_q(a: Int, b: Int, q_prev: Int) -> Int {
74+
if b == 0 { return q_prev; } // source performs no write when b == 0
75+
if a == -2147483648 {
76+
if b == -1 { return -2147483648; }
77+
}
78+
a / b
79+
}
80+
// invert clears the quotient ancilla unconditionally.
81+
pub fn div_simple_invert() -> Int { 0 }
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// SPDX-License-Identifier: MPL-2.0
2+
// hypatia: allow cicd_rules/javascript_detected -- Deno trial component for nextgen-evangelist; production target is Rust/AffineScript (see proposals/nextgen-evangelist/README.adoc)
3+
//
4+
// affine-parity config for VmMulDiv.affine (reversible VM multiply/divide
5+
// opcodes; scalar i32 ABI). Each oracle independently re-derives the ORIGINAL
6+
// vm/lib/ocaml/{Mul,Div}.res value transform in JS -- it is NOT read off the
7+
// .affine. Both sides normalise to i32 (| 0), so two's-complement wraparound is
8+
// covered.
9+
//
10+
// Two correctness points, both real ABI facts surfaced by this opcode pair:
11+
// 1. JS `a * b` is exact only below 2^53; for i32 products it must use
12+
// Math.imul to match wasm i32.mul (e.g. 2147483647*2147483647 -> 1).
13+
// 2. ReScript int `/` truncates through `| 0`, so INT_MIN / -1 wraps to
14+
// INT_MIN; `b == 0` is guarded by the source (q := 0, r := a). The oracle
15+
// reproduces the source's observable output; the .affine reproduces it
16+
// with explicit guards because i32.div_s would otherwise TRAP.
17+
const I = { values: [-2147483648, -1000000, -7, -1, 0, 1, 7, 1000000, 2147483647] };
18+
const i32 = (x) => x | 0;
19+
const imul = (a, b) => Math.imul(a, b); // i32-exact product
20+
const sdiv = (a, b) => (b === 0 ? 0 : i32(a / b)); // Div.res quotient branch
21+
const srem = (a, b) => (b === 0 ? a : i32(a % b)); // Div.res remainder branch
22+
23+
export default {
24+
affine: "VmMulDiv.affine",
25+
cases: [
26+
// --- MUL.make ancilla pair + reversibility ---
27+
{ name: "mul_fwd c+a*b", export: "mul_fwd", args: [I, I, I], oracle: (a, b, c) => i32(c + imul(a, b)) },
28+
{ name: "mul_inv c-a*b", export: "mul_inv", args: [I, I, I], oracle: (a, b, c) => i32(c - imul(a, b)) },
29+
{ name: "mul_roundtrip == c", export: "mul_roundtrip", args: [I, I, I], oracle: (a, b, c) => i32(c) },
30+
31+
// --- DIV.make quotient/remainder + reconstruction (reversibility) ---
32+
{ name: "div_q a/b (guarded)", export: "div_q", args: [I, I], oracle: (a, b) => sdiv(a, b) },
33+
{ name: "div_r a mod b (guarded)", export: "div_r", args: [I, I], oracle: (a, b) => srem(a, b) },
34+
{ name: "div_reconstruct == a", export: "div_reconstruct", args: [I, I], oracle: (a, b) => i32(a) },
35+
36+
// --- MUL.makeInPlace value transforms (intentional-flaw variant; no rt==id) ---
37+
{ name: "mul_inplace_fwd a*b", export: "mul_inplace_fwd", args: [I, I], oracle: (a, b) => imul(a, b) },
38+
{ name: "mul_inplace_inv a/b (b==0 -> a)", export: "mul_inplace_inv", args: [I, I], oracle: (a, b) => (b === 0 ? i32(a) : i32(a / b)) },
39+
40+
// --- DIV.makeSimple quotient (b==0 keeps prior q) + invert clears ---
41+
{ name: "div_simple_q (b==0 -> q_prev)", export: "div_simple_q", args: [I, I, I], oracle: (a, b, q) => (b === 0 ? i32(q) : i32(a / b)) },
42+
{ name: "div_simple_invert == 0", export: "div_simple_invert", args: [], oracle: () => 0 },
43+
],
44+
};

proposals/idaptik/migration-map.json

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,19 @@
9191
"evidence": "proposals/idaptik/migrated/EVIDENCE-C2.adoc",
9292
"compiler_findings": "unary ~ codegen bug (i32.xor missing operand; workaround -a-1); >> is arithmetic + no >>> (logical shift-right emulated for ROL/ROR)"
9393
},
94-
"remaining_wave2": ["Mul", "Div", "Push", "Pop", "Load", "Store", "Call", "Loop", "IfPos", "IfZero", "Recv", "Send", "CoprocessorCall", "InstructionCoprocessor", "State", "VM", "SubroutineRegistry", "VmStateCoprocessor", "bindings"],
94+
"done_wave2a": {
95+
"date": "2026-06-05",
96+
"phase": "G",
97+
"staged_at": "proposals/idaptik/migrated/",
98+
"kernels": ["VmMulDiv"],
99+
"opcodes": ["Mul", "Div"],
100+
"note": "Mul/Div are pure SCALAR transforms, not memory ops; need no array ABI, so split out of wave 2 and landed ahead of 2b. Reversibility via Bennett ancilla (mul c:=c+a*b) and quotient+remainder (div q*b+r==a for all b).",
101+
"gates": "1/1 compile; 3322/3322 parity (incl. mul_roundtrip + div_reconstruct); G3 n/a (numeric transform); 1/1 assail-clean",
102+
"evidence": "proposals/idaptik/migrated/EVIDENCE-C2-wave2a.adoc",
103+
"compiler_findings": "JS a*b loses precision >2^53 -> oracle must use Math.imul to match i32.mul; i32.div_s TRAPS on b==0 and INT_MIN/-1 (ReScript wraps) and i32.rem_s TRAPS on b==0 -> brain guards both (nested-if, avoiding unverified && codegen) for a total wasm"
104+
},
105+
"remaining_wave2b": ["Push", "Pop", "Load", "Store", "Call", "Loop", "IfPos", "IfZero", "Recv", "Send", "CoprocessorCall", "InstructionCoprocessor", "State", "VM", "SubroutineRegistry", "VmStateCoprocessor", "bindings"],
106+
"remaining_wave2b_blocker": "needs array/linear-memory ABI ([len:i32 LE][bytes] + __affine_alloc); Opus-level design; also unblocks the string wall",
95107
"files": [
96108
"vm/lib/ocaml/Add.res",
97109
"vm/lib/ocaml/And.res",

0 commit comments

Comments
 (0)