|
| 1 | +<!-- SPDX-License-Identifier: PMPL-1.0-or-later --> |
| 2 | +# Known AffineScript codegen issues |
| 3 | + |
| 4 | +Minimal reproducers for codegen bugs discovered while using AffineScript |
| 5 | +downstream. Please open a proper GitHub issue (or link an existing one) |
| 6 | +before starting a fix so the fix can be attributed. |
| 7 | + |
| 8 | +--- |
| 9 | + |
| 10 | +## Issue 1 — `match` on enum with distinct zero-arity constructors per arm emits invalid WASM |
| 11 | + |
| 12 | +**Discovered:** 2026-04-19 while writing `web-ecosystem/double-track-browser/tests/affine/extension_lifecycle_test.affine`. |
| 13 | + |
| 14 | +**Symptom:** `affinescript compile` succeeds. The emitted `.wasm` is rejected by every WebAssembly validator (tested: V8 via Deno): |
| 15 | + |
| 16 | +``` |
| 17 | +CompileError: WebAssembly.compile(): Compiling function #1 failed: |
| 18 | + expected 1 elements on the stack for fallthru, found 2 |
| 19 | +``` |
| 20 | + |
| 21 | +**Minimal reproducer:** |
| 22 | + |
| 23 | +```affine |
| 24 | +enum Lifecycle { |
| 25 | + A, |
| 26 | + B, |
| 27 | + C(Int) |
| 28 | +} |
| 29 | +
|
| 30 | +fn step(s: Lifecycle) -> Lifecycle { |
| 31 | + match s { |
| 32 | + A => B(), |
| 33 | + B => B(), |
| 34 | + C(n) => C(n) |
| 35 | + } |
| 36 | +} |
| 37 | +``` |
| 38 | + |
| 39 | +**Workaround:** Downgrade enums to tagged structs when the match must |
| 40 | +return distinct zero-arity constructors across arms: |
| 41 | + |
| 42 | +```affine |
| 43 | +struct State { |
| 44 | + tag: Int, |
| 45 | + // ...payload fields |
| 46 | +} |
| 47 | +``` |
| 48 | + |
| 49 | +**Likely location in compiler:** `lib/codegen.ml` around match-arm |
| 50 | +lowering for enum construction sites. |
| 51 | + |
| 52 | +--- |
| 53 | + |
| 54 | +## Issue 2 — Non-first struct-field read from function parameter returns 0 |
| 55 | + |
| 56 | +**Discovered:** 2026-04-19 in the same pilot. |
| 57 | + |
| 58 | +**Symptom:** Reading `s.tag` (field at offset 0) from a function parameter |
| 59 | +`s: State` works correctly. Reading `s.profile_id` (field at offset 1) or |
| 60 | +`s.activity_count` (field at offset 2) from the same parameter always |
| 61 | +returns 0, regardless of the actual value stored in the struct. Reading |
| 62 | +the same fields from a let-bound local of the same struct type works |
| 63 | +fine. |
| 64 | + |
| 65 | +**Minimal reproducer:** |
| 66 | + |
| 67 | +```affine |
| 68 | +struct State { |
| 69 | + tag: Int, |
| 70 | + profile_id: Int, |
| 71 | + activity_count: Int |
| 72 | +} |
| 73 | +
|
| 74 | +fn build_state(pid: Int) -> State { |
| 75 | + { tag: 2, profile_id: pid, activity_count: 0 } |
| 76 | +} |
| 77 | +
|
| 78 | +fn read_pid(s: State) -> Int { |
| 79 | + s.profile_id |
| 80 | +} |
| 81 | +
|
| 82 | +pub fn test_direct_local() -> Bool { |
| 83 | + let s = { tag: 2, profile_id: 42, activity_count: 0 }; |
| 84 | + s.profile_id == 42 // PASSES |
| 85 | +} |
| 86 | +
|
| 87 | +pub fn test_local_from_builder() -> Bool { |
| 88 | + let s = build_state(42); |
| 89 | + s.profile_id == 42 // PASSES |
| 90 | +} |
| 91 | +
|
| 92 | +pub fn test_via_helper() -> Bool { |
| 93 | + let s = build_state(42); |
| 94 | + read_pid(s) == 42 // FAILS: read_pid returns 0 |
| 95 | +} |
| 96 | +``` |
| 97 | + |
| 98 | +**Workaround:** Pass individual scalars instead of a struct, or read |
| 99 | +struct fields into local variables at the call site before handing off |
| 100 | +to a helper: |
| 101 | + |
| 102 | +```affine |
| 103 | +fn read_pid_scalar(_tag: Int, pid: Int, _count: Int) -> Int { |
| 104 | + pid |
| 105 | +} |
| 106 | +
|
| 107 | +pub fn test_via_scalar_helper() -> Bool { |
| 108 | + let s = build_state(42); |
| 109 | + read_pid_scalar(s.tag, s.profile_id, s.activity_count) == 42 |
| 110 | +} |
| 111 | +``` |
| 112 | + |
| 113 | +**Likely location in compiler:** `lib/codegen.ml` around struct-field |
| 114 | +access codegen for function-parameter locals (likely a wrong local |
| 115 | +index or wrong offset calculation when the struct pointer is a function |
| 116 | +parameter rather than a let-bound local). |
| 117 | + |
| 118 | +--- |
| 119 | + |
| 120 | +## Tracking |
| 121 | + |
| 122 | +Both issues block scaling the `affinescript-deno-test` harness (sibling |
| 123 | +component at `developer-ecosystem/affinescript-ecosystem/ |
| 124 | +affinescript-deno-test/`) from its current 10-test pilot to the |
| 125 | +estate-wide TypeScript-test migration in AI-WORK-todo.md §3c. |
| 126 | + |
| 127 | +Estate tracker: `~/Desktop/AI-WORK-todo.md §11`. |
0 commit comments