1
//! Guest-side helpers for the Commodity type.
2
//!
3
//! Mirrors `register_ratio_helpers` but for commodity-bearing values.
4
//! `commodity_new` is a thin wrapper around `struct.new commodity`;
5
//! the arithmetic helpers (`commodity_add`, `commodity_sub`, ...) do
6
//! an id-equality check at the front and, on mismatch, `throw` a
7
//! `$nomi_error` carrying a `commodity-mismatch` condition (ADR-0014 +
8
//! ADR-0026). The throw is catchable by an in-module `(handler-case)` /
9
//! `(unwind-protect)`; an uncaught one is bridged to `__nomi_raise` by the
10
//! boundary wrapper, surfacing the same `commodity-mismatch` wire code as
11
//! before — engine errors now travel the single exception channel rather
12
//! than the lone `unreachable` trap. Scaling helpers
13
//! (`commodity_mul_by_ratio`, `commodity_div_by_ratio`) accept a pure Ratio
14
//! second operand and keep the commodity id from the first.
15

            
16
use super::CompileContext;
17
use crate::error::Result;
18
use wasm_encoder::{Function, Instruction, ValType};
19

            
20
/// Pre-registered data-segment handles + type/tag indices the
21
/// commodity-mismatch `throw` needs. Computed once in
22
/// `register_commodity_helpers` (where `&mut self` can `add_data`) and
23
/// threaded into each id-check site (which only has `&self`).
24
#[derive(Clone, Copy)]
25
struct MismatchTrap {
26
    i8_array_idx: u32,
27
    condition_idx: u32,
28
    tag: u32,
29
    code_data: u32,
30
    code_len: u32,
31
    message_data: u32,
32
    message_len: u32,
33
}
34

            
35
impl CompileContext {
36
    /// Declares the commodity helper signatures (atomic + compound), no bodies.
37
    /// The compound signatures are registered here too so every commodity index
38
    /// exists before `resolve_ids`; bodies are emitted by
39
    /// [`Self::build_commodity_helpers`] in the same registration order.
40
252750
    pub(super) fn declare_commodity_helpers(&mut self) -> Result<()> {
41
252750
        let commodity_ref = self.commodity_ref();
42
252750
        let ratio_ref = self.ratio_ref();
43
252750
        self.register_function(
44
252750
            "commodity_new",
45
252750
            &[ValType::I64, ValType::I64, ValType::I64, ValType::I64],
46
252750
            &[commodity_ref],
47
        )?;
48
252750
        self.register_function(
49
252750
            "commodity_add",
50
252750
            &[commodity_ref, commodity_ref],
51
252750
            &[commodity_ref],
52
        )?;
53
252750
        self.register_function(
54
252750
            "commodity_sub",
55
252750
            &[commodity_ref, commodity_ref],
56
252750
            &[commodity_ref],
57
        )?;
58
252750
        self.register_function("commodity_neg", &[commodity_ref], &[commodity_ref])?;
59
252750
        self.register_function(
60
252750
            "commodity_mul_by_ratio",
61
252750
            &[commodity_ref, ratio_ref],
62
252750
            &[commodity_ref],
63
        )?;
64
252750
        self.register_function(
65
252750
            "commodity_div_by_ratio",
66
252750
            &[commodity_ref, ratio_ref],
67
252750
            &[commodity_ref],
68
        )?;
69
252750
        self.register_function(
70
252750
            "commodity_eq",
71
252750
            &[commodity_ref, commodity_ref],
72
252750
            &[ValType::I32],
73
        )?;
74
252750
        self.register_function(
75
252750
            "commodity_lt",
76
252750
            &[commodity_ref, commodity_ref],
77
252750
            &[ValType::I32],
78
        )?;
79
        // Exported so the host (`alloc_commodity_ref`) constructs commodity
80
        // values by re-entering this helper rather than building the now
81
        // ref-bearing 5-field struct itself (ADR-0028 E0), mirroring the
82
        // `pair_new` / entity-allocator re-entry pattern.
83
252750
        self.export_func("commodity_new")?;
84
        // Compound-money helpers (ADR-0028 E2): SIGNATURES only, registered
85
        // after the atomic ones so the function-index order is unchanged.
86
252750
        self.declare_commodity_compound_signatures()
87
252750
    }
88

            
89
    /// Emits the commodity helper bodies in registration order. Traps are
90
    /// interned here (build order), so the data-segment index sequence matches
91
    /// the pre-split layout; `&self`-borrowing throw sites read `self.ids`.
92
252750
    pub(super) fn build_commodity_helpers(&mut self) -> Result<()> {
93
252750
        let trap = self.register_mismatch_trap()?;
94
252750
        let non_atomic = self.register_non_atomic_trap()?;
95
252750
        self.build_commodity_new_body();
96
252750
        self.build_commodity_binop_bodies(trap);
97
252750
        self.build_commodity_neg_body();
98
252750
        self.build_commodity_scale_bodies();
99
252750
        self.build_commodity_cmp_bodies(trap);
100
252750
        self.build_commodity_compound_bodies(&non_atomic)?;
101
252750
        Ok(())
102
252750
    }
103

            
104
    /// Interns the `commodity-mismatch` condition's code + message strings as
105
    /// passive data segments and captures the `$nomi_condition` / `$nomi_error`
106
    /// indices, so each id-check site (which only borrows `&self`) can emit the
107
    /// `struct.new` + `throw` without needing `&mut self`.
108
252750
    fn register_mismatch_trap(&mut self) -> Result<MismatchTrap> {
109
        // UPPER-CASE to match the reader's symbol case-folding: a
110
        // `(handler-case … (commodity-mismatch (e) …))` clause upcases its
111
        // code to `COMMODITY-MISMATCH`, and handler dispatch compares the
112
        // condition's code byte-for-byte — so the thrown code must be the
113
        // upcased symbol form (the same convention script raises follow, e.g.
114
        // `(error 'no-such-account …)` → `NO-SUCH-ACCOUNT`). The uncaught wire
115
        // `:code` is therefore `COMMODITY-MISMATCH` too.
116
        const CODE: &str = "COMMODITY-MISMATCH";
117
        const MESSAGE: &str = "cannot combine values of different commodities";
118
252750
        let code_data = self.add_data(CODE.as_bytes())?;
119
252750
        let message_data = self.add_data(MESSAGE.as_bytes())?;
120
252750
        Ok(MismatchTrap {
121
252750
            i8_array_idx: self.ids.ty_i8_array,
122
252750
            condition_idx: self.condition_type_idx(),
123
252750
            tag: self.nomi_error_tag(),
124
252750
            code_data,
125
252750
            code_len: CODE.len() as u32,
126
252750
            message_data,
127
252750
            message_len: MESSAGE.len() as u32,
128
252750
        })
129
252750
    }
130

            
131
252750
    fn build_commodity_new_body(&mut self) {
132
252750
        let new_with_term = self.ids.commodity_new_with_term;
133
        // params: $numer=0, $denom=1, $commodity_hi=2, $commodity_lo=3.
134
        // The atomic constructor: delegate to `commodity_new_with_term` with a
135
        // NULL unit term (ATOMIC `[(atom,1)]`). No reduction — callers pass the
136
        // canonical (numer, denom) pair they want.
137
252750
        let mut f = Function::new([]);
138
252750
        f.instruction(&Instruction::LocalGet(0));
139
252750
        f.instruction(&Instruction::LocalGet(1));
140
252750
        f.instruction(&Instruction::LocalGet(2));
141
252750
        f.instruction(&Instruction::LocalGet(3));
142
252750
        self.emit_null_unit_term(&mut f);
143
252750
        f.instruction(&Instruction::Call(new_with_term));
144
252750
        f.instruction(&Instruction::End);
145
252750
        self.pending_helpers.push(f);
146
252750
    }
147

            
148
    /// Emits the prologue of a same-commodity binop: compares both i64
149
    /// halves of the UUID pair and, on mismatch, throws a `commodity-mismatch`
150
    /// `$nomi_error` (ADR-0026). Caller-supplied param indices (0 and 1 for
151
    /// the standard two-Commodity shape).
152
1011000
    fn emit_commodity_id_check(&self, f: &mut Function, trap: MismatchTrap, a: u32, b: u32) {
153
1011000
        let commodity_idx = self.ids.ty_commodity;
154
        // hi mismatch
155
1011000
        f.instruction(&Instruction::LocalGet(a));
156
1011000
        f.instruction(&Instruction::StructGet {
157
1011000
            struct_type_index: commodity_idx,
158
1011000
            field_index: 2,
159
1011000
        });
160
1011000
        f.instruction(&Instruction::LocalGet(b));
161
1011000
        f.instruction(&Instruction::StructGet {
162
1011000
            struct_type_index: commodity_idx,
163
1011000
            field_index: 2,
164
1011000
        });
165
1011000
        f.instruction(&Instruction::I64Ne);
166
        // lo mismatch
167
1011000
        f.instruction(&Instruction::LocalGet(a));
168
1011000
        f.instruction(&Instruction::StructGet {
169
1011000
            struct_type_index: commodity_idx,
170
1011000
            field_index: 3,
171
1011000
        });
172
1011000
        f.instruction(&Instruction::LocalGet(b));
173
1011000
        f.instruction(&Instruction::StructGet {
174
1011000
            struct_type_index: commodity_idx,
175
1011000
            field_index: 3,
176
1011000
        });
177
1011000
        f.instruction(&Instruction::I64Ne);
178
        // either half differs → build the condition and throw $nomi_error.
179
1011000
        f.instruction(&Instruction::I32Or);
180
1011000
        f.instruction(&Instruction::If(wasm_encoder::BlockType::Empty));
181
1011000
        self.emit_mismatch_throw(f, trap);
182
1011000
        f.instruction(&Instruction::End);
183
1011000
    }
184

            
185
    /// Unit-aware prologue for add/sub/eq/lt (ADR-0028 E2): when both operands
186
    /// are ATOMIC (null term) it is the cheap hi/lo id-check above; otherwise it
187
    /// compares the materialized unit terms with `unit_eq` and throws
188
    /// `commodity-mismatch` if they differ. Atomic money keeps the exact
189
    /// fast-path behaviour it had before compound money existed.
190
1011000
    fn emit_commodity_unit_check(&self, f: &mut Function, trap: MismatchTrap, a: u32, b: u32) {
191
1011000
        let commodity_idx = self.ids.ty_commodity;
192
1011000
        let materialize = self.ids.materialize_unit;
193
1011000
        let unit_eq = self.ids.unit_eq;
194
        // both terms null?
195
1011000
        f.instruction(&Instruction::LocalGet(a));
196
1011000
        f.instruction(&Instruction::StructGet {
197
1011000
            struct_type_index: commodity_idx,
198
1011000
            field_index: 4,
199
1011000
        });
200
1011000
        f.instruction(&Instruction::RefIsNull);
201
1011000
        f.instruction(&Instruction::LocalGet(b));
202
1011000
        f.instruction(&Instruction::StructGet {
203
1011000
            struct_type_index: commodity_idx,
204
1011000
            field_index: 4,
205
1011000
        });
206
1011000
        f.instruction(&Instruction::RefIsNull);
207
1011000
        f.instruction(&Instruction::I32And);
208
1011000
        f.instruction(&Instruction::If(wasm_encoder::BlockType::Empty));
209
1011000
        self.emit_commodity_id_check(f, trap, a, b);
210
1011000
        f.instruction(&Instruction::Else);
211
        // compound: compare materialized terms by multiset equality
212
1011000
        f.instruction(&Instruction::LocalGet(a));
213
1011000
        f.instruction(&Instruction::Call(materialize));
214
1011000
        f.instruction(&Instruction::LocalGet(b));
215
1011000
        f.instruction(&Instruction::Call(materialize));
216
1011000
        f.instruction(&Instruction::Call(unit_eq));
217
1011000
        f.instruction(&Instruction::I32Eqz);
218
1011000
        f.instruction(&Instruction::If(wasm_encoder::BlockType::Empty));
219
1011000
        self.emit_mismatch_throw(f, trap);
220
1011000
        f.instruction(&Instruction::End);
221
1011000
        f.instruction(&Instruction::End);
222
1011000
    }
223

            
224
    /// Builds the `commodity-mismatch` `$nomi_condition` (interned code +
225
    /// message strings) and `throw`s `$nomi_error`. `throw` never returns, so
226
    /// the surrounding binop body's declared result type is satisfied by
227
    /// wasm stack-polymorphism past the throw.
228
2022000
    fn emit_mismatch_throw(&self, f: &mut Function, trap: MismatchTrap) {
229
2022000
        f.instruction(&Instruction::I32Const(0));
230
2022000
        f.instruction(&Instruction::I32Const(trap.code_len as i32));
231
2022000
        f.instruction(&Instruction::ArrayNewData {
232
2022000
            array_type_index: trap.i8_array_idx,
233
2022000
            array_data_index: trap.code_data,
234
2022000
        });
235
2022000
        f.instruction(&Instruction::I32Const(0));
236
2022000
        f.instruction(&Instruction::I32Const(trap.message_len as i32));
237
2022000
        f.instruction(&Instruction::ArrayNewData {
238
2022000
            array_type_index: trap.i8_array_idx,
239
2022000
            array_data_index: trap.message_data,
240
2022000
        });
241
2022000
        f.instruction(&Instruction::StructNew(trap.condition_idx));
242
2022000
        f.instruction(&Instruction::Throw(trap.tag));
243
2022000
    }
244

            
245
    /// Pushes a fresh `ratio_ref` built from the (numer, denom) fields of
246
    /// the commodity at the given param index. Uses `struct.new ratio`
247
    /// directly (no normalization) since the values are already canonical
248
    /// from a prior `ratio_new` / `commodity_new`.
249
3538500
    pub(super) fn emit_ratio_from_commodity(&self, f: &mut Function, param_idx: u32) {
250
3538500
        let commodity_idx = self.ids.ty_commodity;
251
3538500
        let ratio_idx = self.ids.ty_ratio;
252
3538500
        f.instruction(&Instruction::LocalGet(param_idx));
253
3538500
        f.instruction(&Instruction::StructGet {
254
3538500
            struct_type_index: commodity_idx,
255
3538500
            field_index: 0,
256
3538500
        });
257
3538500
        f.instruction(&Instruction::LocalGet(param_idx));
258
3538500
        f.instruction(&Instruction::StructGet {
259
3538500
            struct_type_index: commodity_idx,
260
3538500
            field_index: 1,
261
3538500
        });
262
3538500
        f.instruction(&Instruction::StructNew(ratio_idx));
263
3538500
    }
264

            
265
    /// After a same-commodity binop / scaling produces a result `ratio_ref` (in
266
    /// local $r), repackages it as a `commodity_ref` carrying the id AND unit
267
    /// term of param $a. Add/sub verify both operands share a term, and scaling
268
    /// leaves the unit unchanged, so propagating $a's term (field 4) is correct
269
    /// for every caller and keeps compound money compound (ADR-0028 E2).
270
1011000
    fn emit_commodity_repack(&self, f: &mut Function, ratio_local: u32, a_param: u32) {
271
1011000
        let ratio_idx = self.ids.ty_ratio;
272
1011000
        let commodity_idx = self.ids.ty_commodity;
273
1011000
        let new_with_term = self.ids.commodity_new_with_term;
274
1011000
        f.instruction(&Instruction::LocalGet(ratio_local));
275
1011000
        f.instruction(&Instruction::StructGet {
276
1011000
            struct_type_index: ratio_idx,
277
1011000
            field_index: 0,
278
1011000
        });
279
1011000
        f.instruction(&Instruction::LocalGet(ratio_local));
280
1011000
        f.instruction(&Instruction::StructGet {
281
1011000
            struct_type_index: ratio_idx,
282
1011000
            field_index: 1,
283
1011000
        });
284
1011000
        f.instruction(&Instruction::LocalGet(a_param));
285
1011000
        f.instruction(&Instruction::StructGet {
286
1011000
            struct_type_index: commodity_idx,
287
1011000
            field_index: 2,
288
1011000
        });
289
1011000
        f.instruction(&Instruction::LocalGet(a_param));
290
1011000
        f.instruction(&Instruction::StructGet {
291
1011000
            struct_type_index: commodity_idx,
292
1011000
            field_index: 3,
293
1011000
        });
294
1011000
        f.instruction(&Instruction::LocalGet(a_param));
295
1011000
        f.instruction(&Instruction::StructGet {
296
1011000
            struct_type_index: commodity_idx,
297
1011000
            field_index: 4,
298
1011000
        });
299
1011000
        f.instruction(&Instruction::Call(new_with_term));
300
1011000
    }
301

            
302
252750
    fn build_commodity_binop_bodies(&mut self, trap: MismatchTrap) {
303
252750
        let ratio_ref = self.ratio_ref();
304
252750
        let ratio_add = self.ids.ratio_add;
305
252750
        let ratio_sub = self.ids.ratio_sub;
306

            
307
        // commodity_add(a, b): id-check, then ratio_add(a.ratio, b.ratio),
308
        // re-pack as commodity carrying a's id. Locals: $r=2 (ratio_ref).
309
252750
        let mut f = Function::new([(1, ratio_ref)]);
310
252750
        self.emit_commodity_unit_check(&mut f, trap, 0, 1);
311
252750
        self.emit_ratio_from_commodity(&mut f, 0);
312
252750
        self.emit_ratio_from_commodity(&mut f, 1);
313
252750
        f.instruction(&Instruction::Call(ratio_add));
314
252750
        f.instruction(&Instruction::LocalSet(2));
315
252750
        self.emit_commodity_repack(&mut f, 2, 0);
316
252750
        f.instruction(&Instruction::End);
317
252750
        self.pending_helpers.push(f);
318

            
319
        // commodity_sub(a, b): id-check, then ratio_sub.
320
252750
        let mut f = Function::new([(1, ratio_ref)]);
321
252750
        self.emit_commodity_unit_check(&mut f, trap, 0, 1);
322
252750
        self.emit_ratio_from_commodity(&mut f, 0);
323
252750
        self.emit_ratio_from_commodity(&mut f, 1);
324
252750
        f.instruction(&Instruction::Call(ratio_sub));
325
252750
        f.instruction(&Instruction::LocalSet(2));
326
252750
        self.emit_commodity_repack(&mut f, 2, 0);
327
252750
        f.instruction(&Instruction::End);
328
252750
        self.pending_helpers.push(f);
329
252750
    }
330

            
331
252750
    fn build_commodity_neg_body(&mut self) {
332
252750
        let commodity_idx = self.ids.ty_commodity;
333
252750
        let new_with_term = self.ids.commodity_new_with_term;
334
        // commodity_neg(a) = commodity_new_with_term(-a.numer, a.denom, a.hi,
335
        // a.lo, a.term). Negation flips the sign but preserves the unit term, so
336
        // a compound money stays compound (ADR-0028 E2).
337
252750
        let mut f = Function::new([]);
338
        // -a.numer = 0 - a.numer
339
252750
        f.instruction(&Instruction::I64Const(0));
340
252750
        f.instruction(&Instruction::LocalGet(0));
341
252750
        f.instruction(&Instruction::StructGet {
342
252750
            struct_type_index: commodity_idx,
343
252750
            field_index: 0,
344
252750
        });
345
252750
        f.instruction(&Instruction::I64Sub);
346
        // a.denom
347
252750
        f.instruction(&Instruction::LocalGet(0));
348
252750
        f.instruction(&Instruction::StructGet {
349
252750
            struct_type_index: commodity_idx,
350
252750
            field_index: 1,
351
252750
        });
352
        // a.hi
353
252750
        f.instruction(&Instruction::LocalGet(0));
354
252750
        f.instruction(&Instruction::StructGet {
355
252750
            struct_type_index: commodity_idx,
356
252750
            field_index: 2,
357
252750
        });
358
        // a.lo
359
252750
        f.instruction(&Instruction::LocalGet(0));
360
252750
        f.instruction(&Instruction::StructGet {
361
252750
            struct_type_index: commodity_idx,
362
252750
            field_index: 3,
363
252750
        });
364
        // a.term
365
252750
        f.instruction(&Instruction::LocalGet(0));
366
252750
        f.instruction(&Instruction::StructGet {
367
252750
            struct_type_index: commodity_idx,
368
252750
            field_index: 4,
369
252750
        });
370
252750
        f.instruction(&Instruction::Call(new_with_term));
371
252750
        f.instruction(&Instruction::End);
372
252750
        self.pending_helpers.push(f);
373
252750
    }
374

            
375
252750
    fn build_commodity_scale_bodies(&mut self) {
376
252750
        let ratio_ref = self.ratio_ref();
377
252750
        let ratio_mul = self.ids.ratio_mul;
378
252750
        let ratio_div = self.ids.ratio_div;
379

            
380
        // commodity_mul_by_ratio(c, r): ratio_mul(c.ratio, r) -> repack with c's id.
381
        // Param 0=c (commodity_ref), param 1=r (ratio_ref). Local 2=result ratio_ref.
382
252750
        let mut f = Function::new([(1, ratio_ref)]);
383
252750
        self.emit_ratio_from_commodity(&mut f, 0);
384
252750
        f.instruction(&Instruction::LocalGet(1));
385
252750
        f.instruction(&Instruction::Call(ratio_mul));
386
252750
        f.instruction(&Instruction::LocalSet(2));
387
252750
        self.emit_commodity_repack(&mut f, 2, 0);
388
252750
        f.instruction(&Instruction::End);
389
252750
        self.pending_helpers.push(f);
390

            
391
        // commodity_div_by_ratio(c, r): ratio_div(c.ratio, r) -> repack with c's id.
392
252750
        let mut f = Function::new([(1, ratio_ref)]);
393
252750
        self.emit_ratio_from_commodity(&mut f, 0);
394
252750
        f.instruction(&Instruction::LocalGet(1));
395
252750
        f.instruction(&Instruction::Call(ratio_div));
396
252750
        f.instruction(&Instruction::LocalSet(2));
397
252750
        self.emit_commodity_repack(&mut f, 2, 0);
398
252750
        f.instruction(&Instruction::End);
399
252750
        self.pending_helpers.push(f);
400
252750
    }
401

            
402
252750
    fn build_commodity_cmp_bodies(&mut self, trap: MismatchTrap) {
403
252750
        let ratio_eq = self.ids.ratio_eq;
404
252750
        let ratio_lt = self.ids.ratio_lt;
405

            
406
        // commodity_eq(a, b): id-check, ratio_eq(a.ratio, b.ratio).
407
252750
        let mut f = Function::new([]);
408
252750
        self.emit_commodity_unit_check(&mut f, trap, 0, 1);
409
252750
        self.emit_ratio_from_commodity(&mut f, 0);
410
252750
        self.emit_ratio_from_commodity(&mut f, 1);
411
252750
        f.instruction(&Instruction::Call(ratio_eq));
412
252750
        f.instruction(&Instruction::End);
413
252750
        self.pending_helpers.push(f);
414

            
415
        // commodity_lt(a, b): id-check, ratio_lt(a.ratio, b.ratio).
416
252750
        let mut f = Function::new([]);
417
252750
        self.emit_commodity_unit_check(&mut f, trap, 0, 1);
418
252750
        self.emit_ratio_from_commodity(&mut f, 0);
419
252750
        self.emit_ratio_from_commodity(&mut f, 1);
420
252750
        f.instruction(&Instruction::Call(ratio_lt));
421
252750
        f.instruction(&Instruction::End);
422
252750
        self.pending_helpers.push(f);
423
252750
    }
424
}