Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 37 additions & 5 deletions crates/cranelift/src/func_environ.rs
Original file line number Diff line number Diff line change
Expand Up @@ -983,7 +983,7 @@ impl<'module_environment> FuncEnvironment<'module_environment> {
self.module.tables[index]
}

/// Cast the value to I64 and sign extend if necessary.
/// Cast the value to I64 and zero extend if necessary.
///
/// Returns the value casted to I64.
fn cast_index_to_i64(
Expand Down Expand Up @@ -6100,8 +6100,14 @@ impl FuncEnvironment<'_> {
}
}
}
for segment in self.translation.table_initialization.segments.iter() {
self.module_initialize_table_with_segment(builder, segment)?;
for (segment_index, segment) in self
.translation
.table_initialization
.segments
.iter()
.enumerate()
{
self.module_initialize_table_with_segment(builder, segment, segment_index)?;
}
match &self.translation.memory_init {
MemoryInit::Unprocessed(_) => unreachable!(),
Expand Down Expand Up @@ -6237,12 +6243,13 @@ impl FuncEnvironment<'_> {
///
/// Note that this isn't done for "precomputed" tables which won't have
/// initializers present. Additionally note that this doesn't use
/// `table.init` because the element segment won't actually be present
/// anywhere at runtime.
/// `table.init` because active segments are unavailable to that instruction.
/// Eligible function-index lists are retained separately for lazy reads.
fn module_initialize_table_with_segment(
&mut self,
builder: &mut FunctionBuilder,
segment: &TableSegment,
segment_index: usize,
) -> WasmResult<()> {
let offset = self.translate_const_expr(builder, &segment.offset)?;
let segment_len = builder.ins().iconst(
Expand All @@ -6267,6 +6274,31 @@ impl FuncEnvironment<'_> {
.table_init_per_element;
let fuel = self.pre_translate_bulk_op(builder, segment_len, cost);

if self.tunables.table_lazy_init
&& self.module.is_imported_table(segment.table_index)
&& matches!(segment.elements, TableSegmentElements::Functions(_))
{
// Preserve bounds checks and element fuel charges at the original
// initialization boundary; defer only construction of VMFuncRefs.
let vmctx = self.vmctx_val(&mut builder.cursor());
let table = builder
.ins()
.iconst(I32, i64::from(segment.table_index.as_u32()));
let segment_id = builder
.ins()
.iconst(I32, i64::from(u32::try_from(segment_index).unwrap()));
let offset = self.cast_index_to_i64(
&mut builder.cursor(),
offset,
self.module.tables[segment.table_index].idx_type,
);
let libcall = self.builtin_functions.table_init_func_segment(builder.func);
builder
.ins()
.call(libcall, &[vmctx, table, segment_id, offset]);
return self.post_translate_bulk_op(builder, fuel);
}

// Re-use the `table.set` translation for making this a simple function
// to define. That re-executes the bounds check which is a bit
// inefficient, but can be optimized in the future.
Expand Down
3 changes: 3 additions & 0 deletions crates/environ/src/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,9 @@ macro_rules! foreach_builtin_function {
);
#[cfg(feature = "stack-switching")]
asan_finish_switch_fiber(vmctx: vmctx, fake_stack: pointer, current_csi: pointer);
// Apply an active function segment without eagerly materializing
// all of its function records. Bounds and ordering remain eager.
table_init_func_segment(vmctx: vmctx, table: u32, segment: u32, offset: u64) -> bool;
}
};
}
Expand Down
17 changes: 17 additions & 0 deletions crates/environ/src/compile/module_environ.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1552,6 +1552,23 @@ impl ModuleTranslation<'_> {
pub fn finalize_table_init(&mut self, tunables: &Tunables, types: &mut ModuleTypesBuilder) {
if tunables.table_lazy_init {
self.try_func_table_init();
// try_func_table_init removes a prefix, so capture segment positions
// only afterwards to match startup translation. General expressions
// retain their existing eager evaluation and ordering.
self.module.deferred_table_segments = self
.table_initialization
.segments
.iter()
.map(|segment| match &segment.elements {
TableSegmentElements::Functions(indices)
if self.module.is_imported_table(segment.table_index) =>
{
indices.iter().copied().try_collect().panic_on_oom()
}
_ => TryVec::new(),
})
.try_collect()
.panic_on_oom();
}

// If any table has a non-null initializers, or if there's any active
Expand Down
24 changes: 16 additions & 8 deletions crates/environ/src/module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,19 +96,18 @@ pub struct TableInitialization {
/// Initial values for tables defined within the module itself.
///
/// This contains the initial values and initializers for tables defined
/// within a wasm, so excluding imported tables. This initializer can
/// represent null-initialized tables, element-initialized tables (e.g. with
/// the function-references proposal), or precomputed images of table
/// initialization. For example table initializers to a table that are all
/// in-bounds will get removed from `segment` and moved into
/// `initial_values` here.
/// within a wasm, so excluding imported tables. Initial values are null or
/// constant expressions (e.g. with the function-references proposal).
/// When an initial value is precomputed in [`Module::table_initialization`],
/// its entry here becomes [`TableInitialValue::Null`].
pub initial_values: TryPrimaryMap<DefinedTableIndex, TableInitialValue>,

/// Element segments present in the initial wasm module which are executed
/// at instantiation time.
///
/// These element segments are iterated over during instantiation to apply
/// any segments that weren't already moved into `initial_values` above.
/// Finalization removes segments folded into [`Module::table_initialization`].
/// The remaining segments execute in order during startup; their positions
/// also index [`Module::deferred_table_segments`] when lazy init is enabled.
pub segments: TryVec<TableSegment>,
}

Expand Down Expand Up @@ -201,6 +200,12 @@ pub struct Module {
/// to be infallible as part of [`ModuleTranslation::finalize_table_init`].
pub table_initialization: TryPrimaryMap<DefinedTableIndex, TryVec<FuncIndex>>,

/// Shared function indices for active imported-table segments, indexed by
/// their position in `TableInitialization::segments` after finalization.
/// Ineligible segments have empty lists. Runtime bindings identify the
/// contributing instance, which its Store retains even if startup traps.
pub deferred_table_segments: TryVec<TryVec<FuncIndex>>,

/// WebAssembly linear memory initializer.
///
/// This will track how memory is initialized, either exclusively via
Expand Down Expand Up @@ -335,6 +340,7 @@ impl Module {
exports: Default::default(),
startup: ModuleStartup::None,
table_initialization: Default::default(),
deferred_table_segments: Default::default(),
memory_initialization: Default::default(),
passive_elements: Default::default(),
runtime_data: Default::default(),
Expand Down Expand Up @@ -682,6 +688,7 @@ impl TypeTrace for Module {
exports: _,
startup,
table_initialization: _,
deferred_table_segments: _,
memory_initialization: _,
passive_elements: _,
runtime_data: _,
Expand Down Expand Up @@ -734,6 +741,7 @@ impl TypeTrace for Module {
exports: _,
startup,
table_initialization: _,
deferred_table_segments: _,
memory_initialization: _,
passive_elements: _,
runtime_data: _,
Expand Down
2 changes: 1 addition & 1 deletion crates/wasmtime/src/runtime/func.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2439,7 +2439,7 @@ impl HostFunc {
// SAFETY: this is an entrypoint of wasm which requires correct type
// ascription of `T` itself, meaning that this should be safe to call
// both `enter_host_from_wasm` as well as `unchecked_context_mut`.
unsafe { vm::Instance::enter_host_from_wasm(caller_vmctx, run) }
unsafe { vm::Instance::enter_host_from_wasm(caller_vmctx, true, run) }
}
/// Analog of [`Func::new_unchecked`]
///
Expand Down
4 changes: 3 additions & 1 deletion crates/wasmtime/src/runtime/vm/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,9 @@ impl ComponentInstance {
let store = unsafe { &mut *reference.store.0.as_ptr() };

let instance = Instance::from_wasmtime(store, reference.id);
catch_unwind_and_record_trap(store, UncaughtException::Trap, |store| f(store, instance))
catch_unwind_and_record_trap(store, UncaughtException::Trap, true, |store| {
f(store, instance)
})
}

/// Returns the `InstanceId` associated with the `vmctx` provided.
Expand Down
2 changes: 1 addition & 1 deletion crates/wasmtime/src/runtime/vm/debug_builtins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ pub unsafe extern "C" fn resolve_vmctx_memory_ptr(p: *const u32) -> *const u8 {
VMCTX_AND_MEMORY.0 != NonNull::dangling(),
"must call `__vmctx->set()` before resolving Wasm pointers"
);
Instance::enter_host_from_wasm(VMCTX_AND_MEMORY.0, |store, instance| {
Instance::enter_host_from_wasm(VMCTX_AND_MEMORY.0, true, |store, instance| {
let handle = store.instance_mut(instance);
assert!(
VMCTX_AND_MEMORY.1 < handle.env_module().memories.len(),
Expand Down
76 changes: 62 additions & 14 deletions crates/wasmtime/src/runtime/vm/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,7 @@ impl Instance {
#[inline]
pub(crate) unsafe fn enter_host_from_wasm<R>(
vmctx: NonNull<VMContext>,
call_hooks: bool,
f: impl FnOnce(&mut dyn VMStore, InstanceId) -> R,
) -> R::Abi
where
Expand All @@ -337,7 +338,7 @@ impl Instance {

// Thread the `store` and `instance` through panic/trap infrastructure
// back into `f`.
catch_unwind_and_record_trap(store, UncaughtException::Propagate, |store| {
catch_unwind_and_record_trap(store, UncaughtException::Propagate, call_hooks, |store| {
f(store, instance)
})
}
Expand Down Expand Up @@ -1051,6 +1052,41 @@ impl Instance {
self.wasm_data(range)
}

pub(crate) fn initialize_deferred_func_segment(
self: Pin<&mut Self>,
registry: &ModuleRegistry,
table_index: TableIndex,
segment: u32,
start: u64,
) -> Result<()> {
let (module, mut instance) = self.module_and_self();
let indices = &module.deferred_table_segments[segment as usize];
let source = instance.vmctx().into();
let deferred = {
let (idx, owner) = instance
.as_mut()
.defined_table_index_and_instance(table_index);
owner.get_defined_table(idx).defer_func_segment(
start,
indices.len() as u64,
source,
segment,
)?
};
if !deferred {
for (offset, &function) in indices.iter().enumerate() {
let function = instance.as_mut().get_func_ref(registry, function);
let (idx, owner) = instance
.as_mut()
.defined_table_index_and_instance(table_index);
owner
.get_defined_table(idx)
.set_func(start + offset as u64, function)?;
}
}
Ok(())
}

/// Get a table by index regardless of whether it is locally-defined
/// or an imported, foreign table. Ensure that the given range of
/// elements in the table is lazily initialized. We define this
Expand All @@ -1076,6 +1112,8 @@ impl Instance {
/// given the provided `idx`.
///
/// The `range` specified is eagerly initialized for funcref tables.
/// Deferred segments can access sibling instances even though this table
/// is locally defined; callers must not hold independent instance borrows.
pub fn get_defined_table_with_lazy_init(
mut self: Pin<&mut Self>,
registry: &ModuleRegistry,
Expand All @@ -1097,19 +1135,29 @@ impl Instance {
Err(_) => break,
};

// The table element `i` is uninitialized and is now being
// initialized. This must imply that a `precompiled` list of
// function indices is available for this table. The precompiled
// list is extracted and then it is consulted with `i` to
// determine the function that is going to be initialized. Note
// that `i` may be outside the limits of the static
// initialization so it's a fallible `get` instead of an index.
let module = self.env_module();
let precomputed = &module.table_initialization[idx];
// Panicking here helps catch bugs rather than silently truncating by accident.
let func_index = precomputed.get(usize::try_from(i).unwrap()).cloned();
let func_ref = func_index
.and_then(|func_index| self.as_mut().get_func_ref(registry, func_index));
// An imported-table segment can replace the owner's static
// initializer. Resolve its contributing instance first; outside
// those ranges, a missing static initializer means null.
let index = usize::try_from(i).unwrap();
let deferred = self.tables[idx].1.deferred_func_segment(index);
let func_ref = if let Some(deferred) = deferred {
// SAFETY: startup runs after Store insertion, so the source
// remains alive even if a later segment or start traps.
// Imports are from the same Store. sibling_vmctx_mut ties
// the source borrow to our exclusive instance borrow; no
// table or owner access occurs until it ends.
let source = unsafe {
self.as_mut()
.sibling_vmctx_mut(deferred.source.as_non_null())
};
let function = source.env_module().deferred_table_segments
[deferred.segment as usize][index - deferred.start];
source.get_func_ref(registry, function)
} else {
let precomputed = &self.env_module().table_initialization[idx];
let func_index = precomputed.get(index).copied();
func_index.and_then(|function| self.as_mut().get_func_ref(registry, function))
};
self.as_mut().tables_mut()[idx]
.1
.set_func(i, func_ref)
Expand Down
2 changes: 1 addition & 1 deletion crates/wasmtime/src/runtime/vm/interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -530,7 +530,7 @@ impl InterpreterRef<'_> {
// SAFETY: `caller` is valid and its store is not otherwise
// borrowed, per this function's contract.
unsafe {
Instance::enter_host_from_wasm(caller, |store, _instance| {
Instance::enter_host_from_wasm(caller, true, |store, _instance| {
trap = store
.store_opaque()
.modules()
Expand Down
23 changes: 22 additions & 1 deletion crates/wasmtime/src/runtime/vm/libcalls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ pub mod raw {
) $(-> libcall!(@ty $result))? {
$(#[cfg($attr)])?
unsafe {
Instance::enter_host_from_wasm(vmctx, |store, instance| {
Instance::enter_host_from_wasm(vmctx, libcall!(@call_hooks $name), |store, instance| {
super::$name(store, instance, $($pname),*)
})
}
Expand All @@ -149,6 +149,11 @@ pub mod raw {
)*
};

// Active segments previously used infallible ref_func calls without
// host transitions. Deferred initialization must retain that behavior.
(@call_hooks table_init_func_segment) => (false);
(@call_hooks $name:ident) => (true);

(@ty u32) => (u32);
(@ty u64) => (u64);
(@ty f32) => (f32);
Expand Down Expand Up @@ -405,6 +410,22 @@ fn ref_func(store: &mut dyn VMStore, instance: InstanceId, func_index: u32) -> N
.cast()
}

fn table_init_func_segment(
store: &mut dyn VMStore,
instance: InstanceId,
table_index: u32,
segment: u32,
offset: u64,
) -> Result<()> {
let (instance, registry) = store.instance_and_module_registry_mut(instance);
instance.initialize_deferred_func_segment(
registry,
TableIndex::from_u32(table_index),
segment,
offset,
)
}

// Returns a table entry after lazily initializing it.
fn table_get_lazy_init_func_ref(
store: &mut dyn VMStore,
Expand Down
Loading
Loading