Skip to content

Commit 27a0c8f

Browse files
committed
Use generic array-to-wasm trampolines for components
This commit updates the implementation of compiling array-to-wasm trampolines for component intrinsics to reuse the exact same implementation as core wasm uses. This fixes an issue where the component trampolines were not updated as part of bytecodealliance#11592 to have a try/catch for errors that happen during their execution. The implementation here is intended to be a small, backportable, patch to the 38.0.x release branch. This does not refactor, for example, `TrampolineCompiler` which now always uses the `Wasm` ABI as opposed to using either the wasm or array ABI. Such cleanup is left for a follow-up PR to `main` after this one. In the meantime though the implementation of array-ABI component model intrinsics now looks exactly like array-to-wasm trampolines for core wasm where the array-ABI function performs a `try_call` to the wasm-ABI function, letting the wasm-ABI function doing the actual work. This is a nice simplification for trampolines where the definition of the trampoline is now just in one function instead of duplicated across two.
1 parent 51e5294 commit 27a0c8f

File tree

3 files changed

+203
-133
lines changed

3 files changed

+203
-133
lines changed

crates/cranelift/src/compiler.rs

Lines changed: 149 additions & 132 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,9 @@ use wasmtime_environ::{
3636
Abi, AddressMapSection, BuiltinFunctionIndex, CacheStore, CompileError, CompiledFunctionBody,
3737
DefinedFuncIndex, FlagValue, FrameInstPos, FrameStackShape, FrameStateSlotBuilder,
3838
FrameTableBuilder, FuncKey, FunctionBodyData, FunctionLoc, HostCall, InliningCompiler,
39-
ModuleTranslation, ModuleTypesBuilder, PtrSize, StackMapSection, StaticModuleIndex,
40-
TrapEncodingBuilder, TrapSentinel, TripleExt, Tunables, VMOffsets, WasmFuncType, WasmValType,
39+
ModuleInternedTypeIndex, ModuleTranslation, ModuleTypesBuilder, PtrSize, StackMapSection,
40+
StaticModuleIndex, TrapEncodingBuilder, TrapSentinel, TripleExt, Tunables, WasmFuncType,
41+
WasmValType,
4142
};
4243
use wasmtime_unwinder::ExceptionTableBuilder;
4344

