Browse Source
We had something hacked together to support `(ref.i31 (i32.const N))`. It wasn't a long-term solution. This is the first time that we have to really deal with multi-instruction const expressions. This commit introduces a tiny interpreter to evaluate const expressions.pull/8465/head
Nick Fitzgerald
7 months ago
committed by
GitHub
17 changed files with 500 additions and 372 deletions
@ -0,0 +1,102 @@ |
|||
//! Evaluating const expressions.
|
|||
|
|||
use crate::{Instance, VMGcRef, ValRaw, I31}; |
|||
use anyhow::{anyhow, bail, Result}; |
|||
use smallvec::SmallVec; |
|||
use wasmtime_environ::{ConstExpr, FuncIndex, GlobalIndex, Module}; |
|||
|
|||
/// An interpreter for const expressions.
|
|||
///
|
|||
/// This can be reused across many const expression evaluations to reuse
|
|||
/// allocated resources, if any.
|
|||
#[derive(Default)] |
|||
pub struct ConstExprEvaluator { |
|||
stack: SmallVec<[ValRaw; 2]>, |
|||
} |
|||
|
|||
/// The context within which a particular const expression is evaluated.
|
|||
pub struct ConstEvalContext<'a, 'b> { |
|||
instance: &'a mut Instance, |
|||
module: &'b Module, |
|||
} |
|||
|
|||
impl<'a, 'b> ConstEvalContext<'a, 'b> { |
|||
/// Create a new context.
|
|||
pub fn new(instance: &'a mut Instance, module: &'b Module) -> Self { |
|||
Self { instance, module } |
|||
} |
|||
|
|||
fn global_get(&mut self, index: GlobalIndex) -> Result<ValRaw> { |
|||
unsafe { |
|||
let gc_store = (*self.instance.store()).gc_store(); |
|||
let global = self |
|||
.instance |
|||
.defined_or_imported_global_ptr(index) |
|||
.as_ref() |
|||
.unwrap(); |
|||
Ok(global.to_val_raw(gc_store, self.module.globals[index].wasm_ty)) |
|||
} |
|||
} |
|||
|
|||
fn ref_func(&mut self, index: FuncIndex) -> Result<ValRaw> { |
|||
Ok(ValRaw::funcref( |
|||
self.instance.get_func_ref(index).unwrap().cast(), |
|||
)) |
|||
} |
|||
} |
|||
|
|||
impl ConstExprEvaluator { |
|||
/// Evaluate the given const expression in the given context.
|
|||
///
|
|||
/// # Unsafety
|
|||
///
|
|||
/// The given const expression must be valid within the given context,
|
|||
/// e.g. the const expression must be well-typed and the context must return
|
|||
/// global values of the expected types. This evaluator operates directly on
|
|||
/// untyped `ValRaw`s and does not and cannot check that its operands are of
|
|||
/// the correct type.
|
|||
pub unsafe fn eval( |
|||
&mut self, |
|||
context: &mut ConstEvalContext<'_, '_>, |
|||
expr: &ConstExpr, |
|||
) -> Result<ValRaw> { |
|||
self.stack.clear(); |
|||
|
|||
for op in expr.ops() { |
|||
match op { |
|||
wasmtime_environ::ConstOp::I32Const(i) => self.stack.push(ValRaw::i32(*i)), |
|||
wasmtime_environ::ConstOp::I64Const(i) => self.stack.push(ValRaw::i64(*i)), |
|||
wasmtime_environ::ConstOp::F32Const(f) => self.stack.push(ValRaw::f32(*f)), |
|||
wasmtime_environ::ConstOp::F64Const(f) => self.stack.push(ValRaw::f64(*f)), |
|||
wasmtime_environ::ConstOp::V128Const(v) => self.stack.push(ValRaw::v128(*v)), |
|||
wasmtime_environ::ConstOp::GlobalGet(g) => self.stack.push(context.global_get(*g)?), |
|||
wasmtime_environ::ConstOp::RefNull => self.stack.push(ValRaw::null()), |
|||
wasmtime_environ::ConstOp::RefFunc(f) => self.stack.push(context.ref_func(*f)?), |
|||
wasmtime_environ::ConstOp::RefI31 => { |
|||
let i = self.pop()?.get_i32(); |
|||
let i31 = I31::wrapping_i32(i); |
|||
let raw = VMGcRef::from_i31(i31).as_raw_u32(); |
|||
self.stack.push(ValRaw::anyref(raw)); |
|||
} |
|||
} |
|||
} |
|||
|
|||
if self.stack.len() == 1 { |
|||
Ok(self.stack[0]) |
|||
} else { |
|||
bail!( |
|||
"const expr evaluation error: expected 1 resulting value, found {}", |
|||
self.stack.len() |
|||
) |
|||
} |
|||
} |
|||
|
|||
fn pop(&mut self) -> Result<ValRaw> { |
|||
self.stack.pop().ok_or_else(|| { |
|||
anyhow!( |
|||
"const expr evaluation error: attempted to pop from an empty \ |
|||
evaluation stack" |
|||
) |
|||
}) |
|||
} |
|||
} |
@ -0,0 +1,31 @@ |
|||
(module $env |
|||
(global (export "g1") i32 (i32.const 42)) |
|||
(global (export "g2") i32 (i32.const 99)) |
|||
) |
|||
(register "env") |
|||
|
|||
(module $i31ref_of_global_const_expr_and_tables |
|||
(global $g1 (import "env" "g1") i32) |
|||
(global $g2 (import "env" "g2") i32) |
|||
|
|||
(table $t 3 3 (ref i31) (ref.i31 (global.get $g1))) |
|||
(elem (table $t) (i32.const 2) (ref i31) (ref.i31 (global.get $g2))) |
|||
|
|||
(func (export "get") (param i32) (result i32) |
|||
(i31.get_u (local.get 0) (table.get $t)) |
|||
) |
|||
) |
|||
|
|||
(assert_return (invoke "get" (i32.const 0)) (i32.const 42)) |
|||
(assert_return (invoke "get" (i32.const 1)) (i32.const 42)) |
|||
(assert_return (invoke "get" (i32.const 2)) (i32.const 99)) |
|||
|
|||
(module $i31ref_of_global_const_expr_and_globals |
|||
(global $g1 (import "env" "g1") i32) |
|||
(global $g2 i31ref (ref.i31 (global.get $g1))) |
|||
(func (export "get") (result i32) |
|||
(i31.get_u (global.get $g2)) |
|||
) |
|||
) |
|||
|
|||
(assert_return (invoke "get") (i32.const 42)) |
Loading…
Reference in new issue