use crate::runtime::vm::{Instance, VMGcRef, ValRaw, I31};
use crate::store::AutoAssertNoGc;
use crate::{
prelude::*, ArrayRef, ArrayRefPre, ArrayType, StructRef, StructRefPre, StructType, Val,
};
use smallvec::SmallVec;
use wasmtime_environ::{
ConstExpr, ConstOp, FuncIndex, GlobalIndex, ModuleInternedTypeIndex, WasmCompositeType,
WasmSubType,
};
#[derive(Default)]
pub struct ConstExprEvaluator {
stack: SmallVec<[ValRaw; 2]>,
}
pub struct ConstEvalContext<'a> {
pub(crate) instance: &'a mut Instance,
}
impl<'a> ConstEvalContext<'a> {
pub fn new(instance: &'a mut Instance) -> Self {
Self { instance }
}
fn global_get(&mut self, store: &mut AutoAssertNoGc<'_>, index: GlobalIndex) -> Result<ValRaw> {
unsafe {
let global = self
.instance
.defined_or_imported_global_ptr(index)
.as_ref()
.unwrap();
let mut gc_store = store.unwrap_gc_store_mut();
Ok(global.to_val_raw(
&mut gc_store,
self.instance.env_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(),
))
}
#[cfg(feature = "gc")]
fn struct_fields_len(&self, struct_type_index: ModuleInternedTypeIndex) -> usize {
let module = self
.instance
.runtime_module()
.expect("should never be allocating a struct type defined in a dummy module");
let struct_ty = match &module.types()[struct_type_index].composite_type {
WasmCompositeType::Struct(s) => s,
_ => unreachable!(),
};
struct_ty.fields.len()
}
#[cfg(feature = "gc")]
unsafe fn struct_new(
&mut self,
store: &mut AutoAssertNoGc<'_>,
struct_type_index: ModuleInternedTypeIndex,
fields: &[ValRaw],
) -> Result<ValRaw> {
let module = self
.instance
.runtime_module()
.expect("should never be allocating a struct type defined in a dummy module");
let shared_ty = module
.signatures()
.shared_type(struct_type_index)
.expect("should have an engine type for module type");
let struct_ty = StructType::from_shared_type_index(store.engine(), shared_ty);
let fields = fields
.iter()
.zip(struct_ty.fields())
.map(|(raw, ty)| {
let ty = ty.element_type().unpack();
Val::_from_raw(store, *raw, ty)
})
.collect::<Vec<_>>();
let allocator = StructRefPre::_new(store, struct_ty);
let struct_ref = StructRef::_new(store, &allocator, &fields)?;
let raw = struct_ref.to_anyref()._to_raw(store)?;
Ok(ValRaw::anyref(raw))
}
#[cfg(feature = "gc")]
fn struct_new_default(
&mut self,
store: &mut AutoAssertNoGc<'_>,
struct_type_index: ModuleInternedTypeIndex,
) -> Result<ValRaw> {
let module = self
.instance
.runtime_module()
.expect("should never be allocating a struct type defined in a dummy module");
let shared_ty = module
.signatures()
.shared_type(struct_type_index)
.expect("should have an engine type for module type");
let borrowed = module
.engine()
.signatures()
.borrow(shared_ty)
.expect("should have a registered type for struct");
let WasmSubType {
composite_type: WasmCompositeType::Struct(struct_ty),
..
} = &*borrowed
else {
unreachable!("registered type should be a struct");
};
let fields = struct_ty
.fields
.iter()
.map(|ty| match &ty.element_type {
wasmtime_environ::WasmStorageType::I8 | wasmtime_environ::WasmStorageType::I16 => {
ValRaw::i32(0)
}
wasmtime_environ::WasmStorageType::Val(v) => match v {
wasmtime_environ::WasmValType::I32 => ValRaw::i32(0),
wasmtime_environ::WasmValType::I64 => ValRaw::i64(0),
wasmtime_environ::WasmValType::F32 => ValRaw::f32(0.0f32.to_bits()),
wasmtime_environ::WasmValType::F64 => ValRaw::f64(0.0f64.to_bits()),
wasmtime_environ::WasmValType::V128 => ValRaw::v128(0),
wasmtime_environ::WasmValType::Ref(r) => {
assert!(r.nullable);
ValRaw::null()
}
},
})
.collect::<SmallVec<[_; 8]>>();
unsafe { self.struct_new(store, struct_type_index, &fields) }
}
}
impl ConstExprEvaluator {
pub unsafe fn eval(
&mut self,
context: &mut ConstEvalContext<'_>,
expr: &ConstExpr,
) -> Result<ValRaw> {
self.stack.clear();
let mut store = (*context.instance.store()).store_opaque_mut();
#[cfg(feature = "gc")]
let mut store = crate::OpaqueRootScope::new(&mut store);
let mut store = AutoAssertNoGc::new(&mut store);
for op in expr.ops() {
match op {
ConstOp::I32Const(i) => self.stack.push(ValRaw::i32(*i)),
ConstOp::I64Const(i) => self.stack.push(ValRaw::i64(*i)),
ConstOp::F32Const(f) => self.stack.push(ValRaw::f32(*f)),
ConstOp::F64Const(f) => self.stack.push(ValRaw::f64(*f)),
ConstOp::V128Const(v) => self.stack.push(ValRaw::v128(*v)),
ConstOp::GlobalGet(g) => self.stack.push(context.global_get(&mut store, *g)?),
ConstOp::RefNull => self.stack.push(ValRaw::null()),
ConstOp::RefFunc(f) => self.stack.push(context.ref_func(*f)?),
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));
}
ConstOp::I32Add => {
let b = self.pop()?.get_i32();
let a = self.pop()?.get_i32();
self.stack.push(ValRaw::i32(a.wrapping_add(b)));
}
ConstOp::I32Sub => {
let b = self.pop()?.get_i32();
let a = self.pop()?.get_i32();
self.stack.push(ValRaw::i32(a.wrapping_sub(b)));
}
ConstOp::I32Mul => {
let b = self.pop()?.get_i32();
let a = self.pop()?.get_i32();
self.stack.push(ValRaw::i32(a.wrapping_mul(b)));
}
ConstOp::I64Add => {
let b = self.pop()?.get_i64();
let a = self.pop()?.get_i64();
self.stack.push(ValRaw::i64(a.wrapping_add(b)));
}
ConstOp::I64Sub => {
let b = self.pop()?.get_i64();
let a = self.pop()?.get_i64();
self.stack.push(ValRaw::i64(a.wrapping_sub(b)));
}
ConstOp::I64Mul => {
let b = self.pop()?.get_i64();
let a = self.pop()?.get_i64();
self.stack.push(ValRaw::i64(a.wrapping_mul(b)));
}
#[cfg(not(feature = "gc"))]
ConstOp::StructNew { .. }
| ConstOp::StructNewDefault { .. }
| ConstOp::ArrayNew { .. }
| ConstOp::ArrayNewDefault { .. }
| ConstOp::ArrayNewFixed { .. } => {
bail!(
"const expr evaluation error: struct operations are not \
supported without the `gc` feature"
)
}
#[cfg(feature = "gc")]
ConstOp::StructNew { struct_type_index } => {
let interned_type_index =
context.instance.env_module().types[*struct_type_index];
let len = context.struct_fields_len(interned_type_index);
if self.stack.len() < len {
bail!(
"const expr evaluation error: expected at least {len} values on the stack, found {}",
self.stack.len()
)
}
let start = self.stack.len() - len;
let s = context.struct_new(
&mut store,
interned_type_index,
&self.stack[start..],
)?;
self.stack.truncate(start);
self.stack.push(s);
}
#[cfg(feature = "gc")]
ConstOp::StructNewDefault { struct_type_index } => {
let interned_type_index =
context.instance.env_module().types[*struct_type_index];
self.stack
.push(context.struct_new_default(&mut store, interned_type_index)?);
}
#[cfg(feature = "gc")]
ConstOp::ArrayNew { array_type_index } => {
let interned_type_index =
context.instance.env_module().types[*array_type_index];
let module = context.instance.runtime_module().expect(
"should never be allocating a struct type defined in a dummy module",
);
let shared_ty = module
.signatures()
.shared_type(interned_type_index)
.expect("should have an engine type for module type");
let ty = ArrayType::from_shared_type_index(store.engine(), shared_ty);
#[allow(clippy::cast_sign_loss)]
let len = self.pop()?.get_i32() as u32;
let elem = Val::_from_raw(&mut store, self.pop()?, ty.element_type().unpack());
let pre = ArrayRefPre::_new(&mut store, ty);
let array = ArrayRef::_new(&mut store, &pre, &elem, len)?;
self.stack
.push(ValRaw::anyref(array.to_anyref()._to_raw(&mut store)?));
}
#[cfg(feature = "gc")]
ConstOp::ArrayNewDefault { array_type_index } => {
let interned_type_index =
context.instance.env_module().types[*array_type_index];
let module = context.instance.runtime_module().expect(
"should never be allocating a struct type defined in a dummy module",
);
let shared_ty = module
.signatures()
.shared_type(interned_type_index)
.expect("should have an engine type for module type");
let ty = ArrayType::from_shared_type_index(store.engine(), shared_ty);
#[allow(clippy::cast_sign_loss)]
let len = self.pop()?.get_i32() as u32;
let elem = Val::default_for_ty(ty.element_type().unpack())
.expect("type should have a default value");
let pre = ArrayRefPre::_new(&mut store, ty);
let array = ArrayRef::_new(&mut store, &pre, &elem, len)?;
self.stack
.push(ValRaw::anyref(array.to_anyref()._to_raw(&mut store)?));
}
#[cfg(feature = "gc")]
ConstOp::ArrayNewFixed {
array_type_index,
array_size,
} => {
let interned_type_index =
context.instance.env_module().types[*array_type_index];
let module = context.instance.runtime_module().expect(
"should never be allocating a struct type defined in a dummy module",
);
let shared_ty = module
.signatures()
.shared_type(interned_type_index)
.expect("should have an engine type for module type");
let ty = ArrayType::from_shared_type_index(store.engine(), shared_ty);
let array_size = usize::try_from(*array_size).unwrap();
if self.stack.len() < array_size {
bail!(
"const expr evaluation error: expected at least {array_size} values on the stack, found {}",
self.stack.len()
)
}
let start = self.stack.len() - array_size;
let elem_ty = ty.element_type();
let elem_ty = elem_ty.unpack();
let elems = self
.stack
.drain(start..)
.map(|raw| Val::_from_raw(&mut store, raw, elem_ty))
.collect::<SmallVec<[_; 8]>>();
let pre = ArrayRefPre::_new(&mut store, ty);
let array = ArrayRef::_new_fixed(&mut store, &pre, &elems)?;
self.stack
.push(ValRaw::anyref(array.to_anyref()._to_raw(&mut store)?));
}
}
}
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"
)
})
}
}