@@ -354,140 +355,20 @@ impl wasmtime_environ::Compiler for Compiler {
354355
key: FuncKey,
355356
symbol: &str,
356357
) -> Result<CompiledFunctionBody, CompileError> {
357-
log::trace!("compiling array-to-wasm trampoline: {key:?} = {symbol:?}");
358-
359358
let (module_index, def_func_index) = key.unwrap_array_to_wasm_trampoline();
360-
debug_assert_eq!(translation.module_index(), module_index);
361-
362359
let func_index = translation.module.func_index(def_func_index);
363360
let sig = translation.module.functions[func_index]
364361
.signature
365362
.unwrap_module_type_index();
366-
let wasm_func_ty = types[sig].unwrap_func();
367-
368-
let isa = &*self.isa;
369-
let pointer_type = isa.pointer_type();
370-
let wasm_call_sig = wasm_call_signature(isa, wasm_func_ty, &self.tunables);
371-
let array_call_sig = array_call_signature(isa);
372-
373-
let mut compiler = self.function_compiler();
374-
let func = ir::Function::with_name_signature(key_to_name(key), array_call_sig);
375-
let (mut builder, block0) = compiler.builder(func);
376-
377-
let try_call_block = builder.create_block();
378-
builder.ins().jump(try_call_block, []);
379-
builder.switch_to_block(try_call_block);
380-
381-
let (vmctx, caller_vmctx, values_vec_ptr, values_vec_len) = {
382-
let params = builder.func.dfg.block_params(block0);
383-
(params[0], params[1], params[2], params[3])
384-
};
385-
386-
// First load the actual arguments out of the array.
387-
let mut args = self.load_values_from_array(
388-
wasm_func_ty.params(),
389-
&mut builder,
390-
values_vec_ptr,
391-
values_vec_len,
392-
);
393-
args.insert(0, caller_vmctx);
394-
args.insert(0, vmctx);
395-
396-
// Just before we enter Wasm, save our context information.
397-
//
398-
// Assert that we were really given a core Wasm vmctx, since that's
399-
// what we are assuming with our offsets below.
400-
self.debug_assert_vmctx_kind(&mut builder, vmctx, wasmtime_environ::VMCONTEXT_MAGIC);
401-
let offsets = VMOffsets::new(isa.pointer_bytes(), &translation.module);
402-
let vm_store_context_offset = offsets.ptr.vmctx_store_context();
403-
save_last_wasm_entry_context(
404-
&mut builder,
405-
pointer_type,
406-
&offsets.ptr,
407-
vm_store_context_offset.into(),
408-
vmctx,
409-
try_call_block,
410-
);
411-
412-
// Create the invocation of wasm, which is notably done with a
413-
// `try_call` with an exception handler that's used to handle traps.
414-
let normal_return = builder.create_block();
415-
let exceptional_return = builder.create_block();
416-
let normal_return_values = wasm_call_sig
417-
.returns
418-
.iter()
419-
.map(|ty| {
420-
builder
421-
.func
422-
.dfg
423-
.append_block_param(normal_return, ty.value_type)
424-
})
425-
.collect::<Vec<_>>();
426-
427-
// Then call the Wasm function with those arguments.
428-
let callee_key = FuncKey::DefinedWasmFunction(module_index, def_func_index);
429-
let signature = builder.func.import_signature(wasm_call_sig.clone());
430-
let callee = {
431-
let (namespace, index) = callee_key.into_raw_parts();
432-
let name = ir::ExternalName::User(
433-
builder
434-
.func
435-
.declare_imported_user_function(ir::UserExternalName { namespace, index }),
436-
);
437-
builder.func.dfg.ext_funcs.push(ir::ExtFuncData {
438-
name,
439-
signature,
440-
colocated: true,
441-
})
442-
};
443-
444-
let dfg = &mut builder.func.dfg;
445-
let exception_table = dfg.exception_tables.push(ir::ExceptionTableData::new(
446-
signature,
447-
ir::BlockCall::new(
448-
normal_return,
449-
(0..wasm_call_sig.returns.len())
450-
.map(|i| ir::BlockArg::TryCallRet(i.try_into().unwrap())),
451-
&mut dfg.value_lists,
452-
),
453-
[ir::ExceptionTableItem::Default(ir::BlockCall::new(
454-
exceptional_return,
455-
None,
456-
&mut dfg.value_lists,
457-
))],
458-
));
459-
builder.ins().try_call(callee, &args, exception_table);
460-
461-
builder.seal_block(try_call_block);
462-
builder.seal_block(normal_return);
463-
builder.seal_block(exceptional_return);
464-
465-
// On the normal return path store all the results in the array we were
466-
// provided and return "true" for "returned successfully".
467-
builder.switch_to_block(normal_return);
468-
self.store_values_to_array(
469-
&mut builder,
470-
wasm_func_ty.returns(),
471-
&normal_return_values,
472-
values_vec_ptr,
473-
values_vec_len,
474-
);
475-
let true_return = builder.ins().iconst(ir::types::I8, 1);
476-
builder.ins().return_(&[true_return]);
477-
478-
// On the exceptional return path just return "false" for "did not
479-
// succeed". Note that register restoration is part of the `try_call`
480-
// and handler implementation.
481-
builder.switch_to_block(exceptional_return);
482-
let false_return = builder.ins().iconst(ir::types::I8, 0);
483-
builder.ins().return_(&[false_return]);
484-
485-
builder.finalize();
486-
487-
Ok(CompiledFunctionBody {
488-
code: box_dyn_any_compiler_context(Some(compiler.cx)),
489-
needs_gc_heap: false,
490-
})
363+
self.array_to_wasm_trampoline(
364+
key,
365+
FuncKey::DefinedWasmFunction(module_index, def_func_index),
366+
types,
367+
sig,
368+
symbol,
369+
self.isa.pointer_bytes().vmctx_store_context().into(),
370+
wasmtime_environ::VMCONTEXT_MAGIC,
371+
)
491372
}
492373

