Skip to content

Plumb initial translation of thread.spawn_indirect #10518

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
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
33 changes: 33 additions & 0 deletions crates/cranelift/src/compiler/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}

Expand Down Expand Up @@ -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.
///
Expand Down
3 changes: 3 additions & 0 deletions crates/environ/src/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
22 changes: 22 additions & 0 deletions crates/environ/src/component/dfg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ pub struct ComponentDfg {
/// Same as `reallocs`, but for post-return.
pub memories: Intern<MemoryId, CoreExport<MemoryIndex>>,

/// Tables used by this component.
pub tables: Intern<TableId, CoreExport<TableIndex>>,

/// Metadata about identified fused adapters.
///
/// Note that this list is required to be populated in-order where the
Expand Down Expand Up @@ -393,6 +396,10 @@ pub enum Trampoline {
FutureTransfer,
StreamTransfer,
ErrorContextTransfer,
ThreadSpawnIndirect {
ty: TypeFuncIndex,
table: TableId,
},
}

#[derive(Copy, Clone, Hash, Eq, PartialEq)]
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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);
Expand Down
23 changes: 17 additions & 6 deletions crates/environ/src/component/info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -970,34 +970,44 @@ pub enum Trampoline {
post_return: Option<RuntimePostReturnIndex>,
},

/// 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 {
Expand Down Expand Up @@ -1053,6 +1063,7 @@ impl Trampoline {
FutureTransfer => format!("future-transfer"),
StreamTransfer => format!("stream-transfer"),
ErrorContextTransfer => format!("error-context-transfer"),
ThreadSpawnIndirect { .. } => format!("thread-spawn-indirect"),
}
}
}
18 changes: 17 additions & 1 deletion crates/environ/src/component/translate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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")
}
Expand Down
45 changes: 45 additions & 0 deletions crates/environ/src/component/translate/inline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down Expand Up @@ -1317,6 +1327,41 @@ impl<'a> Inliner<'a> {
(memory, memory64)
}

fn table(
&mut self,
frame: &InlinerFrame<'a>,
types: &ComponentTypesBuilder,
table: TableIndex,
) -> (dfg::CoreExport<TableIndex>, 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(
Expand Down
70 changes: 70 additions & 0 deletions crates/wasmtime/src/runtime/vm/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<u32> {
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()
})
};
Comment on lines +766 to +773
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW technically some and/or most of this is going to go away. Lazy initialization is probably not going to be possible with shared tables.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup. What I'm trying to do is plumb through enough to (a) show how this is currently possible using unshared tables (while still immediately failing) and (b) create a use site here so that future refactoring considers this usage. I was thinking that tests could even check for the initial behavior that is functional today: i.e., we can't run a shared function, but at least we can check that using the wrong index results in a failure.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it's possible to hit this at runtime though? All components would fail validation unless they use shared tables, and shared talbes aren't implemented in Wasmtime, so it's not possible to instantiate any component using this intrinsic?

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 {
Expand Down
17 changes: 17 additions & 0 deletions crates/wasmtime/src/runtime/vm/component/libcalls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1267,3 +1267,20 @@ unsafe fn error_context_drop(
)
})
}

#[cfg(feature = "threads")]
unsafe fn thread_spawn_indirect(
vmctx: NonNull<VMComponentContext>,
func_ty: u32,
table: u32,
element: u32,
context: u32,
) -> Result<u32> {
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)
})
}
Original file line number Diff line number Diff line change
@@ -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")))
)
Loading