Skip to content
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

Add a new Func::wrap_cabi API #3345

Closed
Closed
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
55 changes: 55 additions & 0 deletions crates/c-api/include/wasmtime/func.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,61 @@ WASM_API_EXTERN void wasmtime_func_new(
wasmtime_func_t *ret
);

/**
* \brief Creates a new host-defined function with a custom signature.
*
* This function is the same as #wasmtime_func_new except that the signature of
* `callback` is different. The signature of `callback` must correspond to the
* function `type` specified, if it's not then calling the function is UB.
*
* \param store the store in which to create the function
* \param type the wasm type of the function that's being created
* \param callback the host-defined callback to invoke
* \param env host-specific data passed to the callback invocation, can be
* `NULL`
* \param finalizer optional finalizer for `env`, can be `NULL`
* \param ret the #wasmtime_func_t return value to be filled in.
*
* The returned function can only be used with the specified `store`.
*
* The signature of `callback` is as follows:
*
* * First it takes a `void*` parameter which is the `env` provided here.
* * Next it receives a `wasmtime_caller_t*` which is only valid for the
* duration of that one call.
* * Next it receives each WebAssembly argument individually. Mapping from wasm
* types to host types is provided below.
* * Next it receives an out-pointer for each WebAssembly result individually.
* * Finally the function must return a `wasm_trap_t*` indicating whether the
* host function should trap or not.
*
* The mapping of WebAssembly types to host types is:
*
* * `i32 <=> int32_t`
* * `i64 <=> int64_t`
* * `f32 <=> float`
* * `f64 <=> double`
* * `v128`
* * Received indirectly as `const uint8_t*` when passed as an argument
* * Also passed as `const uint8_t*` for an out-pointer.
* * `extenref` - not supported
* * `funcref` - not supported
*
* All arguments are passed by-value except for `v128`, which is passed
* indirectly to the host function.
*
* This function will return an error if the `type` specified uses either
* `externref` or `funcref`.
*/
WASM_API_EXTERN wasmtime_error_t *wasmtime_func_wrap(
wasmtime_context_t *store,
const wasm_functype_t* type,
size_t callback,
void *env,
void (*finalizer)(void*),
wasmtime_func_t *ret
);

/**
* \brief Returns the type of the function specified
*
Expand Down
43 changes: 42 additions & 1 deletion crates/c-api/include/wasmtime/linker.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ WASM_API_EXTERN wasmtime_error_t* wasmtime_linker_define(
);

/**
* \brief Defines a new function in this linker.
* \brief Defines a new function in this linker along the lines of
* #wasmtime_func_new.
*
* \param linker the linker the name is being defined in.
* \param module the module name the item is defined under.
Expand Down Expand Up @@ -115,6 +116,46 @@ WASM_API_EXTERN wasmtime_error_t* wasmtime_linker_define_func(
void (*finalizer)(void*)
);

/**
* \brief Defines a new function in this linker along the lines of
* #wasmtime_func_wrap.
*
* \param linker the linker the name is being defined in.
* \param module the module name the item is defined under.
* \param module_len the byte length of `module`
* \param name the field name the item is defined under
* \param name_len the byte length of `name`
* \param ty the type of the function that's being defined
* \param cb the host callback to invoke when the function is called
* \param data the host-provided data to provide as the first argument to the callback
* \param finalizer an optional finalizer for the `data` argument.
*
* \return On success `NULL` is returned, otherwise an error is returned which
* describes why the definition failed.
*
* For more information about name resolution consult the [Rust
* documentation](https://bytecodealliance.github.io/wasmtime/api/wasmtime/struct.Linker.html#name-resolution).
*
* Note that this function does not create a #wasmtime_func_t. This creates a
* store-independent function within the linker, allowing this function
* definition to be used with multiple stores.
*
* Also note that the documentation of #wasmtime_func_wrap should be consulted
* to determine the correct function signature for the function provided in
* `cb`.
*/
WASM_API_EXTERN wasmtime_error_t* wasmtime_linker_func_wrap(
wasmtime_linker_t *linker,
const char *module,
size_t module_len,
const char *name,
size_t name_len,
const wasm_functype_t *ty,
size_t cb,
void *data,
void (*finalizer)(void*)
);

