diff --git a/crates/wasmi/Cargo.toml b/crates/wasmi/Cargo.toml index e90658fa0f..f2e8c74353 100644 --- a/crates/wasmi/Cargo.toml +++ b/crates/wasmi/Cargo.toml @@ -21,6 +21,7 @@ spin = { version = "0.9", default-features = false, features = [ "spin_mutex", "rwlock", ] } +smallvec = { version = "1.10.0", features = ["union"] } [dev-dependencies] wat = "1" diff --git a/crates/wasmi/src/engine/func_builder/translator.rs b/crates/wasmi/src/engine/func_builder/translator.rs index 62b7d01344..e3a6df6aa1 100644 --- a/crates/wasmi/src/engine/func_builder/translator.rs +++ b/crates/wasmi/src/engine/func_builder/translator.rs @@ -34,10 +34,10 @@ use crate::{ }, module::{ BlockType, + ConstExpr, FuncIdx, FuncTypeIdx, GlobalIdx, - InitExpr, MemoryIdx, ModuleResources, DEFAULT_MEMORY_INDEX, @@ -416,14 +416,14 @@ impl<'parser> FuncTranslator<'parser> { /// have a chance to be optimized to more efficient instructions. fn optimize_global_get( global_type: &GlobalType, - init_value: Option<&InitExpr>, + init_value: Option<&ConstExpr>, ) -> Option { if let (Mutability::Const, Some(init_expr)) = (global_type.mutability(), init_value) { - if let Some(value) = init_expr.to_const() { + if let Some(value) = init_expr.eval_const() { // We can optimize `global.get` to the constant value. return Some(Instruction::constant(value)); } - if let Some(func_index) = init_expr.func_ref() { + if let Some(func_index) = init_expr.funcref() { // We can optimize `global.get` to the equivalent `ref.func x` instruction. let func_index = bytecode::FuncIdx::from(func_index.into_u32()); return Some(Instruction::RefFunc { func_index }); diff --git a/crates/wasmi/src/module/builder.rs b/crates/wasmi/src/module/builder.rs index 48a59b8af9..988d739122 100644 --- a/crates/wasmi/src/module/builder.rs +++ b/crates/wasmi/src/module/builder.rs @@ -1,6 +1,7 @@ use super::{ export::ExternIdx, import::FuncTypeIdx, + ConstExpr, DataSegment, ElementSegment, ExternTypeIdx, @@ -9,7 +10,6 @@ use super::{ GlobalIdx, Import, ImportName, - InitExpr, Module, }; use crate::{ @@ -33,7 +33,7 @@ pub struct ModuleBuilder<'engine> { pub tables: Vec, pub memories: Vec, pub globals: Vec, - pub globals_init: Vec, + pub globals_init: Vec, pub exports: BTreeMap, ExternIdx>, pub start: Option, pub func_bodies: Vec, @@ -90,7 +90,7 @@ impl<'a> ModuleResources<'a> { } /// Returns the global variable type and optional initial value. - pub fn get_global(&self, global_idx: GlobalIdx) -> (GlobalType, Option<&InitExpr>) { + pub fn get_global(&self, global_idx: GlobalIdx) -> (GlobalType, Option<&ConstExpr>) { let index = global_idx.into_u32() as usize; let len_imports = self.res.imports.len_globals(); let global_type = self.get_type_of_global(global_idx); diff --git a/crates/wasmi/src/module/data.rs b/crates/wasmi/src/module/data.rs index 74726ab6ba..f089a888a0 100644 --- a/crates/wasmi/src/module/data.rs +++ b/crates/wasmi/src/module/data.rs @@ -1,4 +1,4 @@ -use super::{InitExpr, MemoryIdx}; +use super::{ConstExpr, MemoryIdx}; use alloc::sync::Arc; /// A Wasm [`Module`] data segment. @@ -27,7 +27,7 @@ pub struct ActiveDataSegment { /// The linear memory that is to be initialized with this active segment. memory_index: MemoryIdx, /// The offset at which the data segment is initialized. - offset: InitExpr, + offset: ConstExpr, } impl ActiveDataSegment { @@ -37,7 +37,7 @@ impl ActiveDataSegment { } /// Returns the offset expression of the [`ActiveDataSegment`]. - pub fn offset(&self) -> &InitExpr { + pub fn offset(&self) -> &ConstExpr { &self.offset } } @@ -50,7 +50,7 @@ impl From> for DataSegmentKind { offset_expr, } => { let memory_index = MemoryIdx::from(memory_index); - let offset = InitExpr::new(offset_expr); + let offset = ConstExpr::new(offset_expr); Self::Active(ActiveDataSegment { memory_index, offset, diff --git a/crates/wasmi/src/module/element.rs b/crates/wasmi/src/module/element.rs index 14e5bfd250..40df7e14b1 100644 --- a/crates/wasmi/src/module/element.rs +++ b/crates/wasmi/src/module/element.rs @@ -1,4 +1,4 @@ -use super::{InitExpr, TableIdx}; +use super::{ConstExpr, TableIdx}; use crate::module::utils::WasmiValueType; use alloc::sync::Arc; use wasmi_core::ValueType; @@ -19,7 +19,7 @@ pub struct ElementSegment { /// The items of an [`ElementSegment`]. #[derive(Debug, Clone)] pub struct ElementSegmentItems { - exprs: Arc<[InitExpr]>, + exprs: Arc<[ConstExpr]>, } impl ElementSegmentItems { @@ -36,7 +36,7 @@ impl ElementSegmentItems { .map(|item| { item.unwrap_or_else(|error| panic!("failed to parse element item: {error}")) }) - .map(InitExpr::new_funcref) + .map(ConstExpr::new_funcref) .collect::>(), wasmparser::ElementItems::Expressions(items) => items .clone() @@ -44,14 +44,14 @@ impl ElementSegmentItems { .map(|item| { item.unwrap_or_else(|error| panic!("failed to parse element item: {error}")) }) - .map(InitExpr::new) + .map(ConstExpr::new) .collect::>(), }; Self { exprs } } /// Returns a shared reference to the items of the [`ElementSegmentItems`]. - pub fn items(&self) -> &[InitExpr] { + pub fn items(&self) -> &[ConstExpr] { &self.exprs } } @@ -73,7 +73,7 @@ pub struct ActiveElementSegment { /// The index of the Wasm table that is to be initialized. table_index: TableIdx, /// The offset where the Wasm table is to be initialized. - offset: InitExpr, + offset: ConstExpr, } impl ActiveElementSegment { @@ -83,7 +83,7 @@ impl ActiveElementSegment { } /// Returns the offset expression of the [`ActiveElementSegment`]. - pub fn offset(&self) -> &InitExpr { + pub fn offset(&self) -> &ConstExpr { &self.offset } } @@ -96,7 +96,7 @@ impl From> for ElementSegmentKind { offset_expr, } => { let table_index = TableIdx::from(table_index); - let offset = InitExpr::new(offset_expr); + let offset = ConstExpr::new(offset_expr); Self::Active(ActiveElementSegment { table_index, offset, diff --git a/crates/wasmi/src/module/global.rs b/crates/wasmi/src/module/global.rs index ed235e1f65..9683f34b27 100644 --- a/crates/wasmi/src/module/global.rs +++ b/crates/wasmi/src/module/global.rs @@ -1,4 +1,4 @@ -use super::InitExpr; +use super::ConstExpr; use crate::GlobalType; /// The index of a global variable within a [`Module`]. @@ -33,13 +33,13 @@ pub struct Global { /// /// This is represented by a so called initializer expression /// that is run at module instantiation time. - init_expr: InitExpr, + init_expr: ConstExpr, } impl From> for Global { fn from(global: wasmparser::Global<'_>) -> Self { let global_type = GlobalType::from_wasmparser(global.ty); - let init_expr = InitExpr::new(global.init_expr); + let init_expr = ConstExpr::new(global.init_expr); Self { global_type, init_expr, @@ -49,7 +49,7 @@ impl From> for Global { impl Global { /// Splits the [`Global`] into its global type and its global initializer. - pub fn into_type_and_init(self) -> (GlobalType, InitExpr) { + pub fn into_type_and_init(self) -> (GlobalType, ConstExpr) { (self.global_type, self.init_expr) } } diff --git a/crates/wasmi/src/module/init_expr.rs b/crates/wasmi/src/module/init_expr.rs index f6f800bb02..3cce2f0728 100644 --- a/crates/wasmi/src/module/init_expr.rs +++ b/crates/wasmi/src/module/init_expr.rs @@ -1,203 +1,365 @@ -use super::{utils::WasmiValueType, FuncIdx, GlobalIdx}; +use super::FuncIdx; use crate::{ExternRef, FuncRef, Value}; -use wasmi_core::{ValueType, F32, F64}; +use core::fmt; +use smallvec::SmallVec; +use wasmi_core::{UntypedValue, F32, F64}; -/// An initializer expression. +/// Types that allow evluation given an evaluation context. +pub trait Eval { + /// Evaluates `self` given an [`EvalContext`]. + fn eval(&self, ctx: &dyn EvalContext) -> Option; +} + +/// A [`ConstExpr`] evaluation context. /// -/// # Note +/// Required for evaluating a [`ConstExpr`]. +pub trait EvalContext { + /// Returns the [`Value`] of the global value at `index` if any. + fn get_global(&self, index: u32) -> Option; + /// Returns the [`FuncRef`] of the function at `index` if any. + fn get_func(&self, index: u32) -> Option; +} + +/// An empty evaluation context. +pub struct EmptyEvalContext; + +impl EvalContext for EmptyEvalContext { + fn get_global(&self, _index: u32) -> Option { + None + } + + fn get_func(&self, _index: u32) -> Option { + None + } +} + +/// An input parameter to a [`ConstExpr`] operator. +#[derive(Debug)] +pub enum Op { + /// A constant value. + Const(ConstOp), + /// The value of a global variable. + Global(GlobalOp), + /// A Wasm `ref.func index` value. + FuncRef(FuncRefOp), + /// An arbitrary expression. + Expr(ExprOp), +} + +/// A constant value operator. /// -/// This is used by global variables, table element segments and -/// linear memory data segments. +/// This may represent the following Wasm operators: +/// +/// - `i32.const` +/// - `i64.const` +/// - `f32.const` +/// - `f64.const` +/// - `ref.null` #[derive(Debug)] -pub struct InitExpr { - /// The operand of the initializer expression. - /// - /// # Note - /// - /// The Wasm MVP only supports initializer expressions with a single - /// operand (besides the `End` operand). In later Wasm proposals this - /// might change but until then we keep it as simple as possible. - op: InitExprOperand, +pub struct ConstOp { + /// The underlying precomputed untyped value. + value: UntypedValue, } -impl InitExpr { - /// Creates a new [`InitExpr`] from the given Wasm constant expression. - pub fn new(expr: wasmparser::ConstExpr<'_>) -> Self { - let mut reader = expr.get_operators_reader(); - let wasm_op = reader.read().unwrap_or_else(|error| { - panic!("expected valid Wasm const expression operand: {error}") - }); - let op = InitExprOperand::new(wasm_op); - let end_op = reader.read(); - assert!( - matches!(end_op, Ok(wasmparser::Operator::End)), - "expected the Wasm end operator but found {end_op:?}", - ); - assert!( - reader.ensure_end().is_ok(), - "expected no more Wasm operands" - ); - Self { op } +impl Eval for ConstOp { + fn eval(&self, _ctx: &dyn EvalContext) -> Option { + Some(self.value) + } +} + +/// Represents a Wasm `global.get` operator. + +#[derive(Debug)] +pub struct GlobalOp { + /// The index of the global variable. + global_index: u32, +} + +impl Eval for GlobalOp { + fn eval(&self, ctx: &dyn EvalContext) -> Option { + ctx.get_global(self.global_index).map(UntypedValue::from) } +} - /// Create a new `ref.func x` [`InitExpr`]. - pub fn new_funcref(func_index: u32) -> Self { - Self { - op: InitExprOperand::FuncRef(func_index), - } +/// Represents a Wasm `func.ref` operator. + +#[derive(Debug)] +pub struct FuncRefOp { + /// The index of the function. + function_index: u32, +} + +impl Eval for FuncRefOp { + fn eval(&self, ctx: &dyn EvalContext) -> Option { + ctx.get_func(self.function_index).map(UntypedValue::from) } +} - /// Convert the [`InitExpr`] into a Wasm `funcexpr` index if possible. - /// - /// Returns `None` if the function reference is `null`. - /// - /// # Panics - /// - /// If the [`InitExpr`] cannot be interpreted as `funcref` index. - pub fn as_funcref(&self) -> Option { - match self.op { - InitExprOperand::RefNull { .. } => None, - InitExprOperand::FuncRef(func_index) => Some(FuncIdx::from(func_index)), - InitExprOperand::Const(_) | InitExprOperand::GlobalGet(_) => { - panic!("encountered non-funcref Wasm expression {:?}", self.op) - } +/// A generic Wasm expression operator. +/// +/// This may represent one of the following Wasm operators: +/// +/// - `i32.add` +/// - `i32.sub` +/// - `i32.mul` +/// - `i64.add` +/// - `i64.sub` +/// - `i64.mul` +#[allow(clippy::type_complexity)] +pub struct ExprOp { + /// The underlying closure that implements the expression. + expr: Box Option + Send + Sync>, +} + +impl fmt::Debug for ExprOp { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("ExprOp").finish() + } +} + +impl Eval for ExprOp { + fn eval(&self, ctx: &dyn EvalContext) -> Option { + (self.expr)(ctx) + } +} + +impl Op { + /// Creates a new constant operator for the given `value`. + pub fn constant(value: T) -> Self + where + T: Into, + { + Self::Const(ConstOp { + value: value.into().into(), + }) + } + + /// Creates a new global operator with the given index. + pub fn global(global_index: u32) -> Self { + Self::Global(GlobalOp { global_index }) + } + + /// Creates a new global operator with the given index. + pub fn func_ref(function_index: u32) -> Self { + Self::FuncRef(FuncRefOp { function_index }) + } + + /// Creates a new expression operator for the given `expr`. + pub fn expr(expr: T) -> Self + where + T: Fn(&dyn EvalContext) -> Option + Send + Sync + 'static, + { + Self::Expr(ExprOp { + expr: Box::new(expr), + }) + } +} + +impl Eval for Op { + fn eval(&self, ctx: &dyn EvalContext) -> Option { + match self { + Op::Const(op) => op.eval(ctx), + Op::Global(op) => op.eval(ctx), + Op::FuncRef(op) => op.eval(ctx), + Op::Expr(op) => op.eval(ctx), } } +} - /// Converts the [`InitExpr`] into an `externref`. +/// A Wasm constant expression. +/// +/// These are used to determine the offsets of memory data +/// and table element segments as well as the initial value +/// of global variables. +#[derive(Debug)] +pub struct ConstExpr { + /// The root operator of the [`ConstExpr`]. + op: Op, +} + +impl Eval for ConstExpr { + fn eval(&self, ctx: &dyn EvalContext) -> Option { + self.op.eval(ctx) + } +} + +macro_rules! def_expr { + ($lhs:ident, $rhs:ident, $expr:expr) => {{ + Op::expr(move |ctx: &dyn EvalContext| -> Option { + let lhs = $lhs.eval(ctx)?; + let rhs = $rhs.eval(ctx)?; + Some($expr(lhs, rhs)) + }) + }}; +} + +impl ConstExpr { + /// Creates a new [`ConstExpr`] from the given Wasm [`ConstExpr`]. /// - /// # Panics + /// # Note /// - /// If the [`InitExpr`] cannot be interpreted as `externref`. - pub fn as_externref(&self) -> ExternRef { - match self.op { - InitExprOperand::RefNull { - ty: ValueType::ExternRef, - } => ExternRef::null(), - _ => panic!("encountered non-externref Wasm expression: {:?}", self.op), + /// The constructor assumes that Wasm validation already succeeded + /// on the input Wasm [`ConstExpr`]. + pub fn new(expr: wasmparser::ConstExpr<'_>) -> Self { + /// A buffer required for translation of Wasm const expressions. + type TranslationBuffer = SmallVec<[Op; 3]>; + /// Convenience function to create the various expression operators. + fn expr_op( + stack: &mut TranslationBuffer, + expr: fn(UntypedValue, UntypedValue) -> UntypedValue, + ) { + let rhs = stack + .pop() + .expect("must have rhs operator on the stack due to Wasm validation"); + let lhs = stack + .pop() + .expect("must have lhs operator on the stack due to Wasm validation"); + let op = match (lhs, rhs) { + (Op::Const(lhs), Op::Const(rhs)) => def_expr!(lhs, rhs, expr), + (Op::Const(lhs), Op::Global(rhs)) => def_expr!(lhs, rhs, expr), + (Op::Const(lhs), Op::FuncRef(rhs)) => def_expr!(lhs, rhs, expr), + (Op::Const(lhs), Op::Expr(rhs)) => def_expr!(lhs, rhs, expr), + (Op::Global(lhs), Op::Const(rhs)) => def_expr!(lhs, rhs, expr), + (Op::Global(lhs), Op::Global(rhs)) => def_expr!(lhs, rhs, expr), + (Op::Global(lhs), Op::FuncRef(rhs)) => def_expr!(lhs, rhs, expr), + (Op::Global(lhs), Op::Expr(rhs)) => def_expr!(lhs, rhs, expr), + (Op::FuncRef(lhs), Op::Const(rhs)) => def_expr!(lhs, rhs, expr), + (Op::FuncRef(lhs), Op::Global(rhs)) => def_expr!(lhs, rhs, expr), + (Op::FuncRef(lhs), Op::FuncRef(rhs)) => def_expr!(lhs, rhs, expr), + (Op::FuncRef(lhs), Op::Expr(rhs)) => def_expr!(lhs, rhs, expr), + (Op::Expr(lhs), Op::Const(rhs)) => def_expr!(lhs, rhs, expr), + (Op::Expr(lhs), Op::Global(rhs)) => def_expr!(lhs, rhs, expr), + (Op::Expr(lhs), Op::FuncRef(rhs)) => def_expr!(lhs, rhs, expr), + (Op::Expr(lhs), Op::Expr(rhs)) => def_expr!(lhs, rhs, expr), + }; + stack.push(op); } + + let mut reader = expr.get_operators_reader(); + // TODO: we might want to avoid heap allocation in the simple cases that + // only have one operator via the small vector data structure. + let mut stack = TranslationBuffer::new(); + loop { + let op = reader.read().unwrap_or_else(|error| { + panic!("unexpectedly encountered invalid const expression operator: {error}") + }); + match op { + wasmparser::Operator::I32Const { value } => { + stack.push(Op::constant(value)); + } + wasmparser::Operator::I64Const { value } => { + stack.push(Op::constant(value)); + } + wasmparser::Operator::F32Const { value } => { + stack.push(Op::constant(F32::from(value.bits()))); + } + wasmparser::Operator::F64Const { value } => { + stack.push(Op::constant(F64::from(value.bits()))); + } + wasmparser::Operator::GlobalGet { global_index } => { + stack.push(Op::global(global_index)); + } + wasmparser::Operator::RefNull { ty } => { + let value = match ty { + wasmparser::ValType::FuncRef => Value::from(FuncRef::null()), + wasmparser::ValType::ExternRef => Value::from(ExternRef::null()), + ty => panic!("encountered invalid value type for RefNull: {ty:?}"), + }; + stack.push(Op::constant(value)); + } + wasmparser::Operator::RefFunc { function_index } => { + stack.push(Op::func_ref(function_index)); + } + wasmparser::Operator::I32Add => expr_op(&mut stack, UntypedValue::i32_add), + wasmparser::Operator::I32Sub => expr_op(&mut stack, UntypedValue::i32_sub), + wasmparser::Operator::I32Mul => expr_op(&mut stack, UntypedValue::i32_mul), + wasmparser::Operator::I64Add => expr_op(&mut stack, UntypedValue::i64_add), + wasmparser::Operator::I64Sub => expr_op(&mut stack, UntypedValue::i64_sub), + wasmparser::Operator::I64Mul => expr_op(&mut stack, UntypedValue::i64_mul), + wasmparser::Operator::End => break, + op => panic!("encountered invalid Wasm const expression operator: {op:?}"), + }; + } + reader + .ensure_end() + .expect("due to Wasm validation this is guaranteed to suceed"); + let op = stack + .pop() + .expect("due to Wasm validation must have one operator on the stack"); + assert!( + stack.is_empty(), + "due to Wasm validation operator stack must be empty now" + ); + Self { op } } - /// Return the `Const` [`InitExpr`] if any. + /// Create a new `ref.func x` [`ConstExpr`]. /// - /// Returns `None` if the underlying operand is not `Const`. - /// - /// # Panics + /// # Note /// - /// If a non-const expression operand is encountered. - pub fn to_const(&self) -> Option { - match &self.op { - InitExprOperand::Const(value) => Some(value.clone()), - InitExprOperand::RefNull { ty } => match ty { - ValueType::FuncRef => Some(Value::from(FuncRef::null())), - ValueType::ExternRef => Some(Value::from(ExternRef::null())), - _ => panic!("cannot have null reference for non-reftype but found {ty:?}"), - }, - InitExprOperand::GlobalGet(_) => { - // Note: We do not need to handle `global.get` since - // that is only allowed for imported non-mutable - // global variables which have a value that is only - // known post-instantiation time. - None - } - InitExprOperand::FuncRef(_) => { - // Note: We do not need to handle `global.get` here - // since we can do this somewhere else where we - // can properly handle the constant `func.ref`. - // In the function builder we want to replace - // `global.get` of constant `FuncRef(x)` with - // the Wasm `ref.func x` instruction. - None - } + /// Required for setting up table elements. + pub fn new_funcref(function_index: u32) -> Self { + Self { + op: Op::FuncRef(FuncRefOp { function_index }), } } - /// Returns `Some(index)` if the [`InitExpr`] is a `FuncRef(index)`. + /// Returns `Some(index)` if the [`ConstExpr`] is a `func_ref(index)`. /// /// Otherwise returns `None`. - pub fn func_ref(&self) -> Option { - if let InitExprOperand::FuncRef(index) = self.op { - return Some(FuncIdx::from(index)); + pub fn funcref(&self) -> Option { + if let Op::FuncRef(op) = &self.op { + return Some(FuncIdx::from(op.function_index)); } None } - /// Evaluates the [`InitExpr`] given the context for global variables. + /// Evaluates the [`ConstExpr`] in a constant evaluation context. /// - /// # Panics + /// # Note /// - /// If a non-const expression operand is encountered. - pub fn to_const_with_context( - &self, - global_get: impl Fn(u32) -> Value, - func_get: impl Fn(u32) -> Value, - ) -> Value { - match &self.op { - InitExprOperand::Const(value) => value.clone(), - InitExprOperand::GlobalGet(index) => global_get(index.into_u32()), - InitExprOperand::RefNull { ty } => match ty { - ValueType::FuncRef => Value::from(FuncRef::null()), - ValueType::ExternRef => Value::from(ExternRef::null()), - _ => panic!("expected reftype for InitExpr but found {ty:?}"), - }, - InitExprOperand::FuncRef(index) => func_get(*index), - } + /// This is useful for evaluations during Wasm translation to + /// perform optimizations on the translated bytecode. + pub fn eval_const(&self) -> Option { + self.eval(&EmptyEvalContext) } -} -/// A single operand of an initializer expression. -/// -/// # Note -/// -/// The Wasm MVP only supports `const` and `global.get` expressions -/// inside initializer expressions. In later Wasm proposals this might -/// change but until then we keep it as simple as possible. -#[derive(Debug)] -pub enum InitExprOperand { - /// A constant value. - Const(Value), - /// The value of a global variable at the time of evaluation. + /// Evaluates the [`ConstExpr`] given a context for globals and functions. /// - /// # Note + /// Returns `None` if a non-const expression operand is encountered + /// or the provided globals and functions context returns `None`. /// - /// In the Wasm MVP only immutable globals are allowed to be evaluated. - GlobalGet(GlobalIdx), - /// A Wasm `ref.null` value. - RefNull { ty: ValueType }, - /// A Wasm `ref.func index` value. - FuncRef(u32), -} - -impl InitExprOperand { - /// Creates a new [`InitExprOperand`] from the given Wasm operator. - /// - /// # Panics + /// # Note /// - /// If the Wasm operator is not a valid [`InitExprOperand`]. - fn new(operator: wasmparser::Operator<'_>) -> Self { - match operator { - wasmparser::Operator::I32Const { value } => Self::constant(value), - wasmparser::Operator::I64Const { value } => Self::constant(value), - wasmparser::Operator::F32Const { value } => Self::constant(F32::from(value.bits())), - wasmparser::Operator::F64Const { value } => Self::constant(F64::from(value.bits())), - wasmparser::Operator::GlobalGet { global_index } => { - Self::GlobalGet(GlobalIdx::from(global_index)) + /// This is useful for evaluation of [`ConstExpr`] during bytecode execution. + pub fn eval_with_context(&self, global_get: G, func_get: F) -> Option + where + G: Fn(u32) -> Value, + F: Fn(u32) -> FuncRef, + { + /// Context that wraps closures representing partial evaluation contexts. + struct WrappedEvalContext { + /// Wrapped context for global variables. + global_get: G, + /// Wrapped context for functions. + func_get: F, + } + impl EvalContext for WrappedEvalContext + where + G: Fn(u32) -> Value, + F: Fn(u32) -> FuncRef, + { + fn get_global(&self, index: u32) -> Option { + Some((self.global_get)(index)) } - wasmparser::Operator::RefNull { ty } => Self::RefNull { - ty: WasmiValueType::from(ty).into_inner(), - }, - wasmparser::Operator::RefFunc { function_index } => Self::FuncRef(function_index), - operator => { - panic!("encountered unsupported const expression operator: {operator:?}") + + fn get_func(&self, index: u32) -> Option { + Some((self.func_get)(index)) } } - } - - /// Creates a new constant [`InitExprOperand`]. - fn constant(value: T) -> Self - where - T: Into, - { - Self::Const(value.into()) + self.eval(&WrappedEvalContext:: { + global_get, + func_get, + }) } } diff --git a/crates/wasmi/src/module/instantiate/mod.rs b/crates/wasmi/src/module/instantiate/mod.rs index 9abe634180..d788b34a75 100644 --- a/crates/wasmi/src/module/instantiate/mod.rs +++ b/crates/wasmi/src/module/instantiate/mod.rs @@ -5,10 +5,11 @@ mod pre; mod tests; pub use self::{error::InstantiationError, pre::InstancePre}; -use super::{element::ElementSegmentKind, export, DataSegmentKind, InitExpr, Module}; +use super::{element::ElementSegmentKind, export, ConstExpr, DataSegmentKind, Module}; use crate::{ func::WasmFuncEntity, memory::DataSegment, + value::WithType, AsContext, AsContextMut, ElementSegment, @@ -25,7 +26,7 @@ use crate::{ Table, Value, }; -use wasmi_core::Trap; +use wasmi_core::{Trap, UntypedValue}; impl Module { /// Instantiates a new [`Instance`] from the given compiled [`Module`]. @@ -227,9 +228,14 @@ impl Module { builder: &mut InstanceEntityBuilder, ) { for (global_type, global_init) in self.internal_globals() { + let value_type = global_type.content(); let init_value = Self::eval_init_expr(context.as_context_mut(), builder, global_init); let mutability = global_type.mutability(); - let global = Global::new(context.as_context_mut(), init_value, mutability); + let global = Global::new( + context.as_context_mut(), + init_value.with_type(value_type), + mutability, + ); builder.push_global(global); } } @@ -238,12 +244,14 @@ impl Module { fn eval_init_expr( context: impl AsContext, builder: &InstanceEntityBuilder, - init_expr: &InitExpr, - ) -> Value { - init_expr.to_const_with_context( - |global_index| builder.get_global(global_index).get(&context), - |func_index| Value::from(FuncRef::new(builder.get_func(func_index))), - ) + init_expr: &ConstExpr, + ) -> UntypedValue { + init_expr + .eval_with_context( + |global_index| builder.get_global(global_index).get(&context), + |func_index| FuncRef::new(builder.get_func(func_index)), + ) + .expect("must evaluate to proper value") } /// Extracts the Wasm exports from the module and registers them into the [`Instance`]. @@ -291,15 +299,11 @@ impl Module { for segment in &self.element_segments[..] { let element = ElementSegment::new(context.as_context_mut(), segment); if let ElementSegmentKind::Active(active) = segment.kind() { - let dst_index = Self::eval_init_expr(&mut *context, builder, active.offset()) - .i32() - .unwrap_or_else(|| { - panic!( - "expected offset value of type `i32` due to \ - Wasm validation but found: {:?}", - active.offset(), - ) - }) as u32; + let dst_index = u32::from(Self::eval_init_expr( + &mut *context, + builder, + active.offset(), + )); let table = builder.get_table(active.table_index().into_u32()); // Note: This checks not only that the elements in the element segments properly // fit into the table at the given offset but also that the element segment @@ -345,14 +349,8 @@ impl Module { let bytes = segment.bytes(); if let DataSegmentKind::Active(segment) = segment.kind() { let offset_expr = segment.offset(); - let offset = Self::eval_init_expr(&mut *context, builder, offset_expr) - .i32() - .unwrap_or_else(|| { - panic!( - "expected offset value of type `i32` due to \ - Wasm validation but found: {offset_expr:?}", - ) - }) as u32 as usize; + let offset = + u32::from(Self::eval_init_expr(&mut *context, builder, offset_expr)) as usize; let memory = builder.get_memory(segment.memory_index().into_u32()); memory.write(&mut *context, offset, bytes)?; } diff --git a/crates/wasmi/src/module/mod.rs b/crates/wasmi/src/module/mod.rs index c594c16949..291c7d896b 100644 --- a/crates/wasmi/src/module/mod.rs +++ b/crates/wasmi/src/module/mod.rs @@ -34,7 +34,7 @@ pub use self::{ pub(crate) use self::{ data::{DataSegment, DataSegmentKind}, element::{ElementSegment, ElementSegmentItems, ElementSegmentKind}, - init_expr::InitExpr, + init_expr::ConstExpr, }; use crate::{ engine::{DedupFuncType, FuncBody}, @@ -59,7 +59,7 @@ pub struct Module { tables: Box<[TableType]>, memories: Box<[MemoryType]>, globals: Box<[GlobalType]>, - globals_init: Box<[InitExpr]>, + globals_init: Box<[ConstExpr]>, exports: BTreeMap, ExternIdx>, start: Option, func_bodies: Box<[FuncBody]>, @@ -440,11 +440,11 @@ impl<'a> ExactSizeIterator for InternalFuncsIter<'a> { /// An iterator over the internally defined functions of a [`Module`]. #[derive(Debug)] pub struct InternalGlobalsIter<'a> { - iter: iter::Zip, SliceIter<'a, InitExpr>>, + iter: iter::Zip, SliceIter<'a, ConstExpr>>, } impl<'a> Iterator for InternalGlobalsIter<'a> { - type Item = (&'a GlobalType, &'a InitExpr); + type Item = (&'a GlobalType, &'a ConstExpr); fn next(&mut self) -> Option { self.iter.next() diff --git a/crates/wasmi/src/table/element.rs b/crates/wasmi/src/table/element.rs index 1d7226de1c..bcc20ed489 100644 --- a/crates/wasmi/src/table/element.rs +++ b/crates/wasmi/src/table/element.rs @@ -1,6 +1,6 @@ use crate::{ module, - module::{ElementSegmentItems, InitExpr}, + module::{ConstExpr, ElementSegmentItems}, store::Stored, AsContext, AsContextMut, @@ -124,7 +124,7 @@ impl ElementSegmentEntity { } /// Returns the items of the [`ElementSegmentEntity`]. - pub fn items(&self) -> &[InitExpr] { + pub fn items(&self) -> &[ConstExpr] { self.items .as_ref() .map(ElementSegmentItems::items) diff --git a/crates/wasmi/src/table/mod.rs b/crates/wasmi/src/table/mod.rs index 32e8127d33..435e04701e 100644 --- a/crates/wasmi/src/table/mod.rs +++ b/crates/wasmi/src/table/mod.rs @@ -327,14 +327,14 @@ impl TableEntity { ValueType::FuncRef => { // Initialize element interpreted as Wasm `funrefs`. dst_items.iter_mut().zip(src_items).for_each(|(dst, src)| { - let func_or_null = src.as_funcref().map(FuncIdx::into_u32).map(&get_func); + let func_or_null = src.funcref().map(FuncIdx::into_u32).map(&get_func); *dst = FuncRef::new(func_or_null).into(); }); } ValueType::ExternRef => { // Initialize element interpreted as Wasm `externrefs`. dst_items.iter_mut().zip(src_items).for_each(|(dst, src)| { - *dst = UntypedValue::from(src.as_externref()); + *dst = src.eval_const().expect("must evaluate to some value"); }); } _ => panic!("table.init currently only works on reftypes"),