1
//! Guest-side helpers for the commodity **unit term** (ADR-0028 E1/E2).
2
//!
3
//! A unit term is a `(array (mut i64))` holding a sorted, canonical sequence
4
//! of `(hi, lo, exp)` triples — one per commodity that participates in a
5
//! compound money value, with `exp` its integer exponent (`USD·EUR` →
6
//! `[(EUR,1),(USD,1)]`, `USD²` → `[(USD,2)]`). Sorted ascending by the
7
//! unsigned `(hi, lo)` UUID key so equality is positional and merges are a
8
//! single linear pass. A *null* term is the compact encoding of the ATOMIC
9
//! singleton `[(self, 1)]` (the commodity's own id in struct fields 2-3); the
10
//! commodity helpers materialize it via [`unit_singleton`] before any term
11
//! arithmetic. An *empty* (length-0) term is DIMENSIONLESS (money ÷ money).
12
//!
13
//! All bodies assume **non-null** array arguments — callers handle the
14
//! null/atomic case. Exponent sums that reach zero are dropped so the term
15
//! stays canonical (a commodity that cancels out leaves the term).
16

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

            
21
impl CompileContext {
22
    /// Declares the unit-term helper signatures (no bodies). See
23
    /// [`Self::build_unit_term_helpers`] for the body emit.
24
252750
    pub(super) fn declare_unit_term_helpers(&mut self) -> Result<()> {
25
252750
        let unit_ref = self.unit_term_ref();
26
252750
        self.register_function("unit_singleton", &[ValType::I64, ValType::I64], &[unit_ref])?;
27
252750
        self.register_function("unit_negate", &[unit_ref], &[unit_ref])?;
28
252750
        self.register_function("unit_mul", &[unit_ref, unit_ref], &[unit_ref])?;
29
252750
        self.register_function("unit_div", &[unit_ref, unit_ref], &[unit_ref])?;
30
252750
        self.register_function("unit_eq", &[unit_ref, unit_ref], &[ValType::I32])?;
31
        // Exported so the host can inspect / construct unit terms (the
32
        // compound-money decoder + serializer) and so the intricate
33
        // sorted-merge is directly unit-testable, mirroring `pair_new` /
34
        // `commodity_new`.
35
252750
        self.export_func("unit_singleton")?;
36
252750
        self.export_func("unit_negate")?;
37
252750
        self.export_func("unit_mul")?;
38
252750
        self.export_func("unit_div")?;
39
252750
        self.export_func("unit_eq")?;
40
252750
        Ok(())
41
252750
    }
42

            
43
    /// Emits the unit-term helper bodies in registration order.
44
252750
    pub(super) fn build_unit_term_helpers(&mut self) {
45
252750
        self.build_unit_singleton_body();
46
252750
        self.build_unit_negate_body();
47
252750
        self.build_unit_mul_body();
48
252750
        self.build_unit_div_body();
49
252750
        self.build_unit_eq_body();
50
252750
    }
51

            
52
    /// `unit_singleton(hi, lo)` → `[hi, lo, 1]` — the canonical term for a
53
    /// single commodity at exponent 1.
54
252750
    fn build_unit_singleton_body(&mut self) {
55
252750
        let unit_idx = self.ids.ty_unit_term;
56
252750
        let mut f = Function::new([]);
57
252750
        f.instruction(&Instruction::LocalGet(0));
58
252750
        f.instruction(&Instruction::LocalGet(1));
59
252750
        f.instruction(&Instruction::I64Const(1));
60
252750
        f.instruction(&Instruction::ArrayNewFixed {
61
252750
            array_type_index: unit_idx,
62
252750
            array_size: 3,
63
252750
        });
64
252750
        f.instruction(&Instruction::End);
65
252750
        self.pending_helpers.push(f);
66
252750
    }
67

            
68
    /// `unit_negate(t)` → a fresh term with every exponent negated (used by
69
    /// `unit_div`: `a / b = a * negate(b)`). Keys + order are preserved, so the
70
    /// result is already canonical.
71
252750
    fn build_unit_negate_body(&mut self) {
72
252750
        let unit_idx = self.ids.ty_unit_term;
73
        // params: $t=0. locals: $len=1 (i32), $out=2 (unit_ref), $i=3 (i32).
74
252750
        let unit_ref = self.unit_term_ref();
75
252750
        let mut f = Function::new([(1, ValType::I32), (1, unit_ref), (1, ValType::I32)]);
76
252750
        f.instruction(&Instruction::LocalGet(0));
77
252750
        f.instruction(&Instruction::ArrayLen);
78
252750
        f.instruction(&Instruction::LocalSet(1));
79
        // out = array.new_default(len)
80
252750
        f.instruction(&Instruction::LocalGet(1));
81
252750
        f.instruction(&Instruction::ArrayNewDefault(unit_idx));
82
252750
        f.instruction(&Instruction::LocalSet(2));
83
        // i = 0
84
252750
        f.instruction(&Instruction::I32Const(0));
85
252750
        f.instruction(&Instruction::LocalSet(3));
86
252750
        f.instruction(&Instruction::Block(BlockType::Empty));
87
252750
        f.instruction(&Instruction::Loop(BlockType::Empty));
88
        // if i >= len break
89
252750
        f.instruction(&Instruction::LocalGet(3));
90
252750
        f.instruction(&Instruction::LocalGet(1));
91
252750
        f.instruction(&Instruction::I32GeU);
92
252750
        f.instruction(&Instruction::BrIf(1));
93
        // out[i] = t[i] (hi), out[i+1] = t[i+1] (lo): copy verbatim
94
252750
        Self::emit_copy_i64(&mut f, unit_idx, 2, 3, 0, 0);
95
252750
        Self::emit_copy_i64(&mut f, unit_idx, 2, 3, 1, 1);
96
        // out[i+2] = -t[i+2]
97
252750
        f.instruction(&Instruction::LocalGet(2)); // out
98
252750
        f.instruction(&Instruction::LocalGet(3)); // i
99
252750
        f.instruction(&Instruction::I32Const(2));
100
252750
        f.instruction(&Instruction::I32Add); // i+2
101
252750
        f.instruction(&Instruction::I64Const(0));
102
252750
        Self::emit_array_get(&mut f, unit_idx, 0, 3, 2); // t[i+2]
103
252750
        f.instruction(&Instruction::I64Sub); // 0 - exp
104
252750
        f.instruction(&Instruction::ArraySet(unit_idx));
105
        // i += 3
106
252750
        f.instruction(&Instruction::LocalGet(3));
107
252750
        f.instruction(&Instruction::I32Const(3));
108
252750
        f.instruction(&Instruction::I32Add);
109
252750
        f.instruction(&Instruction::LocalSet(3));
110
252750
        f.instruction(&Instruction::Br(0));
111
252750
        f.instruction(&Instruction::End); // loop
112
252750
        f.instruction(&Instruction::End); // block
113
252750
        f.instruction(&Instruction::LocalGet(2));
114
252750
        f.instruction(&Instruction::End);
115
252750
        self.pending_helpers.push(f);
116
252750
    }
117

            
118
    /// `unit_div(a, b)` = `unit_mul(a, unit_negate(b))`.
119
252750
    fn build_unit_div_body(&mut self) {
120
252750
        let unit_negate = self.ids.unit_negate;
121
252750
        let unit_mul = self.ids.unit_mul;
122
252750
        let mut f = Function::new([]);
123
252750
        f.instruction(&Instruction::LocalGet(0));
124
252750
        f.instruction(&Instruction::LocalGet(1));
125
252750
        f.instruction(&Instruction::Call(unit_negate));
126
252750
        f.instruction(&Instruction::Call(unit_mul));
127
252750
        f.instruction(&Instruction::End);
128
252750
        self.pending_helpers.push(f);
129
252750
    }
130

            
131
    /// `unit_eq(a, b)` → 1 iff the two canonical terms are identical (same
132
    /// length, same `(hi, lo, exp)` at every slot). Both args are non-null
133
    /// canonical terms, so positional comparison is a full multiset equality.
134
252750
    fn build_unit_eq_body(&mut self) {
135
252750
        let unit_idx = self.ids.ty_unit_term;
136
        // params: $a=0, $b=1. locals: $len=2 (i32), $i=3 (i32).
137
252750
        let mut f = Function::new([(1, ValType::I32), (1, ValType::I32)]);
138
252750
        f.instruction(&Instruction::LocalGet(0));
139
252750
        f.instruction(&Instruction::ArrayLen);
140
252750
        f.instruction(&Instruction::LocalSet(2));
141
        // if len(b) != len -> 0
142
252750
        f.instruction(&Instruction::LocalGet(1));
143
252750
        f.instruction(&Instruction::ArrayLen);
144
252750
        f.instruction(&Instruction::LocalGet(2));
145
252750
        f.instruction(&Instruction::I32Ne);
146
252750
        f.instruction(&Instruction::If(BlockType::Empty));
147
252750
        f.instruction(&Instruction::I32Const(0));
148
252750
        f.instruction(&Instruction::Return);
149
252750
        f.instruction(&Instruction::End);
150
        // i = 0
151
252750
        f.instruction(&Instruction::I32Const(0));
152
252750
        f.instruction(&Instruction::LocalSet(3));
153
252750
        f.instruction(&Instruction::Block(BlockType::Empty));
154
252750
        f.instruction(&Instruction::Loop(BlockType::Empty));
155
252750
        f.instruction(&Instruction::LocalGet(3));
156
252750
        f.instruction(&Instruction::LocalGet(2));
157
252750
        f.instruction(&Instruction::I32GeU);
158
252750
        f.instruction(&Instruction::BrIf(1));
159
        // if a[i] != b[i] -> return 0
160
252750
        Self::emit_array_get(&mut f, unit_idx, 0, 3, 0);
161
252750
        Self::emit_array_get(&mut f, unit_idx, 1, 3, 0);
162
252750
        f.instruction(&Instruction::I64Ne);
163
252750
        f.instruction(&Instruction::If(BlockType::Empty));
164
252750
        f.instruction(&Instruction::I32Const(0));
165
252750
        f.instruction(&Instruction::Return);
166
252750
        f.instruction(&Instruction::End);
167
        // i++
168
252750
        f.instruction(&Instruction::LocalGet(3));
169
252750
        f.instruction(&Instruction::I32Const(1));
170
252750
        f.instruction(&Instruction::I32Add);
171
252750
        f.instruction(&Instruction::LocalSet(3));
172
252750
        f.instruction(&Instruction::Br(0));
173
252750
        f.instruction(&Instruction::End); // loop
174
252750
        f.instruction(&Instruction::End); // block
175
252750
        f.instruction(&Instruction::I32Const(1));
176
252750
        f.instruction(&Instruction::End);
177
252750
        self.pending_helpers.push(f);
178
252750
    }
179

            
180
    /// `unit_mul(a, b)` — linear sorted-merge of two canonical terms, summing
181
    /// the exponents of matching `(hi, lo)` keys and DROPPING any that cancel
182
    /// to zero, so the result is canonical. Builds into a worst-case
183
    /// `(len_a + len_b)` scratch array, then `array.copy`s the written prefix
184
    /// into an exact-size result. Both args are non-null.
185
252750
    fn build_unit_mul_body(&mut self) {
186
252750
        let unit_idx = self.ids.ty_unit_term;
187
252750
        let unit_ref = self.unit_term_ref();
188
        // params: a=0, b=1. locals: la=2, lb=3, n=4, i=5, j=6 (i32);
189
        // tmp=7, out=8 (unit_ref); sum=9 (i64). n/i/j default to 0.
190
252750
        let mut f = Function::new([(5, ValType::I32), (2, unit_ref), (1, ValType::I64)]);
191
        const A: u32 = 0;
192
        const B: u32 = 1;
193
        const LA: u32 = 2;
194
        const LB: u32 = 3;
195
        const N: u32 = 4;
196
        const I: u32 = 5;
197
        const J: u32 = 6;
198
        const TMP: u32 = 7;
199
        const OUT: u32 = 8;
200
        const SUM: u32 = 9;
201

            
202
252750
        f.instruction(&Instruction::LocalGet(A));
203
252750
        f.instruction(&Instruction::ArrayLen);
204
252750
        f.instruction(&Instruction::LocalSet(LA));
205
252750
        f.instruction(&Instruction::LocalGet(B));
206
252750
        f.instruction(&Instruction::ArrayLen);
207
252750
        f.instruction(&Instruction::LocalSet(LB));
208
        // tmp = array.new_default(la + lb)
209
252750
        f.instruction(&Instruction::LocalGet(LA));
210
252750
        f.instruction(&Instruction::LocalGet(LB));
211
252750
        f.instruction(&Instruction::I32Add);
212
252750
        f.instruction(&Instruction::ArrayNewDefault(unit_idx));
213
252750
        f.instruction(&Instruction::LocalSet(TMP));
214

            
215
252750
        f.instruction(&Instruction::Block(BlockType::Empty));
216
252750
        f.instruction(&Instruction::Loop(BlockType::Empty));
217
        // both exhausted? (i<la | j<lb) == 0 → break to $done (depth 1)
218
252750
        f.instruction(&Instruction::LocalGet(I));
219
252750
        f.instruction(&Instruction::LocalGet(LA));
220
252750
        f.instruction(&Instruction::I32LtU);
221
252750
        f.instruction(&Instruction::LocalGet(J));
222
252750
        f.instruction(&Instruction::LocalGet(LB));
223
252750
        f.instruction(&Instruction::I32LtU);
224
252750
        f.instruction(&Instruction::I32Or);
225
252750
        f.instruction(&Instruction::I32Eqz);
226
252750
        f.instruction(&Instruction::BrIf(1));
227

            
228
        // if a exhausted (i >= la) → take B
229
252750
        f.instruction(&Instruction::LocalGet(I));
230
252750
        f.instruction(&Instruction::LocalGet(LA));
231
252750
        f.instruction(&Instruction::I32LtU);
232
252750
        f.instruction(&Instruction::I32Eqz);
233
252750
        f.instruction(&Instruction::If(BlockType::Empty));
234
252750
        Self::emit_push_triple_from(&mut f, unit_idx, B, J, TMP, N);
235
252750
        Self::emit_advance(&mut f, J);
236
252750
        f.instruction(&Instruction::Else);
237
        // a has elements; if b exhausted (j >= lb) → take A
238
252750
        f.instruction(&Instruction::LocalGet(J));
239
252750
        f.instruction(&Instruction::LocalGet(LB));
240
252750
        f.instruction(&Instruction::I32LtU);
241
252750
        f.instruction(&Instruction::I32Eqz);
242
252750
        f.instruction(&Instruction::If(BlockType::Empty));
243
252750
        Self::emit_push_triple_from(&mut f, unit_idx, A, I, TMP, N);
244
252750
        Self::emit_advance(&mut f, I);
245
252750
        f.instruction(&Instruction::Else);
246
        // both available; key(a,i) < key(b,j) → take A
247
252750
        Self::emit_key_less(&mut f, unit_idx, A, I, B, J);
248
252750
        f.instruction(&Instruction::If(BlockType::Empty));
249
252750
        Self::emit_push_triple_from(&mut f, unit_idx, A, I, TMP, N);
250
252750
        Self::emit_advance(&mut f, I);
251
252750
        f.instruction(&Instruction::Else);
252
        // key(b,j) < key(a,i) → take B
253
252750
        Self::emit_key_less(&mut f, unit_idx, B, J, A, I);
254
252750
        f.instruction(&Instruction::If(BlockType::Empty));
255
252750
        Self::emit_push_triple_from(&mut f, unit_idx, B, J, TMP, N);
256
252750
        Self::emit_advance(&mut f, J);
257
252750
        f.instruction(&Instruction::Else);
258
        // equal keys: sum exponents, emit only if non-zero, advance both
259
252750
        Self::emit_array_get(&mut f, unit_idx, A, I, 2);
260
252750
        Self::emit_array_get(&mut f, unit_idx, B, J, 2);
261
252750
        f.instruction(&Instruction::I64Add);
262
252750
        f.instruction(&Instruction::LocalSet(SUM));
263
252750
        f.instruction(&Instruction::LocalGet(SUM));
264
252750
        f.instruction(&Instruction::I64Const(0));
265
252750
        f.instruction(&Instruction::I64Ne);
266
252750
        f.instruction(&Instruction::If(BlockType::Empty));
267
252750
        Self::emit_push_merged(&mut f, unit_idx, A, I, SUM, TMP, N);
268
252750
        f.instruction(&Instruction::End);
269
252750
        Self::emit_advance(&mut f, I);
270
252750
        Self::emit_advance(&mut f, J);
271
252750
        f.instruction(&Instruction::End); // key(b)<key(a) if
272
252750
        f.instruction(&Instruction::End); // key(a)<key(b) if
273
252750
        f.instruction(&Instruction::End); // b-exhausted if
274
252750
        f.instruction(&Instruction::End); // a-exhausted if
275

            
276
252750
        f.instruction(&Instruction::Br(0)); // continue $merge
277
252750
        f.instruction(&Instruction::End); // loop
278
252750
        f.instruction(&Instruction::End); // block $done
279

            
280
        // out = array.new_default(n); array.copy out[0..n] <- tmp[0..n]
281
252750
        f.instruction(&Instruction::LocalGet(N));
282
252750
        f.instruction(&Instruction::ArrayNewDefault(unit_idx));
283
252750
        f.instruction(&Instruction::LocalSet(OUT));
284
252750
        f.instruction(&Instruction::LocalGet(OUT));
285
252750
        f.instruction(&Instruction::I32Const(0));
286
252750
        f.instruction(&Instruction::LocalGet(TMP));
287
252750
        f.instruction(&Instruction::I32Const(0));
288
252750
        f.instruction(&Instruction::LocalGet(N));
289
252750
        f.instruction(&Instruction::ArrayCopy {
290
252750
            array_type_index_dst: unit_idx,
291
252750
            array_type_index_src: unit_idx,
292
252750
        });
293
252750
        f.instruction(&Instruction::LocalGet(OUT));
294
252750
        f.instruction(&Instruction::End);
295
252750
        self.pending_helpers.push(f);
296
252750
    }
297

            
298
    /// Copies the 3-slot triple at `src[src_idx]` into `tmp[n]`, then `n += 3`.
299
1011000
    fn emit_push_triple_from(
300
1011000
        f: &mut Function,
301
1011000
        unit_idx: u32,
302
1011000
        src_local: u32,
303
1011000
        src_idx_local: u32,
304
1011000
        tmp_local: u32,
305
1011000
        n_local: u32,
306
1011000
    ) {
307
3033000
        for off in 0..3 {
308
3033000
            f.instruction(&Instruction::LocalGet(tmp_local));
309
3033000
            f.instruction(&Instruction::LocalGet(n_local));
310
3033000
            if off != 0 {
311
2022000
                f.instruction(&Instruction::I32Const(off));
312
2022000
                f.instruction(&Instruction::I32Add);
313
2022000
            }
314
3033000
            Self::emit_array_get(f, unit_idx, src_local, src_idx_local, off);
315
3033000
            f.instruction(&Instruction::ArraySet(unit_idx));
316
        }
317
1011000
        Self::emit_advance(f, n_local);
318
1011000
    }
319

            
320
    /// Writes `(a.hi, a.lo, sum)` into `tmp[n]` (the merged-exponent case),
321
    /// then `n += 3`.
322
252750
    fn emit_push_merged(
323
252750
        f: &mut Function,
324
252750
        unit_idx: u32,
325
252750
        a_local: u32,
326
252750
        a_idx_local: u32,
327
252750
        sum_local: u32,
328
252750
        tmp_local: u32,
329
252750
        n_local: u32,
330
252750
    ) {
331
505500
        for off in 0..2 {
332
505500
            f.instruction(&Instruction::LocalGet(tmp_local));
333
505500
            f.instruction(&Instruction::LocalGet(n_local));
334
505500
            if off != 0 {
335
252750
                f.instruction(&Instruction::I32Const(off));
336
252750
                f.instruction(&Instruction::I32Add);
337
252750
            }
338
505500
            Self::emit_array_get(f, unit_idx, a_local, a_idx_local, off);
339
505500
            f.instruction(&Instruction::ArraySet(unit_idx));
340
        }
341
252750
        f.instruction(&Instruction::LocalGet(tmp_local));
342
252750
        f.instruction(&Instruction::LocalGet(n_local));
343
252750
        f.instruction(&Instruction::I32Const(2));
344
252750
        f.instruction(&Instruction::I32Add);
345
252750
        f.instruction(&Instruction::LocalGet(sum_local));
346
252750
        f.instruction(&Instruction::ArraySet(unit_idx));
347
252750
        Self::emit_advance(f, n_local);
348
252750
    }
349

            
350
    /// Leaves a 1/0 i32 on the stack: the unsigned `(hi, lo)` key at
351
    /// `a[a_idx]` is strictly less than the key at `b[b_idx]`.
352
505500
    fn emit_key_less(
353
505500
        f: &mut Function,
354
505500
        unit_idx: u32,
355
505500
        a_local: u32,
356
505500
        a_idx_local: u32,
357
505500
        b_local: u32,
358
505500
        b_idx_local: u32,
359
505500
    ) {
360
505500
        Self::emit_array_get(f, unit_idx, a_local, a_idx_local, 0);
361
505500
        Self::emit_array_get(f, unit_idx, b_local, b_idx_local, 0);
362
505500
        f.instruction(&Instruction::I64LtU);
363
505500
        Self::emit_array_get(f, unit_idx, a_local, a_idx_local, 0);
364
505500
        Self::emit_array_get(f, unit_idx, b_local, b_idx_local, 0);
365
505500
        f.instruction(&Instruction::I64Eq);
366
505500
        Self::emit_array_get(f, unit_idx, a_local, a_idx_local, 1);
367
505500
        Self::emit_array_get(f, unit_idx, b_local, b_idx_local, 1);
368
505500
        f.instruction(&Instruction::I64LtU);
369
505500
        f.instruction(&Instruction::I32And);
370
505500
        f.instruction(&Instruction::I32Or);
371
505500
    }
372

            
373
    /// `local += 3` (advance a triple index or the write cursor).
374
2780250
    fn emit_advance(f: &mut Function, local: u32) {
375
2780250
        f.instruction(&Instruction::LocalGet(local));
376
2780250
        f.instruction(&Instruction::I32Const(3));
377
2780250
        f.instruction(&Instruction::I32Add);
378
2780250
        f.instruction(&Instruction::LocalSet(local));
379
2780250
    }
380

            
381
    /// Pushes `arr[base_local_idx_local + offset]` (an i64) onto the stack.
382
    /// `arr_local` holds the array ref; `idx_local` holds the running base
383
    /// index; `offset` is the constant triple-field offset (0=hi,1=lo,2=exp).
384
8340750
    fn emit_array_get(
385
8340750
        f: &mut Function,
386
8340750
        unit_idx: u32,
387
8340750
        arr_local: u32,
388
8340750
        idx_local: u32,
389
8340750
        offset: i32,
390
8340750
    ) {
391
8340750
        f.instruction(&Instruction::LocalGet(arr_local));
392
8340750
        f.instruction(&Instruction::LocalGet(idx_local));
393
8340750
        if offset != 0 {
394
4296750
            f.instruction(&Instruction::I32Const(offset));
395
4296750
            f.instruction(&Instruction::I32Add);
396
4296750
        }
397
8340750
        f.instruction(&Instruction::ArrayGet(unit_idx));
398
8340750
    }
399

            
400
    /// `dst[dst_idx_local + dst_off] = src...` verbatim copy of one i64 slot
401
    /// from the param-0 source array (`src_local` is the index local). Used by
402
    /// `unit_negate` to copy the hi/lo key fields unchanged.
403
505500
    fn emit_copy_i64(
404
505500
        f: &mut Function,
405
505500
        unit_idx: u32,
406
505500
        dst_local: u32,
407
505500
        idx_local: u32,
408
505500
        dst_off: i32,
409
505500
        src_off: i32,
410
505500
    ) {
411
505500
        f.instruction(&Instruction::LocalGet(dst_local));
412
505500
        f.instruction(&Instruction::LocalGet(idx_local));
413
505500
        if dst_off != 0 {
414
252750
            f.instruction(&Instruction::I32Const(dst_off));
415
252750
            f.instruction(&Instruction::I32Add);
416
252750
        }
417
505500
        Self::emit_array_get(f, unit_idx, 0, idx_local, src_off);
418
505500
        f.instruction(&Instruction::ArraySet(unit_idx));
419
505500
    }
420
}