1
//! Compound-money guest helpers (ADR-0028 E2): the bridge between the
2
//! `$commodity` struct's `(numer, denom, hi, lo)` ratio+id fields and its
3
//! runtime unit term (see [`super::unit_term`]).
4
//!
5
//! `commodity_new_with_term` is the single `struct.new $commodity` site;
6
//! `commodity_new` (the atomic constructor) delegates to it with a null term.
7
//! `commodity_mul` / `commodity_div` multiply/divide the ratios and combine
8
//! the unit terms — a same-currency division cancels to an EMPTY term
9
//! (dimensionless), a cross-currency one produces a compound term. The unit is
10
//! erased at the wasm↔host border by `commodity_assert_atomic`, which throws a
11
//! catchable `NON-ATOMIC-COMMODITY` if a non-atomic value reaches a host fn.
12

            
13
use super::CompileContext;
14
use crate::error::Result;
15
use wasm_encoder::{BlockType, Function, HeapType, Instruction, ValType};
16

            
17
/// Interned `NON-ATOMIC-COMMODITY` condition handles for the host-border guard
18
/// (mirrors `commodity::MismatchTrap`).
19
pub(super) struct NonAtomicTrap {
20
    i8_array_idx: u32,
21
    condition_idx: u32,
22
    tag: u32,
23
    code_data: u32,
24
    code_len: u32,
25
    message_data: u32,
26
    message_len: u32,
27
}
28

            
29
impl CompileContext {
30
    /// Registers the compound-helper SIGNATURES (and interns the
31
    /// `NON-ATOMIC-COMMODITY` condition), returning the trap. Split from body
32
    /// building so the caller registers these signatures up-front — their
33
    /// indices must exist before `commodity_new` / the binop unit-checks
34
    /// reference them — yet emits their BODIES last: `pending_helpers` maps
35
    /// bodies to function indices in registration order, so body-build order
36
    /// must match registration order.
37
219161
    pub(super) fn declare_commodity_compound_signatures(&mut self) -> Result<()> {
38
219161
        let commodity_ref = self.commodity_ref();
39
219161
        let unit_ref = self.unit_term_ref();
40
219161
        self.register_function(
41
219161
            "commodity_new_with_term",
42
219161
            &[
43
219161
                ValType::I64,
44
219161
                ValType::I64,
45
219161
                ValType::I64,
46
219161
                ValType::I64,
47
219161
                unit_ref,
48
219161
            ],
49
219161
            &[commodity_ref],
50
        )?;
51
219161
        self.register_function("materialize_unit", &[commodity_ref], &[unit_ref])?;
52
219161
        self.register_function(
53
219161
            "commodity_mul",
54
219161
            &[commodity_ref, commodity_ref],
55
219161
            &[commodity_ref],
56
        )?;
57
219161
        self.register_function(
58
219161
            "commodity_div",
59
219161
            &[commodity_ref, commodity_ref],
60
219161
            &[commodity_ref],
61
        )?;
62
219161
        self.register_function(
63
219161
            "commodity_assert_atomic",
64
219161
            &[commodity_ref],
65
219161
            &[commodity_ref],
66
        )?;
67
219161
        Ok(())
68
219161
    }
69

            
70
    /// Emits the compound-helper bodies, in the SAME order their signatures were
71
    /// registered by [`Self::register_commodity_compound_signatures`].
72
219161
    pub(super) fn build_commodity_compound_bodies(&mut self, trap: &NonAtomicTrap) -> Result<()> {
73
219161
        self.build_commodity_new_with_term_body();
74
219161
        self.build_materialize_unit_body();
75
219161
        self.build_commodity_mul_body();
76
219161
        self.build_commodity_div_body()?;
77
219161
        self.build_commodity_assert_atomic_body(trap);
78
219161
        Ok(())
79
219161
    }
80

            
81
219161
    pub(super) fn register_non_atomic_trap(&mut self) -> Result<NonAtomicTrap> {
82
        const CODE: &str = "NON-ATOMIC-COMMODITY";
83
        const MESSAGE: &str = "host functions require single-currency (atomic) money";
84
219161
        let code_data = self.add_data(CODE.as_bytes())?;
85
219161
        let message_data = self.add_data(MESSAGE.as_bytes())?;
86
219161
        Ok(NonAtomicTrap {
87
219161
            i8_array_idx: self.ids.ty_i8_array,
88
219161
            condition_idx: self.condition_type_idx(),
89
219161
            tag: self.nomi_error_tag(),
90
219161
            code_data,
91
219161
            code_len: CODE.len() as u32,
92
219161
            message_data,
93
219161
            message_len: MESSAGE.len() as u32,
94
219161
        })
95
219161
    }
96

            
97
    /// The sole `struct.new $commodity` site: `(numer, denom, hi, lo, term)`,
98
    /// with one CANONICALIZATION — a non-null SINGLETON term `[(id, 1)]` is
99
    /// semantically ATOMIC money (e.g. `(* (/ usd usd) usd)` = dimensionless ×
100
    /// usd → a `[(usd,1)]` term), so it is rebuilt as atomic: null term, id from
101
    /// the triple. Otherwise: a null term stays atomic, an empty term stays
102
    /// dimensionless, and a true compound term (len > 3, or len 3 with exp ≠ 1)
103
    /// stays non-null with id fields 0. Without this, valid arithmetic that
104
    /// reduces to a single currency would be mis-flagged compound and rejected
105
    /// by the host-border guard / decoder.
106
219161
    fn build_commodity_new_with_term_body(&mut self) {
107
219161
        let commodity_idx = self.ids.ty_commodity;
108
219161
        let unit_idx = self.ids.ty_unit_term;
109
219161
        let commodity_ref = self.commodity_ref();
110
219161
        let mut f = Function::new([]);
111

            
112
        // Keep id fields + term verbatim (atomic-null / dimensionless-empty /
113
        // compound). Used by every non-canonicalizable branch.
114
657483
        let emit_passthrough = |f: &mut Function| {
115
3287415
            for p in 0..5 {
116
3287415
                f.instruction(&Instruction::LocalGet(p));
117
3287415
            }
118
657483
            f.instruction(&Instruction::StructNew(commodity_idx));
119
657483
        };
120

            
121
219161
        f.instruction(&Instruction::LocalGet(4));
122
219161
        f.instruction(&Instruction::RefIsNull);
123
219161
        f.instruction(&Instruction::I32Eqz); // term non-null?
124
219161
        f.instruction(&Instruction::If(BlockType::Result(commodity_ref)));
125
        // Non-null term: a `len == 3` term MIGHT be a singleton — only then is it
126
        // safe to read its exponent (`array.get 2` on a shorter term would trap).
127
219161
        f.instruction(&Instruction::LocalGet(4));
128
219161
        f.instruction(&Instruction::ArrayLen);
129
219161
        f.instruction(&Instruction::I32Const(3));
130
219161
        f.instruction(&Instruction::I32Eq);
131
219161
        f.instruction(&Instruction::If(BlockType::Result(commodity_ref)));
132
219161
        f.instruction(&Instruction::LocalGet(4));
133
219161
        f.instruction(&Instruction::I32Const(2));
134
219161
        f.instruction(&Instruction::ArrayGet(unit_idx)); // exponent
135
219161
        f.instruction(&Instruction::I64Const(1));
136
219161
        f.instruction(&Instruction::I64Eq);
137
219161
        f.instruction(&Instruction::If(BlockType::Result(commodity_ref)));
138
        // Singleton exponent 1 → canonical atomic: id from the triple, null term.
139
219161
        f.instruction(&Instruction::LocalGet(0));
140
219161
        f.instruction(&Instruction::LocalGet(1));
141
219161
        f.instruction(&Instruction::LocalGet(4));
142
219161
        f.instruction(&Instruction::I32Const(0));
143
219161
        f.instruction(&Instruction::ArrayGet(unit_idx)); // hi
144
219161
        f.instruction(&Instruction::LocalGet(4));
145
219161
        f.instruction(&Instruction::I32Const(1));
146
219161
        f.instruction(&Instruction::ArrayGet(unit_idx)); // lo
147
219161
        self.emit_null_unit_term(&mut f);
148
219161
        f.instruction(&Instruction::StructNew(commodity_idx));
149
219161
        f.instruction(&Instruction::Else);
150
219161
        emit_passthrough(&mut f); // len 3, exp ≠ 1 → genuine compound
151
219161
        f.instruction(&Instruction::End);
152
219161
        f.instruction(&Instruction::Else);
153
219161
        emit_passthrough(&mut f); // empty (dimensionless) or len > 3 (compound)
154
219161
        f.instruction(&Instruction::End);
155
219161
        f.instruction(&Instruction::Else);
156
219161
        emit_passthrough(&mut f); // null term → atomic
157
219161
        f.instruction(&Instruction::End);
158

            
159
219161
        f.instruction(&Instruction::End);
160
219161
        self.pending_helpers.push(f);
161
219161
    }
162

            
163
    /// `materialize_unit(c)` → the effective unit term: a null (atomic) term
164
    /// becomes the singleton `[(c.hi, c.lo, 1)]`; a present term is returned
165
    /// as-is. The result is always non-null, so term arithmetic can `array.len`
166
    /// it without a trap.
167
219161
    fn build_materialize_unit_body(&mut self) {
168
219161
        let commodity_idx = self.ids.ty_commodity;
169
219161
        let unit_ref = self.unit_term_ref();
170
219161
        let unit_singleton = self.ids.unit_singleton;
171
219161
        let mut f = Function::new([]);
172
219161
        f.instruction(&Instruction::LocalGet(0));
173
219161
        f.instruction(&Instruction::StructGet {
174
219161
            struct_type_index: commodity_idx,
175
219161
            field_index: 4,
176
219161
        });
177
219161
        f.instruction(&Instruction::RefIsNull);
178
219161
        f.instruction(&Instruction::If(BlockType::Result(unit_ref)));
179
        // atomic: singleton from the id fields
180
219161
        f.instruction(&Instruction::LocalGet(0));
181
219161
        f.instruction(&Instruction::StructGet {
182
219161
            struct_type_index: commodity_idx,
183
219161
            field_index: 2,
184
219161
        });
185
219161
        f.instruction(&Instruction::LocalGet(0));
186
219161
        f.instruction(&Instruction::StructGet {
187
219161
            struct_type_index: commodity_idx,
188
219161
            field_index: 3,
189
219161
        });
190
219161
        f.instruction(&Instruction::Call(unit_singleton));
191
219161
        f.instruction(&Instruction::Else);
192
        // compound/dimensionless: the present term
193
219161
        f.instruction(&Instruction::LocalGet(0));
194
219161
        f.instruction(&Instruction::StructGet {
195
219161
            struct_type_index: commodity_idx,
196
219161
            field_index: 4,
197
219161
        });
198
219161
        f.instruction(&Instruction::End);
199
219161
        f.instruction(&Instruction::End);
200
219161
        self.pending_helpers.push(f);
201
219161
    }
202

            
203
    /// `commodity_mul(a, b)` — multiply ratios, merge unit terms. Always
204
    /// produces a value whose unit is the term (id fields 0); a money × money
205
    /// is inherently compound.
206
219161
    fn build_commodity_mul_body(&mut self) {
207
219161
        self.build_commodity_combine_body(self.ids.ratio_mul, self.ids.unit_mul);
208
219161
    }
209

            
210
    /// `commodity_div(a, b)` — divide ratios, subtract unit terms. Same
211
    /// currency cancels to an empty (dimensionless) term; different currencies
212
    /// give a compound term. `unit_div` has no `WasmIds` field (it is referenced
213
    /// only here), so its index is looked up from the declared func map once.
214
219161
    fn build_commodity_div_body(&mut self) -> Result<()> {
215
219161
        let unit_div = self.declared_func_index("unit_div")?;
216
219161
        self.build_commodity_combine_body(self.ids.ratio_div, unit_div);
217
219161
        Ok(())
218
219161
    }
219

            
220
    /// Shared body for `commodity_mul`/`commodity_div`: `ratio_op` combines the
221
    /// two ratios, `unit_op` combines the two materialized unit terms, and the
222
    /// result is packed with id fields zeroed (the term is the unit of truth).
223
438322
    fn build_commodity_combine_body(&mut self, ratio_op: u32, unit_op: u32) {
224
438322
        let ratio_idx = self.ids.ty_ratio;
225
438322
        let ratio_ref = self.ratio_ref();
226
438322
        let unit_ref = self.unit_term_ref();
227
438322
        let materialize = self.ids.materialize_unit;
228
438322
        let new_with_term = self.ids.commodity_new_with_term;
229
        // locals: $r=2 (ratio_ref), $term=3 (unit_ref)
230
438322
        let mut f = Function::new([(1, ratio_ref), (1, unit_ref)]);
231
438322
        self.emit_ratio_from_commodity(&mut f, 0);
232
438322
        self.emit_ratio_from_commodity(&mut f, 1);
233
438322
        f.instruction(&Instruction::Call(ratio_op));
234
438322
        f.instruction(&Instruction::LocalSet(2));
235
438322
        f.instruction(&Instruction::LocalGet(0));
236
438322
        f.instruction(&Instruction::Call(materialize));
237
438322
        f.instruction(&Instruction::LocalGet(1));
238
438322
        f.instruction(&Instruction::Call(materialize));
239
438322
        f.instruction(&Instruction::Call(unit_op));
240
438322
        f.instruction(&Instruction::LocalSet(3));
241
        // commodity_new_with_term(r.numer, r.denom, 0, 0, term)
242
438322
        f.instruction(&Instruction::LocalGet(2));
243
438322
        f.instruction(&Instruction::StructGet {
244
438322
            struct_type_index: ratio_idx,
245
438322
            field_index: 0,
246
438322
        });
247
438322
        f.instruction(&Instruction::LocalGet(2));
248
438322
        f.instruction(&Instruction::StructGet {
249
438322
            struct_type_index: ratio_idx,
250
438322
            field_index: 1,
251
438322
        });
252
438322
        f.instruction(&Instruction::I64Const(0));
253
438322
        f.instruction(&Instruction::I64Const(0));
254
438322
        f.instruction(&Instruction::LocalGet(3));
255
438322
        f.instruction(&Instruction::Call(new_with_term));
256
438322
        f.instruction(&Instruction::End);
257
438322
        self.pending_helpers.push(f);
258
438322
    }
259

            
260
    /// `commodity_assert_atomic(c)` → `c` if its term is null (atomic), else a
261
    /// catchable `NON-ATOMIC-COMMODITY` throw. Emitted at the host border so a
262
    /// compound money never reaches a host fn (which sees only fields 0-3).
263
219161
    fn build_commodity_assert_atomic_body(&mut self, trap: &NonAtomicTrap) {
264
219161
        let commodity_idx = self.ids.ty_commodity;
265
219161
        let mut f = Function::new([]);
266
219161
        f.instruction(&Instruction::LocalGet(0));
267
219161
        f.instruction(&Instruction::StructGet {
268
219161
            struct_type_index: commodity_idx,
269
219161
            field_index: 4,
270
219161
        });
271
219161
        f.instruction(&Instruction::RefIsNull);
272
219161
        f.instruction(&Instruction::I32Eqz);
273
219161
        f.instruction(&Instruction::If(BlockType::Empty));
274
219161
        Self::emit_non_atomic_throw(&mut f, trap);
275
219161
        f.instruction(&Instruction::End);
276
219161
        f.instruction(&Instruction::LocalGet(0));
277
219161
        f.instruction(&Instruction::End);
278
219161
        self.pending_helpers.push(f);
279
219161
    }
280

            
281
219161
    fn emit_non_atomic_throw(f: &mut Function, trap: &NonAtomicTrap) {
282
219161
        f.instruction(&Instruction::I32Const(0));
283
219161
        f.instruction(&Instruction::I32Const(trap.code_len as i32));
284
219161
        f.instruction(&Instruction::ArrayNewData {
285
219161
            array_type_index: trap.i8_array_idx,
286
219161
            array_data_index: trap.code_data,
287
219161
        });
288
219161
        f.instruction(&Instruction::I32Const(0));
289
219161
        f.instruction(&Instruction::I32Const(trap.message_len as i32));
290
219161
        f.instruction(&Instruction::ArrayNewData {
291
219161
            array_type_index: trap.i8_array_idx,
292
219161
            array_data_index: trap.message_data,
293
219161
        });
294
219161
        f.instruction(&Instruction::StructNew(trap.condition_idx));
295
219161
        f.instruction(&Instruction::Throw(trap.tag));
296
219161
    }
297

            
298
    /// Pushes `ref.null $unit_term` (the ATOMIC term marker) onto the stack.
299
438322
    pub(super) fn emit_null_unit_term(&self, f: &mut Function) {
300
438322
        f.instruction(&Instruction::RefNull(HeapType::Concrete(
301
438322
            self.ids.ty_unit_term,
302
438322
        )));
303
438322
    }
304
}