Skip to content

Commit 16029c3

Browse files
committed
Plumb initial translation of thread.spawn_indirect
This change in no way implements actual spawning of threads; it simply translates the CM builtin `thread.spawn_indirect` to invoke a real Wasmtime libcall that does some preliminary checks and panics. It is useful, though, to (1) exercise all the new table-related functions and (2) show the limits of Wasmtime's current state: we will need a way of interacting with shared functions and possibly some form of shared store.
1 parent c7e092d commit 16029c3

File tree

8 files changed

+209
-7
lines changed

8 files changed

+209
-7
lines changed

crates/cranelift/src/compiler/component.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,11 @@ impl<'a> TrampolineCompiler<'a> {
280280
rets[0] = me.raise_if_negative_one_and_truncate(rets[0]);
281281
})
282282
}
283+
Trampoline::ThreadSpawnIndirect { ty: _, table } => {
284+
// TODO: eventually pass through the `ty` argument to check the
285+
// table's funcref signature.
286+
self.translate_thread_spawn_indirect(*table)
287+
}
283288
}
284289
}
285290

@@ -1331,6 +1336,26 @@ impl<'a> TrampolineCompiler<'a> {
13311336
);
13321337
}
13331338

1339+
fn translate_thread_spawn_indirect(&mut self, table: RuntimeTableIndex) {
1340+
let args = self.builder.func.dfg.block_params(self.block0).to_vec();
1341+
let vmctx = args[0];
1342+
let element = args[1];
1343+
let context = args[2];
1344+
1345+
// table: u32
1346+
let table = self
1347+
.builder
1348+
.ins()
1349+
.iconst(ir::types::I32, i64::from(self.offsets.runtime_table(table)));
1350+
1351+
self.translate_intrinsic_libcall(
1352+
vmctx,
1353+
host::thread_spawn_indirect,
1354+
&[vmctx, table, element, context],
1355+
TrapSentinel::Falsy,
1356+
);
1357+
}
1358+
13341359
/// Loads a host function pointer for a libcall stored at the `offset`
13351360
/// provided in the libcalls array.
13361361
///

crates/environ/src/component.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,9 @@ macro_rules! foreach_builtin_component_function {
154154
#[cfg(feature = "component-model-async")]
155155
error_context_transfer(vmctx: vmctx, src_idx: u32, src_table: u32, dst_table: u32) -> u64;
156156

157+
#[cfg(feature = "threads")]
158+
thread_spawn_indirect(vmctx: vmctx, table: u32, element: u32, context: u32 ) -> u64;
159+
157160
trap(vmctx: vmctx, code: u8);
158161

159162
utf8_to_utf8(src: ptr_u8, len: size, dst: ptr_u8) -> bool;

crates/environ/src/component/dfg.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ pub struct ComponentDfg {
6666
/// Same as `reallocs`, but for post-return.
6767
pub memories: Intern<MemoryId, CoreExport<MemoryIndex>>,
6868

69+
/// Tables used by this component.
70+
pub tables: Intern<TableId, CoreExport<TableIndex>>,
71+
6972
/// Metadata about identified fused adapters.
7073
///
7174
/// Note that this list is required to be populated in-order where the
@@ -393,6 +396,10 @@ pub enum Trampoline {
393396
FutureTransfer,
394397
StreamTransfer,
395398
ErrorContextTransfer,
399+
ThreadSpawnIndirect {
400+
ty: TypeFuncIndex,
401+
table: TableId,
402+
},
396403
}
397404

398405
#[derive(Copy, Clone, Hash, Eq, PartialEq)]
@@ -706,6 +713,15 @@ impl LinearizeDfg<'_> {
706713
)
707714
}
708715

