Browse Source

Wasmtime(gc): Support `(ref.i31 (global.get $g))` const expressions (#8450)

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
parent
commit
318eb36df4
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 2
      Cargo.lock
  2. 8
      cranelift/wasm/src/environ/spec.rs
  3. 29
      cranelift/wasm/src/sections_translator.rs
  4. 2
      crates/cranelift/src/func_environ.rs
  5. 4
      crates/cranelift/src/gc.rs
  6. 10
      crates/cranelift/src/gc/enabled.rs
  7. 40
      crates/environ/src/module.rs
  8. 145
      crates/environ/src/module_environ.rs
  9. 1
      crates/runtime/Cargo.toml
  10. 102
      crates/runtime/src/const_expr.rs
  11. 155
      crates/runtime/src/instance.rs
  12. 74
      crates/runtime/src/instance/allocator.rs
  13. 1
      crates/runtime/src/lib.rs
  14. 70
      crates/runtime/src/vmcontext.rs
  15. 4
      crates/types/Cargo.toml
  16. 194
      crates/types/src/lib.rs
  17. 31
      tests/misc_testsuite/gc/i31ref-of-global-initializers.wast

2
Cargo.lock

@ -3812,6 +3812,7 @@ dependencies = [
"psm",
"rand",
"rustix",
"smallvec",
"sptr",
"wasm-encoder 0.205.0",
"wasmtime-asm-macros",
@ -3835,6 +3836,7 @@ dependencies = [
"cranelift-entity",
"serde",
"serde_derive",
"smallvec",
"thiserror",
"wasmparser 0.205.0",
]

8
cranelift/wasm/src/environ/spec.rs

@ -8,8 +8,8 @@
use crate::state::FuncTranslationState;
use crate::{
DataIndex, ElemIndex, FuncIndex, Global, GlobalIndex, GlobalInit, Heap, HeapData, Memory,
MemoryIndex, Table, TableIndex, Tag, TagIndex, TypeConvert, TypeIndex, WasmError, WasmFuncType,
DataIndex, ElemIndex, FuncIndex, Global, GlobalIndex, Heap, HeapData, Memory, MemoryIndex,
Table, TableIndex, Tag, TagIndex, TypeConvert, TypeIndex, WasmError, WasmFuncType,
WasmHeapType, WasmResult,
};
use cranelift_codegen::cursor::FuncCursor;
@ -21,7 +21,7 @@ use cranelift_frontend::FunctionBuilder;
use std::boxed::Box;
use std::string::ToString;
use wasmparser::{FuncValidator, FunctionBody, Operator, ValidatorResources, WasmFeatures};
use wasmtime_types::ModuleInternedTypeIndex;
use wasmtime_types::{ConstExpr, ModuleInternedTypeIndex};
/// The value of a WebAssembly global variable.
#[derive(Clone, Copy)]
@ -802,7 +802,7 @@ pub trait ModuleEnvironment<'data>: TypeConvert {
}
/// Declares a global to the environment.
fn declare_global(&mut self, global: Global, init: GlobalInit) -> WasmResult<()>;
fn declare_global(&mut self, global: Global, init: ConstExpr) -> WasmResult<()>;
/// Provides the number of exports up front. By default this does nothing, but
/// implementations can use this to preallocate memory if desired.

29
cranelift/wasm/src/sections_translator.rs

@ -10,8 +10,8 @@
use crate::environ::ModuleEnvironment;
use crate::wasm_unsupported;
use crate::{
DataIndex, ElemIndex, FuncIndex, GlobalIndex, GlobalInit, Memory, MemoryIndex, TableIndex, Tag,
TagIndex, TypeIndex, WasmError, WasmResult,
DataIndex, ElemIndex, FuncIndex, GlobalIndex, Memory, MemoryIndex, TableIndex, Tag, TagIndex,
TypeIndex, WasmError, WasmResult,
};
use cranelift_entity::packed_option::ReservedValue;
use cranelift_entity::EntityRef;
@ -23,6 +23,7 @@ use wasmparser::{
ImportSectionReader, MemorySectionReader, MemoryType, NameSectionReader, Naming, Operator,
TableSectionReader, TagSectionReader, TagType, TypeRef, TypeSectionReader,
};
use wasmtime_types::ConstExpr;
fn memory(ty: MemoryType) -> Memory {
Memory {
@ -169,29 +170,7 @@ pub fn parse_global_section(
for entry in globals {
let wasmparser::Global { ty, init_expr } = entry?;
let mut init_expr_reader = init_expr.get_binary_reader();
let initializer = match init_expr_reader.read_operator()? {
Operator::I32Const { value } => GlobalInit::I32Const(value),
Operator::I64Const { value } => GlobalInit::I64Const(value),
Operator::F32Const { value } => GlobalInit::F32Const(value.bits()),
Operator::F64Const { value } => GlobalInit::F64Const(value.bits()),
Operator::V128Const { value } => {
GlobalInit::V128Const(u128::from_le_bytes(*value.bytes()))
}
Operator::RefNull { hty: _ } => GlobalInit::RefNullConst,
Operator::RefFunc { function_index } => {
GlobalInit::RefFunc(FuncIndex::from_u32(function_index))
}
Operator::GlobalGet { global_index } => {
GlobalInit::GetGlobal(GlobalIndex::from_u32(global_index))
}
ref s => {
return Err(wasm_unsupported!(
"unsupported init expr in global section: {:?}",
s
));
}
};
let (initializer, _escaped) = ConstExpr::from_wasmparser(init_expr)?;
let ty = environ.convert_global_type(&ty);
environ.declare_global(ty, initializer)?;
}

2
crates/cranelift/src/func_environ.rs

@ -759,7 +759,7 @@ impl<'module_environment> FuncEnvironment<'module_environment> {
};
let table = &self.module.table_plans[index].table;
let element_size = if table.wasm_ty.is_gc_heap_type() {
let element_size = if table.wasm_ty.is_vmgcref_type() {
// For GC-managed references, tables store `Option<VMGcRef>`s.
ir::types::I32.bytes()
} else {

4
crates/cranelift/src/gc.rs

@ -74,7 +74,7 @@ pub fn gc_ref_table_grow_builtin(
func_env: &mut FuncEnvironment<'_>,
func: &mut ir::Function,
) -> WasmResult<ir::FuncRef> {
debug_assert!(ty.is_gc_heap_type());
debug_assert!(ty.is_vmgcref_type_and_not_i31());
imp::gc_ref_table_grow_builtin(ty, func_env, func)
}
@ -85,7 +85,7 @@ pub fn gc_ref_table_fill_builtin(
func_env: &mut FuncEnvironment<'_>,
func: &mut ir::Function,
) -> WasmResult<ir::FuncRef> {
debug_assert!(ty.is_gc_heap_type());
debug_assert!(ty.is_vmgcref_type_and_not_i31());
imp::gc_ref_table_fill_builtin(ty, func_env, func)
}

10
crates/cranelift/src/gc/enabled.rs

@ -55,7 +55,7 @@ pub fn gc_ref_table_grow_builtin(
func_env: &mut FuncEnvironment<'_>,
func: &mut ir::Function,
) -> WasmResult<ir::FuncRef> {
debug_assert!(ty.is_gc_heap_type());
debug_assert!(ty.is_vmgcref_type_and_not_i31());
Ok(func_env.builtin_functions.table_grow_gc_ref(func))
}
@ -64,7 +64,7 @@ pub fn gc_ref_table_fill_builtin(
func_env: &mut FuncEnvironment<'_>,
func: &mut ir::Function,
) -> WasmResult<ir::FuncRef> {
debug_assert!(ty.is_gc_heap_type());
debug_assert!(ty.is_vmgcref_type_and_not_i31());
Ok(func_env.builtin_functions.table_fill_gc_ref(func))
}
@ -169,7 +169,7 @@ impl FuncEnvironment<'_> {
ty: WasmRefType,
gc_ref: ir::Value,
) -> ir::Value {
assert!(ty.is_gc_heap_type());
assert!(ty.is_vmgcref_type_and_not_i31());
let might_be_i31 = match ty.heap_type {
WasmHeapType::Any => true,
@ -317,7 +317,7 @@ impl GcCompiler for DrcCompiler {
src: ir::Value,
flags: ir::MemFlags,
) -> WasmResult<ir::Value> {
assert!(ty.is_gc_heap_type());
assert!(ty.is_vmgcref_type_and_not_i31());
let reference_type = func_env.reference_type(ty.heap_type);
@ -460,7 +460,7 @@ impl GcCompiler for DrcCompiler {
new_val: ir::Value,
flags: ir::MemFlags,
) -> WasmResult<()> {
assert!(ty.is_gc_heap_type());
assert!(ty.is_vmgcref_type_and_not_i31());
let ref_ty = func_env.reference_type(ty.heap_type);

40
crates/environ/src/module.rs

@ -418,17 +418,8 @@ pub enum TableInitialValue {
/// case the elements are initialized to null.
precomputed: Vec<FuncIndex>,
},
/// Initialize each table element to the function reference given
/// by the `FuncIndex`.
FuncRef(FuncIndex),
/// At instantiation time this global is loaded and the funcref value is
/// used to initialize the table.
GlobalGet(GlobalIndex),
/// Initialize the table element to an `i31ref` of the given value.
I31Ref(i32),
/// An arbitrary const expression.
Expr(ConstExpr),
}
/// A WebAssembly table initializer segment.
@ -452,7 +443,7 @@ pub enum TableSegmentElements {
/// indicates a null function.
Functions(Box<[FuncIndex]>),
/// Arbitrary expressions, aka either functions, null or a load of a global.
Expressions(Box<[TableElementExpression]>),
Expressions(Box<[ConstExpr]>),
}
impl TableSegmentElements {
@ -465,17 +456,6 @@ impl TableSegmentElements {
}
}
/// Different kinds of expression that can initialize table elements.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum TableElementExpression {
/// `ref.func $f`
Function(FuncIndex),
/// `global.get $g`
GlobalGet(GlobalIndex),
/// `ref.null $ty`
Null,
}
/// A translated WebAssembly module, excluding the function bodies and
/// memory initializers.
#[derive(Default, Debug, Serialize, Deserialize)]
@ -542,7 +522,7 @@ pub struct Module {
pub globals: PrimaryMap<GlobalIndex, Global>,
/// WebAssembly global initializers for locally-defined globals.
pub global_initializers: PrimaryMap<DefinedGlobalIndex, GlobalInit>,
pub global_initializers: PrimaryMap<DefinedGlobalIndex, ConstExpr>,
}
/// Initialization routines for creating an instance, encompassing imports,
@ -720,18 +700,6 @@ impl Module {
func_ref: FuncRefIndex::reserved_value(),
})
}
/// Appends a new function to this module with the given type information.
pub fn push_escaped_function(
&mut self,
signature: ModuleInternedTypeIndex,
func_ref: FuncRefIndex,
) -> FuncIndex {
self.functions.push(FunctionType {
signature,
func_ref,
})
}
}
/// Type information about functions in a wasm module.

145
crates/environ/src/module_environ.rs

@ -1,12 +1,12 @@
use crate::module::{
FuncRefIndex, Initializer, MemoryInitialization, MemoryInitializer, MemoryPlan, Module,
TableElementExpression, TablePlan, TableSegment, TableSegmentElements,
TablePlan, TableSegment, TableSegmentElements,
};
use crate::{
DataIndex, DefinedFuncIndex, ElemIndex, EntityIndex, EntityType, FuncIndex, GlobalIndex,
GlobalInit, InitMemory, MemoryIndex, ModuleTypesBuilder, PrimaryMap, StaticMemoryInitializer,
TableIndex, TableInitialValue, Tunables, TypeConvert, TypeIndex, Unsigned, WasmError,
WasmHeapType, WasmResult, WasmValType, WasmparserTypeConverter,
InitMemory, MemoryIndex, ModuleTypesBuilder, PrimaryMap, StaticMemoryInitializer, TableIndex,
TableInitialValue, Tunables, TypeConvert, TypeIndex, Unsigned, WasmError, WasmHeapType,
WasmResult, WasmValType, WasmparserTypeConverter,
};
use anyhow::{bail, Result};
use cranelift_entity::packed_option::ReservedValue;
@ -20,7 +20,7 @@ use wasmparser::{
FuncToValidate, FunctionBody, NameSectionReader, Naming, Operator, Parser, Payload, TypeRef,
Validator, ValidatorResources, WasmFeatures,
};
use wasmtime_types::ModuleInternedTypeIndex;
use wasmtime_types::{ConstExpr, ConstOp, ModuleInternedTypeIndex};
/// Object containing the standalone environment information.
pub struct ModuleEnvironment<'a, 'data> {
@ -356,41 +356,12 @@ impl<'a, 'data> ModuleEnvironment<'a, 'data> {
wasmparser::TableInit::RefNull => TableInitialValue::Null {
precomputed: Vec::new(),
},
wasmparser::TableInit::Expr(cexpr) => {
let mut init_expr_reader = cexpr.get_binary_reader();
match (
init_expr_reader.read_operator()?,
init_expr_reader.read_operator()?,
) {
(Operator::RefNull { hty: _ }, Operator::End) => {
TableInitialValue::Null {
precomputed: Vec::new(),
}
}
(Operator::RefFunc { function_index }, Operator::End) => {
let index = FuncIndex::from_u32(function_index);
self.flag_func_escaped(index);
TableInitialValue::FuncRef(index)
}
(Operator::GlobalGet { global_index }, Operator::End) => {
let index = GlobalIndex::from_u32(global_index);
TableInitialValue::GlobalGet(index)
}
(Operator::I32Const { value }, Operator::RefI31) => {
// TODO: this is a bit of a hack to get
// `(ref.i31 (i32.const ...))` to work. We
// should also support things like `(ref.i31
// (global.get ...))` but that requires
// deeper changes to `TableInitialValue` and
// is left for follow up work.
TableInitialValue::I31Ref(value)
}
(s, t) => {
bail!(WasmError::Unsupported(format!(
"unsupported init expr in table section: {s:?}; {t:?}",
)));
}
wasmparser::TableInit::Expr(expr) => {
let (init, escaped) = ConstExpr::from_wasmparser(expr)?;
for f in escaped {
self.flag_func_escaped(f);
}
TableInitialValue::Expr(init)
}
};
self.result
@ -430,49 +401,10 @@ impl<'a, 'data> ModuleEnvironment<'a, 'data> {
for entry in globals {
let wasmparser::Global { ty, init_expr } = entry?;
let mut init_expr_reader = init_expr.get_binary_reader();
let initializer = match (
init_expr_reader.read_operator()?,
init_expr_reader.read_operator()?,
) {
(Operator::I32Const { value }, Operator::End) => {
GlobalInit::I32Const(value)
}
(Operator::I64Const { value }, Operator::End) => {
GlobalInit::I64Const(value)
}
(Operator::F32Const { value }, Operator::End) => {
GlobalInit::F32Const(value.bits())
}
(Operator::F64Const { value }, Operator::End) => {
GlobalInit::F64Const(value.bits())
}
(Operator::V128Const { value }, Operator::End) => {
GlobalInit::V128Const(u128::from_le_bytes(*value.bytes()))
}
(Operator::RefNull { hty: _ }, Operator::End) => GlobalInit::RefNullConst,
(Operator::RefFunc { function_index }, Operator::End) => {
let index = FuncIndex::from_u32(function_index);
self.flag_func_escaped(index);
GlobalInit::RefFunc(index)
}
(Operator::GlobalGet { global_index }, Operator::End) => {
GlobalInit::GetGlobal(GlobalIndex::from_u32(global_index))
}
(Operator::I32Const { value }, Operator::RefI31) => {
// TODO: This is a bit of a hack to allow `(ref.i31
// (i32.const ...))`. We should also support
// `(ref.i31 (global.get ...))` but that requires
// deeper changes to `GlobalInit` and is left for
// follow up PRs.
GlobalInit::RefI31Const(value)
}
(s, t) => {
bail!(WasmError::Unsupported(format!(
"unsupported init expr in global section: {s:?}; {t:?}",
)));
}
};
let (initializer, escaped) = ConstExpr::from_wasmparser(init_expr)?;
for f in escaped {
self.flag_func_escaped(f);
}
let ty = self.convert_global_type(&ty);
self.result.module.globals.push(ty);
self.result.module.global_initializers.push(initializer);
@ -546,25 +478,11 @@ impl<'a, 'data> ModuleEnvironment<'a, 'data> {
let mut exprs =
Vec::with_capacity(usize::try_from(items.count()).unwrap());
for expr in items {
let expr = match expr?.get_binary_reader().read_operator()? {
Operator::RefNull { .. } => TableElementExpression::Null,
Operator::RefFunc { function_index } => {
let func = FuncIndex::from_u32(function_index);
self.flag_func_escaped(func);
TableElementExpression::Function(func)
}
Operator::GlobalGet { global_index } => {
let global = GlobalIndex::from_u32(global_index);
TableElementExpression::GlobalGet(global)
}
s => {
bail!(WasmError::Unsupported(format!(
"unsupported init expr in element section: {:?}",
s
)));
}
};
let (expr, escaped) = ConstExpr::from_wasmparser(expr?)?;
exprs.push(expr);
for func in escaped {
self.flag_func_escaped(func);
}
}
TableSegmentElements::Expressions(exprs.into())
}
@ -861,11 +779,16 @@ and for re-adding support for interface types you can see this issue:
fn push_type(&mut self, ty: EntityType) -> EntityIndex {
match ty {
EntityType::Function(ty) => EntityIndex::Function(
self.result
EntityType::Function(ty) => EntityIndex::Function({
let func_index = self
.result
.module
.push_function(ty.unwrap_module_type_index()),
),
.push_function(ty.unwrap_module_type_index());
// Imported functions can escape; in fact, they've already done
// so to get here.
self.flag_func_escaped(func_index);
func_index
}),
EntityType::Table(ty) => {
let plan = TablePlan::for_table(ty, &self.tunables);
EntityIndex::Table(self.result.module.table_plans.push(plan))
@ -1205,10 +1128,12 @@ impl ModuleTranslation<'_> {
if table_size > MAX_FUNC_TABLE_SIZE {
continue;
}
if let TableInitialValue::FuncRef(val) = *init {
*init = TableInitialValue::Null {
precomputed: vec![val; table_size as usize],
};
if let TableInitialValue::Expr(expr) = init {
if let [ConstOp::RefFunc(f)] = expr.ops() {
*init = TableInitialValue::Null {
precomputed: vec![*f; table_size as usize],
};
}
}
}
@ -1289,9 +1214,7 @@ impl ModuleTranslation<'_> {
// Technically this won't trap so it's possible to process
// further initializers, but that's left as a future
// optimization.
TableInitialValue::FuncRef(_)
| TableInitialValue::GlobalGet(_)
| TableInitialValue::I31Ref(_) => break,
TableInitialValue::Expr(_) => break,
};
// At this point we're committing to pre-initializing the table

1
crates/runtime/Cargo.toml

@ -31,6 +31,7 @@ paste = "1.0.3"
encoding_rs = { version = "0.8.31", optional = true }
sptr = "0.3.2"
wasm-encoder = { workspace = true, optional = true }
smallvec = { workspace = true }
[target.'cfg(target_os = "linux")'.dependencies]
memfd = "0.6.2"

102
crates/runtime/src/const_expr.rs

@ -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"
)
})
}
}

155
crates/runtime/src/instance.rs

@ -2,6 +2,7 @@
//! wasm module (except its callstack and register state). An
//! `InstanceHandle` is a reference-counting handle for an `Instance`.
use crate::const_expr::{ConstEvalContext, ConstExprEvaluator};
use crate::export::Export;
use crate::memory::{Memory, RuntimeMemoryCreator};
use crate::table::{Table, TableElement, TableElementType};
@ -12,7 +13,7 @@ use crate::vmcontext::{
};
use crate::{
ExportFunction, ExportGlobal, ExportMemory, ExportTable, GcStore, Imports, ModuleRuntimeInfo,
SendSyncPtr, Store, VMFunctionBody, VMGcRef, WasmFault, I31,
SendSyncPtr, Store, VMFunctionBody, VMGcRef, WasmFault,
};
use anyhow::Error;
use anyhow::Result;
@ -24,13 +25,12 @@ use std::ptr::NonNull;
use std::sync::atomic::AtomicU64;
use std::sync::Arc;
use std::{mem, ptr};
use wasmtime_environ::ModuleInternedTypeIndex;
use wasmtime_environ::WasmHeapType;
use wasmtime_environ::{
packed_option::ReservedValue, DataIndex, DefinedGlobalIndex, DefinedMemoryIndex,
DefinedTableIndex, ElemIndex, EntityIndex, EntityRef, EntitySet, FuncIndex, GlobalIndex,
GlobalInit, HostPtr, MemoryIndex, MemoryPlan, Module, PrimaryMap, TableElementExpression,
TableIndex, TableInitialValue, TableSegmentElements, Trap, VMOffsets, VMSharedTypeIndex,
WasmRefType, WasmValType, VMCONTEXT_MAGIC,
HostPtr, MemoryIndex, MemoryPlan, Module, ModuleInternedTypeIndex, PrimaryMap, TableIndex,
TableInitialValue, TableSegmentElements, Trap, VMOffsets, VMSharedTypeIndex, VMCONTEXT_MAGIC,
};
#[cfg(feature = "wmemcheck")]
use wasmtime_wmemcheck::Wmemcheck;
@ -364,11 +364,6 @@ impl Instance {
.map(|(index, (_alloc_index, memory))| (index, memory))
}
/// Return the indexed `VMGlobalDefinition`.
fn global(&mut self, index: DefinedGlobalIndex) -> &VMGlobalDefinition {
unsafe { &*self.global_ptr(index) }
}
/// Return the indexed `VMGlobalDefinition`.
fn global_ptr(&mut self, index: DefinedGlobalIndex) -> *mut VMGlobalDefinition {
unsafe { self.vmctx_plus_offset_mut(self.offsets().vmctx_vmglobal_definition(index)) }
@ -843,11 +838,13 @@ impl Instance {
}
_ => &empty,
};
self.table_init_segment(table_index, elements, dst, src, len)
let mut const_evaluator = ConstExprEvaluator::default();
self.table_init_segment(&mut const_evaluator, table_index, elements, dst, src, len)
}
pub(crate) fn table_init_segment(
&mut self,
const_evaluator: &mut ConstExprEvaluator,
table_index: TableIndex,
elements: &TableSegmentElements,
dst: u32,
@ -859,6 +856,7 @@ impl Instance {
let table = unsafe { &mut *self.get_table(table_index) };
let src = usize::try_from(src).map_err(|_| Trap::TableOutOfBounds)?;
let len = usize::try_from(len).map_err(|_| Trap::TableOutOfBounds)?;
let module = self.module().clone();
match elements {
TableSegmentElements::Functions(funcs) => {
@ -874,44 +872,42 @@ impl Instance {
)?;
}
TableSegmentElements::Expressions(exprs) => {
let ty = table.element_type();
let exprs = exprs
.get(src..)
.and_then(|s| s.get(..len))
.ok_or(Trap::TableOutOfBounds)?;
match ty {
TableElementType::Func => {
table.init_func(
let mut context = ConstEvalContext::new(self, &module);
match module.table_plans[table_index].table.wasm_ty.heap_type {
WasmHeapType::Extern => table.init_gc_refs(
dst,
exprs.iter().map(|expr| unsafe {
let raw = const_evaluator
.eval(&mut context, expr)
.expect("const expr should be valid");
VMGcRef::from_raw_u32(raw.get_externref())
}),
)?,
WasmHeapType::Any | WasmHeapType::I31 | WasmHeapType::None => table
.init_gc_refs(
dst,
exprs.iter().map(|expr| match expr {
TableElementExpression::Null => std::ptr::null_mut(),
TableElementExpression::Function(idx) => {
self.get_func_ref(*idx).unwrap()
}
TableElementExpression::GlobalGet(idx) => {
let global = self.defined_or_imported_global_ptr(*idx);
unsafe { (*global).as_func_ref() }
}
exprs.iter().map(|expr| unsafe {
let raw = const_evaluator
.eval(&mut context, expr)
.expect("const expr should be valid");
VMGcRef::from_raw_u32(raw.get_anyref())
}),
)?;
}
TableElementType::GcRef => {
table.init_gc_refs(
)?,
WasmHeapType::Func | WasmHeapType::Concrete(_) | WasmHeapType::NoFunc => table
.init_func(
dst,
exprs.iter().map(|expr| match expr {
TableElementExpression::Null => None,
TableElementExpression::Function(_) => unreachable!(),
TableElementExpression::GlobalGet(idx) => {
let global = self.defined_or_imported_global_ptr(*idx);
let gc_ref = unsafe { (*global).as_gc_ref() };
gc_ref.map(|r| {
let store = unsafe { &mut *self.store() };
store.gc_store().clone_gc_ref(r)
})
}
exprs.iter().map(|expr| unsafe {
const_evaluator
.eval(&mut context, expr)
.expect("const expr should be valid")
.get_funcref()
.cast()
}),
)?;
}
)?,
}
}
}
@ -1129,11 +1125,7 @@ impl Instance {
let module = self.module();
let precomputed = match &module.table_initialization.initial_values[idx] {
TableInitialValue::Null { precomputed } => precomputed,
TableInitialValue::FuncRef(_)
| TableInitialValue::GlobalGet(_)
| TableInitialValue::I31Ref(_) => {
unreachable!()
}
TableInitialValue::Expr(_) => unreachable!(),
};
let func_index = precomputed.get(i as usize).cloned();
let func_ref = func_index
@ -1271,66 +1263,33 @@ impl Instance {
}
// Initialize the defined globals
self.initialize_vmctx_globals(module);
let mut const_evaluator = ConstExprEvaluator::default();
self.initialize_vmctx_globals(&mut const_evaluator, module);
}
unsafe fn initialize_vmctx_globals(&mut self, module: &Module) {
unsafe fn initialize_vmctx_globals(
&mut self,
const_evaluator: &mut ConstExprEvaluator,
module: &Module,
) {
for (index, init) in module.global_initializers.iter() {
let mut context = ConstEvalContext::new(self, module);
let raw = const_evaluator
.eval(&mut context, init)
.expect("should be a valid const expr");
let to = self.global_ptr(index);
let wasm_ty = module.globals[module.global_index(index)].wasm_ty;
// Initialize the global before writing to it
ptr::write(to, VMGlobalDefinition::new());
match *init {
GlobalInit::I32Const(x) => {
let index = module.global_index(index);
if index.index() == 0 {
#[cfg(feature = "wmemcheck")]
{
if let Some(wmemcheck) = &mut self.wmemcheck_state {
wmemcheck.set_stack_size(x as usize);
}
}
}
*(*to).as_i32_mut() = x;
}
GlobalInit::I64Const(x) => *(*to).as_i64_mut() = x,
GlobalInit::F32Const(x) => *(*to).as_f32_bits_mut() = x,
GlobalInit::F64Const(x) => *(*to).as_f64_bits_mut() = x,
GlobalInit::V128Const(x) => *(*to).as_u128_mut() = x,
GlobalInit::GetGlobal(x) => {
let from = if let Some(def_x) = module.defined_global_index(x) {
self.global(def_x)
} else {
&*self.imported_global(x).from
};
// GC-managed globals may need to invoke GC barriers,
// everything else is just copy-able bits.
if wasm_ty.is_gc_heap_type() {
let gc_ref = (*from)
.as_gc_ref()
.map(|r| r.unchecked_copy())
.map(|r| (*self.store()).gc_store().clone_gc_ref(&r));
(*to).init_gc_ref(gc_ref);
} else {
ptr::copy_nonoverlapping(from, to, 1);
}
}
GlobalInit::RefFunc(f) => {
*(*to).as_func_ref_mut() = self.get_func_ref(f).unwrap();
}
GlobalInit::RefNullConst => match wasm_ty {
// `VMGlobalDefinition::new()` already zeroed out the bits
WasmValType::Ref(WasmRefType { nullable: true, .. }) => {}
ty => panic!("unsupported reference type for global: {:?}", ty),
},
GlobalInit::RefI31Const(x) => {
let gc_ref = VMGcRef::from_i31(I31::wrapping_i32(x));
(*to).init_gc_ref(Some(gc_ref));
#[cfg(feature = "wmemcheck")]
if index.index() == 0 && wasm_ty == wasmtime_environ::WasmValType::I32 {
if let Some(wmemcheck) = &mut self.wmemcheck_state {
let size = usize::try_from(raw.get_i32()).unwrap();
wmemcheck.set_stack_size(size);
}
}
ptr::write(to, VMGlobalDefinition::from_val_raw(wasm_ty, raw));
}
}

74
crates/runtime/src/instance/allocator.rs

@ -1,9 +1,10 @@
use crate::const_expr::{ConstEvalContext, ConstExprEvaluator};
use crate::imports::Imports;
use crate::instance::{Instance, InstanceHandle};
use crate::memory::Memory;
use crate::mpk::ProtectionKey;
use crate::table::{Table, TableElementType};
use crate::{CompiledModuleId, ModuleRuntimeInfo, Store, VMGcRef, I31};
use crate::table::Table;
use crate::{CompiledModuleId, ModuleRuntimeInfo, Store, VMFuncRef, VMGcRef};
use anyhow::{anyhow, bail, Result};
use std::{alloc, any::Any, mem, ptr, sync::Arc};
use wasmtime_environ::{
@ -557,49 +558,49 @@ fn check_table_init_bounds(instance: &mut Instance, module: &Module) -> Result<(
}
fn initialize_tables(instance: &mut Instance, module: &Module) -> Result<()> {
let mut const_evaluator = ConstExprEvaluator::default();
for (table, init) in module.table_initialization.initial_values.iter() {
match init {
// Tables are always initially null-initialized at this time
TableInitialValue::Null { precomputed: _ } => {}
TableInitialValue::FuncRef(idx) => {
let funcref = instance.get_func_ref(*idx).unwrap();
let table = unsafe { &mut *instance.get_defined_table(table) };
let init = (0..table.size()).map(|_| funcref);
table.init_func(0, init)?;
}
TableInitialValue::Expr(expr) => {
let mut context = ConstEvalContext::new(instance, module);
let raw = unsafe {
const_evaluator
.eval(&mut context, expr)
.expect("const expression should be valid")
};
let idx = module.table_index(table);
let table = unsafe { instance.get_defined_table(table).as_mut().unwrap() };
match module.table_plans[idx].table.wasm_ty.heap_type {
wasmtime_environ::WasmHeapType::Extern => {
let gc_ref = VMGcRef::from_raw_u32(raw.get_externref());
let gc_store = unsafe { (*instance.store()).gc_store() };
let items = (0..table.size())
.map(|_| gc_ref.as_ref().map(|r| gc_store.clone_gc_ref(r)));
table.init_gc_refs(0, items)?;
}
TableInitialValue::GlobalGet(idx) => unsafe {
let global = instance.defined_or_imported_global_ptr(*idx);
let table = &mut *instance.get_defined_table(table);
match table.element_type() {
TableElementType::Func => {
let funcref = (*global).as_func_ref();
let init = (0..table.size()).map(|_| funcref);
table.init_func(0, init)?;
wasmtime_environ::WasmHeapType::Any
| wasmtime_environ::WasmHeapType::I31
| wasmtime_environ::WasmHeapType::None => {
let gc_ref = VMGcRef::from_raw_u32(raw.get_anyref());
let gc_store = unsafe { (*instance.store()).gc_store() };
let items = (0..table.size())
.map(|_| gc_ref.as_ref().map(|r| gc_store.clone_gc_ref(r)));
table.init_gc_refs(0, items)?;
}
TableElementType::GcRef => {
let gc_ref = (*global).as_gc_ref();
let gc_ref = gc_ref.map(|r| r.unchecked_copy());
let init = (0..table.size()).map(|_| {
gc_ref
.as_ref()
.map(|r| (*instance.store()).gc_store().clone_gc_ref(r))
});
table.init_gc_refs(0, init)?;
wasmtime_environ::WasmHeapType::Func
| wasmtime_environ::WasmHeapType::Concrete(_)
| wasmtime_environ::WasmHeapType::NoFunc => {
let funcref = raw.get_funcref().cast::<VMFuncRef>();
let items = (0..table.size()).map(|_| funcref);
table.init_func(0, items)?;
}
}
},
TableInitialValue::I31Ref(value) => {
let value = VMGcRef::from_i31(I31::wrapping_i32(*value));
let table = unsafe { &mut *instance.get_defined_table(table) };
let init = (0..table.size()).map(|_| {
// NB: Okay to use `unchecked_copy` because `i31` doesn't
// need GC barriers.
Some(value.unchecked_copy())
});
table.init_gc_refs(0, init)?;
}
}
}
@ -614,6 +615,7 @@ fn initialize_tables(instance: &mut Instance, module: &Module) -> Result<()> {
for segment in module.table_initialization.segments.iter() {
let start = get_table_init_start(segment, instance)?;
instance.table_init_segment(
&mut const_evaluator,
segment.table_index,
&segment.elements,
start,

1
crates/runtime/src/lib.rs

@ -17,6 +17,7 @@ mod arch;
mod async_yield;
#[cfg(feature = "component-model")]
pub mod component;
mod const_expr;
mod export;
mod gc;
mod imports;

70
crates/runtime/src/vmcontext.rs

@ -14,6 +14,8 @@ use std::sync::atomic::{AtomicUsize, Ordering};
use std::u32;
pub use vm_host_func_context::{VMArrayCallHostFuncContext, VMNativeCallHostFuncContext};
use wasmtime_environ::VMSharedTypeIndex;
use wasmtime_environ::WasmHeapType;
use wasmtime_environ::WasmValType;
use wasmtime_environ::{BuiltinFunctionIndex, DefinedMemoryIndex, Unsigned, VMCONTEXT_MAGIC};
/// A function pointer that exposes the array calling convention.
@ -434,6 +436,62 @@ impl VMGlobalDefinition {
Self { storage: [0; 16] }
}
/// Create a `VMGlobalDefinition` from a `ValRaw`.
///
/// # Unsafety
///
/// This raw value's type must match the given `WasmValType`.
pub unsafe fn from_val_raw(wasm_ty: WasmValType, raw: ValRaw) -> Self {
let mut global = Self::new();
match wasm_ty {
WasmValType::I32 => *global.as_i32_mut() = raw.get_i32(),
WasmValType::I64 => *global.as_i64_mut() = raw.get_i64(),
WasmValType::F32 => *global.as_f32_bits_mut() = raw.get_f32(),
WasmValType::F64 => *global.as_f64_bits_mut() = raw.get_f64(),
WasmValType::V128 => *global.as_u128_mut() = raw.get_v128(),
WasmValType::Ref(r) => match r.heap_type {
WasmHeapType::Extern => {
global.init_gc_ref(VMGcRef::from_raw_u32(raw.get_externref()))
}
WasmHeapType::Any | WasmHeapType::I31 | WasmHeapType::None => {
global.init_gc_ref(VMGcRef::from_raw_u32(raw.get_anyref()))
}
WasmHeapType::Func | WasmHeapType::Concrete(_) | WasmHeapType::NoFunc => {
*global.as_func_ref_mut() = raw.get_funcref().cast()
}
},
}
global
}
/// Get this global's value as a `ValRaw`.
///
/// # Unsafety
///
/// This global's value's type must match the given `WasmValType`.
pub unsafe fn to_val_raw(&self, gc_store: &mut GcStore, wasm_ty: WasmValType) -> ValRaw {
match wasm_ty {
WasmValType::I32 => ValRaw::i32(*self.as_i32()),
WasmValType::I64 => ValRaw::i64(*self.as_i64()),
WasmValType::F32 => ValRaw::f32(*self.as_f32_bits()),
WasmValType::F64 => ValRaw::f64(*self.as_f64_bits()),
WasmValType::V128 => ValRaw::v128(*self.as_u128()),
WasmValType::Ref(r) => match r.heap_type {
WasmHeapType::Extern => ValRaw::externref(
self.as_gc_ref()
.map_or(0, |r| gc_store.clone_gc_ref(r).as_raw_u32()),
),
WasmHeapType::Any | WasmHeapType::I31 | WasmHeapType::None => ValRaw::anyref(
self.as_gc_ref()
.map_or(0, |r| gc_store.clone_gc_ref(r).as_raw_u32()),
),
WasmHeapType::Func | WasmHeapType::Concrete(_) | WasmHeapType::NoFunc => {
ValRaw::funcref(self.as_func_ref().cast())
}
},
}
}
/// Return a reference to the value as an i32.
pub unsafe fn as_i32(&self) -> &i32 {
&*(self.storage.as_ref().as_ptr().cast::<i32>())
@ -1074,6 +1132,18 @@ impl std::fmt::Debug for ValRaw {
}
impl ValRaw {
/// Create a null reference that is compatible with any of
/// `{any,extern,func}ref`.
pub fn null() -> ValRaw {
unsafe {
let raw = mem::MaybeUninit::<Self>::zeroed().assume_init();
debug_assert_eq!(raw.get_anyref(), 0);
debug_assert_eq!(raw.get_externref(), 0);
debug_assert_eq!(raw.get_funcref(), ptr::null_mut());
raw
}
}
/// Creates a WebAssembly `i32` value
#[inline]
pub fn i32(i: i32) -> ValRaw {

4
crates/types/Cargo.toml

@ -12,5 +12,9 @@ edition.workspace = true
cranelift-entity = { workspace = true, features = ['enable-serde'] }
serde = { workspace = true }
serde_derive = { workspace = true }
smallvec = { workspace = true, features = ["serde"] }
thiserror = { workspace = true }
wasmparser = { workspace = true }
[lints]
workspace = true

194
crates/types/src/lib.rs

@ -1,6 +1,7 @@
//! Internal dependency of Wasmtime and Cranelift that defines types for
//! WebAssembly.
use smallvec::SmallVec;
pub use wasmparser;
use cranelift_entity::entity_impl;
@ -190,20 +191,22 @@ impl TypeTrace for WasmValType {
}
impl WasmValType {
/// Is this a type that is represented as a `VMGcRef`?
pub fn is_vmgcref_type(&self) -> bool {
self.is_gc_heap_type()
|| matches!(
self,
WasmValType::Ref(WasmRefType {
heap_type: WasmHeapType::I31,
nullable: _,
})
)
match self {
WasmValType::Ref(r) => r.is_vmgcref_type(),
_ => false,
}
}
pub fn is_gc_heap_type(&self) -> bool {
/// Is this a type that is represented as a `VMGcRef` and is additionally
/// not an `i31`?
///
/// That is, is this a a type that actually refers to an object allocated in
/// a GC heap?
pub fn is_vmgcref_type_and_not_i31(&self) -> bool {
match self {
WasmValType::Ref(r) => r.is_gc_heap_type(),
WasmValType::Ref(r) => r.is_vmgcref_type_and_not_i31(),
_ => false,
}
}
@ -242,10 +245,18 @@ impl WasmRefType {
heap_type: WasmHeapType::Func,
};
/// Is this a GC type that is allocated within the GC heap? (As opposed to
/// `i31ref` which is a GC type that is not allocated on the GC heap.)
pub fn is_gc_heap_type(&self) -> bool {
self.heap_type.is_gc_heap_type()
/// Is this a type that is represented as a `VMGcRef`?
pub fn is_vmgcref_type(&self) -> bool {
self.heap_type.is_vmgcref_type()
}
/// Is this a type that is represented as a `VMGcRef` and is additionally
/// not an `i31`?
///
/// That is, is this a a type that actually refers to an object allocated in
/// a GC heap?
pub fn is_vmgcref_type_and_not_i31(&self) -> bool {
self.heap_type.is_vmgcref_type_and_not_i31()
}
}
@ -409,33 +420,26 @@ impl TypeTrace for WasmHeapType {
}
impl WasmHeapType {
/// Is this a GC type that is allocated within the GC heap? (As opposed to
/// `i31ref` which is a GC type that is not allocated on the GC heap.)
pub fn is_gc_heap_type(&self) -> bool {
// All `t <: (ref null any)` and `t <: (ref null extern)` that are
// not `(ref null? i31)` are GC-managed references.
/// Is this a type that is represented as a `VMGcRef`?
pub fn is_vmgcref_type(&self) -> bool {
match self {
// These types are managed by the GC.
Self::Extern | Self::Any => true,
// TODO: Once we support concrete struct and array types, we will
// need to look at the payload to determine whether the type is
// GC-managed or not.
Self::Concrete(_) => false,
// These are compatible with GC references, but don't actually point
// to GC objects.
Self::I31 => false,
// All `t <: (ref null any)` and `t <: (ref null extern)` are
// represented as `VMGcRef`s.
Self::Extern | Self::Any | Self::I31 | Self::None => true,
// These are a subtype of GC-managed types, but are uninhabited, so
// can never actually point to a GC object. Again, we could return
// `true` here but there is no need.
Self::None => false,
// These types are not managed by the GC.
Self::Func | Self::NoFunc => false,
// All `t <: (ref null func)` are not.
Self::Func | Self::Concrete(_) | Self::NoFunc => false,
}
}
/// Is this a type that is represented as a `VMGcRef` and is additionally
/// not an `i31`?
///
/// That is, is this a a type that actually refers to an object allocated in
/// a GC heap?
pub fn is_vmgcref_type_and_not_i31(&self) -> bool {
self.is_vmgcref_type() && *self != Self::I31
}
}
/// WebAssembly function type -- equivalent of `wasmparser`'s FuncType.
@ -478,8 +482,14 @@ impl TypeTrace for WasmFuncType {
impl WasmFuncType {
#[inline]
pub fn new(params: Box<[WasmValType]>, returns: Box<[WasmValType]>) -> Self {
let non_i31_gc_ref_params_count = params.iter().filter(|p| p.is_gc_heap_type()).count();
let non_i31_gc_ref_returns_count = returns.iter().filter(|r| r.is_gc_heap_type()).count();
let non_i31_gc_ref_params_count = params
.iter()
.filter(|p| p.is_vmgcref_type_and_not_i31())
.count();
let non_i31_gc_ref_returns_count = returns
.iter()
.filter(|r| r.is_vmgcref_type_and_not_i31())
.count();
WasmFuncType {
params,
non_i31_gc_ref_params_count,
@ -856,29 +866,105 @@ impl TypeTrace for Global {
}
}
/// Globals are initialized via the `const` operators or by referring to another import.
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, Serialize, Deserialize)]
pub enum GlobalInit {
/// An `i32.const`.
/// A constant expression.
///
/// These are used to initialize globals, table elements, etc...
#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub struct ConstExpr {
ops: SmallVec<[ConstOp; 2]>,
}
impl ConstExpr {
/// Create a new const expression from the given opcodes.
///
/// Does not do any validation that the const expression is well-typed.
///
/// Panics if given zero opcodes.
pub fn new(ops: impl IntoIterator<Item = ConstOp>) -> Self {
let ops = ops.into_iter().collect::<SmallVec<[ConstOp; 2]>>();
assert!(!ops.is_empty());
ConstExpr { ops }
}
/// Create a new const expression from a `wasmparser` const expression.
///
/// Returns the new const expression as well as the escaping function
/// indices that appeared in `ref.func` instructions, if any.
pub fn from_wasmparser(
expr: wasmparser::ConstExpr<'_>,
) -> WasmResult<(Self, SmallVec<[FuncIndex; 1]>)> {
let mut iter = expr
.get_operators_reader()
.into_iter_with_offsets()
.peekable();
let mut ops = SmallVec::<[ConstOp; 2]>::new();
let mut escaped = SmallVec::<[FuncIndex; 1]>::new();
while let Some(res) = iter.next() {
let (op, offset) = res?;
// If we reach an `end` instruction, and there are no more
// instructions after that, then we are done reading this const
// expression.
if matches!(op, wasmparser::Operator::End) && iter.peek().is_none() {
break;
}
// Track any functions that appear in `ref.func` so that callers can
// make sure to flag them as escaping.
if let wasmparser::Operator::RefFunc { function_index } = &op {
escaped.push(FuncIndex::from_u32(*function_index));
}
ops.push(ConstOp::from_wasmparser(op, offset)?);
}
Ok((Self { ops }, escaped))
}
/// Get the opcodes that make up this const expression.
pub fn ops(&self) -> &[ConstOp] {
&self.ops
}
}
/// The subset of Wasm opcodes that are constant.
#[allow(missing_docs)]
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub enum ConstOp {
I32Const(i32),
/// An `i64.const`.
I64Const(i64),
/// An `f32.const`.
F32Const(u32),
/// An `f64.const`.
F64Const(u64),
/// A `vconst`.
V128Const(u128),
/// A `global.get` of another global.
GetGlobal(GlobalIndex),
/// A `(ref.i31 (global.get N))` initializer.
RefI31Const(i32),
/// A `ref.null`.
RefNullConst,
/// A `ref.func <index>`.
GlobalGet(GlobalIndex),
RefI31,
RefNull,
RefFunc(FuncIndex),
}
impl ConstOp {
/// Convert a `wasmparser::Operator` to a `ConstOp`.
pub fn from_wasmparser(op: wasmparser::Operator<'_>, offset: usize) -> WasmResult<Self> {
use wasmparser::Operator as O;
Ok(match op {
O::I32Const { value } => Self::I32Const(value),
O::I64Const { value } => Self::I64Const(value),
O::F32Const { value } => Self::F32Const(value.bits()),
O::F64Const { value } => Self::F64Const(value.bits()),
O::V128Const { value } => Self::V128Const(u128::from_le_bytes(*value.bytes())),
O::RefNull { hty: _ } => Self::RefNull,
O::RefFunc { function_index } => Self::RefFunc(FuncIndex::from_u32(function_index)),
O::GlobalGet { global_index } => Self::GlobalGet(GlobalIndex::from_u32(global_index)),
O::RefI31 => Self::RefI31,
op => {
return Err(wasm_unsupported!(
"unsupported opcode in const expression at offset {offset:#x}: {op:?}",
));
}
})
}
}
/// WebAssembly table.
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, Serialize, Deserialize)]
pub struct Table {

31
tests/misc_testsuite/gc/i31ref-of-global-initializers.wast

@ -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…
Cancel
Save