use crate::{
compiler::translator::{FuncTranslatorAllocations, InstructionTranslator, ReusableAllocations},
CompilationError, FuncIdx,
};
use rwasm_fuel_policy::FuelCosts;
use wasmparser::{
BinaryReaderError, FuncValidator, FunctionBody, ValType, ValidatorResources, VisitOperator,
};
pub struct FuncBuilder<'a> {
pub(crate) func_body: FunctionBody<'a>,
pub(crate) validator: FuncValidator<ValidatorResources>,
pub(crate) func_idx: FuncIdx,
pub(crate) translator: InstructionTranslator,
pub(crate) pos: usize,
}
impl<'a> FuncBuilder<'a> {
#[allow(clippy::too_many_arguments)]
pub fn new(
func_body: FunctionBody<'a>,
validator: FuncValidator<ValidatorResources>,
func_idx: FuncIdx,
allocations: FuncTranslatorAllocations,
with_consume_fuel: bool,
with_code_snippets: bool,
consume_fuel_for_params_and_locals: bool,
max_allowed_memory_pages: u32,
) -> Self {
Self {
func_body,
validator,
func_idx,
translator: InstructionTranslator::new(
allocations,
with_consume_fuel,
with_code_snippets,
consume_fuel_for_params_and_locals,
max_allowed_memory_pages,
),
pos: 0,
}
}
pub fn translate(mut self) -> Result<ReusableAllocations, CompilationError> {
self.translator.prepare(self.func_idx)?;
self.translator.bump_fuel_consumption(|| FuelCosts::BASE)?;
self.translate_stack_alloc();
self.translate_locals()?;
let offset = self.translate_operators()?;
self.validator.finish(offset)?;
self.translator.finish()?;
Ok(ReusableAllocations {
translation: self.translator.alloc,
validation: self.validator.into_allocations(),
})
}
fn translate_locals(&mut self) -> Result<(), CompilationError> {
if self.translator.consume_fuel_for_params_and_locals {
let mut locals_reader = self.func_body.get_locals_reader()?;
let mut total_body_locals: u32 = 0;
for _ in 0..locals_reader.get_count() {
let (amount, _) = locals_reader.read()?;
total_body_locals = total_body_locals.saturating_add(amount);
}
let cost = FuelCosts::fuel_for_locals(total_body_locals);
if cost > 0 {
self.translator.bump_fuel_consumption(|| cost)?;
}
}
let mut locals_reader = self.func_body.get_locals_reader()?;
let locals_count = locals_reader.get_count() as usize;
for _ in 0..locals_count {
let offset = locals_reader.original_position();
let (amount, value_type) = locals_reader.read()?;
self.validator.define_locals(offset, amount, value_type)?;
match value_type {
ValType::I32 | ValType::I64 => {}
ValType::F32 | ValType::F64 => {}
ValType::V128 => return Err(CompilationError::NotSupportedLocalType),
ValType::FuncRef | ValType::ExternRef => {}
#[allow(unreachable_patterns)]
_ => return Err(CompilationError::NotSupportedLocalType),
}
let mut total_locals_required = 0u32;
for _ in 0..amount as usize {
if value_type == ValType::I64 || value_type == ValType::F64 {
total_locals_required += 2;
} else {
total_locals_required += 1;
}
self.translator.alloc.stack_types.push(value_type);
}
self.translator
.alloc
.instruction_set
.op_bulk_const(total_locals_required);
self.translator.stack_height.push_n(amount);
if value_type == ValType::I64 || value_type == ValType::F64 {
self.translator.stack_height.push_n(amount);
}
}
Ok(())
}
fn translate_stack_alloc(&mut self) {
self.translator
.alloc
.instruction_set
.op_stack_check(u32::MAX);
}
fn translate_operators(&mut self) -> Result<usize, CompilationError> {
let mut reader = self.func_body.get_operators_reader()?;
while !reader.eof() {
self.pos = reader.original_position();
reader.visit_operator(self)??;
}
reader.ensure_end()?;
Ok(reader.original_position())
}
fn validate_then_translate<V, T>(
&mut self,
validate: V,
translate: T,
) -> Result<(), CompilationError>
where
V: FnOnce(&mut FuncValidator<ValidatorResources>) -> Result<(), BinaryReaderError>,
T: FnOnce(&mut InstructionTranslator) -> Result<(), CompilationError>,
{
validate(&mut self.validator)?;
translate(&mut self.translator)?;
Ok(())
}
}
macro_rules! impl_visit_operator {
( @mvp BrTable { $arg:ident: $argty:ty } => $visit:ident $($rest:tt)* ) => {
fn $visit(&mut self, $arg: $argty) -> Self::Output {
let offset = self.pos;
let arg_cloned = $arg.clone();
self.validate_then_translate(
|validator| validator.visitor(offset).$visit(arg_cloned),
|translator| translator.$visit($arg),
)
}
impl_visit_operator!($($rest)*);
};
( @mvp $($rest:tt)* ) => {
impl_visit_operator!(@@supported $($rest)*);
};
( @sign_extension $($rest:tt)* ) => {
impl_visit_operator!(@@supported $($rest)*);
};
( @saturating_float_to_int $($rest:tt)* ) => {
impl_visit_operator!(@@supported $($rest)*);
};
( @bulk_memory $($rest:tt)* ) => {
impl_visit_operator!(@@supported $($rest)*);
};
( @reference_types $($rest:tt)* ) => {
impl_visit_operator!(@@supported $($rest)*);
};
( @tail_call $($rest:tt)* ) => {
impl_visit_operator!(@@supported $($rest)*);
};
( @@supported $op:ident $({ $($arg:ident: $argty:ty),* })? => $visit:ident $($rest:tt)* ) => {
fn $visit(&mut self $($(,$arg: $argty)*)?) -> Self::Output {
let offset = self.pos;
self.validate_then_translate(
|v| v.visitor(offset).$visit($($($arg),*)?),
|t| t.$visit($($($arg),*)?),
)
}
impl_visit_operator!($($rest)*);
};
( @$proposal:ident $op:ident $({ $($arg:ident: $argty:ty),* })? => $visit:ident $($rest:tt)* ) => {
fn $visit(&mut self $($(, $arg: $argty)*)?) -> Self::Output {
let offset = self.pos;
self.validator.visitor(offset).$visit($($($arg),*)?).map_err(::core::convert::Into::into)
}
impl_visit_operator!($($rest)*);
};
() => {};
}
impl<'a> VisitOperator<'a> for FuncBuilder<'a> {
type Output = Result<(), CompilationError>;
wasmparser::for_each_operator!(impl_visit_operator);
}