mod control;
mod conv;
mod core;
mod gc;
mod memory;
mod numeric;
mod ref_;
mod table;
#[cfg(test)]
#[path = "tests.rs"]
mod tests;
mod visit;
#[cfg(feature = "simd")]
mod visit_simd;
use wasmparser::{BinaryReaderError, FuncValidator, FunctionBody, ValidatorResources};
use crate::canon::{AggKind, IrVal, ModuleType};
use crate::error::InternalError;
use crate::module::code::{CodeArenas, Span};
use crate::module::op::{BigMemArg, BranchTarget, CmpKind, CompiledFunc, MemArg, Op, BIG_MEMARG};
use crate::{Error, Result};
use self::control::CtrlFrame;
pub(crate) use self::conv::{
conv_globaltype, conv_heaptype, conv_memtype, conv_reftype_heap, conv_tabletype, conv_valtype,
};
use self::visit::ValidateThenLower;
pub(crate) struct CompileCtx<'a> {
pub types: &'a [ModuleType],
pub kinds: &'a [AggKind],
pub func_types: &'a [u32],
pub tag_types: &'a [u32],
pub max_expanded_locals: usize,
}
fn wp_err(e: BinaryReaderError) -> Error {
Error::msg(e.to_string())
}
#[derive(Default)]
pub(crate) struct Scratch {
ctrl: Vec<CtrlFrame>,
local_types: Vec<IrVal>,
}
#[allow(clippy::too_many_arguments)] pub(crate) fn translate_function(
ctx: &CompileCtx<'_>,
code: &mut CodeArenas,
type_idx: u32,
body: &FunctionBody<'_>,
fv: &mut FuncValidator<ValidatorResources>,
retain_offsets: bool,
scratch: &mut Scratch,
) -> Result<CompiledFunc> {
let (params, results) = ctx.types[type_idx as usize].func_sig();
let n_params = params.len() as u32;
let n_results = results.len() as u32;
fv.read_locals(&mut body.get_binary_reader())
.map_err(wp_err)?;
let locals_start = expand_locals(ctx, code, body)?;
let mut t = Translator::new(ctx, code, retain_offsets, std::mem::take(&mut scratch.ctrl));
t.push_func_frame(n_results);
let mut reader = body.get_binary_reader_for_operators().map_err(wp_err)?;
let mut vl = ValidateThenLower {
validator: fv,
translator: &mut t,
offset: 0,
};
while !reader.eof() {
vl.offset = reader.original_position();
reader.visit_operator(&mut vl).map_err(wp_err)??;
}
reader.finish_expression(&vl).map_err(wp_err)?;
if let Some(error) = t.error {
return Err(error.into());
}
scratch.ctrl = std::mem::take(&mut t.ctrl);
let max_operands = t.max_operands;
let base = t.base;
let span = |start: u32, end: usize| Span {
start,
len: end as u32 - start,
};
Ok(CompiledFunc {
ops: span(base.ops, code.ops.len()),
type_idx,
n_params,
n_results,
local_types: span(locals_start, code.local_types.len()),
max_operands,
handlers: span(base.handlers, code.handlers.len()),
br_tables: span(base.br_tables, code.br_tables.len()),
big_memargs: span(base.big_memargs, code.big_memargs.len()),
offsets: span(base.offsets, code.offsets.len()),
})
}
fn expand_locals(
ctx: &CompileCtx<'_>,
code: &mut CodeArenas,
body: &FunctionBody<'_>,
) -> Result<u32> {
let cap = ctx.max_expanded_locals.min(u32::MAX as usize);
let start = code.local_types.len();
for entry in body.get_locals_reader().map_err(wp_err)? {
let (count, ty) = entry.map_err(wp_err)?;
let vt = conv_valtype(ctx.kinds, ty)?;
let count = count as usize;
let total = code
.local_types
.len()
.checked_add(count)
.filter(|&n| n <= cap)
.ok_or_else(|| {
Error::msg(format!(
"module declares more than {cap} locals in total (configured limit)"
))
})?;
code.local_types
.try_reserve(count)
.map_err(|_| Error::msg("out of memory expanding module locals"))?;
code.local_types.resize(total, vt);
}
Ok(start as u32)
}
#[derive(Copy, Clone)]
struct Bases {
ops: u32,
br_tables: u32,
big_memargs: u32,
handlers: u32,
offsets: u32,
}
struct Translator<'a> {
ctx: &'a CompileCtx<'a>,
code: &'a mut CodeArenas,
base: Bases,
retain_offsets: bool,
height: u32,
max_operands: u32,
ctrl: Vec<CtrlFrame>,
reachable: bool,
cur_offset: u32,
fusable_cmp: Option<CmpKind>,
error: Option<InternalError>,
}
impl<'a> Translator<'a> {
fn new(
ctx: &'a CompileCtx<'a>,
code: &'a mut CodeArenas,
retain_offsets: bool,
ctrl: Vec<CtrlFrame>,
) -> Self {
let base = Bases {
ops: code.ops.len() as u32,
br_tables: code.br_tables.len() as u32,
big_memargs: code.big_memargs.len() as u32,
handlers: code.handlers.len() as u32,
offsets: code.offsets.len() as u32,
};
Translator {
ctx,
code,
base,
retain_offsets,
height: 0,
max_operands: 0,
ctrl,
reachable: true,
cur_offset: 0,
fusable_cmp: None,
error: None,
}
}
fn next_ip(&self) -> u32 {
self.code.ops.len() as u32 - self.base.ops
}
fn op_mut(&mut self, idx: u32) -> &mut Op {
&mut self.code.ops[(self.base.ops + idx) as usize]
}
fn op_ref(&self, idx: u32) -> &Op {
&self.code.ops[(self.base.ops + idx) as usize]
}
fn push_edge(&mut self, t: BranchTarget) -> u32 {
let rel = self.code.br_tables.len() as u32 - self.base.br_tables;
self.code.br_tables.push(t);
rel
}
fn edge_mut(&mut self, rel: u32) -> &mut BranchTarget {
&mut self.code.br_tables[(self.base.br_tables + rel) as usize]
}
fn memarg(&mut self, m: wasmparser::MemArg) -> MemArg {
if m.offset < u64::from(BIG_MEMARG) {
MemArg {
memory: m.memory,
offset: m.offset as u32,
}
} else {
let idx = self.code.big_memargs.len() as u32 - self.base.big_memargs;
self.code.big_memargs.push(BigMemArg {
memory: m.memory,
offset: m.offset,
});
MemArg {
memory: idx,
offset: BIG_MEMARG,
}
}
}
fn emit(&mut self, op: Op) {
if self.retain_offsets {
self.code.offsets.push(self.cur_offset);
}
self.fusable_cmp = fusable_cmp_of(&op);
self.code.ops.push(op);
}
fn push(&mut self, n: u32) {
let Some(height) = self.height.checked_add(n) else {
self.error
.get_or_insert(InternalError::Compiler("operand height overflow"));
return;
};
self.height = height;
self.max_operands = self.max_operands.max(self.height);
}
fn pop(&mut self, n: u32) {
let Some(height) = self.height.checked_sub(n) else {
self.error
.get_or_insert(InternalError::Compiler("operand height underflow"));
return;
};
self.height = height;
}
fn height_below(&mut self, n: u32) -> u32 {
if let Some(height) = self.height.checked_sub(n) {
height
} else {
self.error
.get_or_insert(InternalError::Compiler("control operand height underflow"));
self.height
}
}
fn binop(&mut self, op: Op) {
self.pop(2);
self.push(1);
self.emit(op);
}
fn unop(&mut self, op: Op) {
self.pop(1);
self.push(1);
self.emit(op);
}
fn store(&mut self, op: Op) {
self.pop(2);
self.emit(op);
}
fn constop(&mut self, op: Op) {
self.push(1);
self.emit(op);
}
#[cfg(feature = "simd")]
fn ternary(&mut self, op: Op) {
self.pop(3);
self.push(1);
self.emit(op);
}
}
fn fusable_cmp_of(op: &Op) -> Option<CmpKind> {
Some(match op {
Op::I32Eq => CmpKind::Eq,
Op::I32Ne => CmpKind::Ne,
Op::I32LtS => CmpKind::LtS,
Op::I32LtU => CmpKind::LtU,
Op::I32GtS => CmpKind::GtS,
Op::I32GtU => CmpKind::GtU,
Op::I32LeS => CmpKind::LeS,
Op::I32LeU => CmpKind::LeU,
Op::I32GeS => CmpKind::GeS,
Op::I32GeU => CmpKind::GeU,
_ => return None,
})
}