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

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

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

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

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

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

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

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

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

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

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