Skip to main content

pepl_codegen/
runtime.rs

1//! Runtime helper functions emitted into the WASM module.
2//!
3//! These provide the value-manipulation primitives that expression and
4//! statement codegen builds upon.  Every function is registered during module
5//! assembly (in `compiler.rs`) and referenced by its function index.
6
7use wasm_encoder::{BlockType, Function, Instruction, ValType};
8
9use crate::types::*;
10
11// ══════════════════════════════════════════════════════════════════════════════
12// Runtime function index offsets (relative to IMPORT_COUNT)
13// ══════════════════════════════════════════════════════════════════════════════
14
15/// Bump-allocate `size` bytes; returns pointer.
16///
17/// `alloc(size: i32) -> i32`
18pub const RT_ALLOC: u32 = 0;
19
20/// Create a NIL value on the heap; returns pointer.
21///
22/// `val_nil() -> i32`
23pub const RT_VAL_NIL: u32 = 1;
24
25/// Create a NUMBER value from an f64; returns pointer.
26///
27/// `val_number(n: f64) -> i32`  — note: declared as (i32, i32) low/high
28/// Actually we pass the f64 via two i32 halves and reinterpret.
29/// Simpler approach: pass as i64 bits, but WASM MVP has i64.
30/// Simplest: store the f64 directly using f64.store.
31/// We'll use a helper that takes no args on the WASM stack — the caller
32/// pushes the f64 onto memory via `f64.store` beforehand.
33///
34/// Actually, let's keep it simple: `val_number()` allocates a cell,
35/// and the *caller* writes tag + f64 into that cell.
36/// We provide a helper: `make_number(bits_hi: i32, bits_lo: i32) -> i32`
37/// Or even simpler: emit inline alloc + store in expr.rs.
38///
39/// For the runtime we only expose `alloc` and the constructor helpers.
40pub const RT_VAL_NUMBER: u32 = 2;
41
42/// Create a BOOL value; `val_bool(b: i32) -> i32`
43pub const RT_VAL_BOOL: u32 = 3;
44
45/// Create a STRING value; `val_string(data_ptr: i32, len: i32) -> i32`
46pub const RT_VAL_STRING: u32 = 4;
47
48/// Create a LIST value; `val_list(arr_ptr: i32, count: i32) -> i32`
49pub const RT_VAL_LIST: u32 = 5;
50
51/// Create a RECORD value; `val_record(entries_ptr: i32, count: i32) -> i32`
52pub const RT_VAL_RECORD: u32 = 6;
53
54/// Create a VARIANT value; `val_variant(id: i32, data_ptr: i32) -> i32`
55pub const RT_VAL_VARIANT: u32 = 7;
56
57/// Create an ACTION_REF value; `val_action_ref(action_id: i32) -> i32`
58pub const RT_VAL_ACTION_REF: u32 = 8;
59
60/// Read the tag of a value; `val_tag(ptr: i32) -> i32`
61pub const RT_VAL_TAG: u32 = 9;
62
63/// Read the f64 payload from a NUMBER value; `val_get_number(ptr: i32) -> f64`
64/// We return the raw bits as (i32, i32) or just store to a scratch global.
65/// Actually WASM does support f64 on the value stack, so we can return f64.
66/// But our function type table only has i32 returns. We'll add a special type.
67pub const RT_VAL_GET_NUMBER: u32 = 10;
68
69/// Read the i32 payload word-1; `val_get_w1(ptr: i32) -> i32`
70pub const RT_VAL_GET_W1: u32 = 11;
71
72/// Read the i32 payload word-2; `val_get_w2(ptr: i32) -> i32`
73pub const RT_VAL_GET_W2: u32 = 12;
74
75/// Compare two values for structural equality; `val_eq(a: i32, b: i32) -> i32`
76pub const RT_VAL_EQ: u32 = 13;
77
78/// Convert a value to its string representation for interpolation.
79/// `val_to_string(ptr: i32) -> i32` (returns a STRING value ptr)
80pub const RT_VAL_TO_STRING: u32 = 14;
81
82/// Concatenate two STRING values; `val_string_concat(a: i32, b: i32) -> i32`
83pub const RT_VAL_STRING_CONCAT: u32 = 15;
84
85/// Arithmetic: add two numbers; `val_add(a: i32, b: i32) -> i32`  
86pub const RT_VAL_ADD: u32 = 16;
87/// `val_sub(a: i32, b: i32) -> i32`
88pub const RT_VAL_SUB: u32 = 17;
89/// `val_mul(a: i32, b: i32) -> i32`
90pub const RT_VAL_MUL: u32 = 18;
91/// `val_div(a: i32, b: i32) -> i32` — traps on /0 and NaN
92pub const RT_VAL_DIV: u32 = 19;
93/// `val_mod(a: i32, b: i32) -> i32`
94pub const RT_VAL_MOD: u32 = 20;
95/// `val_neg(a: i32) -> i32` — unary negate
96pub const RT_VAL_NEG: u32 = 21;
97/// `val_not(a: i32) -> i32` — logical not (bool)
98pub const RT_VAL_NOT: u32 = 22;
99
100/// Comparisons returning BOOL value ptr:
101/// `val_lt(a, b) -> i32`, `val_le`, `val_gt`, `val_ge`
102pub const RT_VAL_LT: u32 = 23;
103pub const RT_VAL_LE: u32 = 24;
104pub const RT_VAL_GT: u32 = 25;
105pub const RT_VAL_GE: u32 = 26;
106
107/// Record field access: `val_record_get(rec_ptr: i32, key_ptr: i32, key_len: i32) -> i32`
108pub const RT_VAL_RECORD_GET: u32 = 27;
109
110/// List index access: `val_list_get(list_ptr: i32, index: i32) -> i32`
111pub const RT_VAL_LIST_GET: u32 = 28;
112
113/// NaN check + trap: `check_nan(val_ptr: i32) -> i32` — traps if NaN, returns val_ptr
114pub const RT_CHECK_NAN: u32 = 29;
115
116/// Byte-by-byte memory comparison: `memcmp(ptr_a: i32, ptr_b: i32, len: i32) -> i32`
117/// Returns 1 if all bytes match, 0 otherwise.
118pub const RT_MEMCMP: u32 = 30;
119
120/// Total number of runtime helper functions.
121pub const RT_FUNC_COUNT: u32 = 31;
122
123// ── Absolute function indices ────────────────────────────────────────────────
124
125/// Compute the absolute WASM function index of a runtime helper.
126#[inline]
127pub const fn rt_func_idx(rt_offset: u32) -> u32 {
128    IMPORT_COUNT + rt_offset
129}
130
131// ══════════════════════════════════════════════════════════════════════════════
132// Emit helpers — each builds a `wasm_encoder::Function`
133// ══════════════════════════════════════════════════════════════════════════════
134
135/// Emit the `alloc(size: i32) -> i32` function.
136///
137/// Bump allocator: returns the current `heap_ptr`, then advances it by `size`.
138/// If we exceed memory, call `memory.grow`.
139pub fn emit_alloc(oom_ptr: u32, oom_len: u32) -> Function {
140    let mut f = Function::new(vec![
141        (1, ValType::I32), // local 1: old_ptr
142        (1, ValType::I32), // local 2: new_ptr
143        (1, ValType::I32), // local 3: pages_needed
144    ]);
145    // old_ptr = heap_ptr
146    f.instruction(&Instruction::GlobalGet(GLOBAL_HEAP_PTR));
147    f.instruction(&Instruction::LocalSet(1));
148    // new_ptr = heap_ptr + size
149    f.instruction(&Instruction::GlobalGet(GLOBAL_HEAP_PTR));
150    f.instruction(&Instruction::LocalGet(0)); // param: size
151    f.instruction(&Instruction::I32Add);
152    f.instruction(&Instruction::LocalSet(2));
153
154    // Check if new_ptr exceeds current memory
155    // current_bytes = memory.size * 65536
156    f.instruction(&Instruction::LocalGet(2));
157    f.instruction(&Instruction::MemorySize(0));
158    f.instruction(&Instruction::I32Const(16)); // shift left 16 = * 65536
159    f.instruction(&Instruction::I32Shl);
160    f.instruction(&Instruction::I32GtU);
161    f.instruction(&Instruction::If(BlockType::Empty));
162    {
163        // pages_needed = ceil((new_ptr - current_bytes) / 65536)
164        f.instruction(&Instruction::LocalGet(2));
165        f.instruction(&Instruction::MemorySize(0));
166        f.instruction(&Instruction::I32Const(16));
167        f.instruction(&Instruction::I32Shl);
168        f.instruction(&Instruction::I32Sub);
169        f.instruction(&Instruction::I32Const(65535));
170        f.instruction(&Instruction::I32Add);
171        f.instruction(&Instruction::I32Const(16));
172        f.instruction(&Instruction::I32ShrU);
173        f.instruction(&Instruction::LocalSet(3));
174
175        // Check max memory limit
176        f.instruction(&Instruction::MemorySize(0));
177        f.instruction(&Instruction::LocalGet(3));
178        f.instruction(&Instruction::I32Add);
179        f.instruction(&Instruction::I32Const(MAX_MEMORY_PAGES as i32));
180        f.instruction(&Instruction::I32GtU);
181        f.instruction(&Instruction::If(BlockType::Empty));
182        {
183            // Exceeded max memory — trap with OOM
184            f.instruction(&Instruction::I32Const(oom_ptr as i32));
185            f.instruction(&Instruction::I32Const(oom_len as i32));
186            f.instruction(&Instruction::Call(IMPORT_TRAP));
187            f.instruction(&Instruction::Unreachable);
188        }
189        f.instruction(&Instruction::End);
190
191        // memory.grow(pages_needed)
192        f.instruction(&Instruction::LocalGet(3));
193        f.instruction(&Instruction::MemoryGrow(0));
194        // Check if grow failed (-1)
195        f.instruction(&Instruction::I32Const(-1));
196        f.instruction(&Instruction::I32Eq);
197        f.instruction(&Instruction::If(BlockType::Empty));
198        {
199            f.instruction(&Instruction::I32Const(oom_ptr as i32));
200            f.instruction(&Instruction::I32Const(oom_len as i32));
201            f.instruction(&Instruction::Call(IMPORT_TRAP));
202            f.instruction(&Instruction::Unreachable);
203        }
204        f.instruction(&Instruction::End);
205    }
206    f.instruction(&Instruction::End);
207
208    // heap_ptr = new_ptr
209    f.instruction(&Instruction::LocalGet(2));
210    f.instruction(&Instruction::GlobalSet(GLOBAL_HEAP_PTR));
211    // return old_ptr
212    f.instruction(&Instruction::LocalGet(1));
213    f.instruction(&Instruction::End);
214    f
215}
216
217/// Emit `val_nil() -> i32`.
218pub fn emit_val_nil() -> Function {
219    let mut f = Function::new(vec![(1, ValType::I32)]); // local: ptr
220    // ptr = alloc(VALUE_SIZE)
221    f.instruction(&Instruction::I32Const(VALUE_SIZE as i32));
222    f.instruction(&Instruction::Call(rt_func_idx(RT_ALLOC)));
223    f.instruction(&Instruction::LocalSet(0));
224    // store tag = TAG_NIL
225    f.instruction(&Instruction::LocalGet(0));
226    f.instruction(&Instruction::I32Const(TAG_NIL));
227    f.instruction(&Instruction::I32Store(memarg(0, 2)));
228    // return ptr
229    f.instruction(&Instruction::LocalGet(0));
230    f.instruction(&Instruction::End);
231    f
232}
233
234/// Emit `val_number(n_bits_lo: i32, n_bits_hi: i32) -> i32`.
235///
236/// We pass the f64 as two i32 halves because our type table uses i32-only
237/// signatures.  The function reassembles and stores the f64.
238pub fn emit_val_number() -> Function {
239    let mut f = Function::new(vec![(1, ValType::I32)]); // local: ptr
240    // ptr = alloc(VALUE_SIZE)
241    f.instruction(&Instruction::I32Const(VALUE_SIZE as i32));
242    f.instruction(&Instruction::Call(rt_func_idx(RT_ALLOC)));
243    f.instruction(&Instruction::LocalSet(2));
244    // store tag = TAG_NUMBER
245    f.instruction(&Instruction::LocalGet(2));
246    f.instruction(&Instruction::I32Const(TAG_NUMBER));
247    f.instruction(&Instruction::I32Store(memarg(0, 2)));
248    // store lo word at offset+4
249    f.instruction(&Instruction::LocalGet(2));
250    f.instruction(&Instruction::LocalGet(0)); // bits_lo
251    f.instruction(&Instruction::I32Store(memarg(4, 2)));
252    // store hi word at offset+8
253    f.instruction(&Instruction::LocalGet(2));
254    f.instruction(&Instruction::LocalGet(1)); // bits_hi
255    f.instruction(&Instruction::I32Store(memarg(8, 2)));
256    // return ptr
257    f.instruction(&Instruction::LocalGet(2));
258    f.instruction(&Instruction::End);
259    f
260}
261
262/// Emit `val_bool(b: i32) -> i32`.
263pub fn emit_val_bool() -> Function {
264    let mut f = Function::new(vec![(1, ValType::I32)]); // local: ptr
265    // ptr = alloc(VALUE_SIZE)
266    f.instruction(&Instruction::I32Const(VALUE_SIZE as i32));
267    f.instruction(&Instruction::Call(rt_func_idx(RT_ALLOC)));
268    f.instruction(&Instruction::LocalSet(1));
269    // tag = TAG_BOOL
270    f.instruction(&Instruction::LocalGet(1));
271    f.instruction(&Instruction::I32Const(TAG_BOOL));
272    f.instruction(&Instruction::I32Store(memarg(0, 2)));
273    // w1 = b
274    f.instruction(&Instruction::LocalGet(1));
275    f.instruction(&Instruction::LocalGet(0));
276    f.instruction(&Instruction::I32Store(memarg(4, 2)));
277    // return ptr
278    f.instruction(&Instruction::LocalGet(1));
279    f.instruction(&Instruction::End);
280    f
281}
282
283/// Emit `val_string(data_ptr: i32, len: i32) -> i32`.
284pub fn emit_val_string() -> Function {
285    let mut f = Function::new(vec![(1, ValType::I32)]); // local: ptr
286    // ptr = alloc(VALUE_SIZE)
287    f.instruction(&Instruction::I32Const(VALUE_SIZE as i32));
288    f.instruction(&Instruction::Call(rt_func_idx(RT_ALLOC)));
289    f.instruction(&Instruction::LocalSet(2));
290    // tag
291    f.instruction(&Instruction::LocalGet(2));
292    f.instruction(&Instruction::I32Const(TAG_STRING));
293    f.instruction(&Instruction::I32Store(memarg(0, 2)));
294    // w1 = data_ptr
295    f.instruction(&Instruction::LocalGet(2));
296    f.instruction(&Instruction::LocalGet(0));
297    f.instruction(&Instruction::I32Store(memarg(4, 2)));
298    // w2 = len
299    f.instruction(&Instruction::LocalGet(2));
300    f.instruction(&Instruction::LocalGet(1));
301    f.instruction(&Instruction::I32Store(memarg(8, 2)));
302    // return ptr
303    f.instruction(&Instruction::LocalGet(2));
304    f.instruction(&Instruction::End);
305    f
306}
307
308/// Emit `val_list(arr_ptr: i32, count: i32) -> i32`.
309pub fn emit_val_list() -> Function {
310    let mut f = Function::new(vec![(1, ValType::I32)]);
311    f.instruction(&Instruction::I32Const(VALUE_SIZE as i32));
312    f.instruction(&Instruction::Call(rt_func_idx(RT_ALLOC)));
313    f.instruction(&Instruction::LocalSet(2));
314    f.instruction(&Instruction::LocalGet(2));
315    f.instruction(&Instruction::I32Const(TAG_LIST));
316    f.instruction(&Instruction::I32Store(memarg(0, 2)));
317    f.instruction(&Instruction::LocalGet(2));
318    f.instruction(&Instruction::LocalGet(0));
319    f.instruction(&Instruction::I32Store(memarg(4, 2)));
320    f.instruction(&Instruction::LocalGet(2));
321    f.instruction(&Instruction::LocalGet(1));
322    f.instruction(&Instruction::I32Store(memarg(8, 2)));
323    f.instruction(&Instruction::LocalGet(2));
324    f.instruction(&Instruction::End);
325    f
326}
327
328/// Emit `val_record(entries_ptr: i32, count: i32) -> i32`.
329pub fn emit_val_record() -> Function {
330    let mut f = Function::new(vec![(1, ValType::I32)]);
331    f.instruction(&Instruction::I32Const(VALUE_SIZE as i32));
332    f.instruction(&Instruction::Call(rt_func_idx(RT_ALLOC)));
333    f.instruction(&Instruction::LocalSet(2));
334    f.instruction(&Instruction::LocalGet(2));
335    f.instruction(&Instruction::I32Const(TAG_RECORD));
336    f.instruction(&Instruction::I32Store(memarg(0, 2)));
337    f.instruction(&Instruction::LocalGet(2));
338    f.instruction(&Instruction::LocalGet(0));
339    f.instruction(&Instruction::I32Store(memarg(4, 2)));
340    f.instruction(&Instruction::LocalGet(2));
341    f.instruction(&Instruction::LocalGet(1));
342    f.instruction(&Instruction::I32Store(memarg(8, 2)));
343    f.instruction(&Instruction::LocalGet(2));
344    f.instruction(&Instruction::End);
345    f
346}
347
348/// Emit `val_variant(id: i32, data_ptr: i32) -> i32`.
349pub fn emit_val_variant() -> Function {
350    let mut f = Function::new(vec![(1, ValType::I32)]);
351    f.instruction(&Instruction::I32Const(VALUE_SIZE as i32));
352    f.instruction(&Instruction::Call(rt_func_idx(RT_ALLOC)));
353    f.instruction(&Instruction::LocalSet(2));
354    f.instruction(&Instruction::LocalGet(2));
355    f.instruction(&Instruction::I32Const(TAG_VARIANT));
356    f.instruction(&Instruction::I32Store(memarg(0, 2)));
357    f.instruction(&Instruction::LocalGet(2));
358    f.instruction(&Instruction::LocalGet(0));
359    f.instruction(&Instruction::I32Store(memarg(4, 2)));
360    f.instruction(&Instruction::LocalGet(2));
361    f.instruction(&Instruction::LocalGet(1));
362    f.instruction(&Instruction::I32Store(memarg(8, 2)));
363    f.instruction(&Instruction::LocalGet(2));
364    f.instruction(&Instruction::End);
365    f
366}
367
368/// Emit `val_action_ref(action_id: i32) -> i32`.
369pub fn emit_val_action_ref() -> Function {
370    let mut f = Function::new(vec![(1, ValType::I32)]);
371    f.instruction(&Instruction::I32Const(VALUE_SIZE as i32));
372    f.instruction(&Instruction::Call(rt_func_idx(RT_ALLOC)));
373    f.instruction(&Instruction::LocalSet(1));
374    f.instruction(&Instruction::LocalGet(1));
375    f.instruction(&Instruction::I32Const(TAG_ACTION_REF));
376    f.instruction(&Instruction::I32Store(memarg(0, 2)));
377    f.instruction(&Instruction::LocalGet(1));
378    f.instruction(&Instruction::LocalGet(0));
379    f.instruction(&Instruction::I32Store(memarg(4, 2)));
380    f.instruction(&Instruction::LocalGet(1));
381    f.instruction(&Instruction::End);
382    f
383}
384
385/// Emit `val_tag(ptr: i32) -> i32` — reads the tag word.
386pub fn emit_val_tag() -> Function {
387    let mut f = Function::new(vec![]);
388    f.instruction(&Instruction::LocalGet(0));
389    f.instruction(&Instruction::I32Load(memarg(0, 2)));
390    f.instruction(&Instruction::End);
391    f
392}
393
394/// Emit `val_get_number(ptr: i32) -> i32`.
395///
396/// Returns the low 32 bits of the f64 payload. The high 32 bits are obtained
397/// via `val_get_w2`. Together they reconstruct the f64 via `f64.reinterpret_i64`.
398///
399/// (This is the same as `val_get_w1`, but named separately for clarity.)
400pub fn emit_val_get_number() -> Function {
401    // For simplicity, returns w1 (same as emit_val_get_w1)
402    let mut f = Function::new(vec![]);
403    f.instruction(&Instruction::LocalGet(0));
404    f.instruction(&Instruction::I32Load(memarg(4, 2)));
405    f.instruction(&Instruction::End);
406    f
407}
408
409/// Emit `val_get_w1(ptr: i32) -> i32`.
410pub fn emit_val_get_w1() -> Function {
411    let mut f = Function::new(vec![]);
412    f.instruction(&Instruction::LocalGet(0));
413    f.instruction(&Instruction::I32Load(memarg(4, 2)));
414    f.instruction(&Instruction::End);
415    f
416}
417
418/// Emit `val_get_w2(ptr: i32) -> i32`.
419pub fn emit_val_get_w2() -> Function {
420    let mut f = Function::new(vec![]);
421    f.instruction(&Instruction::LocalGet(0));
422    f.instruction(&Instruction::I32Load(memarg(8, 2)));
423    f.instruction(&Instruction::End);
424    f
425}
426
427/// Emit `val_eq(a: i32, b: i32) -> i32` — structural equality, returns bool value ptr.
428pub fn emit_val_eq() -> Function {
429    // Simplified: compare tags, then compare payloads.
430    // For NUMBER: compare the 8 bytes. For STRING: byte-compare data.
431    // Full structural equality for records/lists would be recursive — we emit
432    // a simplified version that handles primitives and falls back to ptr equality.
433    let mut f = Function::new(vec![
434        (1, ValType::I32), // local 2: tag_a
435        (1, ValType::I32), // local 3: tag_b
436        (1, ValType::I32), // local 4: result (0 or 1)
437    ]);
438
439    // tag_a = load tag(a)
440    f.instruction(&Instruction::LocalGet(0));
441    f.instruction(&Instruction::I32Load(memarg(0, 2)));
442    f.instruction(&Instruction::LocalSet(2));
443    // tag_b = load tag(b)
444    f.instruction(&Instruction::LocalGet(1));
445    f.instruction(&Instruction::I32Load(memarg(0, 2)));
446    f.instruction(&Instruction::LocalSet(3));
447
448    // if tags differ → false
449    f.instruction(&Instruction::LocalGet(2));
450    f.instruction(&Instruction::LocalGet(3));
451    f.instruction(&Instruction::I32Ne);
452    f.instruction(&Instruction::If(BlockType::Empty));
453    f.instruction(&Instruction::I32Const(0));
454    f.instruction(&Instruction::LocalSet(4));
455    f.instruction(&Instruction::Else);
456
457    // tags match — compare by tag type
458    // NIL == NIL always true
459    f.instruction(&Instruction::LocalGet(2));
460    f.instruction(&Instruction::I32Const(TAG_NIL));
461    f.instruction(&Instruction::I32Eq);
462    f.instruction(&Instruction::If(BlockType::Empty));
463    f.instruction(&Instruction::I32Const(1));
464    f.instruction(&Instruction::LocalSet(4));
465    f.instruction(&Instruction::Else);
466
467    // NUMBER: compare both 32-bit words of the f64
468    f.instruction(&Instruction::LocalGet(2));
469    f.instruction(&Instruction::I32Const(TAG_NUMBER));
470    f.instruction(&Instruction::I32Eq);
471    f.instruction(&Instruction::If(BlockType::Empty));
472    // w1 eq
473    f.instruction(&Instruction::LocalGet(0));
474    f.instruction(&Instruction::I32Load(memarg(4, 2)));
475    f.instruction(&Instruction::LocalGet(1));
476    f.instruction(&Instruction::I32Load(memarg(4, 2)));
477    f.instruction(&Instruction::I32Eq);
478    // w2 eq
479    f.instruction(&Instruction::LocalGet(0));
480    f.instruction(&Instruction::I32Load(memarg(8, 2)));
481    f.instruction(&Instruction::LocalGet(1));
482    f.instruction(&Instruction::I32Load(memarg(8, 2)));
483    f.instruction(&Instruction::I32Eq);
484    f.instruction(&Instruction::I32And);
485    f.instruction(&Instruction::LocalSet(4));
486    f.instruction(&Instruction::Else);
487
488    // BOOL: compare w1
489    f.instruction(&Instruction::LocalGet(2));
490    f.instruction(&Instruction::I32Const(TAG_BOOL));
491    f.instruction(&Instruction::I32Eq);
492    f.instruction(&Instruction::If(BlockType::Empty));
493    f.instruction(&Instruction::LocalGet(0));
494    f.instruction(&Instruction::I32Load(memarg(4, 2)));
495    f.instruction(&Instruction::LocalGet(1));
496    f.instruction(&Instruction::I32Load(memarg(4, 2)));
497    f.instruction(&Instruction::I32Eq);
498    f.instruction(&Instruction::LocalSet(4));
499    f.instruction(&Instruction::Else);
500
501    // STRING: compare lengths first, then byte-by-byte memcmp
502    f.instruction(&Instruction::LocalGet(2));
503    f.instruction(&Instruction::I32Const(TAG_STRING));
504    f.instruction(&Instruction::I32Eq);
505    f.instruction(&Instruction::If(BlockType::Empty));
506    // Compare lengths (w2 = byte-length)
507    f.instruction(&Instruction::LocalGet(0));
508    f.instruction(&Instruction::I32Load(memarg(8, 2)));
509    f.instruction(&Instruction::LocalGet(1));
510    f.instruction(&Instruction::I32Load(memarg(8, 2)));
511    f.instruction(&Instruction::I32Eq);
512    f.instruction(&Instruction::If(BlockType::Empty));
513    // Same length — byte-by-byte comparison via RT_MEMCMP
514    // memcmp(a.data_ptr, b.data_ptr, a.len) → 0 or 1
515    f.instruction(&Instruction::LocalGet(0));
516    f.instruction(&Instruction::I32Load(memarg(4, 2))); // a.data_ptr
517    f.instruction(&Instruction::LocalGet(1));
518    f.instruction(&Instruction::I32Load(memarg(4, 2))); // b.data_ptr
519    f.instruction(&Instruction::LocalGet(0));
520    f.instruction(&Instruction::I32Load(memarg(8, 2))); // a.len
521    f.instruction(&Instruction::Call(rt_func_idx(RT_MEMCMP)));
522    f.instruction(&Instruction::LocalSet(4));
523    f.instruction(&Instruction::Else);
524    f.instruction(&Instruction::I32Const(0));
525    f.instruction(&Instruction::LocalSet(4));
526    f.instruction(&Instruction::End); // end length check if
527    f.instruction(&Instruction::Else);
528
529    // Default: compare all payload bytes (fallback for list, record, variant, etc.)
530    f.instruction(&Instruction::LocalGet(0));
531    f.instruction(&Instruction::I32Load(memarg(4, 2)));
532    f.instruction(&Instruction::LocalGet(1));
533    f.instruction(&Instruction::I32Load(memarg(4, 2)));
534    f.instruction(&Instruction::I32Eq);
535    f.instruction(&Instruction::LocalGet(0));
536    f.instruction(&Instruction::I32Load(memarg(8, 2)));
537    f.instruction(&Instruction::LocalGet(1));
538    f.instruction(&Instruction::I32Load(memarg(8, 2)));
539    f.instruction(&Instruction::I32Eq);
540    f.instruction(&Instruction::I32And);
541    f.instruction(&Instruction::LocalSet(4));
542
543    f.instruction(&Instruction::End); // string else
544    f.instruction(&Instruction::End); // bool else
545    f.instruction(&Instruction::End); // number else
546    f.instruction(&Instruction::End); // nil else
547
548    f.instruction(&Instruction::End); // tags differ else
549
550    // Create bool value from result
551    f.instruction(&Instruction::LocalGet(4));
552    f.instruction(&Instruction::Call(rt_func_idx(RT_VAL_BOOL)));
553    f.instruction(&Instruction::End);
554    f
555}
556
557/// Emit `val_to_string(ptr: i32) -> i32` — converts any value to a STRING value.
558///
559/// v1: strings pass through, bools → "true"/"false", nil → "nil",
560/// numbers (integer-valued) → decimal string, others → "[value]".
561pub fn emit_val_to_string(data: &DataSegmentTracker) -> Function {
562    // Locals: 0=ptr, 1=tag, 2=f64_val, 3=is_neg, 4=abs_val(i64), 5=buf_ptr,
563    //         6=write_pos, 7=digit_count, 8=start, 9=result
564    let mut f = Function::new(vec![
565        (1, ValType::I32),  // local 1: tag
566        (1, ValType::F64),  // local 2: f64_val
567        (1, ValType::I32),  // local 3: is_neg
568        (1, ValType::I64),  // local 4: abs_val
569        (1, ValType::I32),  // local 5: buf_ptr
570        (1, ValType::I32),  // local 6: write_pos
571        (1, ValType::I32),  // local 7: digit_count
572        (1, ValType::I32),  // local 8: start_pos
573        (1, ValType::I32),  // local 9: result_ptr
574    ]);
575    f.instruction(&Instruction::LocalGet(0));
576    f.instruction(&Instruction::I32Load(memarg(0, 2)));
577    f.instruction(&Instruction::LocalSet(1));
578
579    // If already a string, return as-is
580    f.instruction(&Instruction::LocalGet(1));
581    f.instruction(&Instruction::I32Const(TAG_STRING));
582    f.instruction(&Instruction::I32Eq);
583    f.instruction(&Instruction::If(BlockType::Result(ValType::I32)));
584    f.instruction(&Instruction::LocalGet(0));
585    f.instruction(&Instruction::Else);
586
587    // NUMBER → integer string conversion
588    f.instruction(&Instruction::LocalGet(1));
589    f.instruction(&Instruction::I32Const(TAG_NUMBER));
590    f.instruction(&Instruction::I32Eq);
591    f.instruction(&Instruction::If(BlockType::Result(ValType::I32)));
592
593    // Load the f64 value (stored as two i32 words at offset 4)
594    f.instruction(&Instruction::LocalGet(0));
595    f.instruction(&Instruction::F64Load(memarg(4, 2)));
596    f.instruction(&Instruction::LocalSet(2));
597
598    // Check: is it a finite integer? (floor == value && not NaN/Inf)
599    // f64.floor(val) == val
600    f.instruction(&Instruction::LocalGet(2));
601    f.instruction(&Instruction::LocalGet(2));
602    f.instruction(&Instruction::F64Floor);
603    f.instruction(&Instruction::F64Eq);
604    // Also: val == val (not NaN)
605    f.instruction(&Instruction::LocalGet(2));
606    f.instruction(&Instruction::LocalGet(2));
607    f.instruction(&Instruction::F64Eq);
608    f.instruction(&Instruction::I32And);
609    // Also: abs(val) < 2^53 (safe integer range)
610    f.instruction(&Instruction::LocalGet(2));
611    f.instruction(&Instruction::F64Abs);
612    f.instruction(&Instruction::F64Const(9007199254740992.0)); // 2^53
613    f.instruction(&Instruction::F64Lt);
614    f.instruction(&Instruction::I32And);
615
616    f.instruction(&Instruction::If(BlockType::Result(ValType::I32)));
617
618    // Integer path: convert f64 → digits string
619    // Check if negative
620    f.instruction(&Instruction::LocalGet(2));
621    f.instruction(&Instruction::F64Const(0.0));
622    f.instruction(&Instruction::F64Lt);
623    f.instruction(&Instruction::LocalSet(3)); // is_neg
624
625    // abs_val = i64.trunc_f64_s(abs(val))
626    f.instruction(&Instruction::LocalGet(2));
627    f.instruction(&Instruction::F64Abs);
628    f.instruction(&Instruction::I64TruncF64S);
629    f.instruction(&Instruction::LocalSet(4)); // abs_val
630
631    // Allocate a 20-byte scratch buffer for digits (max i64 decimal = 19 chars + sign)
632    f.instruction(&Instruction::I32Const(20));
633    f.instruction(&Instruction::Call(rt_func_idx(RT_ALLOC)));
634    f.instruction(&Instruction::LocalSet(5)); // buf_ptr
635
636    // write_pos starts at end of buffer (we write digits right-to-left)
637    f.instruction(&Instruction::LocalGet(5));
638    f.instruction(&Instruction::I32Const(20));
639    f.instruction(&Instruction::I32Add);
640    f.instruction(&Instruction::LocalSet(6)); // write_pos
641
642    // Handle zero specially
643    f.instruction(&Instruction::LocalGet(4));
644    f.instruction(&Instruction::I64Eqz);
645    f.instruction(&Instruction::If(BlockType::Empty));
646    // write '0'
647    f.instruction(&Instruction::LocalGet(6));
648    f.instruction(&Instruction::I32Const(1));
649    f.instruction(&Instruction::I32Sub);
650    f.instruction(&Instruction::LocalSet(6));
651    f.instruction(&Instruction::LocalGet(6));
652    f.instruction(&Instruction::I32Const(48)); // ASCII '0'
653    f.instruction(&Instruction::I32Store8(memarg(0, 0)));
654    f.instruction(&Instruction::Else);
655
656    // Digit extraction loop: while abs_val > 0
657    f.instruction(&Instruction::Block(BlockType::Empty));
658    f.instruction(&Instruction::Loop(BlockType::Empty));
659    f.instruction(&Instruction::LocalGet(4));
660    f.instruction(&Instruction::I64Eqz);
661    f.instruction(&Instruction::BrIf(1)); // break if zero
662
663    // write_pos -= 1
664    f.instruction(&Instruction::LocalGet(6));
665    f.instruction(&Instruction::I32Const(1));
666    f.instruction(&Instruction::I32Sub);
667    f.instruction(&Instruction::LocalSet(6));
668
669    // digit = abs_val % 10
670    f.instruction(&Instruction::LocalGet(6));
671    f.instruction(&Instruction::LocalGet(4));
672    f.instruction(&Instruction::I64Const(10));
673    f.instruction(&Instruction::I64RemU);
674    f.instruction(&Instruction::I32WrapI64);
675    f.instruction(&Instruction::I32Const(48)); // ASCII '0'
676    f.instruction(&Instruction::I32Add);
677    f.instruction(&Instruction::I32Store8(memarg(0, 0)));
678
679    // abs_val /= 10
680    f.instruction(&Instruction::LocalGet(4));
681    f.instruction(&Instruction::I64Const(10));
682    f.instruction(&Instruction::I64DivU);
683    f.instruction(&Instruction::LocalSet(4));
684
685    f.instruction(&Instruction::Br(0)); // continue loop
686    f.instruction(&Instruction::End); // end loop
687    f.instruction(&Instruction::End); // end block
688
689    f.instruction(&Instruction::End); // end zero check
690
691    // If negative, prepend '-'
692    f.instruction(&Instruction::LocalGet(3));
693    f.instruction(&Instruction::If(BlockType::Empty));
694    f.instruction(&Instruction::LocalGet(6));
695    f.instruction(&Instruction::I32Const(1));
696    f.instruction(&Instruction::I32Sub);
697    f.instruction(&Instruction::LocalSet(6));
698    f.instruction(&Instruction::LocalGet(6));
699    f.instruction(&Instruction::I32Const(45)); // ASCII '-'
700    f.instruction(&Instruction::I32Store8(memarg(0, 0)));
701    f.instruction(&Instruction::End);
702
703    // Create STRING value from write_pos..buf_ptr+20
704    // data_ptr = write_pos, len = (buf_ptr + 20) - write_pos
705    f.instruction(&Instruction::LocalGet(6));  // data_ptr
706    f.instruction(&Instruction::LocalGet(5));
707    f.instruction(&Instruction::I32Const(20));
708    f.instruction(&Instruction::I32Add);
709    f.instruction(&Instruction::LocalGet(6));
710    f.instruction(&Instruction::I32Sub);       // len
711    f.instruction(&Instruction::Call(rt_func_idx(RT_VAL_STRING)));
712
713    f.instruction(&Instruction::Else);
714    // Non-integer number → "[value]" placeholder
715    f.instruction(&Instruction::I32Const(data.value_ptr as i32));
716    f.instruction(&Instruction::I32Const(data.value_len as i32));
717    f.instruction(&Instruction::Call(rt_func_idx(RT_VAL_STRING)));
718    f.instruction(&Instruction::End); // end integer check
719
720    f.instruction(&Instruction::Else);
721
722    // BOOL
723    f.instruction(&Instruction::LocalGet(1));
724    f.instruction(&Instruction::I32Const(TAG_BOOL));
725    f.instruction(&Instruction::I32Eq);
726    f.instruction(&Instruction::If(BlockType::Result(ValType::I32)));
727    f.instruction(&Instruction::LocalGet(0));
728    f.instruction(&Instruction::I32Load(memarg(4, 2)));
729    f.instruction(&Instruction::If(BlockType::Result(ValType::I32)));
730    // "true"
731    f.instruction(&Instruction::I32Const(data.true_ptr as i32));
732    f.instruction(&Instruction::I32Const(data.true_len as i32));
733    f.instruction(&Instruction::Call(rt_func_idx(RT_VAL_STRING)));
734    f.instruction(&Instruction::Else);
735    // "false"
736    f.instruction(&Instruction::I32Const(data.false_ptr as i32));
737    f.instruction(&Instruction::I32Const(data.false_len as i32));
738    f.instruction(&Instruction::Call(rt_func_idx(RT_VAL_STRING)));
739    f.instruction(&Instruction::End);
740    f.instruction(&Instruction::Else);
741
742    // NIL → "nil"
743    f.instruction(&Instruction::LocalGet(1));
744    f.instruction(&Instruction::I32Const(TAG_NIL));
745    f.instruction(&Instruction::I32Eq);
746    f.instruction(&Instruction::If(BlockType::Result(ValType::I32)));
747    f.instruction(&Instruction::I32Const(data.nil_ptr as i32));
748    f.instruction(&Instruction::I32Const(data.nil_len as i32));
749    f.instruction(&Instruction::Call(rt_func_idx(RT_VAL_STRING)));
750    f.instruction(&Instruction::Else);
751
752    // Default: return "[value]" placeholder
753    f.instruction(&Instruction::I32Const(data.value_ptr as i32));
754    f.instruction(&Instruction::I32Const(data.value_len as i32));
755    f.instruction(&Instruction::Call(rt_func_idx(RT_VAL_STRING)));
756
757    f.instruction(&Instruction::End); // nil else
758    f.instruction(&Instruction::End); // bool else
759    f.instruction(&Instruction::End); // number else
760    f.instruction(&Instruction::End); // string if/else
761
762    f.instruction(&Instruction::End);
763    f
764}
765
766/// Emit `val_string_concat(a: i32, b: i32) -> i32`.
767///
768/// Allocates a new string buffer, copies both payloads, returns new STRING value.
769pub fn emit_val_string_concat() -> Function {
770    let mut f = Function::new(vec![
771        (1, ValType::I32), // local 2: len_a
772        (1, ValType::I32), // local 3: len_b
773        (1, ValType::I32), // local 4: new_buf
774        (1, ValType::I32), // local 5: total_len
775    ]);
776
777    // len_a = a.w2
778    f.instruction(&Instruction::LocalGet(0));
779    f.instruction(&Instruction::I32Load(memarg(8, 2)));
780    f.instruction(&Instruction::LocalSet(2));
781    // len_b = b.w2
782    f.instruction(&Instruction::LocalGet(1));
783    f.instruction(&Instruction::I32Load(memarg(8, 2)));
784    f.instruction(&Instruction::LocalSet(3));
785    // total_len = len_a + len_b
786    f.instruction(&Instruction::LocalGet(2));
787    f.instruction(&Instruction::LocalGet(3));
788    f.instruction(&Instruction::I32Add);
789    f.instruction(&Instruction::LocalSet(5));
790
791    // new_buf = alloc(total_len)
792    f.instruction(&Instruction::LocalGet(5));
793    f.instruction(&Instruction::Call(rt_func_idx(RT_ALLOC)));
794    f.instruction(&Instruction::LocalSet(4));
795
796    // memory.copy(new_buf, a.w1, len_a)
797    f.instruction(&Instruction::LocalGet(4));        // dst
798    f.instruction(&Instruction::LocalGet(0));
799    f.instruction(&Instruction::I32Load(memarg(4, 2))); // src = a.w1
800    f.instruction(&Instruction::LocalGet(2));        // len_a
801    f.instruction(&Instruction::MemoryCopy { src_mem: 0, dst_mem: 0 });
802
803    // memory.copy(new_buf + len_a, b.w1, len_b)
804    f.instruction(&Instruction::LocalGet(4));
805    f.instruction(&Instruction::LocalGet(2));
806    f.instruction(&Instruction::I32Add);             // dst = new_buf + len_a
807    f.instruction(&Instruction::LocalGet(1));
808    f.instruction(&Instruction::I32Load(memarg(4, 2))); // src = b.w1
809    f.instruction(&Instruction::LocalGet(3));        // len_b
810    f.instruction(&Instruction::MemoryCopy { src_mem: 0, dst_mem: 0 });
811
812    // return val_string(new_buf, total_len)
813    f.instruction(&Instruction::LocalGet(4));
814    f.instruction(&Instruction::LocalGet(5));
815    f.instruction(&Instruction::Call(rt_func_idx(RT_VAL_STRING)));
816
817    f.instruction(&Instruction::End);
818    f
819}
820
821/// Emit an arithmetic binary op helper.
822///
823/// All arithmetic helpers follow the same pattern:
824/// 1. Load f64 from a (w1,w2 → i64.or → f64.reinterpret)
825/// 2. Load f64 from b
826/// 3. Perform f64 op
827/// 4. Store result as new NUMBER value
828fn emit_arith_binop(op: Instruction<'static>) -> Function {
829    let mut f = Function::new(vec![
830        (1, ValType::I32), // local 2: result ptr
831    ]);
832
833    // Allocate result value cell
834    f.instruction(&Instruction::I32Const(VALUE_SIZE as i32));
835    f.instruction(&Instruction::Call(rt_func_idx(RT_ALLOC)));
836    f.instruction(&Instruction::LocalSet(2));
837
838    // Store tag = TAG_NUMBER
839    f.instruction(&Instruction::LocalGet(2));
840    f.instruction(&Instruction::I32Const(TAG_NUMBER));
841    f.instruction(&Instruction::I32Store(memarg(0, 2)));
842
843    // Load f64 from a: f64.load at a+4
844    // Store result f64 at result+4
845    f.instruction(&Instruction::LocalGet(2));
846    f.instruction(&Instruction::LocalGet(0));
847    f.instruction(&Instruction::F64Load(memarg(4, 3)));
848    f.instruction(&Instruction::LocalGet(1));
849    f.instruction(&Instruction::F64Load(memarg(4, 3)));
850    f.instruction(&op);
851    f.instruction(&Instruction::F64Store(memarg(4, 3)));
852
853    // return result
854    f.instruction(&Instruction::LocalGet(2));
855    f.instruction(&Instruction::End);
856    f
857}
858
859pub fn emit_val_add() -> Function {
860    emit_arith_binop(Instruction::F64Add)
861}
862pub fn emit_val_sub() -> Function {
863    emit_arith_binop(Instruction::F64Sub)
864}
865pub fn emit_val_mul() -> Function {
866    emit_arith_binop(Instruction::F64Mul)
867}
868
869/// Emit `val_div` — with division-by-zero and NaN trap guards.
870pub fn emit_val_div(trap_msg_ptr: u32, trap_msg_len: u32) -> Function {
871    let mut f = Function::new(vec![
872        (1, ValType::I32),  // local 2: result ptr
873        (1, ValType::F64),  // local 3: divisor
874        (1, ValType::F64),  // local 4: quotient
875    ]);
876
877    // Load divisor
878    f.instruction(&Instruction::LocalGet(1));
879    f.instruction(&Instruction::F64Load(memarg(4, 3)));
880    f.instruction(&Instruction::LocalSet(3));
881
882    // Check divisor == 0
883    f.instruction(&Instruction::LocalGet(3));
884    f.instruction(&Instruction::F64Const(0.0));
885    f.instruction(&Instruction::F64Eq);
886    f.instruction(&Instruction::If(BlockType::Empty));
887    f.instruction(&Instruction::I32Const(trap_msg_ptr as i32));
888    f.instruction(&Instruction::I32Const(trap_msg_len as i32));
889    f.instruction(&Instruction::Call(IMPORT_TRAP));
890    f.instruction(&Instruction::Unreachable);
891    f.instruction(&Instruction::End);
892
893    // Compute quotient
894    f.instruction(&Instruction::LocalGet(0));
895    f.instruction(&Instruction::F64Load(memarg(4, 3)));
896    f.instruction(&Instruction::LocalGet(3));
897    f.instruction(&Instruction::F64Div);
898    f.instruction(&Instruction::LocalSet(4));
899
900    // NaN check: quotient != quotient → trap
901    f.instruction(&Instruction::LocalGet(4));
902    f.instruction(&Instruction::LocalGet(4));
903    f.instruction(&Instruction::F64Ne);
904    f.instruction(&Instruction::If(BlockType::Empty));
905    f.instruction(&Instruction::I32Const(trap_msg_ptr as i32));
906    f.instruction(&Instruction::I32Const(trap_msg_len as i32));
907    f.instruction(&Instruction::Call(IMPORT_TRAP));
908    f.instruction(&Instruction::Unreachable);
909    f.instruction(&Instruction::End);
910
911    // Allocate + store result
912    f.instruction(&Instruction::I32Const(VALUE_SIZE as i32));
913    f.instruction(&Instruction::Call(rt_func_idx(RT_ALLOC)));
914    f.instruction(&Instruction::LocalSet(2));
915    f.instruction(&Instruction::LocalGet(2));
916    f.instruction(&Instruction::I32Const(TAG_NUMBER));
917    f.instruction(&Instruction::I32Store(memarg(0, 2)));
918    f.instruction(&Instruction::LocalGet(2));
919    f.instruction(&Instruction::LocalGet(4));
920    f.instruction(&Instruction::F64Store(memarg(4, 3)));
921    f.instruction(&Instruction::LocalGet(2));
922    f.instruction(&Instruction::End);
923    f
924}
925
926/// Emit `val_mod(a, b) -> i32` — f64 remainder.
927pub fn emit_val_mod() -> Function {
928    // WASM doesn't have f64.rem, so we implement: a - floor(a/b) * b
929    let mut f = Function::new(vec![
930        (1, ValType::I32), // local 2: result ptr
931        (1, ValType::F64), // local 3: a_val
932        (1, ValType::F64), // local 4: b_val
933    ]);
934
935    f.instruction(&Instruction::LocalGet(0));
936    f.instruction(&Instruction::F64Load(memarg(4, 3)));
937    f.instruction(&Instruction::LocalSet(3));
938    f.instruction(&Instruction::LocalGet(1));
939    f.instruction(&Instruction::F64Load(memarg(4, 3)));
940    f.instruction(&Instruction::LocalSet(4));
941
942    // result = a - floor(a/b) * b
943    f.instruction(&Instruction::I32Const(VALUE_SIZE as i32));
944    f.instruction(&Instruction::Call(rt_func_idx(RT_ALLOC)));
945    f.instruction(&Instruction::LocalSet(2));
946    f.instruction(&Instruction::LocalGet(2));
947    f.instruction(&Instruction::I32Const(TAG_NUMBER));
948    f.instruction(&Instruction::I32Store(memarg(0, 2)));
949
950    f.instruction(&Instruction::LocalGet(2));
951    f.instruction(&Instruction::LocalGet(3)); // a
952    f.instruction(&Instruction::LocalGet(3)); // a
953    f.instruction(&Instruction::LocalGet(4)); // b
954    f.instruction(&Instruction::F64Div);
955    f.instruction(&Instruction::F64Floor);
956    f.instruction(&Instruction::LocalGet(4)); // b
957    f.instruction(&Instruction::F64Mul);
958    f.instruction(&Instruction::F64Sub);
959    f.instruction(&Instruction::F64Store(memarg(4, 3)));
960
961    f.instruction(&Instruction::LocalGet(2));
962    f.instruction(&Instruction::End);
963    f
964}
965
966/// Emit `val_neg(a: i32) -> i32` — unary negate.
967pub fn emit_val_neg() -> Function {
968    let mut f = Function::new(vec![
969        (1, ValType::I32), // local 1: result ptr
970    ]);
971    f.instruction(&Instruction::I32Const(VALUE_SIZE as i32));
972    f.instruction(&Instruction::Call(rt_func_idx(RT_ALLOC)));
973    f.instruction(&Instruction::LocalSet(1));
974    f.instruction(&Instruction::LocalGet(1));
975    f.instruction(&Instruction::I32Const(TAG_NUMBER));
976    f.instruction(&Instruction::I32Store(memarg(0, 2)));
977    f.instruction(&Instruction::LocalGet(1));
978    f.instruction(&Instruction::LocalGet(0));
979    f.instruction(&Instruction::F64Load(memarg(4, 3)));
980    f.instruction(&Instruction::F64Neg);
981    f.instruction(&Instruction::F64Store(memarg(4, 3)));
982    f.instruction(&Instruction::LocalGet(1));
983    f.instruction(&Instruction::End);
984    f
985}
986
987/// Emit `val_not(a: i32) -> i32` — logical not (bool).
988pub fn emit_val_not() -> Function {
989    let mut f = Function::new(vec![
990        (1, ValType::I32), // local 1: bool val
991    ]);
992    // Read bool w1
993    f.instruction(&Instruction::LocalGet(0));
994    f.instruction(&Instruction::I32Load(memarg(4, 2)));
995    f.instruction(&Instruction::I32Eqz);
996    f.instruction(&Instruction::Call(rt_func_idx(RT_VAL_BOOL)));
997    f.instruction(&Instruction::End);
998    f
999}
1000
1001/// Emit a comparison helper (returns BOOL value pointer).
1002fn emit_cmp(op: Instruction<'static>) -> Function {
1003    let mut f = Function::new(vec![]);
1004    // Load f64 from a, f64 from b, compare, wrap as bool value
1005    f.instruction(&Instruction::LocalGet(0));
1006    f.instruction(&Instruction::F64Load(memarg(4, 3)));
1007    f.instruction(&Instruction::LocalGet(1));
1008    f.instruction(&Instruction::F64Load(memarg(4, 3)));
1009    f.instruction(&op);
1010    f.instruction(&Instruction::Call(rt_func_idx(RT_VAL_BOOL)));
1011    f.instruction(&Instruction::End);
1012    f
1013}
1014
1015pub fn emit_val_lt() -> Function {
1016    emit_cmp(Instruction::F64Lt)
1017}
1018pub fn emit_val_le() -> Function {
1019    emit_cmp(Instruction::F64Le)
1020}
1021pub fn emit_val_gt() -> Function {
1022    emit_cmp(Instruction::F64Gt)
1023}
1024pub fn emit_val_ge() -> Function {
1025    emit_cmp(Instruction::F64Ge)
1026}
1027
1028/// Emit `val_record_get(rec_ptr, key_ptr, key_len) -> i32`.
1029///
1030/// Linear scan of record entries to find a matching key.
1031/// Record entries layout: array of 12-byte triples [key_offset: i32, key_len: i32, value_ptr: i32].
1032pub fn emit_val_record_get() -> Function {
1033    let mut f = Function::new(vec![
1034        (1, ValType::I32), // local 3: entries_ptr (rec.w1)
1035        (1, ValType::I32), // local 4: count (rec.w2)
1036        (1, ValType::I32), // local 5: i (loop counter)
1037        (1, ValType::I32), // local 6: entry_ptr
1038        (1, ValType::I32), // local 7: result
1039    ]);
1040
1041    // entries_ptr = rec_ptr.w1
1042    f.instruction(&Instruction::LocalGet(0));
1043    f.instruction(&Instruction::I32Load(memarg(4, 2)));
1044    f.instruction(&Instruction::LocalSet(3));
1045    // count = rec_ptr.w2
1046    f.instruction(&Instruction::LocalGet(0));
1047    f.instruction(&Instruction::I32Load(memarg(8, 2)));
1048    f.instruction(&Instruction::LocalSet(4));
1049
1050    // result = 0 (nil — will create nil if not found)
1051    f.instruction(&Instruction::I32Const(0));
1052    f.instruction(&Instruction::LocalSet(7));
1053    // i = 0
1054    f.instruction(&Instruction::I32Const(0));
1055    f.instruction(&Instruction::LocalSet(5));
1056
1057    // loop over entries
1058    f.instruction(&Instruction::Block(BlockType::Empty)); // break target
1059    f.instruction(&Instruction::Loop(BlockType::Empty));
1060
1061    // if i >= count → break
1062    f.instruction(&Instruction::LocalGet(5));
1063    f.instruction(&Instruction::LocalGet(4));
1064    f.instruction(&Instruction::I32GeU);
1065    f.instruction(&Instruction::BrIf(1));
1066
1067    // entry_ptr = entries_ptr + i * 12
1068    f.instruction(&Instruction::LocalGet(3));
1069    f.instruction(&Instruction::LocalGet(5));
1070    f.instruction(&Instruction::I32Const(12));
1071    f.instruction(&Instruction::I32Mul);
1072    f.instruction(&Instruction::I32Add);
1073    f.instruction(&Instruction::LocalSet(6));
1074
1075    // Compare key length first
1076    f.instruction(&Instruction::LocalGet(6));
1077    f.instruction(&Instruction::I32Load(memarg(4, 2))); // entry key_len
1078    f.instruction(&Instruction::LocalGet(2));            // target key_len
1079    f.instruction(&Instruction::I32Eq);
1080    f.instruction(&Instruction::If(BlockType::Empty));
1081    // Lengths match — byte-by-byte memcmp on key data
1082    f.instruction(&Instruction::LocalGet(6));
1083    f.instruction(&Instruction::I32Load(memarg(0, 2))); // entry key_offset
1084    f.instruction(&Instruction::LocalGet(1));            // target key_ptr
1085    f.instruction(&Instruction::LocalGet(2));            // key_len
1086    f.instruction(&Instruction::Call(rt_func_idx(RT_MEMCMP)));
1087    f.instruction(&Instruction::If(BlockType::Empty));
1088    // Found! result = entry.value_ptr
1089    f.instruction(&Instruction::LocalGet(6));
1090    f.instruction(&Instruction::I32Load(memarg(8, 2)));
1091    f.instruction(&Instruction::LocalSet(7));
1092    f.instruction(&Instruction::Br(3)); // break out of loop + block
1093    f.instruction(&Instruction::End);
1094    f.instruction(&Instruction::End);
1095
1096    // i += 1, continue
1097    f.instruction(&Instruction::LocalGet(5));
1098    f.instruction(&Instruction::I32Const(1));
1099    f.instruction(&Instruction::I32Add);
1100    f.instruction(&Instruction::LocalSet(5));
1101    f.instruction(&Instruction::Br(0));
1102
1103    f.instruction(&Instruction::End); // end loop
1104    f.instruction(&Instruction::End); // end block
1105
1106    // If result is 0 (not found), create nil
1107    f.instruction(&Instruction::LocalGet(7));
1108    f.instruction(&Instruction::I32Eqz);
1109    f.instruction(&Instruction::If(BlockType::Empty));
1110    f.instruction(&Instruction::Call(rt_func_idx(RT_VAL_NIL)));
1111    f.instruction(&Instruction::LocalSet(7));
1112    f.instruction(&Instruction::End);
1113
1114    f.instruction(&Instruction::LocalGet(7));
1115    f.instruction(&Instruction::End);
1116    f
1117}
1118
1119/// Emit `val_list_get(list_ptr, index) -> i32`.
1120pub fn emit_val_list_get() -> Function {
1121    let mut f = Function::new(vec![
1122        (1, ValType::I32), // local 2: arr_ptr
1123        (1, ValType::I32), // local 3: count
1124    ]);
1125    // arr_ptr = list.w1
1126    f.instruction(&Instruction::LocalGet(0));
1127    f.instruction(&Instruction::I32Load(memarg(4, 2)));
1128    f.instruction(&Instruction::LocalSet(2));
1129    // count = list.w2
1130    f.instruction(&Instruction::LocalGet(0));
1131    f.instruction(&Instruction::I32Load(memarg(8, 2)));
1132    f.instruction(&Instruction::LocalSet(3));
1133
1134    // bounds check: if index >= count → return nil
1135    f.instruction(&Instruction::LocalGet(1));
1136    f.instruction(&Instruction::LocalGet(3));
1137    f.instruction(&Instruction::I32GeU);
1138    f.instruction(&Instruction::If(BlockType::Result(ValType::I32)));
1139    f.instruction(&Instruction::Call(rt_func_idx(RT_VAL_NIL)));
1140    f.instruction(&Instruction::Else);
1141    // return *(arr_ptr + index * 4)
1142    f.instruction(&Instruction::LocalGet(2));
1143    f.instruction(&Instruction::LocalGet(1));
1144    f.instruction(&Instruction::I32Const(4));
1145    f.instruction(&Instruction::I32Mul);
1146    f.instruction(&Instruction::I32Add);
1147    f.instruction(&Instruction::I32Load(memarg(0, 2)));
1148    f.instruction(&Instruction::End);
1149
1150    f.instruction(&Instruction::End);
1151    f
1152}
1153
1154/// Emit `check_nan(val_ptr: i32) -> i32` — traps if NUMBER value is NaN.
1155pub fn emit_check_nan(trap_msg_ptr: u32, trap_msg_len: u32) -> Function {
1156    let mut f = Function::new(vec![
1157        (1, ValType::F64), // local 1: the f64
1158    ]);
1159    // Only check if tag == NUMBER
1160    f.instruction(&Instruction::LocalGet(0));
1161    f.instruction(&Instruction::I32Load(memarg(0, 2)));
1162    f.instruction(&Instruction::I32Const(TAG_NUMBER));
1163    f.instruction(&Instruction::I32Eq);
1164    f.instruction(&Instruction::If(BlockType::Empty));
1165    // Load the f64
1166    f.instruction(&Instruction::LocalGet(0));
1167    f.instruction(&Instruction::F64Load(memarg(4, 3)));
1168    f.instruction(&Instruction::LocalSet(1));
1169    // NaN check: x != x
1170    f.instruction(&Instruction::LocalGet(1));
1171    f.instruction(&Instruction::LocalGet(1));
1172    f.instruction(&Instruction::F64Ne);
1173    f.instruction(&Instruction::If(BlockType::Empty));
1174    f.instruction(&Instruction::I32Const(trap_msg_ptr as i32));
1175    f.instruction(&Instruction::I32Const(trap_msg_len as i32));
1176    f.instruction(&Instruction::Call(IMPORT_TRAP));
1177    f.instruction(&Instruction::Unreachable);
1178    f.instruction(&Instruction::End);
1179    f.instruction(&Instruction::End);
1180    // Return the value unchanged
1181    f.instruction(&Instruction::LocalGet(0));
1182    f.instruction(&Instruction::End);
1183    f
1184}
1185
1186/// Emit `memcmp(ptr_a: i32, ptr_b: i32, len: i32) -> i32` — byte-by-byte comparison.
1187///
1188/// Returns 1 if all `len` bytes at `ptr_a` and `ptr_b` are identical, 0 otherwise.
1189/// Used by `val_eq` for string content comparison and `val_record_get` for key lookup.
1190pub fn emit_memcmp() -> Function {
1191    let mut f = Function::new(vec![
1192        (1, ValType::I32), // local 3: loop counter (i)
1193    ]);
1194    // params: 0=ptr_a, 1=ptr_b, 2=len
1195
1196    // If len == 0 → return 1 (empty strings are equal)
1197    f.instruction(&Instruction::LocalGet(2));
1198    f.instruction(&Instruction::I32Eqz);
1199    f.instruction(&Instruction::If(BlockType::Empty));
1200    f.instruction(&Instruction::I32Const(1));
1201    f.instruction(&Instruction::Return);
1202    f.instruction(&Instruction::End);
1203
1204    // If pointers are equal → return 1 (same memory, trivially equal)
1205    f.instruction(&Instruction::LocalGet(0));
1206    f.instruction(&Instruction::LocalGet(1));
1207    f.instruction(&Instruction::I32Eq);
1208    f.instruction(&Instruction::If(BlockType::Empty));
1209    f.instruction(&Instruction::I32Const(1));
1210    f.instruction(&Instruction::Return);
1211    f.instruction(&Instruction::End);
1212
1213    // i = 0
1214    f.instruction(&Instruction::I32Const(0));
1215    f.instruction(&Instruction::LocalSet(3));
1216
1217    // Loop: compare byte at ptr_a+i vs ptr_b+i
1218    f.instruction(&Instruction::Block(BlockType::Empty)); // block (break target)
1219    f.instruction(&Instruction::Loop(BlockType::Empty));   // loop
1220
1221    // if i >= len → break (all matched → return 1)
1222    f.instruction(&Instruction::LocalGet(3));
1223    f.instruction(&Instruction::LocalGet(2));
1224    f.instruction(&Instruction::I32GeU);
1225    f.instruction(&Instruction::BrIf(1)); // br outer block
1226
1227    // load byte at ptr_a + i
1228    f.instruction(&Instruction::LocalGet(0));
1229    f.instruction(&Instruction::LocalGet(3));
1230    f.instruction(&Instruction::I32Add);
1231    f.instruction(&Instruction::I32Load8U(memarg(0, 0)));
1232
1233    // load byte at ptr_b + i
1234    f.instruction(&Instruction::LocalGet(1));
1235    f.instruction(&Instruction::LocalGet(3));
1236    f.instruction(&Instruction::I32Add);
1237    f.instruction(&Instruction::I32Load8U(memarg(0, 0)));
1238
1239    // if bytes differ → return 0
1240    f.instruction(&Instruction::I32Ne);
1241    f.instruction(&Instruction::If(BlockType::Empty));
1242    f.instruction(&Instruction::I32Const(0));
1243    f.instruction(&Instruction::Return);
1244    f.instruction(&Instruction::End);
1245
1246    // i += 1
1247    f.instruction(&Instruction::LocalGet(3));
1248    f.instruction(&Instruction::I32Const(1));
1249    f.instruction(&Instruction::I32Add);
1250    f.instruction(&Instruction::LocalSet(3));
1251
1252    // continue loop
1253    f.instruction(&Instruction::Br(0));
1254
1255    f.instruction(&Instruction::End); // end loop
1256    f.instruction(&Instruction::End); // end block
1257
1258    // Reached here → all bytes matched
1259    f.instruction(&Instruction::I32Const(1));
1260    f.instruction(&Instruction::End);
1261    f
1262}
1263
1264// ══════════════════════════════════════════════════════════════════════════════
1265// Helpers
1266// ══════════════════════════════════════════════════════════════════════════════
1267
1268/// Create a `MemArg` with the given offset and alignment power.
1269pub(crate) fn memarg(offset: u64, align: u32) -> wasm_encoder::MemArg {
1270    wasm_encoder::MemArg {
1271        offset,
1272        align,
1273        memory_index: 0,
1274    }
1275}
1276
1277/// Tracks data-segment offsets for well-known constant strings.
1278///
1279/// Built during module assembly (`compiler.rs`) and passed to runtime helpers
1280/// that need to reference string constants (e.g., `val_to_string`).
1281pub struct DataSegmentTracker {
1282    pub true_ptr: u32,
1283    pub true_len: u32,
1284    pub false_ptr: u32,
1285    pub false_len: u32,
1286    pub nil_ptr: u32,
1287    pub nil_len: u32,
1288    pub value_ptr: u32,
1289    pub value_len: u32,
1290    pub gas_exhausted_ptr: u32,
1291    pub gas_exhausted_len: u32,
1292    pub div_by_zero_ptr: u32,
1293    pub div_by_zero_len: u32,
1294    pub nan_ptr: u32,
1295    pub nan_len: u32,
1296    pub assert_failed_ptr: u32,
1297    pub assert_failed_len: u32,
1298    pub invariant_failed_ptr: u32,
1299    pub invariant_failed_len: u32,
1300    pub unwrap_failed_ptr: u32,
1301    pub unwrap_failed_len: u32,
1302    pub oom_ptr: u32,
1303    pub oom_len: u32,
1304    /// Next free offset in the data segment.
1305    pub next_offset: u32,
1306}
1307
1308impl Default for DataSegmentTracker {
1309    fn default() -> Self {
1310        Self::new()
1311    }
1312}
1313
1314impl DataSegmentTracker {
1315    /// Build the tracker, interning all well-known strings starting at offset 0.
1316    pub fn new() -> Self {
1317        let mut offset = 0u32;
1318
1319        let true_ptr = offset;
1320        let true_len = 4u32; // "true"
1321        offset += true_len;
1322
1323        let false_ptr = offset;
1324        let false_len = 5u32; // "false"
1325        offset += false_len;
1326
1327        let nil_ptr = offset;
1328        let nil_len = 3u32; // "nil"
1329        offset += nil_len;
1330
1331        let value_ptr = offset;
1332        let value_len = 7u32; // "[value]"
1333        offset += value_len;
1334
1335        let gas_exhausted_ptr = offset;
1336        let gas_exhausted_len = 13u32; // "gas exhausted"
1337        offset += gas_exhausted_len;
1338
1339        let div_by_zero_ptr = offset;
1340        let div_by_zero_len = 16u32; // "division by zero"
1341        offset += div_by_zero_len;
1342
1343        let nan_ptr = offset;
1344        let nan_len = 10u32; // "NaN result"
1345        offset += nan_len;
1346
1347        let assert_failed_ptr = offset;
1348        let assert_failed_len = 16u32; // "assertion failed"
1349        offset += assert_failed_len;
1350
1351        let invariant_failed_ptr = offset;
1352        let invariant_failed_len = 18u32; // "invariant violated"
1353        offset += invariant_failed_len;
1354
1355        let unwrap_failed_ptr = offset;
1356        let unwrap_failed_len = 14u32; // "unwrap on Err"
1357        offset += unwrap_failed_len;
1358
1359        let oom_ptr = offset;
1360        let oom_len = 13u32; // "out of memory"
1361        offset += oom_len;
1362
1363        Self {
1364            true_ptr,
1365            true_len,
1366            false_ptr,
1367            false_len,
1368            nil_ptr,
1369            nil_len,
1370            value_ptr,
1371            value_len,
1372            gas_exhausted_ptr,
1373            gas_exhausted_len,
1374            div_by_zero_ptr,
1375            div_by_zero_len,
1376            nan_ptr,
1377            nan_len,
1378            assert_failed_ptr,
1379            assert_failed_len,
1380            invariant_failed_ptr,
1381            invariant_failed_len,
1382            unwrap_failed_ptr,
1383            unwrap_failed_len,
1384            oom_ptr,
1385            oom_len,
1386            next_offset: offset,
1387        }
1388    }
1389
1390    /// The raw bytes for the data segment — all well-known strings concatenated.
1391    pub fn data_bytes(&self) -> Vec<u8> {
1392        let mut buf = Vec::new();
1393        buf.extend_from_slice(b"true");
1394        buf.extend_from_slice(b"false");
1395        buf.extend_from_slice(b"nil");
1396        buf.extend_from_slice(b"[value]");
1397        buf.extend_from_slice(b"gas exhausted");
1398        buf.extend_from_slice(b"division by zero");
1399        buf.extend_from_slice(b"NaN result");
1400        buf.extend_from_slice(b"assertion failed");
1401        buf.extend_from_slice(b"invariant violated");
1402        buf.extend_from_slice(b"unwrap on Err!");
1403        buf.extend_from_slice(b"out of memory");
1404        buf
1405    }
1406
1407    /// Intern a user string literal and return (offset, length).
1408    /// The caller must also append the bytes to the data segment.
1409    pub fn intern_string(&mut self, s: &str) -> (u32, u32) {
1410        let ptr = self.next_offset;
1411        let len = s.len() as u32;
1412        self.next_offset += len;
1413        (ptr, len)
1414    }
1415}