Skip to content

Commit 58f08b9

Browse files
feat(wasm): byte-level load/store IR + env_at/arg_at (ADR-015 S5) (#339)
## Summary Unblocks INT-03 S5 (#180) by extending the wasm IR with the full WebAssembly 1.0 §5.4.6 byte-level load/store family (opcodes `0x2C..0x35`, `0x3A..0x3E`) and shipping the `env_at(i)` / `arg_at(i)` string accessors that were waiting on them. - **IR + encoder** (`lib/wasm.ml` + `lib/wasm_encode.ml`) — 14 new `instr` constructors (`I32Load8{S,U}`, `I32Load16{S,U}`, `I64Load8{S,U}`, `I64Load16{S,U}`, `I64Load32{S,U}`, `I32Store8`, `I32Store16`, `I64Store{8,16,32}`). Encoder pattern mirrors the existing full-width family one-for-one. - **`env_at` / `arg_at` lowering** (`lib/typecheck.ml` + `lib/codegen.ml` + `lib/wasi_runtime.ml`) — bind `Int -> String / { Time }`; lower via on-demand `wasi_snapshot_preview1.{environ,args}_get` imports paired with the existing `*_sizes_get` import. The codegen helper emits a 7-phase sequence: `sizes_get` → alloc ptrvec+bytebuf → `*_get` → resolve `ptrvec[n]` → null-terminator scan (`I32Load8U`) → alloc length-prefixed AS string → byte-copy (`I32Load8U` / `I32Store8`). - **Import-table dedup** (`lib/codegen.ml`) — `env_count` and `env_at` both require `environ_sizes_get` (ditto `arg_count`/`arg_at`). The `optional_wasi` table now lists every `(builtin, wasi_import)` pair and the assembly pass dedupes by WASI import name (first occurrence wins), preserving the canonical-order indexing the existing combo regression hardened. - Docs updated in `docs/ECOSYSTEM.adoc` and `docs/TECH-DEBT.adoc`; stale "tracked follow-up" comments in codegen/typecheck/wasi_runtime cleared. ## Test plan - [ ] CI `dune build` + `dune runtest` (the only build environment available — see note below) - [ ] `./tools/run_codegen_wasm_tests.sh` picks up the three new fixtures automatically: - `tests/codegen/env_at.affine` + `test_env_at.mjs` — smoke for `string_length(env_at(0))`; verifies `environ_sizes_get` + `environ_get` both fire and the byte-scan terminates at the right offset. - `tests/codegen/arg_at.affine` + `test_arg_at.mjs` — same shape over `args_get`, indexed at **1** to exercise the `n*4` ptrvec offset (not just `index = 0` short-circuit). - `tests/codegen/env_count_and_at.affine` + `test_env_count_and_at.mjs` — dedup regression: instantiation fails fast if `environ_sizes_get` is double-imported. ## Caveat — local verification The session container's network policy blocks `opam.ocaml.org` (`host_not_allowed`), so `opam install . --deps-only` fails and `dune build` cannot run locally. The five modified `.ml` files were syntax-checked with `ocamlc -stop-after parsing` (all OK) and the code mirrors existing patterns exactly, but the type-checker hasn't been run end-to-end against this branch. CI is the first real validation — autofix-on-failure is wired up to follow. https://claude.ai/code/session_01FoWm87AnVtLS5euDEAGu2A --- _Generated by [Claude Code](https://claude.ai/code/session_01FoWm87AnVtLS5euDEAGu2A)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent f45afa1 commit 58f08b9

13 files changed

Lines changed: 467 additions & 20 deletions

docs/ECOSYSTEM.adoc

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -219,11 +219,14 @@ builtins lower to on-demand `wasi_snapshot_preview1.*` imports
219219
`ctx.wasi_func_indices`; zero impact on units that don't use them;
220220
verified with a multi-import combo regression). Component path
221221
bridges to `wasi:clocks`/`wasi:cli`. Real-host main-invoke deferred
222-
to S6 (WIT export-lifting / wasi:cli/run command shape). String
223-
accessors (env_at/arg_at) gated on a byte-level wasm-IR extension
224-
(I32Load8U/I32Store8 absent today) — tracked as the next slice
225-
before/with S5 filesystem.** WIT world of
226-
record: `wit/affinescript.wit`
222+
to S6 (WIT export-lifting / wasi:cli/run command shape).
223+
**S5 string accessors (env_at/arg_at) DONE: the wasm IR gained
224+
the byte-level load/store family (I32Load8U/I32Store8 + the full
225+
WebAssembly 1.0 §5.4.6 row, opcodes 0x2C..0x35 / 0x3A..0x3E);
226+
accessors lower to on-demand `environ_get`/`args_get` paired with
227+
the existing `*_sizes_get` import (dedup keeps each WASI import
228+
once even when both `*_count` and `*_at` are used).** WIT world
229+
of record: `wit/affinescript.wit`
227230
|INT-04 |Publish compiler + runtime to JSR (then npm) |#181 |runtime
228231
packaging READY (affine-js + affinescript-tea JSR dry-run green;
229232
manual-only `publish-jsr.yml`; docs/PACKAGING.adoc). INT-01 dep

