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
219162
    pub(super) fn register_pair_type(&mut self) -> Result<u32> {
26
219162
        let pair_idx = self.type_count;
27
219162
        let pair_nullable_ref = ValType::Ref(RefType {
28
219162
            nullable: true,
29
219162
            heap_type: HeapType::Concrete(pair_idx),
30
219162
        });
31
219162
        let anyref_field = ValType::Ref(RefType {
32
219162
            nullable: true,
33
219162
            heap_type: HeapType::ANY,
34
219162
        });
35
219162
        let struct_fields = vec![
36
219162
            FieldType {
37
219162
                element_type: StorageType::Val(anyref_field),
38
219162
                mutable: false,
39
219162
            },
40
219162
            FieldType {
41
219162
                element_type: StorageType::Val(pair_nullable_ref),
42
219162
                mutable: false,
43
219162
            },
44
        ];
45
219162
        self.types.ty().subtype(&SubType {
46
219162
            is_final: true,
47
219162
            supertype_idx: None,
48
219162
            composite_type: CompositeType {
49
219162
                inner: CompositeInnerType::Struct(StructType {
50
219162
                    fields: struct_fields.into_boxed_slice(),
51
219162
                }),
52
219162
                shared: false,
53
219162
                describes: None,
54
219162
                descriptor: None,
55
219162
            },
56
219162
        });
57
219162
        self.type_count = self
58
219162
            .type_count
59
219162
            .checked_add(1)
60
219162
            .ok_or_else(|| Error::Compile("wasm type index space exhausted".to_string()))?;
61
219162
        Ok(pair_idx)
62
219162
    }
63

            
64
219161
    pub(super) fn declare_pair_helpers(&mut self) -> Result<()> {
65
219161
        let pair_ref = self.pair_ref();
66
219161
        let anyref = self.anyref();
67
219161
        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
219161
        self.export_func("pair_new")?;
74
219161
        Ok(())
75
219161
    }
76

            
77
219161
    pub(super) fn build_pair_helpers(&mut self) {
78
219161
        self.build_pair_new_body();
79
219161
    }
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
137026
    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
1096208
        for spec in super::entity_registry::ENTITY_SPECS {
99
3973754
            let params: Vec<ValType> = spec.fields.iter().map(|f| f.as_val_type(self)).collect();
100
1096208
            let entity_ref = self.entity_ref(spec.kind);
101
1096208
            let export_name = format!("alloc_{}", spec.type_name);
102
1096208
            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
1096208
            self.export_func(&export_name)?;
107
        }
108
137026
        Ok(())
109
137026
    }
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
137026
    pub(super) fn build_entity_allocators(&mut self) -> Result<()> {
115
1096208
        for spec in super::entity_registry::ENTITY_SPECS {
116
1096208
            let arity = spec.fields.len();
117
1096208
            let struct_idx = self.ids.entity_type(spec.kind);
118
1096208
            let mut f = Function::new([]);
119
3973754
            for i in 0..arity {
120
3973754
                let idx = u32::try_from(i)
121
3973754
                    .map_err(|_| Error::Compile("entity arity exceeds u32".to_string()))?;
122
3973754
                f.instruction(&Instruction::LocalGet(idx));
123
            }
124
1096208
            f.instruction(&Instruction::StructNew(struct_idx));
125
1096208
            f.instruction(&Instruction::End);
126
1096208
            self.pending_helpers.push(f);
127
        }
128
137026
        Ok(())
129
137026
    }
130

            
131
219161
    fn build_pair_new_body(&mut self) {
132
219161
        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
219161
        let mut f = Function::new([]);
136
219161
        f.instruction(&Instruction::LocalGet(0));
137
219161
        f.instruction(&Instruction::LocalGet(1));
138
219161
        f.instruction(&Instruction::StructNew(pair_idx));
139
219161
        f.instruction(&Instruction::End);
140
219161
        self.pending_helpers.push(f);
141
219161
    }
142

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

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

            
153
219161
    fn build_string_eq_body(&mut self) {
154
219161
        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
219161
        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
219161
        f.instruction(&Instruction::LocalGet(0));
167
219161
        f.instruction(&Instruction::RefIsNull);
168
219161
        f.instruction(&Instruction::If(wasm_encoder::BlockType::Empty));
169
219161
        f.instruction(&Instruction::LocalGet(1));
170
219161
        f.instruction(&Instruction::RefIsNull);
171
219161
        f.instruction(&Instruction::Return);
172
219161
        f.instruction(&Instruction::End);
173
        // a is non-null here; if b is null they differ → return 0
174
219161
        f.instruction(&Instruction::LocalGet(1));
175
219161
        f.instruction(&Instruction::RefIsNull);
176
219161
        f.instruction(&Instruction::If(wasm_encoder::BlockType::Empty));
177
219161
        f.instruction(&Instruction::I32Const(0));
178
219161
        f.instruction(&Instruction::Return);
179
219161
        f.instruction(&Instruction::End);
180

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

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

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

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

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

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

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

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

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