493374
fn compile_wasm_to_array_trampoline(
@@ -1345,6 +1226,142 @@ impl Compiler {
13451226
);
13461227
builder.ins().trapz(is_expected_vmctx, TRAP_INTERNAL_ASSERT);
13471228
}
1229+
1230+
fn array_to_wasm_trampoline(
1231+
&self,
1232+
trampoline_key: FuncKey,
1233+
callee_key: FuncKey,
1234+
types: &ModuleTypesBuilder,
1235+
callee_sig: ModuleInternedTypeIndex,
1236+
symbol: &str,
1237+
vm_store_context_offset: u32,
1238+
expected_vmctx_magic: u32,
1239+
) -> Result<CompiledFunctionBody, CompileError> {
1240+
log::trace!("compiling array-to-wasm trampoline: {trampoline_key:?} = {symbol:?}");
1241+
1242+
let wasm_func_ty = types[callee_sig].unwrap_func();
1243+
1244+
let isa = &*self.isa;
1245+
let pointer_type = isa.pointer_type();
1246+
let wasm_call_sig = wasm_call_signature(isa, wasm_func_ty, &self.tunables);
1247+
let array_call_sig = array_call_signature(isa);
1248+
1249+
let mut compiler = self.function_compiler();
1250+
let func = ir::Function::with_name_signature(key_to_name(trampoline_key), array_call_sig);
1251+
let (mut builder, block0) = compiler.builder(func);
1252+
1253+
let try_call_block = builder.create_block();
1254+
builder.ins().jump(try_call_block, []);
1255+
builder.switch_to_block(try_call_block);
1256+
1257+
let (vmctx, caller_vmctx, values_vec_ptr, values_vec_len) = {
1258+
let params = builder.func.dfg.block_params(block0);
1259+
(params[0], params[1], params[2], params[3])
1260+
};
1261+
1262+
// First load the actual arguments out of the array.
1263+
let mut args = self.load_values_from_array(
1264+
wasm_func_ty.params(),
1265+
&mut builder,
1266+
values_vec_ptr,
1267+
values_vec_len,
1268+
);
1269+
args.insert(0, caller_vmctx);
1270+
args.insert(0, vmctx);
1271+
1272+
// Just before we enter Wasm, save our context information.
1273+
//
1274+
// Assert that we were really given a core Wasm vmctx, since that's
1275+
// what we are assuming with our offsets below.
1276+
self.debug_assert_vmctx_kind(&mut builder, vmctx, expected_vmctx_magic);
1277+
save_last_wasm_entry_context(
1278+
&mut builder,
1279+
pointer_type,
1280+
&self.isa.pointer_bytes(),
1281+
vm_store_context_offset,
1282+
vmctx,
1283+
try_call_block,
1284+
);
1285+
1286+
// Create the invocation of wasm, which is notably done with a
1287+
// `try_call` with an exception handler that's used to handle traps.
1288+
let normal_return = builder.create_block();
1289+
let exceptional_return = builder.create_block();
1290+
let normal_return_values = wasm_call_sig
1291+
.returns
1292+
.iter()
1293+
.map(|ty| {
1294+
builder
1295+
.func
1296+
.dfg
1297+
.append_block_param(normal_return, ty.value_type)
1298+
})
1299+
.collect::<Vec<_>>();
1300+
1301+
// Then call the Wasm function with those arguments.
1302+
let signature = builder.func.import_signature(wasm_call_sig.clone());
1303+
let callee = {
1304+
let (namespace, index) = callee_key.into_raw_parts();
1305+
let name = ir::ExternalName::User(
1306+
builder
1307+
.func
1308+
.declare_imported_user_function(ir::UserExternalName { namespace, index }),
1309+
);
1310+
builder.func.dfg.ext_funcs.push(ir::ExtFuncData {
1311+
name,
1312+
signature,
1313+
colocated: true,
1314+
})
1315+
};
1316+
1317+
let dfg = &mut builder.func.dfg;
1318+
let exception_table = dfg.exception_tables.push(ir::ExceptionTableData::new(
1319+
signature,
1320+
ir::BlockCall::new(
1321+
normal_return,
1322+
(0..wasm_call_sig.returns.len())
1323+
.map(|i| ir::BlockArg::TryCallRet(i.try_into().unwrap())),
1324+
&mut dfg.value_lists,
1325+
),
1326+
[ir::ExceptionTableItem::Default(ir::BlockCall::new(
1327+
exceptional_return,
1328+
None,
1329+
&mut dfg.value_lists,
1330+
))],
1331+
));
1332+
builder.ins().try_call(callee, &args, exception_table);
1333+
1334+
builder.seal_block(try_call_block);
1335+
builder.seal_block(normal_return);
1336+
builder.seal_block(exceptional_return);
1337+
1338+
// On the normal return path store all the results in the array we were
1339+
// provided and return "true" for "returned successfully".
1340+
builder.switch_to_block(normal_return);
1341+
self.store_values_to_array(
1342+
&mut builder,
1343+
wasm_func_ty.returns(),
1344+
&normal_return_values,
1345+
values_vec_ptr,
1346+
values_vec_len,
1347+
);
1348+
let true_return = builder.ins().iconst(ir::types::I8, 1);
1349+
builder.ins().return_(&[true_return]);
1350+
1351+
// On the exceptional return path just return "false" for "did not
1352+
// succeed". Note that register restoration is part of the `try_call`
1353+
// and handler implementation.
1354+
builder.switch_to_block(exceptional_return);
1355+
let false_return = builder.ins().iconst(ir::types::I8, 0);
1356+
builder.ins().return_(&[false_return]);
1357+
1358+
builder.finalize();
1359+
1360+
Ok(CompiledFunctionBody {
1361+
code: box_dyn_any_compiler_context(Some(compiler.cx)),
1362+
needs_gc_heap: false,
1363+
})
1364+
}
13481365
}
13491366

