use wasm_encoder::Function;
use super::super::WasmGcError;
use super::super::types::TypeRegistry;
use super::super::wat_helper;
fn aint_type_decls(registry: &TypeRegistry) -> Result<String, WasmGcError> {
let mag_idx = registry.aint_mag_array_idx.ok_or(WasmGcError::Validation(
"bignum helper needs the $AverInt magnitude array slot, but it wasn't allocated".into(),
))?;
let struct_idx = registry.aint_struct_idx.ok_or(WasmGcError::Validation(
"bignum helper needs the $AverInt struct slot, but it wasn't allocated".into(),
))?;
debug_assert_eq!(struct_idx, mag_idx + 1, "struct must sit right above mag");
let padding = wat_helper::padding_types(mag_idx);
Ok(format!(
"{padding}\
(type $mag (array (mut i64)))\n\
(type $aint (struct (field $small (mut i64)) \
(field $magf (mut (ref null $mag))) \
(field $sign (mut i32))))\n"
))
}
fn decompose(registry: &TypeRegistry, op: &str, opm: &str, ops: &str, umag: &str) -> String {
if let Some(idx) = registry.aint_decompose_fn_idx {
return format!(
r#"
(call {idx} (local.get ${op}))
(local.set ${ops})
(local.set ${opm}) "#
);
}
decompose_inline(op, opm, ops, umag)
}
fn decompose_inline(op: &str, opm: &str, ops: &str, umag: &str) -> String {
format!(
r#"
(if (ref.is_null (struct.get $aint $magf (local.get ${op})))
(then
(if (i64.eqz (struct.get $aint $small (local.get ${op})))
(then
(local.set ${ops} (i32.const 0))
(local.set ${opm} (array.new_default $mag (i32.const 0))))
(else
(local.set ${ops}
(if (result i32) (i64.lt_s (struct.get $aint $small (local.get ${op})) (i64.const 0))
(then (i32.const -1)) (else (i32.const 1))))
(local.set ${umag}
(if (result i64) (i32.lt_s (local.get ${ops}) (i32.const 0))
(then (i64.sub (i64.const 0) (struct.get $aint $small (local.get ${op}))))
(else (struct.get $aint $small (local.get ${op})))))
(local.set ${opm} (array.new_default $mag (i32.const 2)))
(array.set $mag (local.get ${opm}) (i32.const 0)
(i64.and (local.get ${umag}) (i64.const 0xffffffff)))
(array.set $mag (local.get ${opm}) (i32.const 1)
(i64.shr_u (local.get ${umag}) (i64.const 32))))))
(else
(local.set ${opm} (struct.get $aint $magf (local.get ${op})))
(local.set ${ops} (struct.get $aint $sign (local.get ${op}))))) "#
)
}
fn normalize(registry: &TypeRegistry, rm: &str, rs: &str) -> String {
if let Some(idx) = registry.aint_normalize_fn_idx {
return format!("(call {idx} (local.get ${rm}) (local.get ${rs}))");
}
normalize_inline(rm, rs, "rlen", "i", "lo", "hi", "tmpm")
}
fn normalize_inline(
rm: &str,
rs: &str,
rlen: &str,
i: &str,
lo: &str,
hi: &str,
tmpm: &str,
) -> String {
format!(
r#"
;; strip leading zero limbs
(local.set ${rlen} (array.len (local.get ${rm})))
(block $strip_done (loop $strip
(br_if $strip_done (i32.eqz (local.get ${rlen})))
(br_if $strip_done
(i64.ne (array.get $mag (local.get ${rm}) (i32.sub (local.get ${rlen}) (i32.const 1))) (i64.const 0)))
(local.set ${rlen} (i32.sub (local.get ${rlen}) (i32.const 1)))
(br $strip)))
(if (result (ref null $aint)) (i32.eqz (local.get ${rlen}))
(then
(struct.new $aint (i64.const 0) (ref.null $mag) (i32.const 0)))
(else
(if (result (ref null $aint)) (i32.le_u (local.get ${rlen}) (i32.const 2))
(then
;; reassemble ≤2 limbs into a 64-bit unsigned value
(local.set ${lo} (i64.and (array.get $mag (local.get ${rm}) (i32.const 0)) (i64.const 0xffffffff)))
(local.set ${hi}
(if (result i64) (i32.eq (local.get ${rlen}) (i32.const 2))
(then (i64.and (array.get $mag (local.get ${rm}) (i32.const 1)) (i64.const 0xffffffff)))
(else (i64.const 0))))
(local.set ${lo} (i64.or (local.get ${lo}) (i64.shl (local.get ${hi}) (i64.const 32))))
;; $lo now holds the full magnitude as an unsigned i64.
(if (result (ref null $aint))
(i64.eqz (i64.shr_u (local.get ${lo}) (i64.const 63)))
(then
;; top bit clear → fits i64::MAX either sign → Small
(struct.new $aint
(if (result i64) (i32.lt_s (local.get ${rs}) (i32.const 0))
(then (i64.sub (i64.const 0) (local.get ${lo}))) (else (local.get ${lo})))
(ref.null $mag) (i32.const 0)))
(else
;; top bit set: only -2^63 (i64::MIN) demotes.
(if (result (ref null $aint))
(i32.and (i32.lt_s (local.get ${rs}) (i32.const 0))
(i64.eq (local.get ${lo}) (i64.const 0x8000000000000000)))
(then
(struct.new $aint (i64.const 0x8000000000000000) (ref.null $mag) (i32.const 0)))
(else
{tight_big})))))
(else
{tight_big})))) "#,
tight_big = tight_big(rm, rs, rlen, i, tmpm),
)
}
fn tight_big(rm: &str, rs: &str, rlen: &str, i: &str, tmpm: &str) -> String {
format!(
r#"
(local.set ${tmpm} (array.new_default $mag (local.get ${rlen})))
(local.set ${i} (i32.const 0))
(block $copy_done (loop $copy
(br_if $copy_done (i32.ge_u (local.get ${i}) (local.get ${rlen})))
(array.set $mag (local.get ${tmpm}) (local.get ${i})
(array.get $mag (local.get ${rm}) (local.get ${i})))
(local.set ${i} (i32.add (local.get ${i}) (i32.const 1)))
(br $copy)))
(struct.new $aint (i64.const 0) (local.get ${tmpm})
(if (result i32) (i32.lt_s (local.get ${rs}) (i32.const 0)) (then (i32.const -1)) (else (i32.const 1)))) "#
)
}
fn umag_cmp(
registry: &TypeRegistry,
am: &str,
alen: &str,
bm: &str,
blen: &str,
cmp: &str,
j: &str,
) -> String {
if let Some(idx) = registry.aint_umag_cmp_fn_idx {
return format!(
"(local.set ${cmp} (call {idx} (local.get ${am}) (local.get ${alen}) (local.get ${bm}) (local.get ${blen})))"
);
}
umag_cmp_inline(am, alen, bm, blen, cmp, j)
}
fn umag_cmp_inline(am: &str, alen: &str, bm: &str, blen: &str, cmp: &str, j: &str) -> String {
format!(
r#"
(if (i32.ne (local.get ${alen}) (local.get ${blen}))
(then
(local.set ${cmp}
(if (result i32) (i32.gt_u (local.get ${alen}) (local.get ${blen})) (then (i32.const 1)) (else (i32.const -1)))))
(else
(local.set ${cmp} (i32.const 0))
(local.set ${j} (local.get ${alen}))
(block $ucmp_done (loop $ucmp
(br_if $ucmp_done (i32.eqz (local.get ${j})))
(local.set ${j} (i32.sub (local.get ${j}) (i32.const 1)))
(if (i64.ne (array.get $mag (local.get ${am}) (local.get ${j}))
(array.get $mag (local.get ${bm}) (local.get ${j})))
(then
(local.set ${cmp}
(if (result i32) (i64.gt_u (array.get $mag (local.get ${am}) (local.get ${j}))
(array.get $mag (local.get ${bm}) (local.get ${j})))
(then (i32.const 1)) (else (i32.const -1))))
(br $ucmp_done)))
(br $ucmp))))) "#
)
}
fn aint_and_string_decls(registry: &TypeRegistry) -> Result<String, WasmGcError> {
let string_idx = registry
.string_array_type_idx
.ok_or(WasmGcError::Validation(
"bignum String.fromInt needs the String slot, but it wasn't allocated".into(),
))?;
let mag_idx = registry.aint_mag_array_idx.ok_or(WasmGcError::Validation(
"bignum String.fromInt needs the $mag slot, but it wasn't allocated".into(),
))?;
let struct_idx = registry.aint_struct_idx.ok_or(WasmGcError::Validation(
"bignum String.fromInt needs the $aint slot, but it wasn't allocated".into(),
))?;
if !(string_idx < mag_idx && mag_idx + 1 == struct_idx) {
return Err(WasmGcError::Validation(format!(
"bignum String.fromInt expects string({string_idx}) < mag({mag_idx}) and \
struct({struct_idx}) == mag+1; layout invariant broken"
)));
}
let pad_to_string = wat_helper::padding_types(string_idx);
let gap = wat_helper::padding_types(mag_idx - (string_idx + 1));
Ok(format!(
"{pad_to_string}\
(type $string (array (mut i8)))\n\
{gap}\
(type $mag (array (mut i64)))\n\
(type $aint (struct (field $small (mut i64)) \
(field $magf (mut (ref null $mag))) \
(field $sign (mut i32))))\n"
))
}
fn aint_string_result_decls(registry: &TypeRegistry) -> Result<String, WasmGcError> {
let string_idx = registry
.string_array_type_idx
.ok_or(WasmGcError::Validation(
"bignum Int.fromString needs the String slot, but it wasn't allocated".into(),
))?;
let mag_idx = registry.aint_mag_array_idx.ok_or(WasmGcError::Validation(
"bignum Int.fromString needs the $mag slot, but it wasn't allocated".into(),
))?;
let struct_idx = registry.aint_struct_idx.ok_or(WasmGcError::Validation(
"bignum Int.fromString needs the $aint slot, but it wasn't allocated".into(),
))?;
let result_idx =
registry
.result_type_idx("Result<Int,String>")
.ok_or(WasmGcError::Validation(
"bignum Int.fromString needs the Result<Int,String> slot, but it wasn't allocated"
.into(),
))?;
if !(string_idx < mag_idx && mag_idx + 1 == struct_idx && struct_idx < result_idx) {
return Err(WasmGcError::Validation(format!(
"bignum Int.fromString expects string({string_idx}) < mag({mag_idx}), \
struct({struct_idx}) == mag+1, and result({result_idx}) > struct; \
layout invariant broken"
)));
}
let pad_to_string = wat_helper::padding_types(string_idx);
let gap_to_mag = wat_helper::padding_types(mag_idx - (string_idx + 1));
let gap_to_result = wat_helper::padding_types(result_idx - (struct_idx + 1));
Ok(format!(
"{pad_to_string}\
(type $string (array (mut i8)))\n\
{gap_to_mag}\
(type $mag (array (mut i64)))\n\
(type $aint (struct (field $small (mut i64)) \
(field $magf (mut (ref null $mag))) \
(field $sign (mut i32))))\n\
{gap_to_result}\
(type $result (struct (field (mut i32)) (field (mut (ref null $aint))) \
(field (mut (ref null $string)))))\n"
))
}
fn from_string_err_build() -> String {
let prefix: [u8; 14] = *b"Cannot parse '";
let suffix: [u8; 8] = *b"' as Int";
let mut set_prefix = String::new();
for (i, b) in prefix.iter().enumerate() {
set_prefix.push_str(&format!(
"(array.set $string (local.get $err) (i32.const {i}) (i32.const {b}))\n"
));
}
let mut set_suffix = String::new();
for (k, b) in suffix.iter().enumerate() {
set_suffix.push_str(&format!(
"(array.set $string (local.get $err) (i32.add (i32.const {pos}) (local.get $len)) (i32.const {b}))\n",
pos = 14 + k
));
}
format!(
r#"
(local.set $err (array.new_default $string (i32.add (local.get $len) (i32.const 22))))
{set_prefix}
;; copy the original string into err[14..14+len]
(array.copy $string $string (local.get $err) (i32.const 14)
(local.get $s) (i32.const 0) (local.get $len))
{set_suffix}
(struct.new $result (i32.const 0) (ref.null $aint) (local.get $err))
"#
)
}
pub(super) fn emit_aint_from_string(registry: &TypeRegistry) -> Result<Function, WasmGcError> {
let decls = aint_string_result_decls(registry)?;
let err_build = from_string_err_build();
let norm = normalize(registry, "rm", "rs");
let func_pad = func_pad(&[(registry.aint_normalize_fn_idx, NORMALIZE_SIG)]);
let wat = format!(
include_str!("wat/from_string.wat"),
decls = decls,
func_pad = func_pad,
err_build = err_build,
norm = norm,
);
wat_helper::compile_wat_helper(&wat)
}
pub(super) fn emit_string_from_aint(registry: &TypeRegistry) -> Result<Function, WasmGcError> {
let decls = aint_and_string_decls(registry)?;
let wat = format!(include_str!("wat/string_from_aint.wat"), decls = decls);
wat_helper::compile_wat_helper(&wat)
}
const DECOMPOSE_SIG: &str = "(param (ref null $aint)) (result (ref null $mag) i32)";
const NORMALIZE_SIG: &str = "(param (ref null $mag)) (param i32) (result (ref null $aint))";
const STRIP_SIG: &str = "(param (ref null $mag)) (result i32)";
const UMAG_CMP_SIG: &str =
"(param (ref null $mag)) (param i32) (param (ref null $mag)) (param i32) (result i32)";
fn func_pad(callees: &[(Option<u32>, &'static str)]) -> String {
let stubs: Vec<wat_helper::CalleeStub> = callees
.iter()
.filter_map(|(idx, sig)| idx.map(|abs_idx| wat_helper::CalleeStub { abs_idx, sig }))
.collect();
wat_helper::func_placeholders(&stubs)
}
pub(super) fn emit_aint_decompose(registry: &TypeRegistry) -> Result<Function, WasmGcError> {
let decls = aint_type_decls(registry)?;
let wat = format!(include_str!("wat/decompose.wat"), decls = decls);
wat_helper::compile_wat_helper(&wat)
}
pub(super) fn emit_aint_normalize(registry: &TypeRegistry) -> Result<Function, WasmGcError> {
let decls = aint_type_decls(registry)?;
let wat = format!(
include_str!("wat/normalize.wat"),
decls = decls,
tight_big = tight_big("rm", "rs", "rlen", "i", "tmpm"),
);
wat_helper::compile_wat_helper(&wat)
}
pub(super) fn emit_aint_strip(registry: &TypeRegistry) -> Result<Function, WasmGcError> {
let decls = aint_type_decls(registry)?;
let wat = format!(include_str!("wat/strip.wat"), decls = decls);
wat_helper::compile_wat_helper(&wat)
}
pub(super) fn emit_aint_umag_cmp(registry: &TypeRegistry) -> Result<Function, WasmGcError> {
let decls = aint_type_decls(registry)?;
let wat = format!(include_str!("wat/umag_cmp.wat"), decls = decls);
wat_helper::compile_wat_helper(&wat)
}
pub(super) fn emit_aint_from_i64(registry: &TypeRegistry) -> Result<Function, WasmGcError> {
let decls = aint_type_decls(registry)?;
let wat = format!(include_str!("wat/from_i64.wat"), decls = decls);
wat_helper::compile_wat_helper(&wat)
}
pub(super) fn emit_aint_to_f64(registry: &TypeRegistry) -> Result<Function, WasmGcError> {
let decls = aint_type_decls(registry)?;
let wat = format!(include_str!("wat/to_f64.wat"), decls = decls);
wat_helper::compile_wat_helper(&wat)
}
pub(super) fn emit_aint_from_f64(registry: &TypeRegistry) -> Result<Function, WasmGcError> {
let decls = aint_type_decls(registry)?;
let norm = normalize(registry, "rm", "rs");
let func_pad = func_pad(&[(registry.aint_normalize_fn_idx, NORMALIZE_SIG)]);
let wat = format!(
include_str!("wat/from_f64.wat"),
decls = decls,
func_pad = func_pad,
norm = norm
);
wat_helper::compile_wat_helper(&wat)
}
pub(super) fn emit_aint_to_index(registry: &TypeRegistry) -> Result<Function, WasmGcError> {
let decls = aint_type_decls(registry)?;
let wat = format!(include_str!("wat/to_index.wat"), decls = decls);
wat_helper::compile_wat_helper(&wat)
}
pub(super) fn emit_aint_to_i64_sat(registry: &TypeRegistry) -> Result<Function, WasmGcError> {
let decls = aint_type_decls(registry)?;
let wat = format!(include_str!("wat/to_i64_sat.wat"), decls = decls);
wat_helper::compile_wat_helper(&wat)
}
pub(super) fn emit_aint_to_i64_checked(registry: &TypeRegistry) -> Result<Function, WasmGcError> {
let decls = aint_type_decls(registry)?;
let wat = format!(include_str!("wat/to_i64_checked.wat"), decls = decls);
wat_helper::compile_wat_helper(&wat)
}
pub(super) fn emit_aint_neg(registry: &TypeRegistry) -> Result<Function, WasmGcError> {
let decls = aint_type_decls(registry)?;
let wat = format!(include_str!("wat/neg.wat"), decls = decls);
wat_helper::compile_wat_helper(&wat)
}
pub(super) fn emit_aint_abs(registry: &TypeRegistry) -> Result<Function, WasmGcError> {
let decls = aint_type_decls(registry)?;
let wat = format!(include_str!("wat/abs.wat"), decls = decls);
wat_helper::compile_wat_helper(&wat)
}
pub(super) fn emit_aint_eq(registry: &TypeRegistry) -> Result<Function, WasmGcError> {
let decls = aint_type_decls(registry)?;
let wat = format!(include_str!("wat/eq.wat"), decls = decls);
wat_helper::compile_wat_helper(&wat)
}
pub(super) fn emit_aint_hash(registry: &TypeRegistry) -> Result<Function, WasmGcError> {
let decls = aint_type_decls(registry)?;
let wat = format!(include_str!("wat/hash.wat"), decls = decls);
wat_helper::compile_wat_helper(&wat)
}
pub(super) fn emit_aint_cmp(registry: &TypeRegistry) -> Result<Function, WasmGcError> {
let decls = aint_type_decls(registry)?;
let decomp_a = decompose(registry, "a", "am", "as_", "umag");
let decomp_b = decompose(registry, "b", "bm", "bs", "umag");
let strip_a = strip(registry, "am", "alen", "sa", "la");
let strip_b = strip(registry, "bm", "blen", "sb", "lb");
let cmp = umag_cmp(registry, "am", "alen", "bm", "blen", "cmp", "j");
let func_pad = func_pad(&[
(registry.aint_decompose_fn_idx, DECOMPOSE_SIG),
(registry.aint_strip_fn_idx, STRIP_SIG),
(registry.aint_umag_cmp_fn_idx, UMAG_CMP_SIG),
]);
let wat = format!(
include_str!("wat/cmp.wat"),
decls = decls,
func_pad = func_pad,
decomp_a = decomp_a,
decomp_b = decomp_b,
strip_a = strip_a,
strip_b = strip_b,
cmp = cmp,
);
wat_helper::compile_wat_helper(&wat)
}
fn strip(registry: &TypeRegistry, arr: &str, len: &str, bl: &str, lp: &str) -> String {
if let Some(idx) = registry.aint_strip_fn_idx {
return format!("(local.set ${len} (call {idx} (local.get ${arr})))");
}
strip_inline(arr, len, bl, lp)
}
fn strip_inline(arr: &str, len: &str, bl: &str, lp: &str) -> String {
format!(
r#"
(local.set ${len} (array.len (local.get ${arr})))
(block ${bl} (loop ${lp}
(br_if ${bl} (i32.eqz (local.get ${len})))
(br_if ${bl} (i64.ne (array.get $mag (local.get ${arr}) (i32.sub (local.get ${len}) (i32.const 1))) (i64.const 0)))
(local.set ${len} (i32.sub (local.get ${len}) (i32.const 1)))
(br ${lp}))) "#
)
}
fn signed_combine(registry: &TypeRegistry) -> String {
let cmp = umag_cmp(registry, "am", "alen", "bm", "blen", "cmp", "j");
format!(
r#"
;; signs "agree" when one is zero, or the two non-zero signs match.
(if (i32.or (i32.eqz (local.get $as_))
(i32.or (i32.eqz (local.get $beff)) (i32.eq (local.get $as_) (local.get $beff))))
(then
;; ── magnitude ADD ── rlen = max(alen,blen)+1
(local.set $rlen (i32.add (i32.const 1)
(if (result i32) (i32.gt_u (local.get $alen) (local.get $blen)) (then (local.get $alen)) (else (local.get $blen)))))
(local.set $rm (array.new_default $mag (local.get $rlen)))
(local.set $i (i32.const 0))
(local.set $carry (i64.const 0))
(block $add_done (loop $add_lp
(br_if $add_done (i32.ge_u (local.get $i) (local.get $rlen)))
(local.set $carry (i64.add (local.get $carry)
(i64.add
(if (result i64) (i32.lt_u (local.get $i) (local.get $alen)) (then (array.get $mag (local.get $am) (local.get $i))) (else (i64.const 0)))
(if (result i64) (i32.lt_u (local.get $i) (local.get $blen)) (then (array.get $mag (local.get $bm) (local.get $i))) (else (i64.const 0))))))
(array.set $mag (local.get $rm) (local.get $i) (i64.and (local.get $carry) (i64.const 0xffffffff)))
(local.set $carry (i64.shr_u (local.get $carry) (i64.const 32)))
(local.set $i (i32.add (local.get $i) (i32.const 1)))
(br $add_lp)))
(local.set $rs (if (result i32) (i32.eqz (local.get $as_)) (then (local.get $beff)) (else (local.get $as_)))))
(else
;; ── magnitude SUBTRACT (differing signs) ── larger - smaller
{cmp}
(if (i32.eqz (local.get $cmp))
(then
(local.set $rm (array.new_default $mag (i32.const 0)))
(local.set $rs (i32.const 0)))
(else
;; result sign = sign of the larger magnitude operand
(local.set $rs (if (result i32) (i32.gt_s (local.get $cmp) (i32.const 0)) (then (local.get $as_)) (else (local.get $beff))))
(local.set $rlen (if (result i32) (i32.gt_u (local.get $alen) (local.get $blen)) (then (local.get $alen)) (else (local.get $blen))))
(local.set $rm (array.new_default $mag (local.get $rlen)))
(local.set $i (i32.const 0))
(local.set $borrow (i64.const 0))
(block $sub_done (loop $sub_lp
(br_if $sub_done (i32.ge_u (local.get $i) (local.get $rlen)))
;; diff = larger_limb - smaller_limb - borrow, in 64 bits.
;; "larger" is am if cmp>0 else bm.
(local.set $diff (i64.sub
(i64.sub
(if (result i64) (i32.gt_s (local.get $cmp) (i32.const 0))
(then (if (result i64) (i32.lt_u (local.get $i) (local.get $alen)) (then (array.get $mag (local.get $am) (local.get $i))) (else (i64.const 0))))
(else (if (result i64) (i32.lt_u (local.get $i) (local.get $blen)) (then (array.get $mag (local.get $bm) (local.get $i))) (else (i64.const 0)))))
(if (result i64) (i32.gt_s (local.get $cmp) (i32.const 0))
(then (if (result i64) (i32.lt_u (local.get $i) (local.get $blen)) (then (array.get $mag (local.get $bm) (local.get $i))) (else (i64.const 0))))
(else (if (result i64) (i32.lt_u (local.get $i) (local.get $alen)) (then (array.get $mag (local.get $am) (local.get $i))) (else (i64.const 0))))))
(local.get $borrow)))
;; if diff < 0 (as signed) add 2^32 and set borrow=1
(if (i64.lt_s (local.get $diff) (i64.const 0))
(then
(local.set $diff (i64.add (local.get $diff) (i64.const 0x100000000)))
(local.set $borrow (i64.const 1)))
(else (local.set $borrow (i64.const 0))))
(array.set $mag (local.get $rm) (local.get $i) (i64.and (local.get $diff) (i64.const 0xffffffff)))
(local.set $i (i32.add (local.get $i) (i32.const 1)))
(br $sub_lp)))))))
"#
)
}
fn arith_locals() -> &'static str {
r#"
(local $am (ref null $mag)) (local $as_ i32)
(local $bm (ref null $mag)) (local $bs i32) (local $beff i32)
(local $alen i32) (local $blen i32) (local $rlen i32)
(local $rm (ref null $mag)) (local $rs i32)
(local $i i32) (local $j i32) (local $cmp i32)
(local $carry i64) (local $borrow i64) (local $diff i64) (local $umag i64)
(local $lo i64) (local $hi i64) (local $tmpm (ref null $mag)) "#
}
pub(super) fn emit_aint_add(registry: &TypeRegistry) -> Result<Function, WasmGcError> {
emit_aint_addsub(registry, false)
}
pub(super) fn emit_aint_sub(registry: &TypeRegistry) -> Result<Function, WasmGcError> {
emit_aint_addsub(registry, true)
}
fn emit_aint_addsub(registry: &TypeRegistry, is_sub: bool) -> Result<Function, WasmGcError> {
let decls = aint_type_decls(registry)?;
let locals = arith_locals();
let decomp_a = decompose(registry, "a", "am", "as_", "umag");
let decomp_b = decompose(registry, "b", "bm", "bs", "umag");
let strip_a = strip(registry, "am", "alen", "sa", "la");
let strip_b = strip(registry, "bm", "blen", "sb", "lb");
let combine = signed_combine(registry);
let norm = normalize(registry, "rm", "rs");
let func_pad = func_pad(&[
(registry.aint_decompose_fn_idx, DECOMPOSE_SIG),
(registry.aint_strip_fn_idx, STRIP_SIG),
(registry.aint_umag_cmp_fn_idx, UMAG_CMP_SIG),
(registry.aint_normalize_fn_idx, NORMALIZE_SIG),
]);
let (fast_op, overflow_check) = if is_sub {
(
"(i64.sub (struct.get $aint $small (local.get $a)) (struct.get $aint $small (local.get $b)))",
"(i64.lt_s (i64.and (i64.xor (struct.get $aint $small (local.get $a)) (struct.get $aint $small (local.get $b))) (i64.xor (struct.get $aint $small (local.get $a)) (local.get $r))) (i64.const 0))",
)
} else {
(
"(i64.add (struct.get $aint $small (local.get $a)) (struct.get $aint $small (local.get $b)))",
"(i64.lt_s (i64.and (i64.xor (struct.get $aint $small (local.get $a)) (local.get $r)) (i64.xor (struct.get $aint $small (local.get $b)) (local.get $r))) (i64.const 0))",
)
};
let beff_set = if is_sub {
"(local.set $beff (i32.sub (i32.const 0) (local.get $bs)))"
} else {
"(local.set $beff (local.get $bs))"
};
let wat = format!(
include_str!("wat/addsub.wat"),
decls = decls,
func_pad = func_pad,
locals = locals,
decomp_a = decomp_a,
decomp_b = decomp_b,
beff_set = beff_set,
strip_a = strip_a,
strip_b = strip_b,
combine = combine,
norm = norm,
fast_op = fast_op,
overflow_check = overflow_check,
);
wat_helper::compile_wat_helper(&wat)
}
pub(super) fn emit_aint_mul(registry: &TypeRegistry) -> Result<Function, WasmGcError> {
let decls = aint_type_decls(registry)?;
let locals = arith_locals();
let decomp_a = decompose(registry, "a", "am", "as_", "umag");
let decomp_b = decompose(registry, "b", "bm", "bs", "umag");
let strip_a = strip(registry, "am", "alen", "sa", "la");
let strip_b = strip(registry, "bm", "blen", "sb", "lb");
let norm = normalize(registry, "rm", "rs");
let func_pad = func_pad(&[
(registry.aint_decompose_fn_idx, DECOMPOSE_SIG),
(registry.aint_strip_fn_idx, STRIP_SIG),
(registry.aint_normalize_fn_idx, NORMALIZE_SIG),
]);
let wat = format!(
include_str!("wat/mul.wat"),
decls = decls,
func_pad = func_pad,
locals = locals,
decomp_a = decomp_a,
decomp_b = decomp_b,
strip_a = strip_a,
strip_b = strip_b,
norm = norm,
mul_body = mul_magnitude(),
);
wat_helper::compile_wat_helper(&wat)
}
fn divmod_locals() -> &'static str {
r#"
(local $am (ref null $mag)) (local $as_ i32)
(local $bm (ref null $mag)) (local $bs i32)
(local $alen i32) (local $blen i32)
(local $qm (ref null $mag)) (local $rwm (ref null $mag)) (local $rwlen i32)
(local $rwout (ref null $mag)) (local $rm (ref null $mag)) (local $rs i32) (local $rlen i32)
(local $bit i32) (local $word i32) (local $off i32)
(local $i i32) (local $j i32) (local $cmp i32) (local $negadj i32)
(local $carry i64) (local $acc i64) (local $borrow i64) (local $diff i64) (local $umag i64)
(local $lo i64) (local $hi i64) (local $tmpm (ref null $mag)) "#
}
pub(super) fn emit_aint_divmod(registry: &TypeRegistry) -> Result<Function, WasmGcError> {
let decls = aint_type_decls(registry)?;
let locals = divmod_locals();
let decomp_a = decompose(registry, "a", "am", "as_", "umag");
let decomp_b = decompose(registry, "b", "bm", "bs", "umag");
let strip_a = strip(registry, "am", "alen", "sa", "la");
let strip_b = strip(registry, "bm", "blen", "sb", "lb");
let cmp_r_b = umag_cmp(registry, "rwm", "rwlen", "bm", "blen", "cmp", "j");
let norm_q = normalize(registry, "rm", "rs");
let norm_r = normalize(registry, "rm", "rs");
let func_pad = func_pad(&[
(registry.aint_decompose_fn_idx, DECOMPOSE_SIG),
(registry.aint_strip_fn_idx, STRIP_SIG),
(registry.aint_umag_cmp_fn_idx, UMAG_CMP_SIG),
(registry.aint_normalize_fn_idx, NORMALIZE_SIG),
]);
let wat = format!(
include_str!("wat/divmod.wat"),
decls = decls,
func_pad = func_pad,
locals = locals,
decomp_a = decomp_a,
decomp_b = decomp_b,
strip_a = strip_a,
strip_b = strip_b,
cmp_r_b = cmp_r_b,
norm_q = norm_q,
norm_r = norm_r,
);
wat_helper::compile_wat_helper(&wat)
}
fn mul_magnitude() -> String {
r#"
;; result sign: 0 if either operand zero, else product of signs.
(local.set $rs (i32.mul (local.get $as_) (local.get $bs)))
(local.set $rlen (i32.add (local.get $alen) (local.get $blen)))
(if (i32.eqz (local.get $rlen)) (then (local.set $rlen (i32.const 1))))
(local.set $rm (array.new_default $mag (local.get $rlen)))
(local.set $i (i32.const 0))
(block $mi_done (loop $mi
(br_if $mi_done (i32.ge_u (local.get $i) (local.get $alen)))
(local.set $carry (i64.const 0))
(local.set $j (i32.const 0))
(block $mj_done (loop $mj
(br_if $mj_done (i32.ge_u (local.get $j) (local.get $blen)))
(local.set $k (i32.add (local.get $i) (local.get $j)))
;; prod = am[i]*bm[j] + rm[k] + carry (fits in 64 bits:
;; (2^32-1)^2 + 2*(2^32-1) < 2^64)
(local.set $prod (i64.add
(i64.add
(i64.mul (array.get $mag (local.get $am) (local.get $i))
(array.get $mag (local.get $bm) (local.get $j)))
(array.get $mag (local.get $rm) (local.get $k)))
(local.get $carry)))
(array.set $mag (local.get $rm) (local.get $k) (i64.and (local.get $prod) (i64.const 0xffffffff)))
(local.set $carry (i64.shr_u (local.get $prod) (i64.const 32)))
(local.set $j (i32.add (local.get $j) (i32.const 1)))
(br $mj)))
;; ripple the final carry up from rm[i+blen], masking each
;; limb to 32 bits so no lane is left holding > 2^32-1.
(local.set $k (i32.add (local.get $i) (local.get $blen)))
(block $mc_done (loop $mc
(br_if $mc_done (i64.eqz (local.get $carry)))
(local.set $prod (i64.add (array.get $mag (local.get $rm) (local.get $k)) (local.get $carry)))
(array.set $mag (local.get $rm) (local.get $k) (i64.and (local.get $prod) (i64.const 0xffffffff)))
(local.set $carry (i64.shr_u (local.get $prod) (i64.const 32)))
(local.set $k (i32.add (local.get $k) (i32.const 1)))
(br $mc)))
(local.set $i (i32.add (local.get $i) (i32.const 1)))
(br $mi)))
"#
.to_string()
}
#[cfg(test)]
mod tests {
use super::*;
fn bignum_registry() -> TypeRegistry {
bignum_registry_for_validation()
}
pub(super) fn bignum_registry_for_validation() -> TypeRegistry {
let mut registry = TypeRegistry::build_with_handler(&[], &[], false);
registry.bignum = true;
registry.string_array_type_idx = Some(0);
registry.aint_mag_array_idx = Some(1);
registry.aint_struct_idx = Some(2);
registry
.result_types
.insert("Result<Int,String>".to_string(), 3);
registry.result_order.push("Result<Int,String>".to_string());
registry
}
#[test]
fn every_bignum_helper_wat_parses() {
let registry = bignum_registry();
let cases: &[(&str, fn(&TypeRegistry) -> Result<Function, WasmGcError>)] = &[
("emit_aint_from_i64", emit_aint_from_i64),
("emit_aint_neg", emit_aint_neg),
("emit_aint_abs", emit_aint_abs),
("emit_aint_eq", emit_aint_eq),
("emit_aint_hash", emit_aint_hash),
("emit_aint_cmp", emit_aint_cmp),
("emit_aint_add", emit_aint_add),
("emit_aint_sub", emit_aint_sub),
("emit_aint_mul", emit_aint_mul),
("emit_aint_divmod", emit_aint_divmod),
("emit_string_from_aint", emit_string_from_aint),
("emit_aint_from_string", emit_aint_from_string),
("emit_aint_to_f64", emit_aint_to_f64),
("emit_aint_from_f64", emit_aint_from_f64),
("emit_aint_to_index", emit_aint_to_index),
("emit_aint_decompose", emit_aint_decompose),
("emit_aint_normalize", emit_aint_normalize),
("emit_aint_strip", emit_aint_strip),
("emit_aint_umag_cmp", emit_aint_umag_cmp),
];
for (name, emit) in cases {
let result = emit(®istry);
assert!(
result.is_ok(),
"bignum helper `{name}` failed to emit/parse its WAT: {:?}",
result.err()
);
}
}
}
#[cfg(test)]
mod validation_guard {
use super::tests::bignum_registry_for_validation;
use super::*;
#[test]
fn slice2_helper_modules_validate() {
let registry = bignum_registry_for_validation();
let modules: Vec<(&str, String)> = vec![
("__aint_divmod", render_divmod(®istry)),
(
"__aint_abs",
render_simple(®istry, include_str!("wat/abs.wat")),
),
(
"__aint_neg",
render_simple(®istry, include_str!("wat/neg.wat")),
),
("__aint_from_string", render_from_string(®istry)),
(
"__aint_hash",
render_simple(®istry, include_str!("wat/hash.wat")),
),
(
"__aint_to_f64",
render_simple(®istry, include_str!("wat/to_f64.wat")),
),
("__aint_from_f64", render_from_f64(®istry)),
(
"__aint_to_index",
render_simple(®istry, include_str!("wat/to_index.wat")),
),
("__aint_decompose", render_decompose(®istry)),
("__aint_normalize", render_normalize(®istry)),
("__aint_strip", render_strip(®istry)),
("__aint_umag_cmp", render_umag_cmp(®istry)),
];
for (name, wat) in modules {
let bytes = wat::parse_str(&wat)
.unwrap_or_else(|e| panic!("helper `{name}` failed to parse: {e}"));
let mut validator =
wasmparser::Validator::new_with_features(wasmparser::WasmFeatures::all());
validator
.validate_all(&bytes)
.unwrap_or_else(|e| panic!("helper `{name}` failed to VALIDATE: {e}"));
}
}
#[test]
fn shared_subroutine_call_path_validates() {
let mut registry = bignum_registry_for_validation();
registry.aint_decompose_fn_idx = Some(7);
registry.aint_normalize_fn_idx = Some(8);
registry.aint_strip_fn_idx = Some(9);
registry.aint_umag_cmp_fn_idx = Some(10);
let modules: Vec<(&str, String)> = vec![
("__aint_add", render_addsub(®istry, false)),
("__aint_sub", render_addsub(®istry, true)),
("__aint_mul", render_mul(®istry)),
("__aint_divmod", render_divmod(®istry)),
("__aint_cmp", render_cmp(®istry)),
("__aint_from_string", render_from_string(®istry)),
("__aint_from_f64", render_from_f64(®istry)),
];
for (name, wat) in modules {
let bytes = wat::parse_str(&wat)
.unwrap_or_else(|e| panic!("call-path helper `{name}` failed to parse: {e}"));
let mut validator =
wasmparser::Validator::new_with_features(wasmparser::WasmFeatures::all());
validator
.validate_all(&bytes)
.unwrap_or_else(|e| panic!("call-path helper `{name}` failed to VALIDATE: {e}"));
}
}
fn render_addsub(registry: &TypeRegistry, is_sub: bool) -> String {
let decls = aint_type_decls(registry).unwrap();
let locals = arith_locals();
let decomp_a = decompose(registry, "a", "am", "as_", "umag");
let decomp_b = decompose(registry, "b", "bm", "bs", "umag");
let strip_a = strip(registry, "am", "alen", "sa", "la");
let strip_b = strip(registry, "bm", "blen", "sb", "lb");
let combine = signed_combine(registry);
let norm = normalize(registry, "rm", "rs");
let func_pad = func_pad(&[
(registry.aint_decompose_fn_idx, DECOMPOSE_SIG),
(registry.aint_strip_fn_idx, STRIP_SIG),
(registry.aint_umag_cmp_fn_idx, UMAG_CMP_SIG),
(registry.aint_normalize_fn_idx, NORMALIZE_SIG),
]);
let (fast_op, overflow_check) = if is_sub {
(
"(i64.sub (struct.get $aint $small (local.get $a)) (struct.get $aint $small (local.get $b)))",
"(i64.lt_s (i64.and (i64.xor (struct.get $aint $small (local.get $a)) (struct.get $aint $small (local.get $b))) (i64.xor (struct.get $aint $small (local.get $a)) (local.get $r))) (i64.const 0))",
)
} else {
(
"(i64.add (struct.get $aint $small (local.get $a)) (struct.get $aint $small (local.get $b)))",
"(i64.lt_s (i64.and (i64.xor (struct.get $aint $small (local.get $a)) (local.get $r)) (i64.xor (struct.get $aint $small (local.get $b)) (local.get $r))) (i64.const 0))",
)
};
let beff_set = if is_sub {
"(local.set $beff (i32.sub (i32.const 0) (local.get $bs)))"
} else {
"(local.set $beff (local.get $bs))"
};
format!(
include_str!("wat/addsub.wat"),
decls = decls,
func_pad = func_pad,
locals = locals,
decomp_a = decomp_a,
decomp_b = decomp_b,
beff_set = beff_set,
strip_a = strip_a,
strip_b = strip_b,
combine = combine,
norm = norm,
fast_op = fast_op,
overflow_check = overflow_check,
)
}
fn render_mul(registry: &TypeRegistry) -> String {
let decls = aint_type_decls(registry).unwrap();
let locals = arith_locals();
let decomp_a = decompose(registry, "a", "am", "as_", "umag");
let decomp_b = decompose(registry, "b", "bm", "bs", "umag");
let strip_a = strip(registry, "am", "alen", "sa", "la");
let strip_b = strip(registry, "bm", "blen", "sb", "lb");
let norm = normalize(registry, "rm", "rs");
let func_pad = func_pad(&[
(registry.aint_decompose_fn_idx, DECOMPOSE_SIG),
(registry.aint_strip_fn_idx, STRIP_SIG),
(registry.aint_normalize_fn_idx, NORMALIZE_SIG),
]);
format!(
include_str!("wat/mul.wat"),
decls = decls,
func_pad = func_pad,
locals = locals,
decomp_a = decomp_a,
decomp_b = decomp_b,
strip_a = strip_a,
strip_b = strip_b,
norm = norm,
mul_body = mul_magnitude(),
)
}
fn render_cmp(registry: &TypeRegistry) -> String {
let decls = aint_type_decls(registry).unwrap();
let decomp_a = decompose(registry, "a", "am", "as_", "umag");
let decomp_b = decompose(registry, "b", "bm", "bs", "umag");
let strip_a = strip(registry, "am", "alen", "sa", "la");
let strip_b = strip(registry, "bm", "blen", "sb", "lb");
let cmp = umag_cmp(registry, "am", "alen", "bm", "blen", "cmp", "j");
let func_pad = func_pad(&[
(registry.aint_decompose_fn_idx, DECOMPOSE_SIG),
(registry.aint_strip_fn_idx, STRIP_SIG),
(registry.aint_umag_cmp_fn_idx, UMAG_CMP_SIG),
]);
format!(
include_str!("wat/cmp.wat"),
decls = decls,
func_pad = func_pad,
decomp_a = decomp_a,
decomp_b = decomp_b,
strip_a = strip_a,
strip_b = strip_b,
cmp = cmp,
)
}
fn render_decompose(registry: &TypeRegistry) -> String {
let decls = aint_type_decls(registry).unwrap();
format!(include_str!("wat/decompose.wat"), decls = decls)
}
fn render_normalize(registry: &TypeRegistry) -> String {
let decls = aint_type_decls(registry).unwrap();
format!(
include_str!("wat/normalize.wat"),
decls = decls,
tight_big = tight_big("rm", "rs", "rlen", "i", "tmpm"),
)
}
fn render_strip(registry: &TypeRegistry) -> String {
let decls = aint_type_decls(registry).unwrap();
format!(include_str!("wat/strip.wat"), decls = decls)
}
fn render_umag_cmp(registry: &TypeRegistry) -> String {
let decls = aint_type_decls(registry).unwrap();
format!(include_str!("wat/umag_cmp.wat"), decls = decls)
}
fn render_simple(registry: &TypeRegistry, template: &str) -> String {
let decls = aint_type_decls(registry).unwrap();
template.replace("{decls}", &decls)
}
fn render_from_string(registry: &TypeRegistry) -> String {
let decls = aint_string_result_decls(registry).unwrap();
let err_build = from_string_err_build();
let norm = normalize(registry, "rm", "rs");
let func_pad = func_pad(&[(registry.aint_normalize_fn_idx, NORMALIZE_SIG)]);
format!(
include_str!("wat/from_string.wat"),
decls = decls,
func_pad = func_pad,
err_build = err_build,
norm = norm,
)
}
fn render_from_f64(registry: &TypeRegistry) -> String {
let decls = aint_type_decls(registry).unwrap();
let norm = normalize(registry, "rm", "rs");
let func_pad = func_pad(&[(registry.aint_normalize_fn_idx, NORMALIZE_SIG)]);
format!(
include_str!("wat/from_f64.wat"),
decls = decls,
func_pad = func_pad,
norm = norm
)
}
fn render_divmod(registry: &TypeRegistry) -> String {
let decls = aint_type_decls(registry).unwrap();
let locals = divmod_locals();
let decomp_a = decompose(registry, "a", "am", "as_", "umag");
let decomp_b = decompose(registry, "b", "bm", "bs", "umag");
let strip_a = strip(registry, "am", "alen", "sa", "la");
let strip_b = strip(registry, "bm", "blen", "sb", "lb");
let cmp_r_b = umag_cmp(registry, "rwm", "rwlen", "bm", "blen", "cmp", "j");
let norm_q = normalize(registry, "rm", "rs");
let norm_r = normalize(registry, "rm", "rs");
let func_pad = func_pad(&[
(registry.aint_decompose_fn_idx, DECOMPOSE_SIG),
(registry.aint_strip_fn_idx, STRIP_SIG),
(registry.aint_umag_cmp_fn_idx, UMAG_CMP_SIG),
(registry.aint_normalize_fn_idx, NORMALIZE_SIG),
]);
format!(
include_str!("wat/divmod.wat"),
decls = decls,
func_pad = func_pad,
locals = locals,
decomp_a = decomp_a,
decomp_b = decomp_b,
strip_a = strip_a,
strip_b = strip_b,
cmp_r_b = cmp_r_b,
norm_q = norm_q,
norm_r = norm_r,
)
}
}