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

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

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

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

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

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

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

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

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

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