716+
fn runtime_table(&mut self, tbl: TableId) -> RuntimeTableIndex {
717+
self.intern(
718+
tbl,
719+
|me| &mut me.runtime_tables,
720+
|me, tbl| me.core_export(&me.dfg.tables[tbl]),
721+
|index, export| GlobalInitializer::ExtractTable(ExtractTable { index, export }),
722+
)
723+
}
724+
709725
fn runtime_realloc(&mut self, realloc: ReallocId) -> RuntimeReallocIndex {
710726
self.intern(
711727
realloc,
@@ -896,6 +912,12 @@ impl LinearizeDfg<'_> {
896912
Trampoline::FutureTransfer => info::Trampoline::FutureTransfer,
897913
Trampoline::StreamTransfer => info::Trampoline::StreamTransfer,
898914
Trampoline::ErrorContextTransfer => info::Trampoline::ErrorContextTransfer,
915+
Trampoline::ThreadSpawnIndirect { ty, table } => {
916+
info::Trampoline::ThreadSpawnIndirect {
917+
ty: *ty,
918+
table: self.runtime_table(*table),
919+
}
920+
}
899921
};
900922
let i1 = self.trampolines.push(*signature);
901923
let i2 = self.trampoline_defs.push(trampoline);

crates/environ/src/component/info.rs

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -970,34 +970,43 @@ pub enum Trampoline {
970970
post_return: Option<RuntimePostReturnIndex>,
971971
},
972972

973-
/// An intrinisic used by FACT-generated modules to (partially or entirely) transfer
973+
/// An intrinsic used by FACT-generated modules to (partially or entirely) transfer
974974
/// ownership of a `future`.
975975
///
976-
/// Transfering a `future` can either mean giving away the readable end
976+
/// Transferring a `future` can either mean giving away the readable end
977977
/// while retaining the writable end or only the former, depending on the
978978
/// ownership status of the `future`.
979979
FutureTransfer,
980980

981-
/// An intrinisic used by FACT-generated modules to (partially or entirely) transfer
981+
/// An intrinsic used by FACT-generated modules to (partially or entirely) transfer
982982
/// ownership of a `stream`.
983983
///
984-
/// Transfering a `stream` can either mean giving away the readable end
984+
/// Transferring a `stream` can either mean giving away the readable end
985985
/// while retaining the writable end or only the former, depending on the
986986
/// ownership status of the `stream`.
987987
StreamTransfer,
988988

989-
/// An intrinisic used by FACT-generated modules to (partially or entirely) transfer
989+
/// An intrinsic used by FACT-generated modules to (partially or entirely) transfer
990990
/// ownership of an `error-context`.
991991
///
992992
/// Unlike futures, streams, and resource handles, `error-context` handles
993993
/// are reference counted, meaning that sharing the handle with another
994994
/// component does not invalidate the handle in the original component.
995995
ErrorContextTransfer,
996+
997+
/// The `thread.spawn_indirect` intrinsic to spawn a thread from a function
998+
/// of type `ty` stored in a `table`.
999+
ThreadSpawnIndirect {
1000+
/// TODO
1001+
ty: TypeFuncIndex,
1002+
/// TODO
1003+
table: RuntimeTableIndex,
1004+
},
9961005
}
9971006

9981007
impl Trampoline {
9991008
/// Returns the name to use for the symbol of this trampoline in the final
1000-
/// compiled artifact
1009+
/// compiled artifact.
10011010
pub fn symbol_name(&self) -> String {
10021011
use Trampoline::*;
10031012
match self {
@@ -1053,6 +1062,7 @@ impl Trampoline {
10531062
FutureTransfer => format!("future-transfer"),
10541063
StreamTransfer => format!("stream-transfer"),
10551064
ErrorContextTransfer => format!("error-context-transfer"),
1065+
ThreadSpawnIndirect { .. } => format!("thread-spawn-indirect"),
10561066
}
10571067
}
10581068
}

crates/environ/src/component/translate.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,13 @@ enum LocalInitializer<'data> {
328328

329329
// export section
330330
Export(ComponentItem),
331+
332+
// threads
333+
ThreadSpawnIndirect {
334+
ty: ComponentFuncTypeId,
335+
table: TableIndex,
336+
func: ModuleInternedTypeIndex,
337+
},
331338
}
332339