docs/TECH-DEBT.adoc

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -230,11 +230,14 @@ Component-Model re-target, S1..S6); S2 toolchain #251 closed;
230230
S3 componentize done; **S4a (clock) + S4b (env_count, arg_count)
231231
DONE — on-demand preview1 imports via Effect_sites pre-scan,
232232
canonical-order indexing through `ctx.wasi_func_indices`; combo
233-
regression proves no collision. String accessors (env_at/arg_at)
234-
gated on byte-level wasm IR (I32Load8U/I32Store8 absent today) —
235-
tracked next slice. Real-host main-invoke = S6 (WIT export
236-
lifting). Next S5
237-
(native clocks/env/argv)**
233+
regression proves no collision. **S5 (env_at/arg_at) DONE — wasm
234+
IR extended with the byte-level load/store family
235+
(I32Load8U/I32Store8 and siblings); accessors lower to on-demand
236+
`environ_get`/`args_get` imports paired with the existing
237+
`*_sizes_get` (dedup keeps each WASI import exactly once even when
238+
both `*_count` and `*_at` are used in the same unit); guest
239+
allocates a length-prefixed AS string and byte-copies from the
240+
WASI buffer.** Real-host main-invoke = S6 (WIT export lifting)
238241
|INT-04 |Publish to JSR/npm |S2 |#181 packaging READY (dry-run green,
239242
manual workflow); compiler-binary distribution decided = **ADR-019**
240243
(#260, Releases + thin Deno/JSR shim, staged S1..S4) — S1/S2/S3

lib/codegen.ml

Lines changed: 59 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -873,8 +873,9 @@ let rec gen_expr (ctx : context) (expr : expr) : (context * instr list) result =
873873
(added on-demand at module assembly; idx looked up in
874874
[ctx.wasi_func_indices]). The Unit arg satisfies the
875875
zero-param-fn collapse wart; it is evaluated but its value
876-
is unused. String accessors (env_at/arg_at) need byte-level
877-
wasm IR ops (currently absent) and are a tracked follow-up. *)
876+
is unused. The companion string accessors `env_at`/`arg_at`
877+
landed alongside the byte-level wasm IR extension — see
878+
the case below. *)
878879
let wasi_name =
879880
if id.name = "env_count" then "environ_sizes_get"
880881
else "args_sizes_get"
@@ -894,6 +895,47 @@ let rec gen_expr (ctx : context) (expr : expr) : (context * instr list) result =
894895
in
895896
Ok (ctx_with_heap, code)
896897

