Lines
98.69 %
Functions
66.67 %
Branches
100 %
//! Guest-side i32-repr value helpers: range-checked i31 boxing of an index,
//! nil-as-null boxing of a bool, and index division / car-of-nil that throw
//! catchable `$nomi_error` conditions (ADR-0026) instead of silently wrapping
//! or trapping the engine.
use super::CompileContext;
use super::exception::ConditionTrap;
use crate::error::Result;
use wasm_encoder::{BlockType, Function, HeapType, Instruction, ValType};
impl CompileContext {
pub(super) fn declare_index_helpers(&mut self) -> Result<()> {
let anyref = self.anyref();
let pair = self.pair_ref();
let index = ValType::I32;
self.register_function("box_index", &[index], &[anyref])?;
self.register_function("box_bool", &[index], &[anyref])?;
self.register_function("bool_chain_to_anyref", &[pair], &[pair])?;
self.register_function("index_div", &[index, index], &[index])?;
self.register_function("index_rem", &[index, index], &[index])?;
self.register_function("car_of_nil", &[], &[])?;
Ok(())
}
/// Emits the helper bodies in [`Self::declare_index_helpers`] order.
pub(super) fn build_index_helpers(&mut self) -> Result<()> {
let beyond_i31 = self.register_condition_trap(
"INDEX-OVERFLOW",
"index value outside the 31-bit range -1073741824..1073741823",
)?;
let quotient_overflow = self.register_condition_trap(
"index quotient -2147483648 / -1 overflows 32 bits",
let div_by_zero = self.register_condition_trap("DIVISION-BY-ZERO", "division by zero")?;
let car_of_nil = self
.register_condition_trap("CAR-OF-NIL", "car of an empty numeric list has no value")?;
let chain = ChainIds {
pair_idx: self.ids.ty_pair,
pair_ref: self.pair_ref(),
pair_new: self.ids.pair_new,
box_bool: self.ids.box_bool,
};
self.pending_helpers.push(box_index_body(&beyond_i31));
self.pending_helpers.push(box_bool_body(self.anyref()));
self.pending_helpers.push(bool_chain_to_anyref_body(&chain));
self.pending_helpers
.push(index_div_body(&div_by_zero, "ient_overflow));
self.pending_helpers.push(index_rem_body(&div_by_zero));
self.pending_helpers.push(throw_body(&car_of_nil));
struct ChainIds {
pair_idx: u32,
pair_ref: ValType,
pair_new: u32,
box_bool: u32,
/// `box_bool(i32) -> anyref`: in an anyref slot a false bool is `nil` as a null
/// ref (what `null?` tests) and a true bool is i31 1.
fn box_bool_body(anyref: ValType) -> Function {
let mut f = Function::new([]);
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::If(BlockType::Result(anyref)));
f.instruction(&Instruction::I32Const(1));
f.instruction(&Instruction::RefI31);
f.instruction(&Instruction::Else);
f.instruction(&Instruction::RefNull(HeapType::ANY));
f.instruction(&Instruction::End);
f
/// `bool_chain_to_anyref(pair) -> pair`: rebuilds a bool chain (i31 0/1 cells)
/// with anyref cells so its nils become null refs before it rides a widened
/// `AnyRef` chain. Two prepend passes keep the original order.
fn bool_chain_to_anyref_body(chain: &ChainIds) -> Function {
const SOURCE: u32 = 0;
const REVERSED: u32 = 1;
const REBUILT: u32 = 2;
let mut f = Function::new([(2, chain.pair_ref)]);
let rebox = [
Instruction::RefCastNonNull(HeapType::I31),
Instruction::I31GetS,
Instruction::Call(chain.box_bool),
];
prepend_chain(&mut f, chain, SOURCE, REVERSED, &rebox);
prepend_chain(&mut f, chain, REVERSED, REBUILT, &[]);
f.instruction(&Instruction::LocalGet(REBUILT));
/// Prepends every car of the chain in local `src` (passed through `map_car`)
/// onto a fresh chain in local `dst`, reversing it.
fn prepend_chain(
f: &mut Function,
chain: &ChainIds,
src: u32,
dst: u32,
map_car: &[Instruction<'_>],
) {
f.instruction(&Instruction::RefNull(HeapType::Concrete(chain.pair_idx)));
f.instruction(&Instruction::LocalSet(dst));
f.instruction(&Instruction::Block(BlockType::Empty));
f.instruction(&Instruction::Loop(BlockType::Empty));
f.instruction(&Instruction::LocalGet(src));
f.instruction(&Instruction::RefIsNull);
f.instruction(&Instruction::BrIf(1));
f.instruction(&Instruction::StructGet {
struct_type_index: chain.pair_idx,
field_index: 0,
});
for op in map_car {
f.instruction(op);
f.instruction(&Instruction::LocalGet(dst));
f.instruction(&Instruction::Call(chain.pair_new));
field_index: 1,
f.instruction(&Instruction::LocalSet(src));
f.instruction(&Instruction::Br(0));
/// A no-result helper that only throws `trap` (e.g. `car_of_nil`); callers
/// follow the call with `unreachable`.
fn throw_body(trap: &ConditionTrap) -> Function {
trap.emit_throw(&mut f);
/// `box_index(i32) -> anyref`: `ref.i31` keeps only 31 bits, so a value outside
/// [-2^30, 2^30-1] throws rather than wrapping.
fn box_index_body(beyond_i31: &ConditionTrap) -> Function {
// v + 2^30 lands in [0, 2^31) exactly when v is in the i31 range.
f.instruction(&Instruction::I32Const(1 << 30));
f.instruction(&Instruction::I32Add);
f.instruction(&Instruction::I32Const(i32::MIN));
f.instruction(&Instruction::I32GeU);
throw_if(&mut f, beyond_i31);
/// `index_div(a, b) -> i32`: truncating division that throws on a zero divisor
/// and on `i32::MIN / -1`, the one quotient that overflows.
fn index_div_body(div_by_zero: &ConditionTrap, overflow: &ConditionTrap) -> Function {
throw_on_zero_divisor(&mut f, div_by_zero);
f.instruction(&Instruction::I32Eq);
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::I32Const(-1));
f.instruction(&Instruction::I32And);
throw_if(&mut f, overflow);
finish_binop(f, &Instruction::I32DivS)
/// `index_rem(a, b) -> i32`: throws on a zero divisor (`i32.rem_s` defines
/// `i32::MIN rem -1` as 0, so no overflow check is needed).
fn index_rem_body(div_by_zero: &ConditionTrap) -> Function {
finish_binop(f, &Instruction::I32RemS)
fn throw_on_zero_divisor(f: &mut Function, div_by_zero: &ConditionTrap) {
f.instruction(&Instruction::I32Eqz);
throw_if(f, div_by_zero);
/// Consumes the i32 condition on the stack, throwing `trap` when it is set.
fn throw_if(f: &mut Function, trap: &ConditionTrap) {
f.instruction(&Instruction::If(BlockType::Empty));
trap.emit_throw(f);
fn finish_binop(mut f: Function, op: &Instruction<'_>) -> Function {