Implement lazy funcref table and anyfunc initialization. (#3733)

During instance initialization, we build two sorts of arrays eagerly:

- We create an "anyfunc" (a `VMCallerCheckedAnyfunc`) for every function
  in an instance.

- We initialize every element of a funcref table with an initializer to
  a pointer to one of these anyfuncs.

Most instances will not touch (via call_indirect or table.get) all
funcref table elements. And most anyfuncs will never be referenced,
because most functions are never placed in tables or used with
`ref.func`. Thus, both of these initialization tasks are quite wasteful.
Profiling shows that a significant fraction of the remaining
instance-initialization time after our other recent optimizations is
going into these two tasks.

This PR implements two basic ideas:

- The anyfunc array can be lazily initialized as long as we retain the
  information needed to do so. For now, in this PR, we just recreate the
  anyfunc whenever a pointer is taken to it, because doing so is fast
  enough; in the future we could keep some state to know whether the
  anyfunc has been written yet and skip this work if redundant.

  This technique allows us to leave the anyfunc array as uninitialized
  memory, which can be a significant savings. Filling it with
  initialized anyfuncs is very expensive, but even zeroing it is
  expensive: e.g. in a large module, it can be >500KB.

- A funcref table can be lazily initialized as long as we retain a link
  to its corresponding instance and function index for each element. A
  zero in a table element means "uninitialized", and a slowpath does the
  initialization.

Funcref tables are a little tricky because funcrefs can be null. We need
to distinguish "element was initially non-null, but user stored explicit
null later" from "element never touched" (ie the lazy init should not
blow away an explicitly stored null). We solve this by stealing the LSB
from every funcref (anyfunc pointer): when the LSB is set, the funcref
is initialized and we don't hit the lazy-init slowpath. We insert the
bit on storing to the table and mask it off after loading.

We do have to set up a precomputed array of `FuncIndex`s for the table
in order for this to work. We do this as part of the module compilation.

This PR also refactors the way that the runtime crate gains access to
information computed during module compilation.

Performance effect measured with in-tree benches/instantiation.rs, using
SpiderMonkey built for WASI, and with memfd enabled:

```
BEFORE:

sequential/default/spidermonkey.wasm
                        time:   [68.569 us 68.696 us 68.856 us]
sequential/pooling/spidermonkey.wasm
                        time:   [69.406 us 69.435 us 69.465 us]

parallel/default/spidermonkey.wasm: with 1 background thread
                        time:   [69.444 us 69.470 us 69.497 us]
parallel/default/spidermonkey.wasm: with 16 background threads
                        time:   [183.72 us 184.31 us 184.89 us]
parallel/pooling/spidermonkey.wasm: with 1 background thread
                        time:   [69.018 us 69.070 us 69.136 us]
parallel/pooling/spidermonkey.wasm: with 16 background threads
                        time:   [326.81 us 337.32 us 347.01 us]

WITH THIS PR:

sequential/default/spidermonkey.wasm
                        time:   [6.7821 us 6.8096 us 6.8397 us]
                        change: [-90.245% -90.193% -90.142%] (p = 0.00 < 0.05)
                        Performance has improved.
sequential/pooling/spidermonkey.wasm
                        time:   [3.0410 us 3.0558 us 3.0724 us]
                        change: [-95.566% -95.552% -95.537%] (p = 0.00 < 0.05)
                        Performance has improved.

parallel/default/spidermonkey.wasm: with 1 background thread
                        time:   [7.2643 us 7.2689 us 7.2735 us]
                        change: [-89.541% -89.533% -89.525%] (p = 0.00 < 0.05)
                        Performance has improved.
parallel/default/spidermonkey.wasm: with 16 background threads
                        time:   [147.36 us 148.99 us 150.74 us]
                        change: [-18.997% -18.081% -17.285%] (p = 0.00 < 0.05)
                        Performance has improved.
parallel/pooling/spidermonkey.wasm: with 1 background thread
                        time:   [3.1009 us 3.1021 us 3.1033 us]
                        change: [-95.517% -95.511% -95.506%] (p = 0.00 < 0.05)
                        Performance has improved.
parallel/pooling/spidermonkey.wasm: with 16 background threads
                        time:   [49.449 us 50.475 us 51.540 us]
                        change: [-85.423% -84.964% -84.465%] (p = 0.00 < 0.05)
                        Performance has improved.
```