13501367
struct FunctionCompiler<'a> {
@@ -1572,7 +1589,7 @@ fn clif_to_env_frame_tables<'a>(
15721589
fn save_last_wasm_entry_context(
15731590
builder: &mut FunctionBuilder,
15741591
pointer_type: ir::Type,
1575-
ptr_size: &impl PtrSize,
1592+
ptr_size: &dyn PtrSize,
15761593
vm_store_context_offset: u32,
15771594
vmctx: Value,
15781595
block: ir::Block,

crates/cranelift/src/compiler/component.rs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1442,11 +1442,32 @@ impl ComponentCompiler for Compiler {
14421442
key: FuncKey,
14431443
abi: Abi,
14441444
tunables: &Tunables,
1445-
_symbol: &str,
1445+
symbol: &str,
14461446
) -> Result<CompiledFunctionBody> {
14471447
let (abi2, trampoline_index) = key.unwrap_component_trampoline();
14481448
debug_assert_eq!(abi, abi2);
14491449

1450+
match abi {
1451+
// Fall through to the trampoline compiler.
1452+
Abi::Wasm => {}
1453+
1454+
// Implement the array-abi trampoline in terms of calling the
1455+
// wasm-abi trampoline.
1456+
Abi::Array => {
1457+
let offsets =
1458+
VMComponentOffsets::new(self.isa.pointer_bytes(), &component.component);
1459+
return Ok(self.array_to_wasm_trampoline(
1460+
key,
1461+
FuncKey::ComponentTrampoline(Abi::Wasm, trampoline_index),
1462+
types.module_types_builder(),
1463+
component.component.trampolines[trampoline_index],
1464+
symbol,
1465+
offsets.vm_store_context(),
1466+
wasmtime_environ::component::VMCOMPONENT_MAGIC,
1467+
)?);
1468+
}
1469+
}
1470+
14501471
let mut compiler = self.function_compiler();
14511472
let mut c = TrampolineCompiler::new(
14521473
self,

tests/all/component_model/resources.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1641,3 +1641,35 @@ fn resource_dynamic_not_static() -> Result<()> {
16411641

16421642
Ok(())
16431643
}
1644+
1645+
#[test]
1646+
fn intrinsic_trampolines() -> Result<()> {
1647+
let engine = super::engine();
1648+
let mut store = Store::new(&engine, ());
1649+
let linker = Linker::new(&engine);
1650+
let c = Component::new(
1651+
&engine,
1652+
r#"
1653+
(component
1654+
(type $r' (resource (rep i32)))
1655+
(export $r "r" (type $r'))
1656+
1657+
(core func $new (canon resource.new $r))
1658+
(core func $rep (canon resource.rep $r))
1659+
1660+
(func (export "new") (param "x" u32) (result (own $r))
1661+
(canon lift (core func $new)))
1662+
(func (export "rep") (param "x" (borrow $r)) (result u32)
1663+
(canon lift (core func $rep)))
1664+
)
1665+
"#,
1666+
)?;
1667+
let i = linker.instantiate(&mut store, &c)?;
1668+
let new = i.get_typed_func::<(u32,), (ResourceAny,)>(&mut store, "new")?;
1669+
let rep = i.get_typed_func::<(ResourceAny,), (u32,)>(&mut store, "rep")?;
1670+
1671+
let r = new.call(&mut store, (42,))?.0;
1672+
new.post_return(&mut store)?;
1673+
assert!(rep.call(&mut store, (r,)).is_err());
1674+
Ok(())
1675+
}

0 commit comments

Comments
 (0)