Skip to content

Commit c3d448c

Browse files
authored
Use generic array-to-wasm trampolines for components (#11933)
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 #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 e8b8a72 commit c3d448c

File tree

3 files changed

+202
-133
lines changed

3 files changed

+202
-133
lines changed

crates/cranelift/src/compiler.rs

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

482362
fn compile_wasm_to_array_trampoline(
@@ -1284,6 +1164,142 @@ impl Compiler {
12841164
);
12851165
builder.ins().trapz(is_expected_vmctx, TRAP_INTERNAL_ASSERT);
12861166
}
1167+
1168+
fn array_to_wasm_trampoline(
1169+
&self,
1170+
trampoline_key: FuncKey,
1171+
callee_key: FuncKey,
1172+
types: &ModuleTypesBuilder,
1173+
callee_sig: ModuleInternedTypeIndex,
1174+
symbol: &str,
1175+
vm_store_context_offset: u32,
1176+
expected_vmctx_magic: u32,
1177+
) -> Result<CompiledFunctionBody, CompileError> {
1178+
log::trace!("compiling array-to-wasm trampoline: {trampoline_key:?} = {symbol:?}");
1179+
1180+
let wasm_func_ty = types[callee_sig].unwrap_func();
1181+
1182+
let isa = &*self.isa;
1183+
let pointer_type = isa.pointer_type();
1184+
let wasm_call_sig = wasm_call_signature(isa, wasm_func_ty, &self.tunables);
1185+
let array_call_sig = array_call_signature(isa);
1186+
1187+
let mut compiler = self.function_compiler();
1188+
let func = ir::Function::with_name_signature(key_to_name(trampoline_key), array_call_sig);
1189+
let (mut builder, block0) = compiler.builder(func);
1190+
1191+
let try_call_block = builder.create_block();
1192+
builder.ins().jump(try_call_block, []);
1193+
builder.switch_to_block(try_call_block);
1194+
1195+
let (vmctx, caller_vmctx, values_vec_ptr, values_vec_len) = {
1196+
let params = builder.func.dfg.block_params(block0);
1197+
(params[0], params[1], params[2], params[3])
1198+
};
1199+
1200+
// First load the actual arguments out of the array.
1201+
let mut args = self.load_values_from_array(
1202+
wasm_func_ty.params(),
1203+
&mut builder,
1204+
values_vec_ptr,
1205+
values_vec_len,
1206+
);
1207+
args.insert(0, caller_vmctx);
1208+
args.insert(0, vmctx);
1209+
1210+
// Just before we enter Wasm, save our context information.
1211+
//
1212+
// Assert that we were really given a core Wasm vmctx, since that's
1213+
// what we are assuming with our offsets below.
1214+
self.debug_assert_vmctx_kind(&mut builder, vmctx, expected_vmctx_magic);
1215+
save_last_wasm_entry_context(
1216+
&mut builder,
1217+
pointer_type,
1218+
&self.isa.pointer_bytes(),
1219+
vm_store_context_offset,
1220+
vmctx,
1221+
try_call_block,
1222+
);
1223+
1224+
// Create the invocation of wasm, which is notably done with a
1225+
// `try_call` with an exception handler that's used to handle traps.
1226+
let normal_return = builder.create_block();
1227+
let exceptional_return = builder.create_block();
1228+
let normal_return_values = wasm_call_sig
1229+
.returns
1230+
.iter()
1231+
.map(|ty| {
1232+
builder
1233+
.func
1234+
.dfg
1235+
.append_block_param(normal_return, ty.value_type)
1236+
})
1237+
.collect::<Vec<_>>();
1238+
1239+
// Then call the Wasm function with those arguments.
1240+
let signature = builder.func.import_signature(wasm_call_sig.clone());
1241+
let callee = {
1242+
let (namespace, index) = callee_key.into_raw_parts();
1243+
let name = ir::ExternalName::User(
1244+
builder
1245+
.func
1246+
.declare_imported_user_function(ir::UserExternalName { namespace, index }),
1247+
);
1248+
builder.func.dfg.ext_funcs.push(ir::ExtFuncData {
1249+
name,
1250+
signature,
1251+
colocated: true,
1252+
})
1253+
};
1254+
1255+
let dfg = &mut builder.func.dfg;
1256+
let exception_table = dfg.exception_tables.push(ir::ExceptionTableData::new(
1257+
signature,
1258+
ir::BlockCall::new(
1259+
normal_return,
1260+
(0..wasm_call_sig.returns.len())
1261+
.map(|i| ir::BlockArg::TryCallRet(i.try_into().unwrap())),
1262+
&mut dfg.value_lists,
1263+
),
1264+
[ir::ExceptionTableItem::Default(ir::BlockCall::new(
1265+
exceptional_return,
1266+
None,
1267+
&mut dfg.value_lists,
1268+
))],
1269+
));
1270+
builder.ins().try_call(callee, &args, exception_table);
1271+
1272+
builder.seal_block(try_call_block);
1273+
builder.seal_block(normal_return);
1274+
builder.seal_block(exceptional_return);
1275+
1276+
// On the normal return path store all the results in the array we were
1277+
// provided and return "true" for "returned successfully".
1278+
builder.switch_to_block(normal_return);
1279+
self.store_values_to_array(
1280+
&mut builder,
1281+
wasm_func_ty.returns(),
1282+
&normal_return_values,
1283+
values_vec_ptr,
1284+
values_vec_len,
1285+
);
1286+
let true_return = builder.ins().iconst(ir::types::I8, 1);
1287+
builder.ins().return_(&[true_return]);
1288+
1289+
// On the exceptional return path just return "false" for "did not
1290+
// succeed". Note that register restoration is part of the `try_call`
1291+
// and handler implementation.
1292+
builder.switch_to_block(exceptional_return);
1293+
let false_return = builder.ins().iconst(ir::types::I8, 0);
1294+
builder.ins().return_(&[false_return]);
1295+
1296+
builder.finalize();
1297+
1298+
Ok(CompiledFunctionBody {
1299+
code: box_dyn_any_compiler_context(Some(compiler.cx)),
1300+
needs_gc_heap: false,
1301+
})
1302+
}
12871303
}
12881304

