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
477238
    pub(super) fn declare_commodity_compound_signatures(&mut self) -> Result<()> {
38
477238
        let commodity_ref = self.commodity_ref();
39
477238
        let unit_ref = self.unit_term_ref();
40
477238
        self.register_function(
41
477238
            "commodity_new_with_term",
42
477238
            &[
43
477238
                ValType::I64,
44
477238
                ValType::I64,
45
477238
                ValType::I64,
46
477238
                ValType::I64,
47
477238
                unit_ref,
48
477238
            ],
49
477238
            &[commodity_ref],
50
        )?;
51
477238
        self.register_function("materialize_unit", &[commodity_ref], &[unit_ref])?;
52
477238
        self.register_function(
53
477238
            "commodity_mul",
54
477238
            &[commodity_ref, commodity_ref],
55
477238
            &[commodity_ref],
56
        )?;
57
477238
        self.register_function(
58
477238
            "commodity_div",
59
477238
            &[commodity_ref, commodity_ref],
60
477238
            &[commodity_ref],
61
        )?;
62
477238
        self.register_function(
63
477238
            "commodity_assert_atomic",
64
477238
            &[commodity_ref],
65
477238
            &[commodity_ref],
66
        )?;
67
477238
        Ok(())
68
477238
    }
69

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

            
81
477238
    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
477238
        let code_data = self.add_data(CODE.as_bytes())?;
85
477238
        let message_data = self.add_data(MESSAGE.as_bytes())?;
86
477238
        Ok(NonAtomicTrap {
87
477238
            i8_array_idx: self.ids.ty_i8_array,
88
477238
            condition_idx: self.condition_type_idx(),
89
477238
            tag: self.nomi_error_tag(),
90
477238
            code_data,
91
477238
            code_len: CODE.len() as u32,
92
477238
            message_data,
93
477238
            message_len: MESSAGE.len() as u32,
94
477238
        })
