Lines
100 %
Functions
60 %
Branches
//! Guest-side helpers for the commodity **unit term** (ADR-0028 E1/E2).
//!
//! A unit term is a `(array (mut i64))` holding a sorted, canonical sequence
//! of `(hi, lo, exp)` triples — one per commodity that participates in a
//! compound money value, with `exp` its integer exponent (`USD·EUR` →
//! `[(EUR,1),(USD,1)]`, `USD²` → `[(USD,2)]`). Sorted ascending by the
//! unsigned `(hi, lo)` UUID key so equality is positional and merges are a
//! single linear pass. A *null* term is the compact encoding of the ATOMIC
//! singleton `[(self, 1)]` (the commodity's own id in struct fields 2-3); the
//! commodity helpers materialize it via [`unit_singleton`] before any term
//! arithmetic. An *empty* (length-0) term is DIMENSIONLESS (money ÷ money).
//! All bodies assume **non-null** array arguments — callers handle the
//! null/atomic case. Exponent sums that reach zero are dropped so the term
//! stays canonical (a commodity that cancels out leaves the term).
use super::CompileContext;
use crate::error::Result;
use wasm_encoder::{BlockType, Function, Instruction, ValType};
impl CompileContext {
/// Declares the unit-term helper signatures (no bodies). See
/// [`Self::build_unit_term_helpers`] for the body emit.
pub(super) fn declare_unit_term_helpers(&mut self) -> Result<()> {
let unit_ref = self.unit_term_ref();
self.register_function("unit_singleton", &[ValType::I64, ValType::I64], &[unit_ref])?;
self.register_function("unit_negate", &[unit_ref], &[unit_ref])?;
self.register_function("unit_mul", &[unit_ref, unit_ref], &[unit_ref])?;
self.register_function("unit_div", &[unit_ref, unit_ref], &[unit_ref])?;
self.register_function("unit_eq", &[unit_ref, unit_ref], &[ValType::I32])?;
// Exported so the host can inspect / construct unit terms (the
// compound-money decoder + serializer) and so the intricate
// sorted-merge is directly unit-testable, mirroring `pair_new` /
// `commodity_new`.
self.export_func("unit_singleton")?;
self.export_func("unit_negate")?;
self.export_func("unit_mul")?;
self.export_func("unit_div")?;
self.export_func("unit_eq")?;
Ok(())
}
/// Emits the unit-term helper bodies in registration order.
pub(super) fn build_unit_term_helpers(&mut self) {
self.build_unit_singleton_body();
self.build_unit_negate_body();
self.build_unit_mul_body();
self.build_unit_div_body();
self.build_unit_eq_body();
/// `unit_singleton(hi, lo)` → `[hi, lo, 1]` — the canonical term for a
/// single commodity at exponent 1.
fn build_unit_singleton_body(&mut self) {
let unit_idx = self.ids.ty_unit_term;
let mut f = Function::new([]);
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::I64Const(1));
f.instruction(&Instruction::ArrayNewFixed {
array_type_index: unit_idx,
array_size: 3,
});
f.instruction(&Instruction::End);
self.pending_helpers.push(f);
/// `unit_negate(t)` → a fresh term with every exponent negated (used by
/// `unit_div`: `a / b = a * negate(b)`). Keys + order are preserved, so the
/// result is already canonical.
fn build_unit_negate_body(&mut self) {
// params: $t=0. locals: $len=1 (i32), $out=2 (unit_ref), $i=3 (i32).
let mut f = Function::new([(1, ValType::I32), (1, unit_ref), (1, ValType::I32)]);
f.instruction(&Instruction::ArrayLen);
f.instruction(&Instruction::LocalSet(1));
// out = array.new_default(len)
f.instruction(&Instruction::ArrayNewDefault(unit_idx));
f.instruction(&Instruction::LocalSet(2));
// i = 0
f.instruction(&Instruction::I32Const(0));
f.instruction(&Instruction::LocalSet(3));
f.instruction(&Instruction::Block(BlockType::Empty));
f.instruction(&Instruction::Loop(BlockType::Empty));
// if i >= len break
f.instruction(&Instruction::LocalGet(3));
f.instruction(&Instruction::I32GeU);
f.instruction(&Instruction::BrIf(1));
// out[i] = t[i] (hi), out[i+1] = t[i+1] (lo): copy verbatim
Self::emit_copy_i64(&mut f, unit_idx, 2, 3, 0, 0);
Self::emit_copy_i64(&mut f, unit_idx, 2, 3, 1, 1);
// out[i+2] = -t[i+2]
f.instruction(&Instruction::LocalGet(2)); // out
f.instruction(&Instruction::LocalGet(3)); // i
f.instruction(&Instruction::I32Const(2));
f.instruction(&Instruction::I32Add); // i+2
f.instruction(&Instruction::I64Const(0));
Self::emit_array_get(&mut f, unit_idx, 0, 3, 2); // t[i+2]
f.instruction(&Instruction::I64Sub); // 0 - exp
f.instruction(&Instruction::ArraySet(unit_idx));
// i += 3
f.instruction(&Instruction::I32Const(3));
f.instruction(&Instruction::I32Add);
f.instruction(&Instruction::Br(0));
f.instruction(&Instruction::End); // loop
f.instruction(&Instruction::End); // block
f.instruction(&Instruction::LocalGet(2));
/// `unit_div(a, b)` = `unit_mul(a, unit_negate(b))`.
fn build_unit_div_body(&mut self) {
let unit_negate = self.ids.unit_negate;
let unit_mul = self.ids.unit_mul;
f.instruction(&Instruction::Call(unit_negate));
f.instruction(&Instruction::Call(unit_mul));
/// `unit_eq(a, b)` → 1 iff the two canonical terms are identical (same
/// length, same `(hi, lo, exp)` at every slot). Both args are non-null
/// canonical terms, so positional comparison is a full multiset equality.
fn build_unit_eq_body(&mut self) {
// params: $a=0, $b=1. locals: $len=2 (i32), $i=3 (i32).
let mut f = Function::new([(1, ValType::I32), (1, ValType::I32)]);
// if len(b) != len -> 0
f.instruction(&Instruction::I32Ne);
f.instruction(&Instruction::If(BlockType::Empty));
f.instruction(&Instruction::Return);
// if a[i] != b[i] -> return 0
Self::emit_array_get(&mut f, unit_idx, 0, 3, 0);
Self::emit_array_get(&mut f, unit_idx, 1, 3, 0);
f.instruction(&Instruction::I64Ne);
// i++
f.instruction(&Instruction::I32Const(1));
/// `unit_mul(a, b)` — linear sorted-merge of two canonical terms, summing
/// the exponents of matching `(hi, lo)` keys and DROPPING any that cancel
/// to zero, so the result is canonical. Builds into a worst-case
/// `(len_a + len_b)` scratch array, then `array.copy`s the written prefix
/// into an exact-size result. Both args are non-null.
fn build_unit_mul_body(&mut self) {
// params: a=0, b=1. locals: la=2, lb=3, n=4, i=5, j=6 (i32);
// tmp=7, out=8 (unit_ref); sum=9 (i64). n/i/j default to 0.
let mut f = Function::new([(5, ValType::I32), (2, unit_ref), (1, ValType::I64)]);
const A: u32 = 0;
const B: u32 = 1;
const LA: u32 = 2;
const LB: u32 = 3;
const N: u32 = 4;
const I: u32 = 5;
const J: u32 = 6;
const TMP: u32 = 7;
const OUT: u32 = 8;
const SUM: u32 = 9;
f.instruction(&Instruction::LocalGet(A));
f.instruction(&Instruction::LocalSet(LA));
f.instruction(&Instruction::LocalGet(B));
f.instruction(&Instruction::LocalSet(LB));
// tmp = array.new_default(la + lb)
f.instruction(&Instruction::LocalGet(LA));
f.instruction(&Instruction::LocalGet(LB));
f.instruction(&Instruction::LocalSet(TMP));
// both exhausted? (i<la | j<lb) == 0 → break to $done (depth 1)
f.instruction(&Instruction::LocalGet(I));
f.instruction(&Instruction::I32LtU);
f.instruction(&Instruction::LocalGet(J));
f.instruction(&Instruction::I32Or);
f.instruction(&Instruction::I32Eqz);
// if a exhausted (i >= la) → take B
Self::emit_push_triple_from(&mut f, unit_idx, B, J, TMP, N);
Self::emit_advance(&mut f, J);
f.instruction(&Instruction::Else);
// a has elements; if b exhausted (j >= lb) → take A
Self::emit_push_triple_from(&mut f, unit_idx, A, I, TMP, N);
Self::emit_advance(&mut f, I);
// both available; key(a,i) < key(b,j) → take A
Self::emit_key_less(&mut f, unit_idx, A, I, B, J);
// key(b,j) < key(a,i) → take B
Self::emit_key_less(&mut f, unit_idx, B, J, A, I);
// equal keys: sum exponents, emit only if non-zero, advance both
Self::emit_array_get(&mut f, unit_idx, A, I, 2);
Self::emit_array_get(&mut f, unit_idx, B, J, 2);
f.instruction(&Instruction::I64Add);
f.instruction(&Instruction::LocalSet(SUM));
f.instruction(&Instruction::LocalGet(SUM));
Self::emit_push_merged(&mut f, unit_idx, A, I, SUM, TMP, N);
f.instruction(&Instruction::End); // key(b)<key(a) if
f.instruction(&Instruction::End); // key(a)<key(b) if
f.instruction(&Instruction::End); // b-exhausted if
f.instruction(&Instruction::End); // a-exhausted if
f.instruction(&Instruction::Br(0)); // continue $merge
f.instruction(&Instruction::End); // block $done
// out = array.new_default(n); array.copy out[0..n] <- tmp[0..n]
f.instruction(&Instruction::LocalGet(N));
f.instruction(&Instruction::LocalSet(OUT));
f.instruction(&Instruction::LocalGet(OUT));
f.instruction(&Instruction::LocalGet(TMP));
f.instruction(&Instruction::ArrayCopy {
array_type_index_dst: unit_idx,
array_type_index_src: unit_idx,
/// Copies the 3-slot triple at `src[src_idx]` into `tmp[n]`, then `n += 3`.
fn emit_push_triple_from(
f: &mut Function,
unit_idx: u32,
src_local: u32,
src_idx_local: u32,
tmp_local: u32,
n_local: u32,
) {
for off in 0..3 {
f.instruction(&Instruction::LocalGet(tmp_local));
f.instruction(&Instruction::LocalGet(n_local));
if off != 0 {
f.instruction(&Instruction::I32Const(off));
Self::emit_array_get(f, unit_idx, src_local, src_idx_local, off);
Self::emit_advance(f, n_local);
/// Writes `(a.hi, a.lo, sum)` into `tmp[n]` (the merged-exponent case),
/// then `n += 3`.
fn emit_push_merged(
a_local: u32,
a_idx_local: u32,
sum_local: u32,
for off in 0..2 {
Self::emit_array_get(f, unit_idx, a_local, a_idx_local, off);
f.instruction(&Instruction::LocalGet(sum_local));
/// Leaves a 1/0 i32 on the stack: the unsigned `(hi, lo)` key at
/// `a[a_idx]` is strictly less than the key at `b[b_idx]`.
fn emit_key_less(
b_local: u32,
b_idx_local: u32,
Self::emit_array_get(f, unit_idx, a_local, a_idx_local, 0);
Self::emit_array_get(f, unit_idx, b_local, b_idx_local, 0);
f.instruction(&Instruction::I64LtU);
f.instruction(&Instruction::I64Eq);
Self::emit_array_get(f, unit_idx, a_local, a_idx_local, 1);
Self::emit_array_get(f, unit_idx, b_local, b_idx_local, 1);
f.instruction(&Instruction::I32And);
/// `local += 3` (advance a triple index or the write cursor).
fn emit_advance(f: &mut Function, local: u32) {
f.instruction(&Instruction::LocalGet(local));
f.instruction(&Instruction::LocalSet(local));
/// Pushes `arr[base_local_idx_local + offset]` (an i64) onto the stack.
/// `arr_local` holds the array ref; `idx_local` holds the running base
/// index; `offset` is the constant triple-field offset (0=hi,1=lo,2=exp).
fn emit_array_get(
arr_local: u32,
idx_local: u32,
offset: i32,
f.instruction(&Instruction::LocalGet(arr_local));
f.instruction(&Instruction::LocalGet(idx_local));
if offset != 0 {
f.instruction(&Instruction::I32Const(offset));
f.instruction(&Instruction::ArrayGet(unit_idx));
/// `dst[dst_idx_local + dst_off] = src...` verbatim copy of one i64 slot
/// from the param-0 source array (`src_local` is the index local). Used by
/// `unit_negate` to copy the hi/lo key fields unchanged.
fn emit_copy_i64(
dst_local: u32,
dst_off: i32,
src_off: i32,
f.instruction(&Instruction::LocalGet(dst_local));
if dst_off != 0 {
f.instruction(&Instruction::I32Const(dst_off));
Self::emit_array_get(f, unit_idx, 0, idx_local, src_off);