1
//! `$pair` setup + entity allocators + string-equality helper.
2
//!
3
//! Three loosely-related setup chunks share this file: they all
4
//! register type + function pairs that the rest of the compiler treats
5
//! as opaque imports. Pair is the canonical list cell, entity
6
//! allocators expose typed `alloc_<kind>` exports for host-side
7
//! struct construction, and `string_eq` is the byte-by-byte array
8
//! equality helper.
9

            
10
use super::CompileContext;
11
use crate::error::{Error, Result};
12
use wasm_encoder::{
13
    CompositeInnerType, CompositeType, FieldType, Function, HeapType, Instruction, RefType,
14
    StorageType, StructType, SubType, ValType,
15
};
16

            
17
impl CompileContext {
18
    /// Canonical list cell: `(struct (field anyref car) (field (ref null
19
    /// $pair) cdr))`. The only list-cell type in the compiler. CONS boxes
20
    /// the car (`ref.i31` for i32, implicit anyref subtype for Ratio /
21
    /// Commodity / String); CAR downcasts using the compile-time
22
    /// `PairElement` carried by `WasmType::PairRef`. Heterogeneous lists
23
    /// are refused at the language layer, so the wasm shape is uniform
24
    /// regardless of element type.
25
477239
    pub(super) fn register_pair_type(&mut self) -> Result<u32> {
26
477239
        let pair_idx = self.type_count;
27
477239
        let pair_nullable_ref = ValType::Ref(RefType {
28
477239
            nullable: true,
29
477239
            heap_type: HeapType::Concrete(pair_idx),
30
477239
        });
31
477239
        let anyref_field = ValType::Ref(RefType {
32
477239
            nullable: true,
33
477239
            heap_type: HeapType::ANY,
34
477239
        });
35
477239
        let struct_fields = vec![
36
477239
            FieldType {
37
477239
                element_type: StorageType::Val(anyref_field),
38
477239
                mutable: false,
39
477239
            },
40
477239
            FieldType {
41
477239
                element_type: StorageType::Val(pair_nullable_ref),
42
477239
                mutable: false,
43
477239
            },
44
        ];
45
477239
        self.types.ty().subtype(&SubType {
46
477239
            is_final: true,
47
477239
            supertype_idxs: Vec::new(),
48
477239
            composite_type: CompositeType {
49
477239
                inner: CompositeInnerType::Struct(StructType {
50
477239
                    fields: struct_fields.into_boxed_slice(),
51
477239
                }),
52
477239
                shared: false,
53
477239
                describes: None,
54
477239
                descriptor: None,
55
477239
            },
56
477239
        });
57
477239
        self.type_count = self
58
477239
            .type_count
59
477239
            .checked_add(1)
60
477239
            .ok_or_else(|| Error::Compile("wasm type index space exhausted".to_string()))?;
61
477239
        Ok(pair_idx)
62
477239
    }
63

            
64
477238
    pub(super) fn declare_pair_helpers(&mut self) -> Result<()> {
65
477238
        let pair_ref = self.pair_ref();
66
477238
        let anyref = self.anyref();
67
477238
        self.register_function("pair_new", &[anyref, pair_ref], &[pair_ref])?;
68
        // Host helpers (`scripting::runtime::alloc_pair_chain`)
69
        // reach `pair_new` via `caller.get_export("pair_new")` to
70
        // fold chains element-by-element. Re-entry through the
71
        // guest's own allocator dodges engine type canonicalization
72
        // for the `$pair` struct's `(ref null $pair)` cdr field.
73
477238
        self.export_func("pair_new")?;
74
477238
        Ok(())
75
477238
    }
76

            
77
477238
    pub(super) fn build_pair_helpers(&mut self) {
78
477238
        self.build_pair_new_body();
79
477238
    }
80

            
81
    /// One exported `alloc_<kind>` per entity wasm struct. Body is a
82
    /// straight `struct.new $<kind>` over the params. Host fns that
83
    /// produce typed entity returns (`list-accounts`, `get-account`,
84
    /// ...) re-enter the module via these exports — host-side
85
    /// `StructType::new` can't reproduce the entity's reference-typed
86
    /// field signature (`(ref null $i8_array)`) because engine
87
    /// canonicalization compares the field's concrete type index, not
88
    /// just the abstract heap class. Re-entry through the module's
89
    /// own allocator dodges the issue: each call produces a struct ref
90
    /// of the exact `$<kind>` type the guest's `ref.cast` then accepts.
91
    /// Declares + exports one `alloc_<kind>` per entity wasm struct (no bodies).
92
    /// Walks `ENTITY_SPECS` in order so [`Self::build_entity_allocators`] can
93
    /// emit the matching bodies positionally.
94
376909
    pub(super) fn declare_entity_allocators(&mut self) -> Result<()> {
95
        // Walks the same `ENTITY_SPECS` table that `new_skeleton`
96
        // consults — single source of truth keeps the allocator
97
        // signature aligned with the struct layout.
98
2638363
        for spec in super::entity_registry::ENTITY_SPECS {
99
10930361
            let params: Vec<ValType> = spec.fields.iter().map(|f| f.as_val_type(self)).collect();
100
2638363
            let entity_ref = self.entity_ref(spec.kind);
101
2638363
            let export_name = format!("alloc_{}", spec.type_name);
102
2638363
            self.register_function(&export_name, &params, &[entity_ref])?;
103
            // Host helpers (`scripting::runtime::alloc_entity_via_export`)
104
            // reach each allocator via `caller.get_export(...)` — the
105
            // export must be present or the host fn traps at runtime.
106
2638363
            self.export_func(&export_name)?;
107
        }
108
376909
        Ok(())
109
376909
    }
110

            
111
    /// Emits each `alloc_<kind>` body — a flat `local.get $i` × n +
112
    /// `struct.new $kind` — in `ENTITY_SPECS` order, reading the struct index
113
    /// from `self.ids.entity_type`.
114
376909
    pub(super) fn build_entity_allocators(&mut self) -> Result<()> {
115
2638363
        for spec in super::entity_registry::ENTITY_SPECS {
116
2638363
            let arity = spec.fields.len();
117
2638363
            let struct_idx = self.ids.entity_type(spec.kind);
118
2638363
            let mut f = Function::new([]);
119
10930361
            for i in 0..arity {
120
10930361
                let idx = u32::try_from(i)
121
10930361
                    .map_err(|_| Error::Compile("entity arity exceeds u32".to_string()))?;
122
10930361
                f.instruction(&Instruction::LocalGet(idx));
123
            }
124
2638363
            f.instruction(&Instruction::StructNew(struct_idx));
125
2638363
            f.instruction(&Instruction::End);
126
2638363
            self.pending_helpers.push(f);
127
        }
128
376909
        Ok(())
129
376909
    }
130

            
131
477238
    fn build_pair_new_body(&mut self) {
132
477238
        let pair_idx = self.ids.ty_pair;
133
        // params: $car=0 (anyref), $cdr=1 (ref null $pair).
134
        // body: push both, struct.new $pair.
135
477238
        let mut f = Function::new([]);
136
477238
        f.instruction(&Instruction::LocalGet(0));
137
477238
        f.instruction(&Instruction::LocalGet(1));
138
477238
        f.instruction(&Instruction::StructNew(pair_idx));
139
477238
        f.instruction(&Instruction::End);
140
477238
        self.pending_helpers.push(f);
141
477238
    }
142

            
143
477238
    pub(super) fn declare_string_eq_helper(&mut self) -> Result<()> {
144
477238
        let string_ref = self.string_ref();
145
477238
        self.register_function("string_eq", &[string_ref, string_ref], &[ValType::I32])?;
146
477238
        Ok(())
147
477238
    }
148

            
149
477238
    pub(super) fn build_string_eq_helper(&mut self) {
150
477238
        self.build_string_eq_body();
151
477238
    }
152

            
153
477238
    fn build_string_eq_body(&mut self) {
154
477238
        let i8_array_idx = self.ids.ty_i8_array;
155
        // params: $a=0 (ref null $i8_array), $b=1 (ref null $i8_array)
156
        // locals: $len=2 (i32), $i=3 (i32)
157
477238
        let mut f = Function::new([(1, ValType::I32), (1, ValType::I32)]);
158

            
159
        // Null guard. A null `$i8_array` is the runtime shape of an absent
160
        // string (nil), distinct from a present empty string "". `array.len`
161
        // traps on null, so handle the null cases before touching length:
162
        //   null == null  → 1  (both absent ≡ nil = nil)
163
        //   null == "..."  → 0  (absent ≠ present, even ≠ "")
164
        // Lispy nil≠"" semantics — see builtin_reference.org.
165
        // if a is null: return (b is null) as i32
166
477238
        f.instruction(&Instruction::LocalGet(0));
167
477238
        f.instruction(&Instruction::RefIsNull);
168
477238
        f.instruction(&Instruction::If(wasm_encoder::BlockType::Empty));
169
477238
        f.instruction(&Instruction::LocalGet(1));
170
477238
        f.instruction(&Instruction::RefIsNull);
171
477238
        f.instruction(&Instruction::Return);
172
477238
        f.instruction(&Instruction::End);
173
        // a is non-null here; if b is null they differ → return 0
174
477238
        f.instruction(&Instruction::LocalGet(1));
175
477238
        f.instruction(&Instruction::RefIsNull);
176
477238
        f.instruction(&Instruction::If(wasm_encoder::BlockType::Empty));
177
477238
        f.instruction(&Instruction::I32Const(0));
178
477238
        f.instruction(&Instruction::Return);
179
477238
        f.instruction(&Instruction::End);
180

            
181
        // len = array.len(a)
182
477238
        f.instruction(&Instruction::LocalGet(0));
183
477238
        f.instruction(&Instruction::ArrayLen);
184
477238
        f.instruction(&Instruction::LocalSet(2));
185

            
186
        // if array.len(b) != len → return 0
187
477238
        f.instruction(&Instruction::LocalGet(1));
188
477238
        f.instruction(&Instruction::ArrayLen);
189
477238
        f.instruction(&Instruction::LocalGet(2));
190
477238
        f.instruction(&Instruction::I32Ne);
191
477238
        f.instruction(&Instruction::If(wasm_encoder::BlockType::Empty));
192
477238
        f.instruction(&Instruction::I32Const(0));
193
477238
        f.instruction(&Instruction::Return);
194
477238
        f.instruction(&Instruction::End);
195

            
196
        // i = 0
197
477238
        f.instruction(&Instruction::I32Const(0));
198
477238
        f.instruction(&Instruction::LocalSet(3));
199

            
200
        // block $exit
201
477238
        f.instruction(&Instruction::Block(wasm_encoder::BlockType::Empty));
202
        // loop $cmp
203
477238
        f.instruction(&Instruction::Loop(wasm_encoder::BlockType::Empty));
204

            
205
        // if i >= len → break (equal)
206
477238
        f.instruction(&Instruction::LocalGet(3));
207
477238
        f.instruction(&Instruction::LocalGet(2));
208
477238
        f.instruction(&Instruction::I32GeU);
209
477238
        f.instruction(&Instruction::BrIf(1));
210

            
211
        // if a[i] != b[i] → return 0
212
477238
        f.instruction(&Instruction::LocalGet(0));
213
477238
        f.instruction(&Instruction::LocalGet(3));
214
477238
        f.instruction(&Instruction::ArrayGetU(i8_array_idx));
215
477238
        f.instruction(&Instruction::LocalGet(1));
216
477238
        f.instruction(&Instruction::LocalGet(3));
217
477238
        f.instruction(&Instruction::ArrayGetU(i8_array_idx));
218
477238
        f.instruction(&Instruction::I32Ne);
219
477238
        f.instruction(&Instruction::If(wasm_encoder::BlockType::Empty));
220
477238
        f.instruction(&Instruction::I32Const(0));
221
477238
        f.instruction(&Instruction::Return);
222
477238
        f.instruction(&Instruction::End);
223

            
224
        // i++
225
477238
        f.instruction(&Instruction::LocalGet(3));
226
477238
        f.instruction(&Instruction::I32Const(1));
227
477238
        f.instruction(&Instruction::I32Add);
228
477238
        f.instruction(&Instruction::LocalSet(3));
229
477238
        f.instruction(&Instruction::Br(0));
230

            
231
477238
        f.instruction(&Instruction::End); // end loop
232
477238
        f.instruction(&Instruction::End); // end block
233

            
234
        // return 1 (equal)
235
477238
        f.instruction(&Instruction::I32Const(1));
236
477238
        f.instruction(&Instruction::End);
237
477238
        self.pending_helpers.push(f);
238
477238
    }
239
}