333340
/// The "closure environment" of components themselves.
@@ -810,10 +817,19 @@ impl<'a, 'data> Translator<'a, 'data> {
810817
core_func_index += 1;
811818
LocalInitializer::ErrorContextDrop { func }
812819
}
820+
wasmparser::CanonicalFunction::ThreadSpawnIndirect {
821+
func_ty_index,
822+
table_index,
823+
} => {
824+
let ty = types.component_any_type_at(func_ty_index).unwrap_func();
825+
let func = self.core_func_signature(core_func_index)?;
826+
let table = TableIndex::from_u32(table_index);
827+
core_func_index += 1;
828+
LocalInitializer::ThreadSpawnIndirect { ty, func, table }
829+
}
813830
wasmparser::CanonicalFunction::ContextGet(..)
814831
| wasmparser::CanonicalFunction::ContextSet(..)
815832
| wasmparser::CanonicalFunction::ThreadSpawnRef { .. }
816-
| wasmparser::CanonicalFunction::ThreadSpawnIndirect { .. }
817833
| wasmparser::CanonicalFunction::ThreadAvailableParallelism => {
818834
bail!("unsupported intrinsic")
819835
}

crates/environ/src/component/translate/inline.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -991,6 +991,16 @@ impl<'a> Inliner<'a> {
991991
.push((*func, dfg::Trampoline::ErrorContextDrop { ty }));
992992
frame.funcs.push(dfg::CoreDef::Trampoline(index));
993993
}
994+
ThreadSpawnIndirect { func, ty, table } => {
995+
let (table, _) = self.table(frame, types, *table);
996+
let table = self.result.tables.push(table);
997+
let ty = types.convert_component_func_type(frame.translation.types_ref(), *ty)?;
998+
let index = self
999+
.result
1000+
.trampolines
1001+
.push((*func, dfg::Trampoline::ThreadSpawnIndirect { ty, table }));
1002+
frame.funcs.push(dfg::CoreDef::Trampoline(index));
1003+
}
9941004

