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
219161
    pub(super) fn declare_commodity_helpers(&mut self) -> Result<()> {
41
219161
        let commodity_ref = self.commodity_ref();
42
219161
        let ratio_ref = self.ratio_ref();
43
219161
        self.register_function(
44
219161
            "commodity_new",
45
219161
            &[ValType::I64, ValType::I64, ValType::I64, ValType::I64],
46
219161
            &[commodity_ref],
47
        )?;
48
219161
        self.register_function(
49
219161
            "commodity_add",
50
219161
            &[commodity_ref, commodity_ref],
51
219161
            &[commodity_ref],
52
        )?;
53
219161
        self.register_function(
54
219161
            "commodity_sub",
55
219161
            &[commodity_ref, commodity_ref],
56
219161
            &[commodity_ref],
57
        )?;
58
219161
        self.register_function("commodity_neg", &[commodity_ref], &[commodity_ref])?;
59
219161
        self.register_function(
60
219161
            "commodity_mul_by_ratio",
61
219161
            &[commodity_ref, ratio_ref],
62
219161
            &[commodity_ref],
63
        )?;
64
219161
        self.register_function(
65
219161
            "commodity_div_by_ratio",
66
219161
            &[commodity_ref, ratio_ref],
67
219161
            &[commodity_ref],
68
        )?;
69
219161
        self.register_function(
70
219161
            "commodity_eq",
71
219161
            &[commodity_ref, commodity_ref],
72
219161
            &[ValType::I32],
73
        )?;
74
219161
        self.register_function(
75
219161
            "commodity_lt",
76
219161
            &[commodity_ref, commodity_ref],
77
219161
            &[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
219161
        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
219161
        self.declare_commodity_compound_signatures()
87
219161
    }
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
219161
    pub(super) fn build_commodity_helpers(&mut self) -> Result<()> {
93
219161
        let trap = self.register_mismatch_trap()?;
94
219161
        let non_atomic = self.register_non_atomic_trap()?;
95
219161
        self.build_commodity_new_body();
96
219161
        self.build_commodity_binop_bodies(trap);
97
219161
        self.build_commodity_neg_body();
98
219161
        self.build_commodity_scale_bodies();
99
219161
        self.build_commodity_cmp_bodies(trap);
100
219161
        self.build_commodity_compound_bodies(&non_atomic)?;
101
219161
        Ok(())
102
219161
    }
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
219161
    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
219161
        let code_data = self.add_data(CODE.as_bytes())?;
119
219161
        let message_data = self.add_data(MESSAGE.as_bytes())?;
120
219161
        Ok(MismatchTrap {
121
219161
            i8_array_idx: self.ids.ty_i8_array,
122
219161
            condition_idx: self.condition_type_idx(),
123
219161
            tag: self.nomi_error_tag(),
124
219161
            code_data,
125
219161
            code_len: CODE.len() as u32,
126
219161
            message_data,
127
219161
            message_len: MESSAGE.len() as u32,
128
219161
        })
129
219161
    }
130

            
131
219161
    fn build_commodity_new_body(&mut self) {
132
219161
        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
219161
        let mut f = Function::new([]);
138
219161
        f.instruction(&Instruction::LocalGet(0));
139
219161
        f.instruction(&Instruction::LocalGet(1));
140
219161
        f.instruction(&Instruction::LocalGet(2));
141
219161
        f.instruction(&Instruction::LocalGet(3));
142
219161
        self.emit_null_unit_term(&mut f);
143
219161
        f.instruction(&Instruction::Call(new_with_term));
144
219161
        f.instruction(&Instruction::End);
145
219161
        self.pending_helpers.push(f);
146
219161
    }
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
876644
    fn emit_commodity_id_check(&self, f: &mut Function, trap: MismatchTrap, a: u32, b: u32) {
153
876644
        let commodity_idx = self.ids.ty_commodity;
154
        // hi mismatch
155
876644
        f.instruction(&Instruction::LocalGet(a));
156
876644
        f.instruction(&Instruction::StructGet {
157
876644
            struct_type_index: commodity_idx,
158
876644
            field_index: 2,
159
876644
        });
160
876644
        f.instruction(&Instruction::LocalGet(b));
161
876644
        f.instruction(&Instruction::StructGet {
162
876644
            struct_type_index: commodity_idx,
163
876644
            field_index: 2,
164
876644
        });
165
876644
        f.instruction(&Instruction::I64Ne);
166
        // lo mismatch
167
876644
        f.instruction(&Instruction::LocalGet(a));
168
876644
        f.instruction(&Instruction::StructGet {
169
876644
            struct_type_index: commodity_idx,
170
876644
            field_index: 3,
171
876644
        });
172
876644
        f.instruction(&Instruction::LocalGet(b));
173
876644
        f.instruction(&Instruction::StructGet {
174
876644
            struct_type_index: commodity_idx,
175
876644
            field_index: 3,
176
876644
        });
177
876644
        f.instruction(&Instruction::I64Ne);
178
        // either half differs → build the condition and throw $nomi_error.
179
876644
        f.instruction(&Instruction::I32Or);
180
876644
        f.instruction(&Instruction::If(wasm_encoder::BlockType::Empty));
181
876644
        self.emit_mismatch_throw(f, trap);
182
876644
        f.instruction(&Instruction::End);
183
876644
    }
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
876644
    fn emit_commodity_unit_check(&self, f: &mut Function, trap: MismatchTrap, a: u32, b: u32) {
191
876644
        let commodity_idx = self.ids.ty_commodity;
192
876644
        let materialize = self.ids.materialize_unit;
193
876644
        let unit_eq = self.ids.unit_eq;
194
        // both terms null?
195
876644
        f.instruction(&Instruction::LocalGet(a));
196
876644
        f.instruction(&Instruction::StructGet {
197
876644
            struct_type_index: commodity_idx,
198
876644
            field_index: 4,
199
876644
        });
200
876644
        f.instruction(&Instruction::RefIsNull);
201
876644
        f.instruction(&Instruction::LocalGet(b));
202
876644
        f.instruction(&Instruction::StructGet {
203
876644
            struct_type_index: commodity_idx,
204
876644
            field_index: 4,
205
876644
        });
206
876644
        f.instruction(&Instruction::RefIsNull);
207
876644
        f.instruction(&Instruction::I32And);
208
876644
        f.instruction(&Instruction::If(wasm_encoder::BlockType::Empty));
209
876644
        self.emit_commodity_id_check(f, trap, a, b);
210
876644
        f.instruction(&Instruction::Else);
211
        // compound: compare materialized terms by multiset equality
212
876644
        f.instruction(&Instruction::LocalGet(a));
213
876644
        f.instruction(&Instruction::Call(materialize));
214
876644
        f.instruction(&Instruction::LocalGet(b));
215
876644
        f.instruction(&Instruction::Call(materialize));
216
876644
        f.instruction(&Instruction::Call(unit_eq));
217
876644
        f.instruction(&Instruction::I32Eqz);
218
876644
        f.instruction(&Instruction::If(wasm_encoder::BlockType::Empty));
219
876644
        self.emit_mismatch_throw(f, trap);
220
876644
        f.instruction(&Instruction::End);
221
876644
        f.instruction(&Instruction::End);
222
876644
    }
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
1753288
    fn emit_mismatch_throw(&self, f: &mut Function, trap: MismatchTrap) {
229
1753288
        f.instruction(&Instruction::I32Const(0));
230
1753288
        f.instruction(&Instruction::I32Const(trap.code_len as i32));
231
1753288
        f.instruction(&Instruction::ArrayNewData {
232
1753288
            array_type_index: trap.i8_array_idx,
233
1753288
            array_data_index: trap.code_data,
234
1753288
        });
235
1753288
        f.instruction(&Instruction::I32Const(0));
236
1753288
        f.instruction(&Instruction::I32Const(trap.message_len as i32));
237
1753288
        f.instruction(&Instruction::ArrayNewData {
238
1753288
            array_type_index: trap.i8_array_idx,
239
1753288
            array_data_index: trap.message_data,
240
1753288
        });
241
1753288
        f.instruction(&Instruction::StructNew(trap.condition_idx));
242
1753288
        f.instruction(&Instruction::Throw(trap.tag));
243
1753288
    }
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
3068254
    pub(super) fn emit_ratio_from_commodity(&self, f: &mut Function, param_idx: u32) {
250
3068254
        let commodity_idx = self.ids.ty_commodity;
251
3068254
        let ratio_idx = self.ids.ty_ratio;
252
3068254
        f.instruction(&Instruction::LocalGet(param_idx));
253
3068254
        f.instruction(&Instruction::StructGet {
254
3068254
            struct_type_index: commodity_idx,
255
3068254
            field_index: 0,
256
3068254
        });
257
3068254
        f.instruction(&Instruction::LocalGet(param_idx));
258
3068254
        f.instruction(&Instruction::StructGet {
259
3068254
            struct_type_index: commodity_idx,
260
3068254
            field_index: 1,
261
3068254
        });
262
3068254
        f.instruction(&Instruction::StructNew(ratio_idx));
263
3068254
    }
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
876644
    fn emit_commodity_repack(&self, f: &mut Function, ratio_local: u32, a_param: u32) {
271
876644
        let ratio_idx = self.ids.ty_ratio;
272
876644
        let commodity_idx = self.ids.ty_commodity;
273
876644
        let new_with_term = self.ids.commodity_new_with_term;
274
876644
        f.instruction(&Instruction::LocalGet(ratio_local));
275
876644
        f.instruction(&Instruction::StructGet {
276
876644
            struct_type_index: ratio_idx,
277
876644
            field_index: 0,
278
876644
        });
279
876644
        f.instruction(&Instruction::LocalGet(ratio_local));
280
876644
        f.instruction(&Instruction::StructGet {
281
876644
            struct_type_index: ratio_idx,
282
876644
            field_index: 1,
283
876644
        });
284
876644
        f.instruction(&Instruction::LocalGet(a_param));
285
876644
        f.instruction(&Instruction::StructGet {
286
876644
            struct_type_index: commodity_idx,
287
876644
            field_index: 2,
288
876644
        });
289
876644
        f.instruction(&Instruction::LocalGet(a_param));
290
876644
        f.instruction(&Instruction::StructGet {
291
876644
            struct_type_index: commodity_idx,
292
876644
            field_index: 3,
293
876644
        });
294
876644
        f.instruction(&Instruction::LocalGet(a_param));
295
876644
        f.instruction(&Instruction::StructGet {
296
876644
            struct_type_index: commodity_idx,
297
876644
            field_index: 4,
298
876644
        });
299
876644
        f.instruction(&Instruction::Call(new_with_term));
300
876644
    }
301

            
302
219161
    fn build_commodity_binop_bodies(&mut self, trap: MismatchTrap) {
303
219161
        let ratio_ref = self.ratio_ref();
304
219161
        let ratio_add = self.ids.ratio_add;
305
219161
        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
219161
        let mut f = Function::new([(1, ratio_ref)]);
310
219161
        self.emit_commodity_unit_check(&mut f, trap, 0, 1);
311
219161
        self.emit_ratio_from_commodity(&mut f, 0);
312
219161
        self.emit_ratio_from_commodity(&mut f, 1);
313
219161
        f.instruction(&Instruction::Call(ratio_add));
314
219161
        f.instruction(&Instruction::LocalSet(2));
315
219161
        self.emit_commodity_repack(&mut f, 2, 0);
316
219161
        f.instruction(&Instruction::End);
317
219161
        self.pending_helpers.push(f);
318

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

            
331
219161
    fn build_commodity_neg_body(&mut self) {
332
219161
        let commodity_idx = self.ids.ty_commodity;
333
219161
        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
219161
        let mut f = Function::new([]);
338
        // -a.numer = 0 - a.numer
339
219161
        f.instruction(&Instruction::I64Const(0));
340
219161
        f.instruction(&Instruction::LocalGet(0));
341
219161
        f.instruction(&Instruction::StructGet {
342
219161
            struct_type_index: commodity_idx,
343
219161
            field_index: 0,
344
219161
        });
345
219161
        f.instruction(&Instruction::I64Sub);
346
        // a.denom
347
219161
        f.instruction(&Instruction::LocalGet(0));
348
219161
        f.instruction(&Instruction::StructGet {
349
219161
            struct_type_index: commodity_idx,
350
219161
            field_index: 1,
351
219161
        });
352
        // a.hi
353
219161
        f.instruction(&Instruction::LocalGet(0));
354
219161
        f.instruction(&Instruction::StructGet {
355
219161
            struct_type_index: commodity_idx,
356
219161
            field_index: 2,
357
219161
        });
358
        // a.lo
359
219161
        f.instruction(&Instruction::LocalGet(0));
360
219161
        f.instruction(&Instruction::StructGet {
361
219161
            struct_type_index: commodity_idx,
362
219161
            field_index: 3,
363
219161
        });
364
        // a.term
365
219161
        f.instruction(&Instruction::LocalGet(0));
366
219161
        f.instruction(&Instruction::StructGet {
367
219161
            struct_type_index: commodity_idx,
368
219161
            field_index: 4,
369
219161
        });
370
219161
        f.instruction(&Instruction::Call(new_with_term));
371
219161
        f.instruction(&Instruction::End);
372
219161
        self.pending_helpers.push(f);
373
219161
    }
374

            
375
219161
    fn build_commodity_scale_bodies(&mut self) {
376
219161
        let ratio_ref = self.ratio_ref();
377
219161
        let ratio_mul = self.ids.ratio_mul;
378
219161
        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
219161
        let mut f = Function::new([(1, ratio_ref)]);
383
219161
        self.emit_ratio_from_commodity(&mut f, 0);
384
219161
        f.instruction(&Instruction::LocalGet(1));
385
219161
        f.instruction(&Instruction::Call(ratio_mul));
386
219161
        f.instruction(&Instruction::LocalSet(2));
387
219161
        self.emit_commodity_repack(&mut f, 2, 0);
388
219161
        f.instruction(&Instruction::End);
389
219161
        self.pending_helpers.push(f);
390

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

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

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

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