898+
| ExprVar id when (id.name = "env_at" || id.name = "arg_at")
899+
&& List.length args = 1 ->
900+
(* ADR-015 S5 (#180): env_at(i: Int) / arg_at(i: Int) -> String.
901+
Allocates a length-prefixed AS string and byte-copies the
902+
i-th null-terminated entry from the WASI environ/argv
903+
buffer. Uses [I32Load8U]/[I32Store8] (the byte-level wasm
904+
IR extension landed alongside this slice). Pairs the
905+
existing on-demand `*_sizes_get` import with the matching
906+
`environ_get`/`args_get` import (registered above in the
907+
`optional_wasi` table; deduped by wasi name). *)
908+
let sizes_name, get_name =
909+
if id.name = "env_at" then "environ_sizes_get", "environ_get"
910+
else "args_sizes_get", "args_get"
911+
in
912+
let sizes_func_idx =
913+
try List.assoc sizes_name ctx.wasi_func_indices
914+
with Not_found -> 1
915+
in
916+
let get_func_idx =
917+
try List.assoc get_name ctx.wasi_func_indices
918+
with Not_found -> 2
919+
in
920+
let* (ctx0, arg_code) = gen_expr ctx (List.hd args) in
921+
let (c1, n_local) = alloc_local ctx0 ("__" ^ id.name ^ "_n") in
922+
let (c2, scratch_local) = alloc_local c1 ("__" ^ id.name ^ "_scratch") in
923+
let (c3, count_local) = alloc_local c2 ("__" ^ id.name ^ "_count") in
924+
let (c4, bufsize_local) = alloc_local c3 ("__" ^ id.name ^ "_bufsize") in
925+
let (c5, ptrvec_local) = alloc_local c4 ("__" ^ id.name ^ "_ptrvec") in
926+
let (c6, src_local) = alloc_local c5 ("__" ^ id.name ^ "_src") in
927+
let (c7, dst_local) = alloc_local c6 ("__" ^ id.name ^ "_dst") in
928+
let (c8, result_local) = alloc_local c7 ("__" ^ id.name ^ "_result") in
929+
let (ctx_with_heap, heap_idx) = ensure_heap_ptr c8 in
930+
let code =
931+
arg_code @
932+
Wasi_runtime.gen_str_at_via_get
933+
heap_idx n_local scratch_local count_local bufsize_local
934+
ptrvec_local src_local dst_local result_local
935+
sizes_func_idx get_func_idx
936+
in
937+
Ok (ctx_with_heap, code)
938+
897939
| ExprVar id when List.mem_assoc id.name ctx.variant_tags ->
898940
(* Enum constructor called as a function: Circle(5), Rect({x:1,y:2}), etc.
899941
Layout: [tag: i32][field1: i32][field2: i32]...
@@ -2549,13 +2591,25 @@ let generate_module ?loader (prog : program) : wasm_module result =
25492591
false prog
25502592
in
25512593
let optional_wasi =
2552-
(* (guest_builtin_name, wasi_import_name, factory) — canonical order. *)
2594+
(* (guest_builtin_name, wasi_import_name, factory) — canonical order.
2595+
Multiple builtins MAY require the same WASI import (e.g. both
2596+
`env_count` and `env_at` need `environ_sizes_get`); the dedup
2597+
pass below keeps the first occurrence so each wasm import shows
2598+
up exactly once with a stable index. *)
25532599
[ ("clock_now_ms", "clock_time_get", Wasi_runtime.create_clock_time_get_import);
25542600
("env_count", "environ_sizes_get", Wasi_runtime.create_environ_sizes_get_import);
25552601
("arg_count", "args_sizes_get", Wasi_runtime.create_args_sizes_get_import);
2602+
("env_at", "environ_sizes_get", Wasi_runtime.create_environ_sizes_get_import);
2603+
("env_at", "environ_get", Wasi_runtime.create_environ_get_import);
2604+
("arg_at", "args_sizes_get", Wasi_runtime.create_args_sizes_get_import);
2605+
("arg_at", "args_get", Wasi_runtime.create_args_get_import);
25562606
]
2557-
|> List.filter_map
2558-
(fun (b, w, f) -> if uses b then Some (w, f ()) else None)
2607+
|> List.filter (fun (b, _, _) -> uses b)
2608+
|> List.fold_left
2609+
(fun acc (_, w, f) ->
2610+
if List.exists (fun (w', _) -> w' = w) acc then acc
2611+
else acc @ [(w, f ())])
2612+
[]
25592613
|> List.mapi (fun i (w, (imp, ty)) -> (i + 1, w, imp, ty))
25602614
in
25612615
let opt_types = List.map (fun (_, _, _, ty) -> ty) optional_wasi in

lib/typecheck.ml

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1324,11 +1324,18 @@ let register_builtins (ctx : context) : unit =
13241324
(TArrow (ty_int, QOmega, ty_int, ESingleton "Time"));
13251325
(* ADR-015 S4b (#180): WASI environment / argv COUNTS. The Unit arg
13261326
satisfies the zero-param-fn collapse wart (`fn()->T` lowers to
1327-
bare `T`; callable zero-arg builtins take `Unit -> R`). String
1328-
accessors (env_at/arg_at) need byte-level wasm IR ops — tracked
1329-
follow-up. Effect row `Time` (reserved). *)
1327+
bare `T`; callable zero-arg builtins take `Unit -> R`).
1328+
Effect row `Time` (reserved). *)
13301329
bind_var ctx "env_count" (TArrow (ty_unit, QOmega, ty_int, ESingleton "Time"));
13311330
bind_var ctx "arg_count" (TArrow (ty_unit, QOmega, ty_int, ESingleton "Time"));
1331+
(* ADR-015 S5 (#180): WASI environment / argv STRING ACCESSORS. Returns
1332+
the i-th entry as a length-prefixed AS string. Lowered via
1333+
`environ_get`/`args_get` + a byte-level scan + byte-copy, which
1334+
became expressible once `I32Load8U`/`I32Store8` joined the wasm IR.
1335+
Index out-of-bounds is UB at this layer — the guest is expected to
1336+
bound-check against `env_count(())`/`arg_count(())`. *)
1337+
bind_var ctx "env_at" (TArrow (ty_int, QOmega, ty_string, ESingleton "Time"));
1338+
bind_var ctx "arg_at" (TArrow (ty_int, QOmega, ty_string, ESingleton "Time"));
13321339
bind_var ctx "eprint" (TArrow (ty_string, QOmega, ty_unit, ESingleton "IO"));
13331340
bind_var ctx "eprintln" (TArrow (ty_string, QOmega, ty_unit, ESingleton "IO"));
13341341
bind_var ctx "read_line"

lib/wasi_runtime.ml

Lines changed: 158 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -359,8 +359,8 @@ let gen_print_str (heap_ptr_global : int) (str_ptr_local : int) (fd_write_idx :
359359
Signature: `(envc_out: i32, envbuf_size_out: i32) -> errno: i32`.
360360
Writes the env-var count and the total byte size of the
361361
null-terminated `KEY=VAL\0…` buffer the next call would need.
362-
String accessor (`env_at`) is gated on byte-level wasm IR ops,
363-
deferred to a follow-up slice. *)
362+
Paired with `environ_get` (created by
363+
{!create_environ_get_import}) for the `env_at` string accessor. *)
364364
let create_environ_sizes_get_import () : import * func_type =
365365
let func_type = {
366366
ft_params = [I32; I32]; (* envc_out_ptr, envbuf_size_out_ptr *)
@@ -413,3 +413,159 @@ let gen_count_via_sizes_get
413413
LocalGet scratch_local;
414414
I32Load (2, 0);
415415
]
416+
417+
(** Create the WASI `environ_get` import (ADR-015 S5, #180).
418+
Signature: `(environ_ptr_ptr: i32, environ_buf_ptr: i32) -> errno: i32`.
419+
Fills two regions: a vector of pointers (one per env-var, written
420+
at `environ_ptr_ptr`) and a contiguous buffer of null-terminated
421+
`KEY=VAL` strings (written at `environ_buf_ptr`). The sizes that
422+
must be allocated are reported by `environ_sizes_get`. *)
423+
let create_environ_get_import () : import * func_type =
424+
let func_type = {
425+
ft_params = [I32; I32]; (* environ_ptr_ptr, environ_buf_ptr *)
426+
ft_results = [I32]; (* errno *)
427+
} in
428+
let import = {
429+
i_module = "wasi_snapshot_preview1";
430+
i_name = "environ_get";
431+
i_desc = ImportFunc 0;
432+
} in
433+
(import, func_type)
434+
435+
(** Create the WASI `args_get` import (ADR-015 S5, #180).
436+
Signature: `(argv_ptr_ptr: i32, argv_buf_ptr: i32) -> errno: i32`.
437+
Same shape as `environ_get`. *)
438+
let create_args_get_import () : import * func_type =
439+
let func_type = {
440+
ft_params = [I32; I32];
441+
ft_results = [I32];
442+
} in
443+
let import = {
444+
i_module = "wasi_snapshot_preview1";
445+
i_name = "args_get";
446+
i_desc = ImportFunc 0;
447+
} in
448+
(import, func_type)
449+
450+
(** Emit `env_at(i)` / `arg_at(i)`: fetch the i-th entry from the WASI
451+
environ/argv vector and return it as a length-prefixed AffineScript
452+
string. Sequence:
453+
1. `*_sizes_get(&count, &bufsize)`
454+
2. allocate `count*4` bytes for the pointer vector + `bufsize`
455+
bytes for the string buffer
456+
3. `*_get(ptrvec, ptrvec + count*4)`
457+
4. resolve `src = ptrvec[i]`
458+
5. scan `src` for the null terminator to compute length
459+
6. allocate `(4 + length)` bytes for the result string,
460+
store length at +0, byte-copy `src..src+length` to `result+4`
461+
7. leave the result pointer on the stack
462+
463+
The byte loops use `I32Load8U`/`I32Store8` (added with the
464+
byte-level wasm IR extension). The caller has placed the index `i`
465+
on the stack; this helper consumes it via [LocalSet n_local].
466+
467+
All locals must be pre-allocated by the caller (8 in total). The
468+
helper itself does not modify the type or scope context — it only
469+
emits instructions. *)
470+
let gen_str_at_via_get
471+
(heap_ptr_global : int)
472+
(n_local : int)
473+
(scratch_local : int)
474+
(count_local : int)
475+
(bufsize_local : int)
476+
(ptrvec_local : int)
477+
(src_local : int)
478+
(dst_local : int)
479+
(result_local : int)
480+
(sizes_func_idx : int)
481+
(get_func_idx : int)
482+
: instr list =
483+
[
484+
(* Index `i` is on the stack from the caller's arg_code. *)
485+
LocalSet n_local;
486+
487+
(* --- Phase 1: sizes_get -> count, bufsize --- *)
488+
GlobalGet heap_ptr_global;
489+
I32Const 8l; I32Add;
490+
GlobalSet heap_ptr_global;
491+
GlobalGet heap_ptr_global;
492+
I32Const 8l; I32Sub;
493+
LocalSet scratch_local;
494+
LocalGet scratch_local; (* count_ptr *)
495+
LocalGet scratch_local; I32Const 4l; I32Add; (* bufsize_ptr *)
496+
Call sizes_func_idx;
497+
Drop;
498+
LocalGet scratch_local; I32Load (2, 0); LocalSet count_local;
499+
LocalGet scratch_local; I32Load (2, 4); LocalSet bufsize_local;
500+
501+
(* --- Phase 2: allocate ptrvec (count*4) + bytebuf (bufsize) --- *)
502+
GlobalGet heap_ptr_global;
503+
LocalSet ptrvec_local;
504+
GlobalGet heap_ptr_global;
505+
LocalGet count_local; I32Const 4l; I32Mul;
506+
LocalGet bufsize_local; I32Add;
507+
I32Add;
508+
GlobalSet heap_ptr_global;
509+
510+
(* --- Phase 3: get(ptrvec, ptrvec + count*4) --- *)
511+
LocalGet ptrvec_local;
512+
LocalGet ptrvec_local; LocalGet count_local; I32Const 4l; I32Mul; I32Add;
513+
Call get_func_idx;
514+
Drop;
515+
516+
(* --- Phase 4: src = *(ptrvec + i*4) --- *)
517+
LocalGet ptrvec_local;
518+
LocalGet n_local; I32Const 4l; I32Mul; I32Add;
519+
I32Load (2, 0);
520+
LocalSet src_local;
521+
522+
(* --- Phase 5: scan for null terminator. Use scratch as cursor. --- *)
523+
LocalGet src_local; LocalSet scratch_local;
524+
Block (BtEmpty, [
525+
Loop (BtEmpty, [
526+
LocalGet scratch_local;
527+
I32Load8U (0, 0);
528+
I32Eqz; BrIf 1; (* exit on 0 byte *)
529+
LocalGet scratch_local; I32Const 1l; I32Add;
530+
LocalSet scratch_local;
531+
Br 0
532+
])
533+
]);
534+
(* length = cursor - src (excludes the null terminator).
535+
Stash it back into count_local, which we are done with. *)
536+
LocalGet scratch_local; LocalGet src_local; I32Sub;
537+
LocalSet count_local;
538+
539+
(* --- Phase 6: allocate (4 + length) for the AS string --- *)
540+
GlobalGet heap_ptr_global;
541+
LocalSet result_local;
542+
GlobalGet heap_ptr_global;
543+
I32Const 4l; LocalGet count_local; I32Add;
544+
I32Add;
545+
GlobalSet heap_ptr_global;
546+
547+
(* Store length at result+0. *)
548+
LocalGet result_local;
549+
LocalGet count_local;
550+
I32Store (2, 0);
551+
552+
(* --- Phase 7: byte-copy src..src+length -> result+4 ---
553+
Reuses scratch as src cursor and count_local as the loop count. *)
554+
LocalGet src_local; LocalSet scratch_local;
555+
LocalGet result_local; I32Const 4l; I32Add; LocalSet dst_local;
556+
Block (BtEmpty, [
557+
Loop (BtEmpty, [
558+
LocalGet count_local; I32Eqz; BrIf 1;
559+
LocalGet dst_local;
560+
LocalGet scratch_local; I32Load8U (0, 0);
561+
I32Store8 (0, 0);
562+
LocalGet scratch_local; I32Const 1l; I32Add; LocalSet scratch_local;
563+
LocalGet dst_local; I32Const 1l; I32Add; LocalSet dst_local;
564+
LocalGet count_local; I32Const 1l; I32Sub; LocalSet count_local;
565+
Br 0
566+
])
567+
]);
568+
569+
(* --- Result: leave the string pointer on the stack. --- *)
570+
LocalGet result_local;
571+
]

lib/wasm.ml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,25 @@ type instr =
5151
| I64Load of int * int
5252
| F32Load of int * int
5353
| F64Load of int * int
54+
| I32Load8S of int * int (** load 1 byte, sign-extend to i32 *)
55+
| I32Load8U of int * int (** load 1 byte, zero-extend to i32 *)
56+
| I32Load16S of int * int (** load 2 bytes, sign-extend to i32 *)
57+
| I32Load16U of int * int (** load 2 bytes, zero-extend to i32 *)
58+
| I64Load8S of int * int
59+
| I64Load8U of int * int
60+
| I64Load16S of int * int
61+
| I64Load16U of int * int
62+
| I64Load32S of int * int
63+
| I64Load32U of int * int
5464
| I32Store of int * int
5565
| I64Store of int * int
5666
| F32Store of int * int
5767
| F64Store of int * int
68+
| I32Store8 of int * int (** store low 1 byte of i32 *)
69+
| I32Store16 of int * int (** store low 2 bytes of i32 *)
70+
| I64Store8 of int * int
71+
| I64Store16 of int * int
72+
| I64Store32 of int * int
5873
| MemorySize
5974
| MemoryGrow
6075

lib/wasm_encode.ml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,10 +123,25 @@ let rec add_instr buf = function
123123
| I64Load (align, offset) -> add_u8 buf 0x29; add_memarg buf align offset
124124
| F32Load (align, offset) -> add_u8 buf 0x2A; add_memarg buf align offset
125125
| F64Load (align, offset) -> add_u8 buf 0x2B; add_memarg buf align offset
126+
| I32Load8S (align, offset) -> add_u8 buf 0x2C; add_memarg buf align offset
127+
| I32Load8U (align, offset) -> add_u8 buf 0x2D; add_memarg buf align offset
128+
| I32Load16S (align, offset) -> add_u8 buf 0x2E; add_memarg buf align offset
129+
| I32Load16U (align, offset) -> add_u8 buf 0x2F; add_memarg buf align offset
130+
| I64Load8S (align, offset) -> add_u8 buf 0x30; add_memarg buf align offset
131+
| I64Load8U (align, offset) -> add_u8 buf 0x31; add_memarg buf align offset
132+
| I64Load16S (align, offset) -> add_u8 buf 0x32; add_memarg buf align offset
133+
| I64Load16U (align, offset) -> add_u8 buf 0x33; add_memarg buf align offset
134+
| I64Load32S (align, offset) -> add_u8 buf 0x34; add_memarg buf align offset
135+
| I64Load32U (align, offset) -> add_u8 buf 0x35; add_memarg buf align offset
126136
| I32Store (align, offset) -> add_u8 buf 0x36; add_memarg buf align offset
127137
| I64Store (align, offset) -> add_u8 buf 0x37; add_memarg buf align offset
128138
| F32Store (align, offset) -> add_u8 buf 0x38; add_memarg buf align offset
129139
| F64Store (align, offset) -> add_u8 buf 0x39; add_memarg buf align offset
140+
| I32Store8 (align, offset) -> add_u8 buf 0x3A; add_memarg buf align offset
141+
| I32Store16 (align, offset) -> add_u8 buf 0x3B; add_memarg buf align offset
142+
| I64Store8 (align, offset) -> add_u8 buf 0x3C; add_memarg buf align offset
143+
| I64Store16 (align, offset) -> add_u8 buf 0x3D; add_memarg buf align offset
144+
| I64Store32 (align, offset) -> add_u8 buf 0x3E; add_memarg buf align offset
130145
| MemorySize -> add_u8 buf 0x3F; add_u8 buf 0x00
131146
| MemoryGrow -> add_u8 buf 0x40; add_u8 buf 0x00
132147
| I32Const v -> add_u8 buf 0x41; add_sleb32 buf v

tests/codegen/arg_at.affine

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// SPDX-License-Identifier: MPL-2.0
2+
// ADR-015 S5 (#180): arg_at(i) smoke. Same shape as env_at; differs
3+
// only in the underlying WASI import (`args_sizes_get` + `args_get`).
4+
// Indexing 1 (not 0) exercises the `n_local * 4` pointer-vector
5+
// offset path that env_at(0) would short-circuit.
6+
pub fn main() -> Int / { Time } {
7+
string_length(arg_at(1))
8+
}

tests/codegen/env_at.affine

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// SPDX-License-Identifier: MPL-2.0
2+
// ADR-015 S5 (#180): env_at(i) smoke. Lowers to a `*_sizes_get` +
3+
// `environ_get` pair, then byte-scans the i-th entry to compute
4+
// length and byte-copies it into a length-prefixed AS string.
5+
// Returning `string_length` proves the scan terminated at the
6+
// host-written null terminator at the expected offset.
7+
pub fn main() -> Int / { Time } {
8+
string_length(env_at(0))
9+
}

0 commit comments

Comments
 (0)