So an improvement of something like 80-95% for a very large module (7420
functions in its one funcref table, 31928 functions total).
This commit is contained in:
Chris Fallin
2022-02-09 13:56:53 -08:00
committed by GitHub
parent 1b27508a42
commit 39a52ceb4f
26 changed files with 1000 additions and 437 deletions

View File

@@ -477,7 +477,7 @@ impl Table {
let init = init.into_table_element(store, ty.element())?;
unsafe {
let table = Table::from_wasmtime_table(wasmtime_export, store);
(*table.wasmtime_table(store))
(*table.wasmtime_table(store, std::iter::empty()))
.fill(0, init, ty.minimum())
.map_err(Trap::from_runtime)?;
@@ -497,12 +497,16 @@ impl Table {
TableType::from_wasmtime_table(ty)
}
fn wasmtime_table(&self, store: &mut StoreOpaque) -> *mut runtime::Table {
fn wasmtime_table(
&self,
store: &mut StoreOpaque,
lazy_init_range: impl Iterator<Item = u32>,
) -> *mut runtime::Table {
unsafe {
let export = &store[self.0];
let mut handle = InstanceHandle::from_vmctx(export.vmctx);
let idx = handle.table_index(&*export.definition);
handle.get_defined_table(idx)
handle.get_defined_table_with_lazy_init(idx, lazy_init_range)
}
}
@@ -515,7 +519,7 @@ impl Table {
/// Panics if `store` does not own this table.
pub fn get(&self, mut store: impl AsContextMut, index: u32) -> Option<Val> {
let store = store.as_context_mut().0;
let table = self.wasmtime_table(store);
let table = self.wasmtime_table(store, std::iter::once(index));
unsafe {
match (*table).get(index)? {
runtime::TableElement::FuncRef(f) => {
@@ -526,6 +530,9 @@ impl Table {
runtime::TableElement::ExternRef(Some(x)) => {
Some(Val::ExternRef(Some(ExternRef { inner: x })))
}
runtime::TableElement::UninitFunc => {
unreachable!("lazy init above should have converted UninitFunc")
}
}
}
}
@@ -545,7 +552,7 @@ impl Table {
let store = store.as_context_mut().0;
let ty = self.ty(&store).element().clone();
let val = val.into_table_element(store, ty)?;
let table = self.wasmtime_table(store);
let table = self.wasmtime_table(store, std::iter::empty());
unsafe {
(*table)
.set(index, val)
@@ -591,7 +598,7 @@ impl Table {
let store = store.as_context_mut().0;
let ty = self.ty(&store).element().clone();
let init = init.into_table_element(store, ty)?;
let table = self.wasmtime_table(store);
let table = self.wasmtime_table(store, std::iter::empty());
unsafe {
match (*table).grow(delta, init, store)? {
Some(size) => {
@@ -656,10 +663,11 @@ impl Table {
bail!("tables do not have the same element type");
}
let dst = dst_table.wasmtime_table(store);
let src = src_table.wasmtime_table(store);
let dst_table = dst_table.wasmtime_table(store, std::iter::empty());
let src_range = src_index..(src_index.checked_add(len).unwrap_or(u32::MAX));
let src_table = src_table.wasmtime_table(store, src_range);
unsafe {
runtime::Table::copy(dst, src, dst_index, src_index, len)
runtime::Table::copy(dst_table, src_table, dst_index, src_index, len)
.map_err(Trap::from_runtime)?;
}
Ok(())
@@ -686,7 +694,7 @@ impl Table {
let ty = self.ty(&store).element().clone();
let val = val.into_table_element(store, ty)?;
let table = self.wasmtime_table(store);
let table = self.wasmtime_table(store, std::iter::empty());
unsafe {
(*table).fill(dst, val, len).map_err(Trap::from_runtime)?;
}

View File

@@ -2060,7 +2060,7 @@ impl HostFunc {
/// Requires that this function's signature is already registered within
/// `Engine`. This happens automatically during the above two constructors.
fn _new(engine: &Engine, instance: InstanceHandle, trampoline: VMTrampoline) -> Self {
fn _new(engine: &Engine, mut instance: InstanceHandle, trampoline: VMTrampoline) -> Self {
let idx = EntityIndex::Function(FuncIndex::from_u32(0));
let export = match instance.lookup_by_declaration(&idx) {
wasmtime_runtime::Export::Function(f) => f,

View File

@@ -328,13 +328,16 @@ impl Instance {
// Instantiated instances will lazily fill in exports, so we process
// all that lazy logic here.
InstanceData::Instantiated { id, exports, .. } => {
let instance = store.instance(*id);
let (i, _, index) = instance.module().exports.get_full(name)?;
let id = *id;
let instance = store.instance(id);
let (i, _, &index) = instance.module().exports.get_full(name)?;
if let Some(export) = &exports[i] {
return Some(export.clone());
}
let instance = store.instance_mut(id); // reborrow the &mut Instancehandle
let item = unsafe {
Extern::from_wasmtime_export(instance.lookup_by_declaration(index), store)
Extern::from_wasmtime_export(instance.lookup_by_declaration(&index), store)
};
let exports = match &mut store[self.0] {
InstanceData::Instantiated { exports, .. } => exports,
@@ -690,9 +693,6 @@ impl<'a> Instantiator<'a> {
// properly referenced while in use by the store.
store.modules_mut().register(&self.cur.module);
// Initialize any memfd images now.
let memfds = self.cur.module.memfds()?;
unsafe {
// The first thing we do is issue an instance allocation request
// to the instance allocator. This, on success, will give us an
@@ -704,21 +704,16 @@ impl<'a> Instantiator<'a> {
// this instance, so we determine what the ID is and then assert
// it's the same later when we do actually insert it.
let instance_to_be = store.store_data().next_id::<InstanceData>();
let mut instance_handle =
store
.engine()
.allocator()
.allocate(InstanceAllocationRequest {
module: compiled_module.module(),
unique_id: Some(compiled_module.unique_id()),
memfds,
image_base: compiled_module.code().as_ptr() as usize,
functions: compiled_module.functions(),
runtime_info: &self.cur.module.runtime_info(),
imports: self.cur.build(),
shared_signatures: self.cur.module.signatures().as_module_map().into(),
host_state: Box::new(Instance(instance_to_be)),
store: StorePtr::new(store.traitobj()),
wasm_data: compiled_module.wasm_data(),
})?;
// The instance still has lots of setup, for example
@@ -821,7 +816,7 @@ impl<'a> Instantiator<'a> {
};
// If a start function is present, invoke it. Make sure we use all the
// trap-handling configuration in `store` as well.
let instance = store.0.instance(id);
let instance = store.0.instance_mut(id);
let f = match instance.lookup_by_declaration(&EntityIndex::Function(start)) {
wasmtime_runtime::Export::Function(f) => f,
_ => unreachable!(), // valid modules shouldn't hit this

View File

@@ -10,9 +10,12 @@ use std::mem;
use std::path::Path;
use std::sync::Arc;
use wasmparser::{Parser, ValidPayload, Validator};
use wasmtime_environ::{ModuleEnvironment, ModuleIndex, PrimaryMap};
use wasmtime_environ::{
DefinedFuncIndex, DefinedMemoryIndex, FunctionInfo, ModuleEnvironment, ModuleIndex, PrimaryMap,
SignatureIndex,
};
use wasmtime_jit::{CompiledModule, CompiledModuleInfo, MmapVec, TypeTables};
use wasmtime_runtime::ModuleMemFds;
use wasmtime_runtime::{CompiledModuleId, MemoryMemFd, ModuleMemFds, VMSharedSignatureIndex};
mod registry;
mod serialization;
@@ -110,10 +113,10 @@ struct ModuleInner {
/// Registered shared signature for the module.
signatures: Arc<SignatureCollection>,
/// A set of memfd images for memories, if any. Note that module
/// instantiation (hence the need for lazy init) may happen for the
/// same module concurrently in multiple Stores, so we use a
/// instantiation (hence the need for lazy init) may happen for
/// the same module concurrently in multiple Stores, so we use a
/// OnceCell.
memfds: OnceCell<Option<Arc<ModuleMemFds>>>,
memfds: OnceCell<Option<ModuleMemFds>>,
}
impl Module {
@@ -421,6 +424,11 @@ impl Module {
translation.try_paged_init();
}
// Attempt to convert table initializer segments to
// FuncTable representation where possible, to enable
// table lazy init.
translation.try_func_table_init();
let (mmap, info) =
wasmtime_jit::finish_compile(translation, obj, funcs, trampolines, tunables)?;
Ok((mmap, Some(info)))
@@ -723,19 +731,6 @@ impl Module {
&self.inner.signatures
}
pub(crate) fn memfds(&self) -> Result<Option<&Arc<ModuleMemFds>>> {
if !self.engine().config().memfd {
return Ok(None);
}
Ok(self
.inner
.memfds
.get_or_try_init(|| {
ModuleMemFds::new(self.inner.module.module(), self.inner.module.wasm_data())
})?
.as_ref())
}
/// Looks up the module upvar value at the `index` specified.
///
/// Note that this panics if `index` is out of bounds since this should
@@ -953,6 +948,14 @@ impl Module {
pub fn engine(&self) -> &Engine {
&self.inner.engine
}
/// Returns the `ModuleInner` cast as `ModuleRuntimeInfo` for use
/// by the runtime.
pub(crate) fn runtime_info(&self) -> Arc<dyn wasmtime_runtime::ModuleRuntimeInfo> {
// N.B.: this needs to return a clone because we cannot
// statically cast the &Arc<ModuleInner> to &Arc<dyn Trait...>.
self.inner.clone()
}
}
fn _assert_send_sync() {
@@ -987,3 +990,131 @@ impl std::hash::Hash for HashedEngineCompileEnv<'_> {
env!("CARGO_PKG_VERSION").hash(hasher);
}
}
impl wasmtime_runtime::ModuleRuntimeInfo for ModuleInner {
fn module(&self) -> &Arc<wasmtime_environ::Module> {
self.module.module()
}
fn signature(&self, index: SignatureIndex) -> VMSharedSignatureIndex {
self.signatures.as_module_map()[index]
}
fn image_base(&self) -> usize {
self.module.code().as_ptr() as usize
}
fn function_info(&self, index: DefinedFuncIndex) -> &FunctionInfo {
self.module.func_info(index)
}
fn memfd_image(&self, memory: DefinedMemoryIndex) -> Result<Option<&Arc<MemoryMemFd>>> {
if !self.engine.config().memfd {
return Ok(None);
}
let memfds = self
.memfds
.get_or_try_init(|| ModuleMemFds::new(self.module.module(), self.module.wasm_data()))?;
Ok(memfds
.as_ref()
.and_then(|memfds| memfds.get_memory_image(memory)))
}
fn unique_id(&self) -> Option<CompiledModuleId> {
Some(self.module.unique_id())
}
fn wasm_data(&self) -> &[u8] {
self.module.wasm_data()
}
}
/// A barebones implementation of ModuleRuntimeInfo that is useful for
/// cases where a purpose-built environ::Module is used and a full
/// CompiledModule does not exist (for example, for tests or for the
/// default-callee instance).
pub(crate) struct BareModuleInfo {
module: Arc<wasmtime_environ::Module>,
image_base: usize,
one_signature: Option<(SignatureIndex, VMSharedSignatureIndex)>,
function_info: PrimaryMap<DefinedFuncIndex, FunctionInfo>,
}
impl BareModuleInfo {
pub(crate) fn empty(module: Arc<wasmtime_environ::Module>) -> Self {
BareModuleInfo {
module,
image_base: 0,
one_signature: None,
function_info: PrimaryMap::default(),
}
}
pub(crate) fn maybe_imported_func(
module: Arc<wasmtime_environ::Module>,
one_signature: Option<(SignatureIndex, VMSharedSignatureIndex)>,
) -> Self {
BareModuleInfo {
module,
image_base: 0,
one_signature,
function_info: PrimaryMap::default(),
}
}
pub(crate) fn one_func(
module: Arc<wasmtime_environ::Module>,
image_base: usize,
info: FunctionInfo,
signature_id: SignatureIndex,
signature: VMSharedSignatureIndex,
) -> Self {
let mut function_info = PrimaryMap::with_capacity(1);
function_info.push(info);
BareModuleInfo {
module,
image_base,
function_info,
one_signature: Some((signature_id, signature)),
}
}
pub(crate) fn into_traitobj(self) -> Arc<dyn wasmtime_runtime::ModuleRuntimeInfo> {
Arc::new(self)
}
}
impl wasmtime_runtime::ModuleRuntimeInfo for BareModuleInfo {
fn module(&self) -> &Arc<wasmtime_environ::Module> {
&self.module
}
fn signature(&self, index: SignatureIndex) -> VMSharedSignatureIndex {
let (signature_id, signature) = self
.one_signature
.expect("Signature for one function should be present if queried");
assert_eq!(index, signature_id);
signature
}
fn image_base(&self) -> usize {
self.image_base
}
fn function_info(&self, index: DefinedFuncIndex) -> &FunctionInfo {
&self.function_info[index]
}
fn memfd_image(&self, _memory: DefinedMemoryIndex) -> Result<Option<&Arc<MemoryMemFd>>> {
Ok(None)
}
fn unique_id(&self) -> Option<CompiledModuleId> {
None
}
fn wasm_data(&self) -> &[u8] {
&[]
}
}

View File

@@ -76,6 +76,7 @@
//! contents of `StoreOpaque`. This is an invariant that we, as the authors of
//! `wasmtime`, must uphold for the public interface to be safe.
use crate::module::BareModuleInfo;
use crate::{module::ModuleRegistry, Engine, Module, Trap, Val, ValRaw};
use anyhow::{bail, Result};
use std::cell::UnsafeCell;
@@ -409,7 +410,6 @@ impl<T> Store<T> {
/// tables created to 10,000. This can be overridden with the
/// [`Store::limiter`] configuration method.
pub fn new(engine: &Engine, data: T) -> Self {
let functions = &Default::default();
// Wasmtime uses the callee argument to host functions to learn about
// the original pointer to the `Store` itself, allowing it to
// reconstruct a `StoreContextMut<T>`. When we initially call a `Func`,
@@ -419,18 +419,13 @@ impl<T> Store<T> {
// is never null.
let default_callee = unsafe {
let module = Arc::new(wasmtime_environ::Module::default());
let shim = BareModuleInfo::empty(module).into_traitobj();
OnDemandInstanceAllocator::default()
.allocate(InstanceAllocationRequest {
host_state: Box::new(()),
image_base: 0,
functions,
shared_signatures: None.into(),
imports: Default::default(),
module: &module,
unique_id: None,
memfds: None,
store: StorePtr::empty(),
wasm_data: &[],
runtime_info: &shim,
})
.expect("failed to allocate default callee")
};

View File

@@ -11,12 +11,13 @@ pub use self::func::*;
use self::global::create_global;
use self::memory::create_memory;
use self::table::create_table;
use crate::module::BareModuleInfo;
use crate::store::{InstanceId, StoreOpaque};
use crate::{GlobalType, MemoryType, TableType, Val};
use anyhow::Result;
use std::any::Any;
use std::sync::Arc;
use wasmtime_environ::{EntityIndex, GlobalIndex, MemoryIndex, Module, TableIndex};
use wasmtime_environ::{EntityIndex, GlobalIndex, MemoryIndex, Module, SignatureIndex, TableIndex};
use wasmtime_runtime::{
Imports, InstanceAllocationRequest, InstanceAllocator, OnDemandInstanceAllocator, StorePtr,
VMFunctionImport, VMSharedSignatureIndex,
@@ -27,11 +28,10 @@ fn create_handle(
store: &mut StoreOpaque,
host_state: Box<dyn Any + Send + Sync>,
func_imports: &[VMFunctionImport],
shared_signature_id: Option<VMSharedSignatureIndex>,
one_signature: Option<(SignatureIndex, VMSharedSignatureIndex)>,
) -> Result<InstanceId> {
let mut imports = Imports::default();
imports.functions = func_imports;
let functions = &Default::default();
unsafe {
let config = store.engine().config();
@@ -39,18 +39,14 @@ fn create_handle(
// The configured instance allocator should only be used when creating module instances
// as we don't want host objects to count towards instance limits.
let module = Arc::new(module);
let runtime_info =
&BareModuleInfo::maybe_imported_func(module, one_signature).into_traitobj();
let handle = OnDemandInstanceAllocator::new(config.mem_creator.clone(), 0).allocate(
InstanceAllocationRequest {
module: &module,
unique_id: None,
memfds: None,
functions,
image_base: 0,
imports,
shared_signatures: shared_signature_id.into(),
host_state,
store: StorePtr::new(store.traitobj()),
wasm_data: &[],
runtime_info,
},
)?;
@@ -65,7 +61,7 @@ pub fn generate_global_export(
) -> Result<wasmtime_runtime::ExportGlobal> {
let instance = create_global(store, gt, val)?;
let idx = EntityIndex::Global(GlobalIndex::from_u32(0));
match store.instance(instance).lookup_by_declaration(&idx) {
match store.instance_mut(instance).lookup_by_declaration(&idx) {
wasmtime_runtime::Export::Global(g) => Ok(g),
_ => unreachable!(),
}
@@ -77,7 +73,7 @@ pub fn generate_memory_export(
) -> Result<wasmtime_runtime::ExportMemory> {
let instance = create_memory(store, m)?;
let idx = EntityIndex::Memory(MemoryIndex::from_u32(0));
match store.instance(instance).lookup_by_declaration(&idx) {
match store.instance_mut(instance).lookup_by_declaration(&idx) {
wasmtime_runtime::Export::Memory(m) => Ok(m),
_ => unreachable!(),
}
@@ -89,7 +85,7 @@ pub fn generate_table_export(
) -> Result<wasmtime_runtime::ExportTable> {
let instance = create_table(store, t)?;
let idx = EntityIndex::Table(TableIndex::from_u32(0));
match store.instance(instance).lookup_by_declaration(&idx) {
match store.instance_mut(instance).lookup_by_declaration(&idx) {
wasmtime_runtime::Export::Table(t) => Ok(t),
_ => unreachable!(),
}

View File

@@ -1,11 +1,12 @@
//! Support for a calling of an imported function.
use crate::module::BareModuleInfo;
use crate::{Engine, FuncType, Trap, ValRaw};
use anyhow::Result;
use std::any::Any;
use std::panic::{self, AssertUnwindSafe};
use std::sync::Arc;
use wasmtime_environ::{EntityIndex, Module, ModuleType, PrimaryMap, SignatureIndex};
use wasmtime_environ::{EntityIndex, FunctionInfo, Module, ModuleType, SignatureIndex};
use wasmtime_jit::{CodeMemory, MmapVec, ProfilingAgent};
use wasmtime_runtime::{
Imports, InstanceAllocationRequest, InstanceAllocator, InstanceHandle,
@@ -148,8 +149,6 @@ pub unsafe fn create_raw_function(
host_state: Box<dyn Any + Send + Sync>,
) -> Result<InstanceHandle> {
let mut module = Module::new();
let mut functions = PrimaryMap::new();
functions.push(Default::default());
let sig_id = SignatureIndex::from_u32(u32::max_value() - 1);
module.types.push(ModuleType::Function(sig_id));
@@ -159,18 +158,21 @@ pub unsafe fn create_raw_function(
.insert(String::new(), EntityIndex::Function(func_id));
let module = Arc::new(module);
let runtime_info = &BareModuleInfo::one_func(
module.clone(),
(*func).as_ptr() as usize,
FunctionInfo::default(),
sig_id,
sig,
)
.into_traitobj();
Ok(
OnDemandInstanceAllocator::default().allocate(InstanceAllocationRequest {
module: &module,
unique_id: None,
memfds: None,
functions: &functions,
image_base: (*func).as_ptr() as usize,
imports: Imports::default(),
shared_signatures: sig.into(),
host_state,
store: StorePtr::empty(),
wasm_data: &[],
runtime_info,
})?,
)
}

View File

@@ -9,7 +9,7 @@ pub fn create_global(store: &mut StoreOpaque, gt: &GlobalType, val: Val) -> Resu
let mut module = Module::new();
let mut func_imports = Vec::new();
let mut externref_init = None;
let mut shared_signature_id = None;
let mut one_signature = None;
let global = Global {
wasm_ty: gt.content().to_wasm_type(),
@@ -37,8 +37,8 @@ pub fn create_global(store: &mut StoreOpaque, gt: &GlobalType, val: Val) -> Resu
// our global with a `ref.func` to grab that imported function.
let f = f.caller_checked_anyfunc(store);
let f = unsafe { f.as_ref() };
shared_signature_id = Some(f.type_index);
let sig_id = SignatureIndex::from_u32(u32::max_value() - 1);
one_signature = Some((sig_id, f.type_index));
module.types.push(ModuleType::Function(sig_id));
let func_index = module.functions.push(sig_id);
module.num_imported_funcs = 1;
@@ -64,16 +64,10 @@ pub fn create_global(store: &mut StoreOpaque, gt: &GlobalType, val: Val) -> Resu
module
.exports
.insert(String::new(), EntityIndex::Global(global_id));
let id = create_handle(
module,
store,
Box::new(()),
&func_imports,
shared_signature_id,
)?;
let id = create_handle(module, store, Box::new(()), &func_imports, one_signature)?;
if let Some(x) = externref_init {
let instance = store.instance(id);
let instance = store.instance_mut(id);
match instance.lookup_by_declaration(&EntityIndex::Global(global_id)) {
wasmtime_runtime::Export::Global(g) => unsafe {
*(*g.definition).as_externref_mut() = Some(x.inner);