9951005
ModuleStatic(idx, ty) => {
9961006
frame.modules.push(ModuleDef::Static(*idx, *ty));
@@ -1317,6 +1327,41 @@ impl<'a> Inliner<'a> {
13171327
(memory, memory64)
13181328
}
13191329

1330+
fn table(
1331+
&mut self,
1332+
frame: &InlinerFrame<'a>,
1333+
types: &ComponentTypesBuilder,
1334+
table: TableIndex,
1335+
) -> (dfg::CoreExport<TableIndex>, bool) {
1336+
let table = frame.tables[table].clone().map_index(|i| match i {
1337+
EntityIndex::Table(i) => i,
1338+
_ => unreachable!(),
1339+
});
1340+
let table64 = match &self.runtime_instances[table.instance] {
1341+
InstanceModule::Static(idx) => match &table.item {
1342+
ExportItem::Index(i) => {
1343+
let ty = &self.nested_modules[*idx].module.tables[*i];
1344+
match ty.idx_type {
1345+
IndexType::I32 => false,
1346+
IndexType::I64 => true,
1347+
}
1348+
}
1349+
ExportItem::Name(_) => unreachable!(),
1350+
},
1351+
InstanceModule::Import(ty) => match &table.item {
1352+
ExportItem::Name(name) => match types[*ty].exports[name] {
1353+
EntityType::Memory(m) => match m.idx_type {
1354+
IndexType::I32 => false,
1355+
IndexType::I64 => true,
1356+
},
1357+
_ => unreachable!(),
1358+
},
1359+
ExportItem::Index(_) => unreachable!(),
1360+
},
1361+
};
1362+
(table, table64)
1363+
}
1364+
13201365
/// Translates a `LocalCanonicalOptions` which indexes into the `frame`
13211366
/// specified into a runtime representation.
13221367
fn adapter_options(

crates/wasmtime/src/runtime/vm/component.rs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -745,6 +745,75 @@ impl ComponentInstance {
745745
_ = (src_idx, src, dst);
746746
todo!()
747747
}
748+
749+
#[cfg(feature = "threads")]
750+
pub(crate) fn thread_spawn_indirect(
751+
&mut self,
752+
table: u32,
753+
element: u32,
754+
_context: u32,
755+
) -> Result<u32> {
756+
use crate::vm::{Instance, TableElement};
757+
use crate::{Func, ValType};
758+
use core::iter;
759+
760+
// Retrieve the table referenced by the canonical builtin (i.e.,
761+
// `table`). By validation this is guaranteed to be a `shared funcref`
762+
// table.
763+
let VMTable { vmctx, from } = self.runtime_table(RuntimeTableIndex::from_u32(table));
764+
let element = u64::from(element);
765+
let table = unsafe {
766+
Instance::from_vmctx(vmctx.as_non_null(), |handle| {
767+
let idx = handle.table_index(from.as_non_null().as_ref());
768+
handle
769+
.get_defined_table_with_lazy_init(idx, iter::once(element))
770+
.as_ref()
771+
})
772+
};
773+
let table = *table
774+
.as_ref()
775+
.ok_or_else(|| anyhow!("failed to get table for thread spawn indirect"))?;
776+
777+
// Retrieve the actual function reference from the table. The CM
778+
// specification tells us to trap if
779+
let element = table.get(None, element);
780+
let element = element
781+
.ok_or_else(|| anyhow!("failed to get table element for thread spawn indirect"))?;
782+
match element {
783+
TableElement::FuncRef(Some(func)) => {
784+
// Build a `Func` from the function reference--this is
785+
// incorrect, but temporarily necessary! In the future this will
786+
// require additional infrastructure to build a `SharedFunc` or
787+
// some more-correct type. It is unclear yet how to do this from
788+
// a `Store` (e.g., `SharedStore`) but this use here--never
789+
// actually invoked!--makes the problem concrete (TODO).
790+
let store = unsafe { (*self.store()).store_opaque_mut() };
791+
let func = unsafe { Func::from_vm_func_ref(store, func) };
792+
793+
// Check the function signature matches what we expect.
794+
// Currently, this is temporarily set to `[i32] -> []` but in
795+
// the future, with some additional plumbing of the core type
796+
// index, we must check that the function signature matches
797+
// that (TODO).
798+
let ty = func.load_ty(store);
799+
if ty.params().len() != 1
800+
|| !matches!(ty.params().nth(0).unwrap(), ValType::I32)
801+
|| ty.results().len() != 0
802+
{
803+
bail!("thread start function signature is invalid");
804+
}
805+
806+
// At this point we should spawn the thread with the function
807+
// provided, returning 0 on success, -1 otherwise. None of that
808+
// infrastructure is built yet so we crash instead (TODO).
809+
unimplemented!("`thread.spawn_indirect` is not implemented yet");
810+
}
811+
TableElement::FuncRef(None) => {
812+
bail!("thread start function is not present in the table")
813+
}
814+
_ => bail!("thread start element is not a function reference"),
815+
}
816+
}
748817
}
749818

750819
impl VMComponentContext {

crates/wasmtime/src/runtime/vm/component/libcalls.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1267,3 +1267,15 @@ unsafe fn error_context_drop(
12671267
)
12681268
})
12691269
}
1270+
1271+
#[cfg(feature = "threads")]
1272+
unsafe fn thread_spawn_indirect(
1273+
vmctx: NonNull<VMComponentContext>,
1274+
table: u32,
1275+
element: u32,
1276+
context: u32,
1277+
) -> Result<u32> {
1278+
ComponentInstance::from_vmctx(vmctx, |instance| {
1279+
instance.thread_spawn_indirect(table, element, context)
1280+
})
1281+
}

0 commit comments

Comments
 (0)