12891305
struct FunctionCompiler<'a> {
@@ -1450,7 +1466,7 @@ fn clif_to_env_exception_tables<'a>(
14501466
fn save_last_wasm_entry_context(
14511467
builder: &mut FunctionBuilder,
14521468
pointer_type: ir::Type,
1453-
ptr_size: &impl PtrSize,
1469+
ptr_size: &dyn PtrSize,
14541470
vm_store_context_offset: u32,
14551471
vmctx: Value,
14561472
block: ir::Block,

crates/cranelift/src/compiler/component.rs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1390,11 +1390,32 @@ impl ComponentCompiler for Compiler {
13901390
key: FuncKey,
13911391
abi: Abi,
13921392
tunables: &Tunables,
1393-
_symbol: &str,
1393+
symbol: &str,
13941394
) -> Result<CompiledFunctionBody> {
13951395
let (abi2, trampoline_index) = key.unwrap_component_trampoline();
13961396
debug_assert_eq!(abi, abi2);
13971397

1398+
match abi {
1399+
// Fall through to the trampoline compiler.
1400+
Abi::Wasm => {}
1401+
1402+
// Implement the array-abi trampoline in terms of calling the
1403+
// wasm-abi trampoline.
1404+
Abi::Array => {
1405+
let offsets =
1406+
VMComponentOffsets::new(self.isa.pointer_bytes(), &component.component);
1407+
return Ok(self.array_to_wasm_trampoline(
1408+
key,
1409+
FuncKey::ComponentTrampoline(Abi::Wasm, trampoline_index),
1410+
types.module_types_builder(),
1411+
component.component.trampolines[trampoline_index],
1412+
symbol,
1413+
offsets.vm_store_context(),
1414+
wasmtime_environ::component::VMCOMPONENT_MAGIC,
1415+
)?);
1416+
}
1417+
}
1418+
13981419
let mut compiler = self.function_compiler();
13991420
let mut c = TrampolineCompiler::new(
14001421
self,

tests/all/component_model/resources.rs

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

15311531
Ok(())
15321532
}
1533+
1534+
#[test]
1535+
fn intrinsic_trampolines() -> Result<()> {
1536+
let engine = super::engine();
1537+
let mut store = Store::new(&engine, ());
1538+
let linker = Linker::new(&engine);
1539+
let c = Component::new(
1540+
&engine,
1541+
r#"
1542+
(component
1543+
(type $r' (resource (rep i32)))
1544+
(export $r "r" (type $r'))
1545+
1546+
(core func $new (canon resource.new $r))
1547+
(core func $rep (canon resource.rep $r))
1548+
1549+
(func (export "new") (param "x" u32) (result (own $r))
1550+
(canon lift (core func $new)))
1551+
(func (export "rep") (param "x" (borrow $r)) (result u32)
1552+
(canon lift (core func $rep)))
1553+
)
1554+
"#,
1555+
)?;
1556+
let i = linker.instantiate(&mut store, &c)?;
1557+
let new = i.get_typed_func::<(u32,), (ResourceAny,)>(&mut store, "new")?;
1558+
let rep = i.get_typed_func::<(ResourceAny,), (u32,)>(&mut store, "rep")?;
1559+
1560+
let r = new.call(&mut store, (42,))?.0;
1561+
new.post_return(&mut store)?;
1562+
assert!(rep.call(&mut store, (r,)).is_err());
1563+
Ok(())
1564+
}

0 commit comments

Comments
 (0)