diff --git a/crates/cranelift/src/compiler/component.rs b/crates/cranelift/src/compiler/component.rs index 5fdb54777b2e..b208e96326f4 100644 --- a/crates/cranelift/src/compiler/component.rs +++ b/crates/cranelift/src/compiler/component.rs @@ -279,6 +279,9 @@ impl<'a> TrampolineCompiler<'a> { rets[0] = me.raise_if_negative_one_and_truncate(rets[0]); }) } + Trampoline::ThreadSpawnIndirect { ty, table } => { + self.translate_thread_spawn_indirect(*ty, *table) + } } } @@ -1330,6 +1333,36 @@ impl<'a> TrampolineCompiler<'a> { ); } + fn translate_thread_spawn_indirect( + &mut self, + func_ty: TypeFuncIndex, + table: RuntimeTableIndex, + ) { + let args = self.builder.func.dfg.block_params(self.block0).to_vec(); + let vmctx = args[0]; + let element = args[1]; + let context = args[2]; + + // func_ty: u32 + let func_ty = self + .builder + .ins() + .iconst(ir::types::I32, i64::from(func_ty.as_u32())); + + // table: u32 + let table = self + .builder + .ins() + .iconst(ir::types::I32, i64::from(table.as_u32())); + + self.translate_intrinsic_libcall( + vmctx, + host::thread_spawn_indirect, + &[vmctx, func_ty, table, element, context], + TrapSentinel::Falsy, + ); + } + /// Loads a host function pointer for a libcall stored at the `offset` /// provided in the libcalls array. /// diff --git a/crates/environ/src/component.rs b/crates/environ/src/component.rs index ed44ed62f234..3cf956791c8c 100644 --- a/crates/environ/src/component.rs +++ b/crates/environ/src/component.rs @@ -154,6 +154,9 @@ macro_rules! foreach_builtin_component_function { #[cfg(feature = "component-model-async")] error_context_transfer(vmctx: vmctx, src_idx: u32, src_table: u32, dst_table: u32) -> u64; + #[cfg(feature = "threads")] + thread_spawn_indirect(vmctx: vmctx, func_ty: u32, table: u32, element: u32, context: u32 ) -> u64; + trap(vmctx: vmctx, code: u8); utf8_to_utf8(src: ptr_u8, len: size, dst: ptr_u8) -> bool; diff --git a/crates/environ/src/component/dfg.rs b/crates/environ/src/component/dfg.rs index ebbd0e65dad1..3e99e7ee9242 100644 --- a/crates/environ/src/component/dfg.rs +++ b/crates/environ/src/component/dfg.rs @@ -66,6 +66,9 @@ pub struct ComponentDfg { /// Same as `reallocs`, but for post-return. pub memories: Intern>, + /// Tables used by this component. + pub tables: Intern>, + /// Metadata about identified fused adapters. /// /// Note that this list is required to be populated in-order where the @@ -393,6 +396,10 @@ pub enum Trampoline { FutureTransfer, StreamTransfer, ErrorContextTransfer, + ThreadSpawnIndirect { + ty: TypeFuncIndex, + table: TableId, + }, } #[derive(Copy, Clone, Hash, Eq, PartialEq)] @@ -706,6 +713,15 @@ impl LinearizeDfg<'_> { ) } + fn runtime_table(&mut self, tbl: TableId) -> RuntimeTableIndex { + self.intern( + tbl, + |me| &mut me.runtime_tables, + |me, tbl| me.core_export(&me.dfg.tables[tbl]), + |index, export| GlobalInitializer::ExtractTable(ExtractTable { index, export }), + ) + } + fn runtime_realloc(&mut self, realloc: ReallocId) -> RuntimeReallocIndex { self.intern( realloc, @@ -896,6 +912,12 @@ impl LinearizeDfg<'_> { Trampoline::FutureTransfer => info::Trampoline::FutureTransfer, Trampoline::StreamTransfer => info::Trampoline::StreamTransfer, Trampoline::ErrorContextTransfer => info::Trampoline::ErrorContextTransfer, + Trampoline::ThreadSpawnIndirect { ty, table } => { + info::Trampoline::ThreadSpawnIndirect { + ty: *ty, + table: self.runtime_table(*table), + } + } }; let i1 = self.trampolines.push(*signature); let i2 = self.trampoline_defs.push(trampoline); diff --git a/crates/environ/src/component/info.rs b/crates/environ/src/component/info.rs index f4207e6d4eb5..ade8b90ab926 100644 --- a/crates/environ/src/component/info.rs +++ b/crates/environ/src/component/info.rs @@ -970,34 +970,44 @@ pub enum Trampoline { post_return: Option, }, - /// An intrinisic used by FACT-generated modules to (partially or entirely) transfer + /// An intrinsic used by FACT-generated modules to (partially or entirely) transfer /// ownership of a `future`. /// - /// Transfering a `future` can either mean giving away the readable end + /// Transferring a `future` can either mean giving away the readable end /// while retaining the writable end or only the former, depending on the /// ownership status of the `future`. FutureTransfer, - /// An intrinisic used by FACT-generated modules to (partially or entirely) transfer + /// An intrinsic used by FACT-generated modules to (partially or entirely) transfer /// ownership of a `stream`. /// - /// Transfering a `stream` can either mean giving away the readable end + /// Transferring a `stream` can either mean giving away the readable end /// while retaining the writable end or only the former, depending on the /// ownership status of the `stream`. StreamTransfer, - /// An intrinisic used by FACT-generated modules to (partially or entirely) transfer + /// An intrinsic used by FACT-generated modules to (partially or entirely) transfer /// ownership of an `error-context`. /// /// Unlike futures, streams, and resource handles, `error-context` handles /// are reference counted, meaning that sharing the handle with another /// component does not invalidate the handle in the original component. ErrorContextTransfer, + + /// The `thread.spawn_indirect` intrinsic to spawn a thread from a function + /// of type `ty` stored in a `table`. + ThreadSpawnIndirect { + /// The type of the function that is being spawned. + ty: TypeFuncIndex, + /// The table from which to indirectly retrieve retrieve the spawn + /// function. + table: RuntimeTableIndex, + }, } impl Trampoline { /// Returns the name to use for the symbol of this trampoline in the final - /// compiled artifact + /// compiled artifact. pub fn symbol_name(&self) -> String { use Trampoline::*; match self { @@ -1053,6 +1063,7 @@ impl Trampoline { FutureTransfer => format!("future-transfer"), StreamTransfer => format!("stream-transfer"), ErrorContextTransfer => format!("error-context-transfer"), + ThreadSpawnIndirect { .. } => format!("thread-spawn-indirect"), } } } diff --git a/crates/environ/src/component/translate.rs b/crates/environ/src/component/translate.rs index c2c9a94aa75e..ed15429e4a1d 100644 --- a/crates/environ/src/component/translate.rs +++ b/crates/environ/src/component/translate.rs @@ -328,6 +328,13 @@ enum LocalInitializer<'data> { // export section Export(ComponentItem), + + // threads + ThreadSpawnIndirect { + ty: ComponentFuncTypeId, + table: TableIndex, + func: ModuleInternedTypeIndex, + }, } /// The "closure environment" of components themselves. @@ -810,10 +817,19 @@ impl<'a, 'data> Translator<'a, 'data> { core_func_index += 1; LocalInitializer::ErrorContextDrop { func } } + wasmparser::CanonicalFunction::ThreadSpawnIndirect { + func_ty_index, + table_index, + } => { + let ty = types.component_any_type_at(func_ty_index).unwrap_func(); + let func = self.core_func_signature(core_func_index)?; + let table = TableIndex::from_u32(table_index); + core_func_index += 1; + LocalInitializer::ThreadSpawnIndirect { ty, func, table } + } wasmparser::CanonicalFunction::ContextGet(..) | wasmparser::CanonicalFunction::ContextSet(..) | wasmparser::CanonicalFunction::ThreadSpawnRef { .. } - | wasmparser::CanonicalFunction::ThreadSpawnIndirect { .. } | wasmparser::CanonicalFunction::ThreadAvailableParallelism => { bail!("unsupported intrinsic") } diff --git a/crates/environ/src/component/translate/inline.rs b/crates/environ/src/component/translate/inline.rs index a2a2f211ec97..e4bd59666248 100644 --- a/crates/environ/src/component/translate/inline.rs +++ b/crates/environ/src/component/translate/inline.rs @@ -991,6 +991,16 @@ impl<'a> Inliner<'a> { .push((*func, dfg::Trampoline::ErrorContextDrop { ty })); frame.funcs.push(dfg::CoreDef::Trampoline(index)); } + ThreadSpawnIndirect { func, ty, table } => { + let (table, _) = self.table(frame, types, *table); + let table = self.result.tables.push(table); + let ty = types.convert_component_func_type(frame.translation.types_ref(), *ty)?; + let index = self + .result + .trampolines + .push((*func, dfg::Trampoline::ThreadSpawnIndirect { ty, table })); + frame.funcs.push(dfg::CoreDef::Trampoline(index)); + } ModuleStatic(idx, ty) => { frame.modules.push(ModuleDef::Static(*idx, *ty)); @@ -1317,6 +1327,41 @@ impl<'a> Inliner<'a> { (memory, memory64) } + fn table( + &mut self, + frame: &InlinerFrame<'a>, + types: &ComponentTypesBuilder, + table: TableIndex, + ) -> (dfg::CoreExport, bool) { + let table = frame.tables[table].clone().map_index(|i| match i { + EntityIndex::Table(i) => i, + _ => unreachable!(), + }); + let table64 = match &self.runtime_instances[table.instance] { + InstanceModule::Static(idx) => match &table.item { + ExportItem::Index(i) => { + let ty = &self.nested_modules[*idx].module.tables[*i]; + match ty.idx_type { + IndexType::I32 => false, + IndexType::I64 => true, + } + } + ExportItem::Name(_) => unreachable!(), + }, + InstanceModule::Import(ty) => match &table.item { + ExportItem::Name(name) => match types[*ty].exports[name] { + EntityType::Memory(m) => match m.idx_type { + IndexType::I32 => false, + IndexType::I64 => true, + }, + _ => unreachable!(), + }, + ExportItem::Index(_) => unreachable!(), + }, + }; + (table, table64) + } + /// Translates a `LocalCanonicalOptions` which indexes into the `frame` /// specified into a runtime representation. fn adapter_options( diff --git a/crates/wasmtime/src/runtime/vm/component.rs b/crates/wasmtime/src/runtime/vm/component.rs index c6aa14f056e3..cd244e843f1c 100644 --- a/crates/wasmtime/src/runtime/vm/component.rs +++ b/crates/wasmtime/src/runtime/vm/component.rs @@ -745,6 +745,76 @@ impl ComponentInstance { _ = (src_idx, src, dst); todo!() } + + #[cfg(feature = "threads")] + pub(crate) fn thread_spawn_indirect( + &mut self, + _func_ty: TypeFuncIndex, + table: RuntimeTableIndex, + element: u32, + _context: u32, + ) -> Result { + use crate::vm::{Instance, TableElement}; + use crate::{Func, ValType}; + use core::iter; + + // Retrieve the table referenced by the canonical builtin (i.e., + // `table`). By validation this is guaranteed to be a `shared funcref` + // table. + let VMTable { vmctx, from } = self.runtime_table(table); + let element = u64::from(element); + let table = unsafe { + Instance::from_vmctx(vmctx.as_non_null(), |handle| { + let idx = handle.table_index(from.as_non_null().as_ref()); + handle + .get_defined_table_with_lazy_init(idx, iter::once(element)) + .as_ref() + }) + }; + let table = *table + .as_ref() + .ok_or_else(|| anyhow!("failed to get table for thread spawn indirect"))?; + + // Retrieve the actual function reference from the table. The CM + // specification tells us to trap if + let element = table.get(None, element); + let element = element + .ok_or_else(|| anyhow!("failed to get table element for thread spawn indirect"))?; + match element { + TableElement::FuncRef(Some(func)) => { + // Build a `Func` from the function reference--this is + // incorrect, but temporarily necessary! In the future this will + // require additional infrastructure to build a `SharedFunc` or + // some more-correct type. It is unclear yet how to do this from + // a `Store` (e.g., `SharedStore`) but this use here--never + // actually invoked!--makes the problem concrete (TODO). + let store = unsafe { (*self.store()).store_opaque_mut() }; + let func = unsafe { Func::from_vm_func_ref(store, func) }; + + // Check the function signature matches what we expect. + // Currently, this is temporarily set to `[i32] -> []` but in + // the future, with some additional plumbing of the core type + // index, we must check that the function signature matches + // that (TODO). + let ty = func.load_ty(store); + if ty.params().len() != 1 + || !matches!(ty.params().nth(0).unwrap(), ValType::I32) + || ty.results().len() != 0 + { + bail!("thread start function signature is invalid"); + } + + // At this point we should spawn the thread with the function + // provided, returning 0 on success, -1 otherwise. None of that + // infrastructure is built yet so we crash instead (TODO). + unimplemented!("`thread.spawn_indirect` is not implemented yet"); + } + TableElement::FuncRef(None) => { + bail!("thread start function is not present in the table") + } + _ => bail!("thread start element is not a function reference"), + } + } } impl VMComponentContext { diff --git a/crates/wasmtime/src/runtime/vm/component/libcalls.rs b/crates/wasmtime/src/runtime/vm/component/libcalls.rs index 47c475af6924..20fedcf9167f 100644 --- a/crates/wasmtime/src/runtime/vm/component/libcalls.rs +++ b/crates/wasmtime/src/runtime/vm/component/libcalls.rs @@ -1267,3 +1267,20 @@ unsafe fn error_context_drop( ) }) } + +#[cfg(feature = "threads")] +unsafe fn thread_spawn_indirect( + vmctx: NonNull, + func_ty: u32, + table: u32, + element: u32, + context: u32, +) -> Result { + use wasmtime_environ::component::{RuntimeTableIndex, TypeFuncIndex}; + + let func_ty = TypeFuncIndex::from_bits(func_ty); + let table = RuntimeTableIndex::from_u32(table); + ComponentInstance::from_vmctx(vmctx, |instance| { + instance.thread_spawn_indirect(func_ty, table, element, context) + }) +} diff --git a/tests/misc_testsuite/shared-everything-threads/builtins.wast b/tests/misc_testsuite/shared-everything-threads/builtins.wast new file mode 100644 index 000000000000..49d03424be1d --- /dev/null +++ b/tests/misc_testsuite/shared-everything-threads/builtins.wast @@ -0,0 +1,8 @@ +;;! shared_everything_threads = true +;;! reference_types = true +(component + (core type $start (shared (func (param $context i32)))) + (core module $libc (table (export "start-table") shared 1 (ref null (shared func)))) + (core instance $libc (instantiate $libc)) + (core func $spawn_indirect (canon thread.spawn_indirect $start (table $libc "start-table"))) +)