95
477238
    }
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
477238
    fn build_commodity_new_with_term_body(&mut self) {
107
477238
        let commodity_idx = self.ids.ty_commodity;
108
477238
        let unit_idx = self.ids.ty_unit_term;
109
477238
        let commodity_ref = self.commodity_ref();
110
477238
        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
1431714
        let emit_passthrough = |f: &mut Function| {
115
7158570
            for p in 0..5 {
116
7158570
                f.instruction(&Instruction::LocalGet(p));
117
7158570
            }
118
1431714
            f.instruction(&Instruction::StructNew(commodity_idx));
119
1431714
        };
120

            
121
477238
        f.instruction(&Instruction::LocalGet(4));
122
477238
        f.instruction(&Instruction::RefIsNull);
123
477238
        f.instruction(&Instruction::I32Eqz); // term non-null?
124
477238
        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
477238
        f.instruction(&Instruction::LocalGet(4));
128
477238
        f.instruction(&Instruction::ArrayLen);
129
477238
        f.instruction(&Instruction::I32Const(3));
130
477238
        f.instruction(&Instruction::I32Eq);
131
477238
        f.instruction(&Instruction::If(BlockType::Result(commodity_ref)));
132
477238
        f.instruction(&Instruction::LocalGet(4));
133
477238
        f.instruction(&Instruction::I32Const(2));
134
477238
        f.instruction(&Instruction::ArrayGet(unit_idx)); // exponent
135
477238
        f.instruction(&Instruction::I64Const(1));
136
477238
        f.instruction(&Instruction::I64Eq);
137
477238
        f.instruction(&Instruction::If(BlockType::Result(commodity_ref)));
138
        // Singleton exponent 1 → canonical atomic: id from the triple, null term.
139
477238
        f.instruction(&Instruction::LocalGet(0));
140
477238
        f.instruction(&Instruction::LocalGet(1));
141
477238
        f.instruction(&Instruction::LocalGet(4));
142
477238
        f.instruction(&Instruction::I32Const(0));
143
477238
        f.instruction(&Instruction::ArrayGet(unit_idx)); // hi
144
477238
        f.instruction(&Instruction::LocalGet(4));
145
477238
        f.instruction(&Instruction::I32Const(1));
146
477238
        f.instruction(&Instruction::ArrayGet(unit_idx)); // lo
147
477238
        self.emit_null_unit_term(&mut f);
148
477238
        f.instruction(&Instruction::StructNew(commodity_idx));
149
477238
        f.instruction(&Instruction::Else);
150
477238
        emit_passthrough(&mut f); // len 3, exp ≠ 1 → genuine compound
151
477238
        f.instruction(&Instruction::End);
152
477238
        f.instruction(&Instruction::Else);
153
477238
        emit_passthrough(&mut f); // empty (dimensionless) or len > 3 (compound)
154
477238
        f.instruction(&Instruction::End);
155
477238
        f.instruction(&Instruction::Else);
156
477238
        emit_passthrough(&mut f); // null term → atomic
157
477238
        f.instruction(&Instruction::End);
158

            
159
477238
        f.instruction(&Instruction::End);
160
477238
        self.pending_helpers.push(f);
161
477238
    }
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
477238
    fn build_materialize_unit_body(&mut self) {
168
477238
        let commodity_idx = self.ids.ty_commodity;
169
477238
        let unit_ref = self.unit_term_ref();
170
477238
        let unit_singleton = self.ids.unit_singleton;
171
477238
        let mut f = Function::new([]);
172
477238
        f.instruction(&Instruction::LocalGet(0));
173
477238
        f.instruction(&Instruction::StructGet {
174
477238
            struct_type_index: commodity_idx,
175
477238
            field_index: 4,
176
477238
        });
177
477238
        f.instruction(&Instruction::RefIsNull);
178
477238
        f.instruction(&Instruction::If(BlockType::Result(unit_ref)));
179
        // atomic: singleton from the id fields
180
477238
        f.instruction(&Instruction::LocalGet(0));
181
477238
        f.instruction(&Instruction::StructGet {
182
477238
            struct_type_index: commodity_idx,
183
477238
            field_index: 2,
184
477238
        });
185
477238
        f.instruction(&Instruction::LocalGet(0));
186
477238
        f.instruction(&Instruction::StructGet {
187
477238
            struct_type_index: commodity_idx,
188
477238
            field_index: 3,
189
477238
        });
190
477238
        f.instruction(&Instruction::Call(unit_singleton));
191
477238
        f.instruction(&Instruction::Else);
192
        // compound/dimensionless: the present term
193
477238
        f.instruction(&Instruction::LocalGet(0));
194
477238
        f.instruction(&Instruction::StructGet {
195
477238
            struct_type_index: commodity_idx,
196
477238
            field_index: 4,
197
477238
        });
198
477238
        f.instruction(&Instruction::End);
199
477238
        f.instruction(&Instruction::End);
200
477238
        self.pending_helpers.push(f);
201
477238
    }
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
477238
    fn build_commodity_mul_body(&mut self) {
207
477238
        self.build_commodity_combine_body(self.ids.ratio_mul, self.ids.unit_mul);
208
477238
    }
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
477238
    fn build_commodity_div_body(&mut self) -> Result<()> {
215
477238
        let unit_div = self.declared_func_index("unit_div")?;
216
477238
        self.build_commodity_combine_body(self.ids.ratio_div, unit_div);
217
477238
        Ok(())
218
477238
    }
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
954476
    fn build_commodity_combine_body(&mut self, ratio_op: u32, unit_op: u32) {
224
954476
        let ratio_idx = self.ids.ty_ratio;
225
954476
        let ratio_ref = self.ratio_ref();
226
954476
        let unit_ref = self.unit_term_ref();
227
954476
        let materialize = self.ids.materialize_unit;
228
954476
        let new_with_term = self.ids.commodity_new_with_term;
229
        // locals: $r=2 (ratio_ref), $term=3 (unit_ref)
230
954476
        let mut f = Function::new([(1, ratio_ref), (1, unit_ref)]);
231
954476
        self.emit_ratio_from_commodity(&mut f, 0);
232
954476
        self.emit_ratio_from_commodity(&mut f, 1);
233
954476
        f.instruction(&Instruction::Call(ratio_op));
234
954476
        f.instruction(&Instruction::LocalSet(2));
235
954476
        f.instruction(&Instruction::LocalGet(0));
236
954476
        f.instruction(&Instruction::Call(materialize));
237
954476
        f.instruction(&Instruction::LocalGet(1));
238
954476
        f.instruction(&Instruction::Call(materialize));
239
954476
        f.instruction(&Instruction::Call(unit_op));
240
954476
        f.instruction(&Instruction::LocalSet(3));
241
        // commodity_new_with_term(r.numer, r.denom, 0, 0, term)
242
954476
        f.instruction(&Instruction::LocalGet(2));
243
954476
        f.instruction(&Instruction::StructGet {
244
954476
            struct_type_index: ratio_idx,
245
954476
            field_index: 0,
246
954476
        });
247
954476
        f.instruction(&Instruction::LocalGet(2));
248
954476
        f.instruction(&Instruction::StructGet {
249
954476
            struct_type_index: ratio_idx,
250
954476
            field_index: 1,
251
954476
        });
252
954476
        f.instruction(&Instruction::I64Const(0));
253
954476
        f.instruction(&Instruction::I64Const(0));
254
954476
        f.instruction(&Instruction::LocalGet(3));
255
954476
        f.instruction(&Instruction::Call(new_with_term));
256
954476
        f.instruction(&Instruction::End);
257
954476
        self.pending_helpers.push(f);
258
954476
    }
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
477238
    fn build_commodity_assert_atomic_body(&mut self, trap: &NonAtomicTrap) {
264
477238
        let commodity_idx = self.ids.ty_commodity;
265
477238
        let mut f = Function::new([]);
266
477238
        f.instruction(&Instruction::LocalGet(0));
267
477238
        f.instruction(&Instruction::StructGet {
268
477238
            struct_type_index: commodity_idx,
269
477238
            field_index: 4,
270
477238
        });
271
477238
        f.instruction(&Instruction::RefIsNull);
272
477238
        f.instruction(&Instruction::I32Eqz);
273
477238
        f.instruction(&Instruction::If(BlockType::Empty));
274
477238
        Self::emit_non_atomic_throw(&mut f, trap);
275
477238
        f.instruction(&Instruction::End);
276
477238
        f.instruction(&Instruction::LocalGet(0));
277
477238
        f.instruction(&Instruction::End);
278
477238
        self.pending_helpers.push(f);
279
477238
    }
280

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

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