Skip to main content

pepl_codegen/
space.rs

1//! Space-level code generation.
2//!
3//! Generates the top-level exported functions:
4//! - `init()` — initialise state to defaults (gas limit set to default constant)
5//! - `dispatch_action(action_id: i32, payload_ptr: i32, payload_len: i32)` — run an action
6//! - `render(view_id: i32) -> i32` — render a view to Surface tree
7//! - `get_state() -> i32` — return current state as a record value ptr
8//! - Conditionally: `update(dt_ptr: i32)`, `handle_event(event_ptr: i32)`
9
10use pepl_types::ast::*;
11use wasm_encoder::{BlockType, Function, Instruction, ValType};
12
13use crate::compiler::FuncContext;
14use crate::error::CodegenResult;
15use crate::expr::emit_expr;
16use crate::gas;
17use crate::runtime::*;
18use crate::stmt::emit_stmts;
19use crate::types::*;
20
21// ══════════════════════════════════════════════════════════════════════════════
22// init
23// ══════════════════════════════════════════════════════════════════════════════
24
25/// Default gas limit when init is called without parameters.
26const DEFAULT_GAS_LIMIT: i32 = 1_000_000;
27
28/// Emit the `init()` function (parameterless).
29///
30/// - Sets global gas_limit to DEFAULT_GAS_LIMIT
31/// - Resets gas counter to 0
32/// - Evaluates each state field's default expression
33/// - Builds the state record
34/// - Evaluates derived fields
35pub fn emit_init(
36    state: &StateBlock,
37    derived: Option<&DerivedBlock>,
38    ctx: &mut FuncContext,
39    f: &mut Function,
40) -> CodegenResult<()> {
41    // gas_limit = DEFAULT_GAS_LIMIT (no param — parameterless init)
42    f.instruction(&Instruction::I32Const(DEFAULT_GAS_LIMIT));
43    f.instruction(&Instruction::GlobalSet(GLOBAL_GAS_LIMIT));
44    // gas = 0
45    f.instruction(&Instruction::I32Const(0));
46    f.instruction(&Instruction::GlobalSet(GLOBAL_GAS));
47
48    // Build state record from defaults
49    let field_count = state.fields.len();
50    let entries_local = ctx.alloc_local(ValType::I32);
51
52    f.instruction(&Instruction::I32Const((field_count * 12) as i32));
53    f.instruction(&Instruction::Call(rt_func_idx(RT_ALLOC)));
54    f.instruction(&Instruction::LocalSet(entries_local));
55
56    for (i, field) in state.fields.iter().enumerate() {
57        let (key_ptr, key_len) = ctx.intern_string(&field.name.name);
58        let val_local = ctx.alloc_local(ValType::I32);
59        let base = (i * 12) as u64;
60
61        // Evaluate default
62        emit_expr(&field.default, ctx, f)?;
63        f.instruction(&Instruction::LocalSet(val_local));
64
65        // key_offset
66        f.instruction(&Instruction::LocalGet(entries_local));
67        f.instruction(&Instruction::I32Const(key_ptr as i32));
68        f.instruction(&Instruction::I32Store(memarg(base, 2)));
69        // key_len
70        f.instruction(&Instruction::LocalGet(entries_local));
71        f.instruction(&Instruction::I32Const(key_len as i32));
72        f.instruction(&Instruction::I32Store(memarg(base + 4, 2)));
73        // value_ptr
74        f.instruction(&Instruction::LocalGet(entries_local));
75        f.instruction(&Instruction::LocalGet(val_local));
76        f.instruction(&Instruction::I32Store(memarg(base + 8, 2)));
77    }
78
79    // state_ptr = record(entries, count)
80    f.instruction(&Instruction::LocalGet(entries_local));
81    f.instruction(&Instruction::I32Const(field_count as i32));
82    f.instruction(&Instruction::Call(rt_func_idx(RT_VAL_RECORD)));
83    f.instruction(&Instruction::GlobalSet(GLOBAL_STATE_PTR));
84
85    // Recompute derived fields (if any)
86    if let Some(derived_block) = derived {
87        emit_recompute_derived(derived_block, ctx, f)?;
88    }
89
90    f.instruction(&Instruction::End);
91    Ok(())
92}
93
94// ══════════════════════════════════════════════════════════════════════════════
95// dispatch_action
96// ══════════════════════════════════════════════════════════════════════════════
97
98/// Emit the `dispatch_action(action_id: i32, payload_ptr: i32, payload_len: i32)` function.
99///
100/// Dispatches to the appropriate action handler based on action_id.
101/// `payload_ptr` (param 1) is a pointer to the arguments list value.
102/// `payload_len` (param 2) is reserved for future use (byte length of serialised payload).
103/// Checks invariants after execution and rolls back on failure.
104/// Returns void.
105pub fn emit_dispatch_action(
106    actions: &[ActionDecl],
107    invariants: &[InvariantDecl],
108    derived: Option<&DerivedBlock>,
109    ctx: &mut FuncContext,
110    f: &mut Function,
111) -> CodegenResult<()> {
112    // Save state snapshot for rollback
113    let snapshot_local = ctx.alloc_local(ValType::I32);
114    f.instruction(&Instruction::GlobalGet(GLOBAL_STATE_PTR));
115    f.instruction(&Instruction::LocalSet(snapshot_local));
116
117    // Reset gas counter for this dispatch
118    f.instruction(&Instruction::I32Const(0));
119    f.instruction(&Instruction::GlobalSet(GLOBAL_GAS));
120
121    // Switch on action_id (param 0)
122    // We emit a chain of if/else: if action_id == 0 { ... } else if == 1 { ... } ...
123    f.instruction(&Instruction::Block(BlockType::Empty)); // outer break target
124
125    for (i, action) in actions.iter().enumerate() {
126        f.instruction(&Instruction::LocalGet(0)); // action_id (param 0)
127        f.instruction(&Instruction::I32Const(i as i32));
128        f.instruction(&Instruction::I32Eq);
129        f.instruction(&Instruction::If(BlockType::Empty));
130
131        // Bind action parameters from payload_ptr (param 1)
132        // payload_ptr is a PEPL list value — extract elements by index
133        // param 2 (payload_len) reserved for future use
134        for (pi, param) in action.params.iter().enumerate() {
135            let param_local = ctx.alloc_local(ValType::I32);
136            f.instruction(&Instruction::LocalGet(1)); // payload_ptr
137            f.instruction(&Instruction::I32Const(pi as i32));
138            f.instruction(&Instruction::Call(rt_func_idx(RT_VAL_LIST_GET)));
139            f.instruction(&Instruction::LocalSet(param_local));
140            ctx.push_local(&param.name.name, param_local);
141        }
142
143        // Execute action body
144        emit_stmts(&action.body.stmts, ctx, f)?;
145
146        // Pop param bindings
147        for param in action.params.iter().rev() {
148            ctx.pop_local(&param.name.name);
149        }
150
151        f.instruction(&Instruction::Br(0)); // break to outer
152        f.instruction(&Instruction::End); // end if
153    }
154
155    f.instruction(&Instruction::End); // end outer block
156
157    // Recompute derived fields
158    if let Some(derived_block) = derived {
159        emit_recompute_derived(derived_block, ctx, f)?;
160    }
161
162    // Check invariants — if any fail, rollback
163    for inv in invariants {
164        emit_expr(&inv.condition, ctx, f)?;
165        f.instruction(&Instruction::I32Load(memarg(4, 2)));
166        f.instruction(&Instruction::I32Eqz);
167        f.instruction(&Instruction::If(BlockType::Empty));
168        // Rollback: restore snapshot
169        f.instruction(&Instruction::LocalGet(snapshot_local));
170        f.instruction(&Instruction::GlobalSet(GLOBAL_STATE_PTR));
171        // Trap with invariant name
172        let (ptr, len) = ctx.intern_string(&format!("invariant violated: {}", inv.name.name));
173        f.instruction(&Instruction::I32Const(ptr as i32));
174        f.instruction(&Instruction::I32Const(len as i32));
175        f.instruction(&Instruction::Call(IMPORT_TRAP));
176        f.instruction(&Instruction::Unreachable);
177        f.instruction(&Instruction::End);
178    }
179
180    // void return — no value pushed
181    f.instruction(&Instruction::End);
182    Ok(())
183}
184
185// ══════════════════════════════════════════════════════════════════════════════
186// render
187// ══════════════════════════════════════════════════════════════════════════════
188
189/// Emit the `render(view_id: i32) -> i32` function.
190///
191/// Evaluates the specified view and returns a serialized Surface tree
192/// as a record value.
193pub fn emit_render(
194    views: &[ViewDecl],
195    ctx: &mut FuncContext,
196    f: &mut Function,
197) -> CodegenResult<()> {
198    let result_local = ctx.alloc_local(ValType::I32);
199    f.instruction(&Instruction::Call(rt_func_idx(RT_VAL_NIL)));
200    f.instruction(&Instruction::LocalSet(result_local));
201
202    f.instruction(&Instruction::Block(BlockType::Empty));
203
204    for (i, view) in views.iter().enumerate() {
205        f.instruction(&Instruction::LocalGet(0)); // view_id
206        f.instruction(&Instruction::I32Const(i as i32));
207        f.instruction(&Instruction::I32Eq);
208        f.instruction(&Instruction::If(BlockType::Empty));
209
210        // Bind view params (if any — usually views are parameterless in main render)
211        for param in view.params.iter() {
212            let param_local = ctx.alloc_local(ValType::I32);
213            f.instruction(&Instruction::Call(rt_func_idx(RT_VAL_NIL)));
214            f.instruction(&Instruction::LocalSet(param_local));
215            ctx.push_local(&param.name.name, param_local);
216        }
217
218        // Emit UI block → Surface node tree
219        emit_ui_block(&view.body, ctx, f)?;
220        f.instruction(&Instruction::LocalSet(result_local));
221
222        // Pop param bindings
223        for param in view.params.iter().rev() {
224            ctx.pop_local(&param.name.name);
225        }
226
227        f.instruction(&Instruction::Br(0));
228        f.instruction(&Instruction::End);
229    }
230
231    f.instruction(&Instruction::End);
232
233    f.instruction(&Instruction::LocalGet(result_local));
234    f.instruction(&Instruction::End);
235    Ok(())
236}
237
238/// Emit a UI block → record value representing the Surface tree.
239///
240/// Each component becomes a record: `{ component: "Name", props: {...}, children: [...] }`
241fn emit_ui_block(block: &UIBlock, ctx: &mut FuncContext, f: &mut Function) -> CodegenResult<()> {
242    // Build a list of surface nodes
243    let count = block.elements.len();
244    if count == 0 {
245        f.instruction(&Instruction::I32Const(0));
246        f.instruction(&Instruction::I32Const(0));
247        f.instruction(&Instruction::Call(rt_func_idx(RT_VAL_LIST)));
248        return Ok(());
249    }
250
251    let arr_local = ctx.alloc_local(ValType::I32);
252    f.instruction(&Instruction::I32Const((count * 4) as i32));
253    f.instruction(&Instruction::Call(rt_func_idx(RT_ALLOC)));
254    f.instruction(&Instruction::LocalSet(arr_local));
255
256    for (i, elem) in block.elements.iter().enumerate() {
257        let node_local = ctx.alloc_local(ValType::I32);
258        emit_ui_element(elem, ctx, f)?;
259        f.instruction(&Instruction::LocalSet(node_local));
260        f.instruction(&Instruction::LocalGet(arr_local));
261        f.instruction(&Instruction::LocalGet(node_local));
262        f.instruction(&Instruction::I32Store(memarg(i as u64 * 4, 2)));
263    }
264
265    f.instruction(&Instruction::LocalGet(arr_local));
266    f.instruction(&Instruction::I32Const(count as i32));
267    f.instruction(&Instruction::Call(rt_func_idx(RT_VAL_LIST)));
268    Ok(())
269}
270
271/// Emit a single UI element → surface node record.
272fn emit_ui_element(elem: &UIElement, ctx: &mut FuncContext, f: &mut Function) -> CodegenResult<()> {
273    match elem {
274        UIElement::Component(comp) => emit_component_expr(comp, ctx, f),
275        UIElement::Let(let_bind) => {
276            crate::stmt::emit_stmt(&Stmt::Let(let_bind.clone()), ctx, f)?;
277            f.instruction(&Instruction::Call(rt_func_idx(RT_VAL_NIL)));
278            Ok(())
279        }
280        UIElement::If(ui_if) => emit_ui_if(ui_if, ctx, f),
281        UIElement::For(ui_for) => emit_ui_for(ui_for, ctx, f),
282    }
283}
284
285/// Emit a component expression → surface record.
286///
287/// Produces: `{ component: "Name", props: { ... }, children: [...] }`
288fn emit_component_expr(
289    comp: &ComponentExpr,
290    ctx: &mut FuncContext,
291    f: &mut Function,
292) -> CodegenResult<()> {
293    // We build a 3-field record: component, props, children
294    let entries_local = ctx.alloc_local(ValType::I32);
295    f.instruction(&Instruction::I32Const(3 * 12)); // 3 fields × 12 bytes
296    f.instruction(&Instruction::Call(rt_func_idx(RT_ALLOC)));
297    f.instruction(&Instruction::LocalSet(entries_local));
298
299    // Field 0: "component" = "CompName"
300    let (ck_ptr, ck_len) = ctx.intern_string("component");
301    let (cn_ptr, cn_len) = ctx.intern_string(&comp.name.name);
302    let comp_val = ctx.alloc_local(ValType::I32);
303    f.instruction(&Instruction::I32Const(cn_ptr as i32));
304    f.instruction(&Instruction::I32Const(cn_len as i32));
305    f.instruction(&Instruction::Call(rt_func_idx(RT_VAL_STRING)));
306    f.instruction(&Instruction::LocalSet(comp_val));
307
308    f.instruction(&Instruction::LocalGet(entries_local));
309    f.instruction(&Instruction::I32Const(ck_ptr as i32));
310    f.instruction(&Instruction::I32Store(memarg(0, 2)));
311    f.instruction(&Instruction::LocalGet(entries_local));
312    f.instruction(&Instruction::I32Const(ck_len as i32));
313    f.instruction(&Instruction::I32Store(memarg(4, 2)));
314    f.instruction(&Instruction::LocalGet(entries_local));
315    f.instruction(&Instruction::LocalGet(comp_val));
316    f.instruction(&Instruction::I32Store(memarg(8, 2)));
317
318    // Field 1: "props" = record of prop assignments
319    let (pk_ptr, pk_len) = ctx.intern_string("props");
320    let props_val = ctx.alloc_local(ValType::I32);
321    emit_props(&comp.props, ctx, f)?;
322    f.instruction(&Instruction::LocalSet(props_val));
323
324    f.instruction(&Instruction::LocalGet(entries_local));
325    f.instruction(&Instruction::I32Const(pk_ptr as i32));
326    f.instruction(&Instruction::I32Store(memarg(12, 2)));
327    f.instruction(&Instruction::LocalGet(entries_local));
328    f.instruction(&Instruction::I32Const(pk_len as i32));
329    f.instruction(&Instruction::I32Store(memarg(16, 2)));
330    f.instruction(&Instruction::LocalGet(entries_local));
331    f.instruction(&Instruction::LocalGet(props_val));
332    f.instruction(&Instruction::I32Store(memarg(20, 2)));
333
334    // Field 2: "children" = list of child nodes
335    let (chk_ptr, chk_len) = ctx.intern_string("children");
336    let children_val = ctx.alloc_local(ValType::I32);
337    if let Some(children_block) = &comp.children {
338        emit_ui_block(children_block, ctx, f)?;
339    } else {
340        f.instruction(&Instruction::I32Const(0));
341        f.instruction(&Instruction::I32Const(0));
342        f.instruction(&Instruction::Call(rt_func_idx(RT_VAL_LIST)));
343    }
344    f.instruction(&Instruction::LocalSet(children_val));
345
346    f.instruction(&Instruction::LocalGet(entries_local));
347    f.instruction(&Instruction::I32Const(chk_ptr as i32));
348    f.instruction(&Instruction::I32Store(memarg(24, 2)));
349    f.instruction(&Instruction::LocalGet(entries_local));
350    f.instruction(&Instruction::I32Const(chk_len as i32));
351    f.instruction(&Instruction::I32Store(memarg(28, 2)));
352    f.instruction(&Instruction::LocalGet(entries_local));
353    f.instruction(&Instruction::LocalGet(children_val));
354    f.instruction(&Instruction::I32Store(memarg(32, 2)));
355
356    // Build the surface record
357    f.instruction(&Instruction::LocalGet(entries_local));
358    f.instruction(&Instruction::I32Const(3));
359    f.instruction(&Instruction::Call(rt_func_idx(RT_VAL_RECORD)));
360    Ok(())
361}
362
363/// Emit props as a record value.
364fn emit_props(props: &[PropAssign], ctx: &mut FuncContext, f: &mut Function) -> CodegenResult<()> {
365    if props.is_empty() {
366        f.instruction(&Instruction::I32Const(0));
367        f.instruction(&Instruction::I32Const(0));
368        f.instruction(&Instruction::Call(rt_func_idx(RT_VAL_RECORD)));
369        return Ok(());
370    }
371
372    let entries_local = ctx.alloc_local(ValType::I32);
373    f.instruction(&Instruction::I32Const((props.len() * 12) as i32));
374    f.instruction(&Instruction::Call(rt_func_idx(RT_ALLOC)));
375    f.instruction(&Instruction::LocalSet(entries_local));
376
377    for (i, prop) in props.iter().enumerate() {
378        let (key_ptr, key_len) = ctx.intern_string(&prop.name.name);
379        let val_local = ctx.alloc_local(ValType::I32);
380        emit_expr(&prop.value, ctx, f)?;
381        f.instruction(&Instruction::LocalSet(val_local));
382
383        let base = (i * 12) as u64;
384        f.instruction(&Instruction::LocalGet(entries_local));
385        f.instruction(&Instruction::I32Const(key_ptr as i32));
386        f.instruction(&Instruction::I32Store(memarg(base, 2)));
387        f.instruction(&Instruction::LocalGet(entries_local));
388        f.instruction(&Instruction::I32Const(key_len as i32));
389        f.instruction(&Instruction::I32Store(memarg(base + 4, 2)));
390        f.instruction(&Instruction::LocalGet(entries_local));
391        f.instruction(&Instruction::LocalGet(val_local));
392        f.instruction(&Instruction::I32Store(memarg(base + 8, 2)));
393    }
394
395    f.instruction(&Instruction::LocalGet(entries_local));
396    f.instruction(&Instruction::I32Const(props.len() as i32));
397    f.instruction(&Instruction::Call(rt_func_idx(RT_VAL_RECORD)));
398    Ok(())
399}
400
401fn emit_ui_if(ui_if: &UIIf, ctx: &mut FuncContext, f: &mut Function) -> CodegenResult<()> {
402    emit_expr(&ui_if.condition, ctx, f)?;
403    f.instruction(&Instruction::I32Load(memarg(4, 2)));
404    f.instruction(&Instruction::If(BlockType::Result(ValType::I32)));
405    emit_ui_block(&ui_if.then_block, ctx, f)?;
406    f.instruction(&Instruction::Else);
407    match &ui_if.else_block {
408        Some(UIElse::Block(block)) => emit_ui_block(block, ctx, f)?,
409        Some(UIElse::ElseIf(elif)) => emit_ui_if(elif, ctx, f)?,
410        None => {
411            f.instruction(&Instruction::I32Const(0));
412            f.instruction(&Instruction::I32Const(0));
413            f.instruction(&Instruction::Call(rt_func_idx(RT_VAL_LIST)));
414        }
415    }
416    f.instruction(&Instruction::End);
417    Ok(())
418}
419
420fn emit_ui_for(ui_for: &UIFor, ctx: &mut FuncContext, f: &mut Function) -> CodegenResult<()> {
421    // For loops in UI produce a list of surface nodes
422    let list_local = ctx.alloc_local(ValType::I32);
423    let arr_local = ctx.alloc_local(ValType::I32);
424    let count_local = ctx.alloc_local(ValType::I32);
425    let i_local = ctx.alloc_local(ValType::I32);
426    let result_arr = ctx.alloc_local(ValType::I32);
427
428    emit_expr(&ui_for.iterable, ctx, f)?;
429    f.instruction(&Instruction::LocalSet(list_local));
430
431    f.instruction(&Instruction::LocalGet(list_local));
432    f.instruction(&Instruction::I32Load(memarg(4, 2)));
433    f.instruction(&Instruction::LocalSet(arr_local));
434
435    f.instruction(&Instruction::LocalGet(list_local));
436    f.instruction(&Instruction::I32Load(memarg(8, 2)));
437    f.instruction(&Instruction::LocalSet(count_local));
438
439    // Allocate result array
440    f.instruction(&Instruction::LocalGet(count_local));
441    f.instruction(&Instruction::I32Const(4));
442    f.instruction(&Instruction::I32Mul);
443    f.instruction(&Instruction::Call(rt_func_idx(RT_ALLOC)));
444    f.instruction(&Instruction::LocalSet(result_arr));
445
446    f.instruction(&Instruction::I32Const(0));
447    f.instruction(&Instruction::LocalSet(i_local));
448
449    let item_local = ctx.alloc_local(ValType::I32);
450    let index_local = if ui_for.index.is_some() {
451        Some(ctx.alloc_local(ValType::I32))
452    } else {
453        None
454    };
455
456    ctx.push_local(&ui_for.item.name, item_local);
457    if let (Some(idx_ident), Some(idx_local)) = (&ui_for.index, index_local) {
458        ctx.push_local(&idx_ident.name, idx_local);
459    }
460
461    f.instruction(&Instruction::Block(BlockType::Empty));
462    f.instruction(&Instruction::Loop(BlockType::Empty));
463
464    f.instruction(&Instruction::LocalGet(i_local));
465    f.instruction(&Instruction::LocalGet(count_local));
466    f.instruction(&Instruction::I32GeU);
467    f.instruction(&Instruction::BrIf(1));
468
469    // item = arr[i]
470    f.instruction(&Instruction::LocalGet(arr_local));
471    f.instruction(&Instruction::LocalGet(i_local));
472    f.instruction(&Instruction::I32Const(4));
473    f.instruction(&Instruction::I32Mul);
474    f.instruction(&Instruction::I32Add);
475    f.instruction(&Instruction::I32Load(memarg(0, 2)));
476    f.instruction(&Instruction::LocalSet(item_local));
477
478    if let Some(idx_local) = index_local {
479        f.instruction(&Instruction::I32Const(VALUE_SIZE as i32));
480        f.instruction(&Instruction::Call(rt_func_idx(RT_ALLOC)));
481        f.instruction(&Instruction::LocalTee(idx_local));
482        f.instruction(&Instruction::I32Const(TAG_NUMBER));
483        f.instruction(&Instruction::I32Store(memarg(0, 2)));
484        f.instruction(&Instruction::LocalGet(idx_local));
485        f.instruction(&Instruction::LocalGet(i_local));
486        f.instruction(&Instruction::F64ConvertI32U);
487        f.instruction(&Instruction::F64Store(memarg(4, 3)));
488    }
489
490    // Emit body → list of nodes
491    let body_result = ctx.alloc_local(ValType::I32);
492    emit_ui_block(&ui_for.body, ctx, f)?;
493    f.instruction(&Instruction::LocalSet(body_result));
494
495    // Store body_result at result_arr[i]
496    f.instruction(&Instruction::LocalGet(result_arr));
497    f.instruction(&Instruction::LocalGet(i_local));
498    f.instruction(&Instruction::I32Const(4));
499    f.instruction(&Instruction::I32Mul);
500    f.instruction(&Instruction::I32Add);
501    f.instruction(&Instruction::LocalGet(body_result));
502    f.instruction(&Instruction::I32Store(memarg(0, 2)));
503
504    // i += 1
505    f.instruction(&Instruction::LocalGet(i_local));
506    f.instruction(&Instruction::I32Const(1));
507    f.instruction(&Instruction::I32Add);
508    f.instruction(&Instruction::LocalSet(i_local));
509    f.instruction(&Instruction::Br(0));
510
511    f.instruction(&Instruction::End);
512    f.instruction(&Instruction::End);
513
514    ctx.pop_local(&ui_for.item.name);
515    if let Some(idx_ident) = &ui_for.index {
516        ctx.pop_local(&idx_ident.name);
517    }
518
519    f.instruction(&Instruction::LocalGet(result_arr));
520    f.instruction(&Instruction::LocalGet(count_local));
521    f.instruction(&Instruction::Call(rt_func_idx(RT_VAL_LIST)));
522    Ok(())
523}
524
525// ══════════════════════════════════════════════════════════════════════════════
526// get_state
527// ══════════════════════════════════════════════════════════════════════════════
528
529/// Emit the `get_state() -> i32` function — returns the current state record.
530pub fn emit_get_state(f: &mut Function) {
531    f.instruction(&Instruction::GlobalGet(GLOBAL_STATE_PTR));
532    f.instruction(&Instruction::End);
533}
534
535// ══════════════════════════════════════════════════════════════════════════════
536// update / handle_event
537// ══════════════════════════════════════════════════════════════════════════════
538
539/// Emit `update(dt_ptr: i32)` — param is a NUMBER value pointer.
540pub fn emit_update(
541    update_decl: &UpdateDecl,
542    derived: Option<&DerivedBlock>,
543    ctx: &mut FuncContext,
544    f: &mut Function,
545) -> CodegenResult<()> {
546    gas::emit_gas_tick(f, ctx.data.gas_exhausted_ptr, ctx.data.gas_exhausted_len);
547
548    // Bind dt param
549    let dt_local = ctx.alloc_local(ValType::I32);
550    f.instruction(&Instruction::LocalGet(0));
551    f.instruction(&Instruction::LocalSet(dt_local));
552    ctx.push_local(&update_decl.param.name.name, dt_local);
553
554    emit_stmts(&update_decl.body.stmts, ctx, f)?;
555
556    ctx.pop_local(&update_decl.param.name.name);
557
558    // Recompute derived
559    if let Some(derived_block) = derived {
560        emit_recompute_derived(derived_block, ctx, f)?;
561    }
562
563    f.instruction(&Instruction::End);
564    Ok(())
565}
566
567/// Emit `handle_event(event_ptr: i32)` — param is a record value pointer.
568pub fn emit_handle_event(
569    handle_event_decl: &HandleEventDecl,
570    derived: Option<&DerivedBlock>,
571    ctx: &mut FuncContext,
572    f: &mut Function,
573) -> CodegenResult<()> {
574    gas::emit_gas_tick(f, ctx.data.gas_exhausted_ptr, ctx.data.gas_exhausted_len);
575
576    let event_local = ctx.alloc_local(ValType::I32);
577    f.instruction(&Instruction::LocalGet(0));
578    f.instruction(&Instruction::LocalSet(event_local));
579    ctx.push_local(&handle_event_decl.param.name.name, event_local);
580
581    emit_stmts(&handle_event_decl.body.stmts, ctx, f)?;
582
583    ctx.pop_local(&handle_event_decl.param.name.name);
584
585    if let Some(derived_block) = derived {
586        emit_recompute_derived(derived_block, ctx, f)?;
587    }
588
589    f.instruction(&Instruction::End);
590    Ok(())
591}
592
593// ══════════════════════════════════════════════════════════════════════════════
594// Derived field recomputation
595// ══════════════════════════════════════════════════════════════════════════════
596
597/// Recompute all derived fields and update the state record.
598fn emit_recompute_derived(
599    derived: &DerivedBlock,
600    ctx: &mut FuncContext,
601    f: &mut Function,
602) -> CodegenResult<()> {
603    for field in &derived.fields {
604        let val_local = ctx.alloc_local(ValType::I32);
605        emit_expr(&field.value, ctx, f)?;
606        f.instruction(&Instruction::LocalSet(val_local));
607
608        // Update state record with the computed derived value
609        let field_name = field.name.name.clone();
610        crate::stmt::emit_state_field_set(&field_name, val_local, ctx, f)?;
611    }
612    Ok(())
613}
614
615// ══════════════════════════════════════════════════════════════════════════════
616// Helper
617// ══════════════════════════════════════════════════════════════════════════════
618
619fn memarg(offset: u64, align: u32) -> wasm_encoder::MemArg {
620    wasm_encoder::MemArg {
621        offset,
622        align,
623        memory_index: 0,
624    }
625}