use super::FuncIdx;
use crate::{
core::{UntypedVal, F32, F64},
ExternRef,
FuncRef,
Val,
};
use core::fmt;
use smallvec::SmallVec;
use std::boxed::Box;
pub trait Eval {
fn eval(&self, ctx: &dyn EvalContext) -> Option<UntypedVal>;
}
pub trait EvalContext {
fn get_global(&self, index: u32) -> Option<Val>;
fn get_func(&self, index: u32) -> Option<FuncRef>;
}
pub struct EmptyEvalContext;
impl EvalContext for EmptyEvalContext {
fn get_global(&self, _index: u32) -> Option<Val> {
None
}
fn get_func(&self, _index: u32) -> Option<FuncRef> {
None
}
}
#[derive(Debug)]
pub enum Op {
Const(ConstOp),
Global(GlobalOp),
FuncRef(FuncRefOp),
Expr(ExprOp),
}
#[derive(Debug)]
pub struct ConstOp {
value: UntypedVal,
}
impl Eval for ConstOp {
fn eval(&self, _ctx: &dyn EvalContext) -> Option<UntypedVal> {
Some(self.value)
}
}
#[derive(Debug)]
pub struct GlobalOp {
global_index: u32,
}
impl Eval for GlobalOp {
fn eval(&self, ctx: &dyn EvalContext) -> Option<UntypedVal> {
ctx.get_global(self.global_index).map(UntypedVal::from)
}
}
#[derive(Debug)]
pub struct FuncRefOp {
function_index: u32,
}
impl Eval for FuncRefOp {
fn eval(&self, ctx: &dyn EvalContext) -> Option<UntypedVal> {
ctx.get_func(self.function_index).map(UntypedVal::from)
}
}
#[allow(clippy::type_complexity)]
pub struct ExprOp {
expr: Box<dyn Fn(&dyn EvalContext) -> Option<UntypedVal> + 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<UntypedVal> {
(self.expr)(ctx)
}
}
impl Op {
pub fn constant<T>(value: T) -> Self
where
T: Into<Val>,
{
Self::Const(ConstOp {
value: value.into().into(),
})
}
pub fn global(global_index: u32) -> Self {
Self::Global(GlobalOp { global_index })
}
pub fn funcref(function_index: u32) -> Self {
Self::FuncRef(FuncRefOp { function_index })
}
pub fn expr<T>(expr: T) -> Self
where
T: Fn(&dyn EvalContext) -> Option<UntypedVal> + Send + Sync + 'static,
{
Self::Expr(ExprOp {
expr: Box::new(expr),
})
}
}
impl Eval for Op {
fn eval(&self, ctx: &dyn EvalContext) -> Option<UntypedVal> {
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),
}
}
}
#[derive(Debug)]
pub struct ConstExpr {
op: Op,
}
impl Eval for ConstExpr {
fn eval(&self, ctx: &dyn EvalContext) -> Option<UntypedVal> {
self.op.eval(ctx)
}
}
macro_rules! def_expr {
($lhs:ident, $rhs:ident, $expr:expr) => {{
Op::expr(move |ctx: &dyn EvalContext| -> Option<UntypedVal> {
let lhs = $lhs.eval(ctx)?;
let rhs = $rhs.eval(ctx)?;
Some($expr(lhs, rhs))
})
}};
}
impl ConstExpr {
pub fn new(expr: wasmparser::ConstExpr<'_>) -> Self {
type TranslationBuffer = SmallVec<[Op; 3]>;
fn expr_op(stack: &mut TranslationBuffer, expr: fn(UntypedVal, UntypedVal) -> UntypedVal) {
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();
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 => Val::from(FuncRef::null()),
wasmparser::ValType::ExternRef => Val::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::funcref(function_index));
}
wasmparser::Operator::I32Add => expr_op(&mut stack, UntypedVal::i32_add),
wasmparser::Operator::I32Sub => expr_op(&mut stack, UntypedVal::i32_sub),
wasmparser::Operator::I32Mul => expr_op(&mut stack, UntypedVal::i32_mul),
wasmparser::Operator::I64Add => expr_op(&mut stack, UntypedVal::i64_add),
wasmparser::Operator::I64Sub => expr_op(&mut stack, UntypedVal::i64_sub),
wasmparser::Operator::I64Mul => expr_op(&mut stack, UntypedVal::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 succeed");
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 }
}
pub fn new_funcref(function_index: u32) -> Self {
Self {
op: Op::FuncRef(FuncRefOp { function_index }),
}
}
pub fn funcref(&self) -> Option<FuncIdx> {
if let Op::FuncRef(op) = &self.op {
return Some(FuncIdx::from(op.function_index));
}
None
}
pub fn eval_const(&self) -> Option<UntypedVal> {
self.eval(&EmptyEvalContext)
}
pub fn eval_with_context<G, F>(&self, global_get: G, func_get: F) -> Option<UntypedVal>
where
G: Fn(u32) -> Val,
F: Fn(u32) -> FuncRef,
{
struct WrappedEvalContext<G, F> {
global_get: G,
func_get: F,
}
impl<G, F> EvalContext for WrappedEvalContext<G, F>
where
G: Fn(u32) -> Val,
F: Fn(u32) -> FuncRef,
{
fn get_global(&self, index: u32) -> Option<Val> {
Some((self.global_get)(index))
}
fn get_func(&self, index: u32) -> Option<FuncRef> {
Some((self.func_get)(index))
}
}
self.eval(&WrappedEvalContext::<G, F> {
global_get,
func_get,
})
}
}