3131//! emitted whenever the source declares discipline (`own` / `&mut` /
3232//! `&` qualifiers) — the parser records them into [`Module::ownership`]
3333//! and `emit` writes the carrier (exercised by `tests/example03.rs`).
34- //! * Full function-body semantics (indexing, `region.scan`, null checks):
35- //! v0 emits type-correct representative bodies, not the full lowering.
34+ //! * Function bodies lower for real where the statement lowerer covers
35+ //! them (`let`, assignment, `if`/`else`, `while`, indexed access,
36+ //! `cast<>` — see `parser.rs`); `region.scan` closures and `opt<T>`
37+ //! null-checks still fall back to type-correct representative stubs.
3638
3739use typed_wasm_verify:: section:: {
3840 build_access_sites_section_payload, AccessSiteEntry , ACCESS_SITE_UNPINNED , NO_TARGET_REGION ,
@@ -47,7 +49,7 @@ use typed_wasm_verify::{
4749 REGION_IMPORTS_SECTION_NAME ,
4850} ;
4951use wasm_encoder:: {
50- CodeSection , CustomSection , EntityType , ExportKind , ExportSection , Function , FunctionSection ,
52+ BlockType , CodeSection , CustomSection , EntityType , ExportKind , ExportSection , Function , FunctionSection ,
5153 ImportSection , Instruction , MemArg , MemorySection , MemoryType , Module as WasmModule ,
5254 NameMap , NameSection , TypeSection , ValType ,
5355} ;
@@ -239,6 +241,61 @@ pub enum Op {
239241 Call ( u32 ) ,
240242 /// Drop the top stack value — consumes a value exactly once.
241243 Drop ,
244+ // --- Locals + control flow + arithmetic (front-end statement
245+ // lowering, ADR-0006: `let`, assignment, `if`/`else`, `while`) ---
246+ LocalSet ( u32 ) ,
247+ LocalTee ( u32 ) ,
248+ /// `block (empty)` — the `while` exit target.
249+ Block ,
250+ /// `loop (empty)` — the `while` back-edge target.
251+ Loop ,
252+ /// `if (empty)` — statement-position conditional (no result value).
253+ If ,
254+ Else ,
255+ End ,
256+ Br ( u32 ) ,
257+ BrIf ( u32 ) ,
258+ Return ,
259+ I32Eqz ,
260+ I32Add ,
261+ I32Sub ,
262+ I32Mul ,
263+ I32DivS ,
264+ I32Eq ,
265+ I32Ne ,
266+ I32LtS ,
267+ I32LeS ,
268+ I32GtS ,
269+ I32GeS ,
270+ I64Add ,
271+ I64Sub ,
272+ I64Mul ,
273+ F32Add ,
274+ F32Sub ,
275+ F32Mul ,
276+ F32Div ,
277+ F32Eq ,
278+ F32Ne ,
279+ F32Lt ,
280+ F32Le ,
281+ F32Gt ,
282+ F32Ge ,
283+ F64Add ,
284+ F64Sub ,
285+ F64Mul ,
286+ F64Div ,
287+ F64Eq ,
288+ F64Ne ,
289+ F64Lt ,
290+ F64Le ,
291+ F64Gt ,
292+ F64Ge ,
293+ /// `cast<i32>(f32/f64 expr)` — saturating-free truncation (traps on
294+ /// NaN/overflow, matching wasm core `i32.trunc_f*_s`).
295+ I32TruncF32S ,
296+ I32TruncF64S ,
297+ F32ConvertI32S ,
298+ F64ConvertI32S ,
242299}
243300
244301/// Ownership discipline for a function parameter, emitted into the
@@ -297,6 +354,11 @@ pub struct Func {
297354 pub name : String ,
298355 pub params : Vec < Wty > ,
299356 pub results : Vec < Wty > ,
357+ /// Extra (non-param) locals, indexed after the params. Statement
358+ /// lowering allocates these for `let` bindings, `region.get`
359+ /// destinations, and the per-handle base-pointer copies that keep
360+ /// L7/L10 param-use counts at exactly one.
361+ pub locals : Vec < Wty > ,
300362 pub body : Vec < Op > ,
301363 pub accesses : Vec < AccessSite > ,
302364 pub export : bool ,
@@ -399,6 +461,54 @@ fn op_to_instruction(op: Op) -> Instruction<'static> {
399461 Op :: F64Store { offset } => Instruction :: F64Store ( memarg ( offset, 3 ) ) ,
400462 Op :: Call ( i) => Instruction :: Call ( i) ,
401463 Op :: Drop => Instruction :: Drop ,
464+ Op :: LocalSet ( i) => Instruction :: LocalSet ( i) ,
465+ Op :: LocalTee ( i) => Instruction :: LocalTee ( i) ,
466+ Op :: Block => Instruction :: Block ( BlockType :: Empty ) ,
467+ Op :: Loop => Instruction :: Loop ( BlockType :: Empty ) ,
468+ Op :: If => Instruction :: If ( BlockType :: Empty ) ,
469+ Op :: Else => Instruction :: Else ,
470+ Op :: End => Instruction :: End ,
471+ Op :: Br ( d) => Instruction :: Br ( d) ,
472+ Op :: BrIf ( d) => Instruction :: BrIf ( d) ,
473+ Op :: Return => Instruction :: Return ,
474+ Op :: I32Eqz => Instruction :: I32Eqz ,
475+ Op :: I32Add => Instruction :: I32Add ,
476+ Op :: I32Sub => Instruction :: I32Sub ,
477+ Op :: I32Mul => Instruction :: I32Mul ,
478+ Op :: I32DivS => Instruction :: I32DivS ,
479+ Op :: I32Eq => Instruction :: I32Eq ,
480+ Op :: I32Ne => Instruction :: I32Ne ,
481+ Op :: I32LtS => Instruction :: I32LtS ,
482+ Op :: I32LeS => Instruction :: I32LeS ,
483+ Op :: I32GtS => Instruction :: I32GtS ,
484+ Op :: I32GeS => Instruction :: I32GeS ,
485+ Op :: I64Add => Instruction :: I64Add ,
486+ Op :: I64Sub => Instruction :: I64Sub ,
487+ Op :: I64Mul => Instruction :: I64Mul ,
488+ Op :: F32Add => Instruction :: F32Add ,
489+ Op :: F32Sub => Instruction :: F32Sub ,
490+ Op :: F32Mul => Instruction :: F32Mul ,
491+ Op :: F32Div => Instruction :: F32Div ,
492+ Op :: F32Eq => Instruction :: F32Eq ,
493+ Op :: F32Ne => Instruction :: F32Ne ,
494+ Op :: F32Lt => Instruction :: F32Lt ,
495+ Op :: F32Le => Instruction :: F32Le ,
496+ Op :: F32Gt => Instruction :: F32Gt ,
497+ Op :: F32Ge => Instruction :: F32Ge ,
498+ Op :: F64Add => Instruction :: F64Add ,
499+ Op :: F64Sub => Instruction :: F64Sub ,
500+ Op :: F64Mul => Instruction :: F64Mul ,
501+ Op :: F64Div => Instruction :: F64Div ,
502+ Op :: F64Eq => Instruction :: F64Eq ,
503+ Op :: F64Ne => Instruction :: F64Ne ,
504+ Op :: F64Lt => Instruction :: F64Lt ,
505+ Op :: F64Le => Instruction :: F64Le ,
506+ Op :: F64Gt => Instruction :: F64Gt ,
507+ Op :: F64Ge => Instruction :: F64Ge ,
508+ Op :: I32TruncF32S => Instruction :: I32TruncF32S ,
509+ Op :: I32TruncF64S => Instruction :: I32TruncF64S ,
510+ Op :: F32ConvertI32S => Instruction :: F32ConvertI32S ,
511+ Op :: F64ConvertI32S => Instruction :: F64ConvertI32S ,
402512 }
403513}
404514
@@ -443,7 +553,8 @@ pub fn emit(module: &Module) -> Vec<u8> {
443553 let global_idx = import_count + local_i as u32 ;
444554 funcs. function ( global_idx) ;
445555
446- let mut f = Function :: new ( [ ] ) ;
556+ let mut f =
557+ Function :: new_with_locals_types ( func. locals . iter ( ) . map ( |l| l. to_val_type ( ) ) ) ;
447558 for op in & func. body {
448559 f. instruction ( & op_to_instruction ( * op) ) ;
449560 }
@@ -606,6 +717,7 @@ pub fn example01() -> Module {
606717 name: "get_player_hp" . into( ) ,
607718 params: vec![ Wty :: I32 , Wty :: I32 ] ,
608719 results: vec![ Wty :: I32 ] ,
720+ locals: vec![ ] ,
609721 body: vec![ Op :: LocalGet ( 0 ) , Op :: I32Load { offset: 0 } ] ,
610722 accesses: vec![ AccessSite {
611723 region: 1 ,
@@ -619,6 +731,7 @@ pub fn example01() -> Module {
619731 name: "damage_player" . into( ) ,
620732 params: vec![ Wty :: I32 , Wty :: I32 , Wty :: I32 ] ,
621733 results: vec![ ] ,
734+ locals: vec![ ] ,
622735 body: vec![ Op :: LocalGet ( 0 ) , Op :: LocalGet ( 2 ) , Op :: I32Store { offset: 0 } ] ,
623736 accesses: vec![ AccessSite {
624737 region: 1 ,
@@ -633,6 +746,7 @@ pub fn example01() -> Module {
633746 name: "get_enemy_target_hp" . into( ) ,
634747 params: vec![ Wty :: I32 , Wty :: I32 , Wty :: I32 ] ,
635748 results: vec![ Wty :: I32 ] ,
749+ locals: vec![ ] ,
636750 body: vec![ Op :: LocalGet ( 0 ) , Op :: I32Load { offset: 0 } ] ,
637751 accesses: vec![
638752 AccessSite {
@@ -653,6 +767,7 @@ pub fn example01() -> Module {
653767 name: "count_active_enemies" . into( ) ,
654768 params: vec![ Wty :: I32 ] ,
655769 results: vec![ Wty :: I32 ] ,
770+ locals: vec![ ] ,
656771 body: vec![ Op :: I32Const ( 0 ) ] ,
657772 accesses: vec![ AccessSite {
658773 region: 2 ,
@@ -666,6 +781,7 @@ pub fn example01() -> Module {
666781 name: "move_player" . into( ) ,
667782 params: vec![ Wty :: I32 , Wty :: I32 , Wty :: F32 , Wty :: F32 ] ,
668783 results: vec![ ] ,
784+ locals: vec![ ] ,
669785 body: vec![
670786 Op :: LocalGet ( 0 ) ,
671787 Op :: LocalGet ( 2 ) ,
@@ -746,6 +862,7 @@ pub fn paint_type_tile() -> Module {
746862 name: "alloc_tile" . into( ) ,
747863 params: vec![ Wty :: I32 , Wty :: I32 ] ,
748864 results: vec![ Wty :: I32 ] ,
865+ locals: vec![ ] ,
749866 body: vec![ Op :: LocalGet ( 0 ) , Op :: Drop , Op :: I32Const ( 0 ) ] ,
750867 accesses: vec![ ] ,
751868 export: true ,
@@ -756,6 +873,7 @@ pub fn paint_type_tile() -> Module {
756873 name: "free_tile" . into( ) ,
757874 params: vec![ Wty :: I32 ] ,
758875 results: vec![ ] ,
876+ locals: vec![ ] ,
759877 body: vec![ Op :: LocalGet ( 0 ) , Op :: Drop ] ,
760878 accesses: vec![ ] ,
761879 export: true ,
@@ -766,6 +884,7 @@ pub fn paint_type_tile() -> Module {
766884 name: "fill_tile" . into( ) ,
767885 params: vec![ Wty :: I32 , Wty :: I32 , Wty :: I32 , Wty :: I32 , Wty :: I32 ] ,
768886 results: vec![ ] ,
887+ locals: vec![ ] ,
769888 body: vec![
770889 Op :: LocalGet ( 0 ) , Op :: Drop ,
771890 Op :: LocalGet ( 1 ) , Op :: Drop ,
@@ -784,6 +903,7 @@ pub fn paint_type_tile() -> Module {
784903 name: "read_pixel" . into( ) ,
785904 params: vec![ Wty :: I32 , Wty :: I32 , Wty :: I32 ] ,
786905 results: vec![ Wty :: I32 ] ,
906+ locals: vec![ ] ,
787907 body: vec![
788908 Op :: LocalGet ( 0 ) , Op :: Drop ,
789909 Op :: LocalGet ( 1 ) , Op :: Drop ,
@@ -800,6 +920,7 @@ pub fn paint_type_tile() -> Module {
800920 name: "write_pixel" . into( ) ,
801921 params: vec![ Wty :: I32 , Wty :: I32 , Wty :: I32 , Wty :: I32 , Wty :: I32 , Wty :: I32 , Wty :: I32 ] ,
802922 results: vec![ ] ,
923+ locals: vec![ ] ,
803924 body: vec![
804925 Op :: LocalGet ( 0 ) , Op :: Drop ,
805926 Op :: LocalGet ( 1 ) , Op :: Drop ,
@@ -820,6 +941,7 @@ pub fn paint_type_tile() -> Module {
820941 name: "blit_tile" . into( ) ,
821942 params: vec![ Wty :: I32 , Wty :: I32 ] ,
822943 results: vec![ ] ,
944+ locals: vec![ ] ,
823945 body: vec![
824946 Op :: LocalGet ( 0 ) , Op :: Drop ,
825947 Op :: LocalGet ( 1 ) , Op :: Drop ,
@@ -893,6 +1015,7 @@ pub fn paint_type_layer() -> Module {
8931015 name: "stack_new" . into( ) ,
8941016 params: vec![ ] ,
8951017 results: vec![ Wty :: I32 ] ,
1018+ locals: vec![ ] ,
8961019 body: vec![ Op :: I32Const ( 0 ) ] ,
8971020 accesses: vec![ ] ,
8981021 export: true ,
@@ -902,6 +1025,7 @@ pub fn paint_type_layer() -> Module {
9021025 name: "stack_free" . into( ) ,
9031026 params: vec![ Wty :: I32 ] ,
9041027 results: vec![ ] ,
1028+ locals: vec![ ] ,
9051029 body: vec![ Op :: LocalGet ( 0 ) , Op :: Drop ] ,
9061030 accesses: vec![ ] ,
9071031 export: true ,
@@ -911,6 +1035,7 @@ pub fn paint_type_layer() -> Module {
9111035 name: "push_layer" . into( ) ,
9121036 params: vec![ Wty :: I32 , Wty :: I32 , Wty :: I32 ] ,
9131037 results: vec![ Wty :: I32 ] ,
1038+ locals: vec![ ] ,
9141039 body: vec![
9151040 Op :: LocalGet ( 0 ) , Op :: Drop ,
9161041 Op :: LocalGet ( 1 ) , Op :: Drop ,
@@ -929,6 +1054,7 @@ pub fn paint_type_layer() -> Module {
9291054 name: "get_id_at" . into( ) ,
9301055 params: vec![ Wty :: I32 , Wty :: I32 ] ,
9311056 results: vec![ Wty :: I32 ] ,
1057+ locals: vec![ ] ,
9321058 body: vec![
9331059 Op :: LocalGet ( 0 ) , Op :: Drop ,
9341060 Op :: LocalGet ( 1 ) , Op :: Drop ,
@@ -944,6 +1070,7 @@ pub fn paint_type_layer() -> Module {
9441070 name: "set_opacity" . into( ) ,
9451071 params: vec![ Wty :: I32 , Wty :: I32 , Wty :: I32 ] ,
9461072 results: vec![ Wty :: I32 ] ,
1073+ locals: vec![ ] ,
9471074 body: vec![
9481075 Op :: LocalGet ( 0 ) , Op :: Drop ,
9491076 Op :: LocalGet ( 1 ) , Op :: Drop ,
@@ -1003,6 +1130,7 @@ pub fn multimodule_callee() -> Module {
10031130 name: "consume" . into( ) ,
10041131 params: vec![ Wty :: I32 ] ,
10051132 results: vec![ ] ,
1133+ locals: vec![ ] ,
10061134 body: vec![ Op :: LocalGet ( 0 ) , Op :: Drop ] , // uses the Linear param once
10071135 accesses: vec![ ] ,
10081136 export: true ,
@@ -1035,6 +1163,7 @@ pub fn multimodule_caller(call_count: u32) -> Module {
10351163 name: "use_resource" . into( ) ,
10361164 params: vec![ Wty :: I32 ] ,
10371165 results: vec![ ] ,
1166+ locals: vec![ ] ,
10381167 body,
10391168 accesses: vec![ ] ,
10401169 export: false ,
0 commit comments