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
477238
    pub(super) fn declare_unit_term_helpers(&mut self) -> Result<()> {
25
477238
        let unit_ref = self.unit_term_ref();
26
477238
        self.register_function("unit_singleton", &[ValType::I64, ValType::I64], &[unit_ref])?;
27
477238
        self.register_function("unit_negate", &[unit_ref], &[unit_ref])?;
28
477238
        self.register_function("unit_mul", &[unit_ref, unit_ref], &[unit_ref])?;
29
477238
        self.register_function("unit_div", &[unit_ref, unit_ref], &[unit_ref])?;
30
477238
        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
477238
        self.export_func("unit_singleton")?;
36
477238
        self.export_func("unit_negate")?;
37
477238
        self.export_func("unit_mul")?;
38
477238
        self.export_func("unit_div")?;
39
477238
        self.export_func("unit_eq")?;
40
477238
        Ok(())
41
477238
    }
42

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

            
52
    /// `unit_singleton(hi, lo)` → `[hi, lo, 1]` — the canonical term for a
53
    /// single commodity at exponent 1.
54
477238
    fn build_unit_singleton_body(&mut self) {
55
477238
        let unit_idx = self.ids.ty_unit_term;
56
477238
        let mut f = Function::new([]);
57
477238
        f.instruction(&Instruction::LocalGet(0));
58
477238
        f.instruction(&Instruction::LocalGet(1));
59
477238
        f.instruction(&Instruction::I64Const(1));
60
477238
        f.instruction(&Instruction::ArrayNewFixed {
61
477238
            array_type_index: unit_idx,
62
477238
            array_size: 3,
63
477238
        });
64
477238
        f.instruction(&Instruction::End);
65
477238
        self.pending_helpers.push(f);
66
477238
    }
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
477238
    fn build_unit_negate_body(&mut self) {
72
477238
        let unit_idx = self.ids.ty_unit_term;
73
        // params: $t=0. locals: $len=1 (i32), $out=2 (unit_ref), $i=3 (i32).
74
477238
        let unit_ref = self.unit_term_ref();
75
477238
        let mut f = Function::new([(1, ValType::I32), (1, unit_ref), (1, ValType::I32)]);
76
477238
        f.instruction(&Instruction::LocalGet(0));
77
477238
        f.instruction(&Instruction::ArrayLen);
78
477238
        f.instruction(&Instruction::LocalSet(1));
79
        // out = array.new_default(len)
80
477238
        f.instruction(&Instruction::LocalGet(1));
81
477238
        f.instruction(&Instruction::ArrayNewDefault(unit_idx));
82
477238
        f.instruction(&Instruction::LocalSet(2));
83
        // i = 0
84
477238
        f.instruction(&Instruction::I32Const(0));
85
477238
        f.instruction(&Instruction::LocalSet(3));
86
477238
        f.instruction(&Instruction::Block(BlockType::Empty));
87
477238
        f.instruction(&Instruction::Loop(BlockType::Empty));
88
        // if i >= len break
89
477238
        f.instruction(&Instruction::LocalGet(3));
90
477238
        f.instruction(&Instruction::LocalGet(1));
91
477238
        f.instruction(&Instruction::I32GeU);
92
477238
        f.instruction(&Instruction::BrIf(1));
93
        // out[i] = t[i] (hi), out[i+1] = t[i+1] (lo): copy verbatim
94
477238
        Self::emit_copy_i64(&mut f, unit_idx, 2, 3, 0, 0);
95
477238
        Self::emit_copy_i64(&mut f, unit_idx, 2, 3, 1, 1);
96
        // out[i+2] = -t[i+2]
97
477238
        f.instruction(&Instruction::LocalGet(2)); // out
98
477238
        f.instruction(&Instruction::LocalGet(3)); // i
99
477238
        f.instruction(&Instruction::I32Const(2));
100
477238
        f.instruction(&Instruction::I32Add); // i+2
101
477238
        f.instruction(&Instruction::I64Const(0));
102
477238
        Self::emit_array_get(&mut f, unit_idx, 0, 3, 2); // t[i+2]
103
477238
        f.instruction(&Instruction::I64Sub); // 0 - exp
104
477238
        f.instruction(&Instruction::ArraySet(unit_idx));
105
        // i += 3
106
477238
        f.instruction(&Instruction::LocalGet(3));
107
477238
        f.instruction(&Instruction::I32Const(3));
108
477238
        f.instruction(&Instruction::I32Add);
109
477238
        f.instruction(&Instruction::LocalSet(3));
110
477238
        f.instruction(&Instruction::Br(0));
111
477238
        f.instruction(&Instruction::End); // loop
112
477238
        f.instruction(&Instruction::End); // block
113
477238
        f.instruction(&Instruction::LocalGet(2));
114
477238
        f.instruction(&Instruction::End);
115
477238
        self.pending_helpers.push(f);
116
477238
    }