/**
* \brief Defines WASI functions in this linker.
*
Expand Down
26 changes: 23 additions & 3 deletions crates/c-api/src/func.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use crate::wasm_trap_t;
use crate::{
wasm_extern_t, wasm_functype_t, wasm_store_t, wasm_val_t, wasm_val_vec_t, wasmtime_error_t,
wasmtime_extern_t, wasmtime_val_t, wasmtime_val_union, CStoreContext, CStoreContextMut,
handle_result, wasm_extern_t, wasm_functype_t, wasm_store_t, wasm_val_t, wasm_val_vec_t,
wasmtime_error_t, wasmtime_extern_t, wasmtime_val_t, wasmtime_val_union, CStoreContext,
CStoreContextMut,
};
use anyhow::anyhow;
use std::ffi::c_void;
Expand Down Expand Up @@ -182,7 +183,10 @@ pub extern "C" fn wasm_func_as_extern_const(f: &wasm_func_t) -> &wasm_extern_t {
&(*f).ext
}

#[repr(C)]
// Note the `transparent` annotation which is required due to the
// `wasmtime_func_wrap` function where the C code uses `*mut wasmtime_caller_t`
// to represent the `&mut Caller<'_, T>` type in Rust.
#[repr(transparent)]
pub struct wasmtime_caller_t<'a> {
caller: Caller<'a, crate::StoreData>,
}
Expand Down Expand Up @@ -211,6 +215,22 @@ pub unsafe extern "C" fn wasmtime_func_new(
*func = f;
}

#[no_mangle]
pub unsafe extern "C" fn wasmtime_func_wrap(
store: CStoreContextMut<'_>,
ty: &wasm_functype_t,
callback: usize,
data: usize,
finalizer: Option<extern "C" fn(usize)>,
func: &mut Func,
) -> Option<Box<wasmtime_error_t>> {
let ty = ty.ty().ty.clone();

handle_result(Func::wrap_cabi(store, ty, callback, data, finalizer), |f| {
*func = f;
})
}

pub(crate) unsafe fn c_callback_to_rust_fn(
callback: wasmtime_func_callback_t,
data: *mut c_void,
Expand Down
23 changes: 23 additions & 0 deletions crates/c-api/src/linker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,29 @@ pub unsafe extern "C" fn wasmtime_linker_define_func(
handle_result(linker.linker.func_new(module, name, ty, cb), |_linker| ())
}

#[no_mangle]
pub unsafe extern "C" fn wasmtime_linker_func_wrap(
linker: &mut wasmtime_linker_t,
module: *const u8,
module_len: usize,
name: *const u8,
name_len: usize,
ty: &wasm_functype_t,
callback: usize,
data: usize,
finalizer: Option<extern "C" fn(usize)>,
) -> Option<Box<wasmtime_error_t>> {
let ty = ty.ty().ty.clone();
let module = to_str!(module, module_len);
let name = to_str!(name, name_len);
handle_result(
linker
.linker
.func_wrap_cabi(module, name, ty, callback, data, finalizer),
|_linker| (),
)
}

#[cfg(feature = "wasi")]
#[no_mangle]
pub extern "C" fn wasmtime_linker_define_wasi(
Expand Down
7 changes: 6 additions & 1 deletion crates/c-api/src/trap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@ use crate::{wasm_frame_vec_t, wasm_instance_t, wasm_name_t, wasm_store_t};
use once_cell::unsync::OnceCell;
use wasmtime::{Trap, TrapCode};

#[repr(C)]
// Note the `transparent` representation here which is required by the
// `wasmtime_func_wrap` API since the C-ABI function pointers return `*mut
// wasm_trap_t` which is interpreted as `Option<Box<Trap>>` in Rust. Note that
// `wasm_trap_t` is opaque in C, though, so it's always asking this library to
// allocate.
#[repr(transparent)]
#[derive(Clone)]
pub struct wasm_trap_t {
pub(crate) trap: Trap,
Expand Down
132 changes: 128 additions & 4 deletions crates/cranelift/src/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::{
};
use anyhow::{Context as _, Result};
use cranelift_codegen::ir::{self, ExternalName, InstBuilder, MemFlags};
use cranelift_codegen::isa::TargetIsa;
use cranelift_codegen::isa::{CallConv, TargetIsa};
use cranelift_codegen::print_errors::pretty_error;
use cranelift_codegen::settings;
use cranelift_codegen::MachSrcLoc;
Expand All @@ -29,7 +29,7 @@ use std::sync::Mutex;
use wasmtime_environ::{
AddressMapSection, CompileError, FilePos, FlagValue, FunctionBodyData, FunctionInfo,
InstructionAddressMap, Module, ModuleTranslation, StackMapInformation, Trampoline, TrapCode,
TrapEncodingBuilder, TrapInformation, Tunables, TypeTables, VMOffsets,
TrapEncodingBuilder, TrapInformation, Tunables, TypeTables, VMOffsets, WasmType,
};

/// A compiler that compiles a WebAssembly module with Compiler, translating
Expand Down Expand Up @@ -325,16 +325,18 @@ impl wasmtime_environ::Compiler for Compiler {
ty: &WasmFuncType,
host_fn: usize,
obj: &mut Object,
) -> Result<(Trampoline, Trampoline)> {
) -> Result<(Trampoline, Trampoline, Trampoline)> {
let host_to_wasm = self.host_to_wasm_trampoline(ty)?;
let wasm_to_host = self.wasm_to_host_trampoline(ty, host_fn)?;
let host_to_c = self.host_to_c_trampoline(ty)?;
let module = Module::new();
let mut builder = ObjectBuilder::new(obj, &module, &*self.isa);
let a = builder.trampoline(SignatureIndex::new(0), &host_to_wasm);
let b = builder.trampoline(SignatureIndex::new(1), &wasm_to_host);
let c = builder.trampoline(SignatureIndex::new(2), &host_to_c);
builder.unwind_info();
builder.finish()?;
Ok((a, b))
Ok((a, b, c))
}

fn triple(&self) -> &target_lexicon::Triple {
Expand Down Expand Up @@ -525,6 +527,128 @@ impl Compiler {
Ok(func)
}

fn host_to_c_trampoline(&self, ty: &WasmFuncType) -> Result<CompiledFunction, CompileError> {
let isa = &*self.isa;
let pointer_type = isa.pointer_type();
let mut uses_reference_types = false;

// This is the signature of the C stub that this trampoline calls.
// Effectively this is the definition of how wasmtime translates a wasm
// signature to a C signature. Currently the signature parameters are:
//
// * a void* pointer argument (user-provided)
// * a pointer argument for a "caller" structure (`wasmtime::Caller`)
// * each wasm type as an individual parameter
// * each wasm return as an out-pointer
//
// And then the function returns a pointer which is a trap object. The
// trap is a nullable pointer.
let mut c_sig = ir::Signature::new(CallConv::triple_default(isa.triple()));
c_sig.params.push(ir::AbiParam::new(pointer_type));
c_sig.params.push(ir::AbiParam::new(pointer_type));
for ty in ty.params.iter() {
let ty = match ty {
WasmType::I32 | WasmType::I64 | WasmType::F32 | WasmType::F64 => {
value_type(isa, *ty)
}
WasmType::V128 => pointer_type,
WasmType::FuncRef | WasmType::ExternRef | WasmType::ExnRef => {
uses_reference_types = true;
continue;
}
};
c_sig.params.push(ir::AbiParam::new(ty));
}
for _ in ty.returns.iter() {
c_sig.params.push(ir::AbiParam::new(pointer_type));
}
c_sig.returns.push(ir::AbiParam::new(pointer_type));

// This trampoline itself has a fixed signature so it can be called from
// Rust. The parameters here are:
//
// * a void* pionter argument (user-provided)
// * a pointer argument for a "caller" structure (`wasmtime::Caller`)
// * the `*const u128` values_vec allocation for arguments/returns
// * the C function that we'll be calling
//
// This function returns a pointer which is the trap the C function
// returns.
let mut stub_signature = ir::Signature::new(CallConv::triple_default(isa.triple()));
stub_signature.params.push(ir::AbiParam::new(pointer_type));
stub_signature.params.push(ir::AbiParam::new(pointer_type));
stub_signature.params.push(ir::AbiParam::new(pointer_type));
stub_signature.params.push(ir::AbiParam::new(pointer_type));
stub_signature.returns.push(ir::AbiParam::new(pointer_type));

let value_size = mem::size_of::<u128>();
let mut context = Context::new();
context.func =
ir::Function::with_name_signature(ir::ExternalName::user(0, 0), stub_signature);
let mut func_translator = self.take_translator();
let mut builder = FunctionBuilder::new(&mut context.func, func_translator.context());
let block0 = builder.create_block();
builder.append_block_params_for_function_params(block0);
builder.switch_to_block(block0);
builder.seal_block(block0);

if uses_reference_types {
// If this signature uses reference types then we can't generate a
// valid trampoline at this time. This won't ever actually be used
// in `wasmtime` so we just finish up the function to trap and leave
// it alone.
builder.ins().trap(ir::TrapCode::UnreachableCodeReached);
} else {
// Build the arguments to the C function that we're going to call. The
// first two arguments are simply forwarding our own first two
// arguments. Next comes all the wasm parameters which are passed
// by-value. Finally all returns get a return pointer which points back
// into our `values_vec` parameter.
let mut args = Vec::new();
args.push(builder.func.dfg.block_params(block0)[0]);
args.push(builder.func.dfg.block_params(block0)[1]);
let values_vec_ptr_val = builder.func.dfg.block_params(block0)[2];
for (i, ty) in ty.params.iter().enumerate() {
let offset = (i * value_size) as i32;
let arg = match ty {
WasmType::I32 | WasmType::I64 | WasmType::F32 | WasmType::F64 => {
builder.ins().load(
value_type(isa, *ty),
MemFlags::trusted(),
values_vec_ptr_val,
offset,
)
}
WasmType::V128 => builder
.ins()
.iadd_imm(values_vec_ptr_val, i64::from(offset)),
WasmType::FuncRef | WasmType::ExternRef | WasmType::ExnRef => unimplemented!(),
};
args.push(arg);
}
for (i, _ty) in ty.returns.iter().enumerate() {
let addr = builder
.ins()
.iadd_imm(values_vec_ptr_val, (i * value_size) as i64);
args.push(addr);
}

// With all the arguments we call the indirect function passed as a
// parameter to our function, then we're good to go and we simply return
// that function's value.
let new_sig = builder.import_signature(c_sig);
let callee_value = builder.func.dfg.block_params(block0)[3];
let call = builder.ins().call_indirect(new_sig, callee_value, &args);
let trap = builder.inst_results(call)[0];
builder.ins().return_(&[trap]);
}
builder.finalize();

let func = self.finish_trampoline(context, isa)?;
self.save_translator(func_translator);
Ok(func)
}

fn finish_trampoline(
&self,
mut context: Context,
Expand Down
Loading