117

            
118
    /// `unit_div(a, b)` = `unit_mul(a, unit_negate(b))`.
119
477238
    fn build_unit_div_body(&mut self) {
120
477238
        let unit_negate = self.ids.unit_negate;
121
477238
        let unit_mul = self.ids.unit_mul;
122
477238
        let mut f = Function::new([]);
123
477238
        f.instruction(&Instruction::LocalGet(0));
124
477238
        f.instruction(&Instruction::LocalGet(1));
125
477238
        f.instruction(&Instruction::Call(unit_negate));
126
477238
        f.instruction(&Instruction::Call(unit_mul));
127
477238
        f.instruction(&Instruction::End);
128
477238
        self.pending_helpers.push(f);
129
477238
    }
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
477238
    fn build_unit_eq_body(&mut self) {
135
477238
        let unit_idx = self.ids.ty_unit_term;
136
        // params: $a=0, $b=1. locals: $len=2 (i32), $i=3 (i32).
137
477238
        let mut f = Function::new([(1, ValType::I32), (1, ValType::I32)]);
138
477238
        f.instruction(&Instruction::LocalGet(0));
139
477238
        f.instruction(&Instruction::ArrayLen);
140
477238
        f.instruction(&Instruction::LocalSet(2));
141
        // if len(b) != len -> 0
142
477238
        f.instruction(&Instruction::LocalGet(1));
143
477238
        f.instruction(&Instruction::ArrayLen);
144
477238
        f.instruction(&Instruction::LocalGet(2));
145
477238
        f.instruction(&Instruction::I32Ne);
146
477238
        f.instruction(&Instruction::If(BlockType::Empty));
147
477238
        f.instruction(&Instruction::I32Const(0));
148
477238
        f.instruction(&Instruction::Return);
149
477238
        f.instruction(&Instruction::End);
150
        // i = 0
151
477238
        f.instruction(&Instruction::I32Const(0));
152
477238
        f.instruction(&Instruction::LocalSet(3));
153
477238
        f.instruction(&Instruction::Block(BlockType::Empty));
154
477238
        f.instruction(&Instruction::Loop(BlockType::Empty));
155
477238
        f.instruction(&Instruction::LocalGet(3));
156
477238
        f.instruction(&Instruction::LocalGet(2));
157
477238
        f.instruction(&Instruction::I32GeU);
158
477238
        f.instruction(&Instruction::BrIf(1));
159
        // if a[i] != b[i] -> return 0
160
477238
        Self::emit_array_get(&mut f, unit_idx, 0, 3, 0);
161
477238
        Self::emit_array_get(&mut f, unit_idx, 1, 3, 0);
162
477238
        f.instruction(&Instruction::I64Ne);
163
477238
        f.instruction(&Instruction::If(BlockType::Empty));
164
477238
        f.instruction(&Instruction::I32Const(0));
165
477238
        f.instruction(&Instruction::Return);
166
477238
        f.instruction(&Instruction::End);
167
        // i++
168
477238
        f.instruction(&Instruction::LocalGet(3));
169
477238
        f.instruction(&Instruction::I32Const(1));
170
477238
        f.instruction(&Instruction::I32Add);
171
477238
        f.instruction(&Instruction::LocalSet(3));
172
477238
        f.instruction(&Instruction::Br(0));
173
477238
        f.instruction(&Instruction::End); // loop
174
477238
        f.instruction(&Instruction::End); // block
175
477238
        f.instruction(&Instruction::I32Const(1));
176
477238
        f.instruction(&Instruction::End);
177
477238
        self.pending_helpers.push(f);
178
477238
    }
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
477238
    fn build_unit_mul_body(&mut self) {
186
477238
        let unit_idx = self.ids.ty_unit_term;
187
477238
        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
477238
        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
477238
        f.instruction(&Instruction::LocalGet(A));
203
477238
        f.instruction(&Instruction::ArrayLen);
204
477238
        f.instruction(&Instruction::LocalSet(LA));
205
477238
        f.instruction(&Instruction::LocalGet(B));
206
477238
        f.instruction(&Instruction::ArrayLen);
207
477238
        f.instruction(&Instruction::LocalSet(LB));
208
        // tmp = array.new_default(la + lb)
209
477238
        f.instruction(&Instruction::LocalGet(LA));
210
477238
        f.instruction(&Instruction::LocalGet(LB));
211
477238
        f.instruction(&Instruction::I32Add);
212
477238
        f.instruction(&Instruction::ArrayNewDefault(unit_idx));
213
477238
        f.instruction(&Instruction::LocalSet(TMP));
214

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

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

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

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

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

            
320
    /// Writes `(a.hi, a.lo, sum)` into `tmp[n]` (the merged-exponent case),
321
    /// then `n += 3`.
322
477238
    fn emit_push_merged(
323
477238
        f: &mut Function,
324
477238
        unit_idx: u32,
325
477238
        a_local: u32,
326
477238
        a_idx_local: u32,
327
477238
        sum_local: u32,
328
477238
        tmp_local: u32,
329
477238
        n_local: u32,
330
477238
    ) {
331
954476
        for off in 0..2 {
332
954476
            f.instruction(&Instruction::LocalGet(tmp_local));
333
954476
            f.instruction(&Instruction::LocalGet(n_local));
334
954476
            if off != 0 {
335
477238
                f.instruction(&Instruction::I32Const(off));
336
477238
                f.instruction(&Instruction::I32Add);
337
477238
            }
338
954476
            Self::emit_array_get(f, unit_idx, a_local, a_idx_local, off);
339
954476
            f.instruction(&Instruction::ArraySet(unit_idx));
340
        }
341
477238
        f.instruction(&Instruction::LocalGet(tmp_local));
342
477238
        f.instruction(&Instruction::LocalGet(n_local));
343
477238
        f.instruction(&Instruction::I32Const(2));
344
477238
        f.instruction(&Instruction::I32Add);
345
477238
        f.instruction(&Instruction::LocalGet(sum_local));
346
477238
        f.instruction(&Instruction::ArraySet(unit_idx));
347
477238
        Self::emit_advance(f, n_local);
348
477238
    }
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
954476
    fn emit_key_less(
353
954476
        f: &mut Function,
354
954476
        unit_idx: u32,
355
954476
        a_local: u32,
356
954476
        a_idx_local: u32,
357
954476
        b_local: u32,
358
954476
        b_idx_local: u32,
359
954476
    ) {
360
954476
        Self::emit_array_get(f, unit_idx, a_local, a_idx_local, 0);
361
954476
        Self::emit_array_get(f, unit_idx, b_local, b_idx_local, 0);
362
954476
        f.instruction(&Instruction::I64LtU);
363
954476
        Self::emit_array_get(f, unit_idx, a_local, a_idx_local, 0);
364
954476
        Self::emit_array_get(f, unit_idx, b_local, b_idx_local, 0);
365
954476
        f.instruction(&Instruction::I64Eq);
366
954476
        Self::emit_array_get(f, unit_idx, a_local, a_idx_local, 1);
367
954476
        Self::emit_array_get(f, unit_idx, b_local, b_idx_local, 1);
368
954476
        f.instruction(&Instruction::I64LtU);
369
954476
        f.instruction(&Instruction::I32And);
370
954476
        f.instruction(&Instruction::I32Or);
371
954476
    }
372

            
373
    /// `local += 3` (advance a triple index or the write cursor).
374
5249618
    fn emit_advance(f: &mut Function, local: u32) {
375
5249618
        f.instruction(&Instruction::LocalGet(local));
376
5249618
        f.instruction(&Instruction::I32Const(3));
377
5249618
        f.instruction(&Instruction::I32Add);
378
5249618
        f.instruction(&Instruction::LocalSet(local));
379
5249618
    }
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
15748854
    fn emit_array_get(
385
15748854
        f: &mut Function,
386
15748854
        unit_idx: u32,
387
15748854
        arr_local: u32,
388
15748854
        idx_local: u32,
389
15748854
        offset: i32,
390
15748854
    ) {
391
15748854
        f.instruction(&Instruction::LocalGet(arr_local));
392
15748854
        f.instruction(&Instruction::LocalGet(idx_local));
393
15748854
        if offset != 0 {
394
8113046
            f.instruction(&Instruction::I32Const(offset));
395
8113046
            f.instruction(&Instruction::I32Add);
396
8113046
        }
397
15748854
        f.instruction(&Instruction::ArrayGet(unit_idx));
398
15748854
    }
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
954476
    fn emit_copy_i64(
404
954476
        f: &mut Function,
405
954476
        unit_idx: u32,
406
954476
        dst_local: u32,
407
954476
        idx_local: u32,
408
954476
        dst_off: i32,
409
954476
        src_off: i32,
410
954476
    ) {
411
954476
        f.instruction(&Instruction::LocalGet(dst_local));
412
954476
        f.instruction(&Instruction::LocalGet(idx_local));
413
954476
        if dst_off != 0 {
414
477238
            f.instruction(&Instruction::I32Const(dst_off));
415
477238
            f.instruction(&Instruction::I32Add);
416
477238
        }
417
954476
        Self::emit_array_get(f, unit_idx, 0, idx_local, src_off);
418
954476
        f.instruction(&Instruction::ArraySet(unit_idx));
419
954476
    }
420
}