diff --git a/lib/runtime-c-api/src/lib.rs b/lib/runtime-c-api/src/lib.rs index 054cd14e1..fb95c7dc6 100644 --- a/lib/runtime-c-api/src/lib.rs +++ b/lib/runtime-c-api/src/lib.rs @@ -95,6 +95,8 @@ pub mod instance; pub mod memory; pub mod module; pub mod table; +#[cfg(all(unix, target_arch = "x86_64"))] +pub mod trampoline; pub mod value; #[allow(non_camel_case_types)] diff --git a/lib/runtime-c-api/src/trampoline.rs b/lib/runtime-c-api/src/trampoline.rs new file mode 100644 index 000000000..923622aa5 --- /dev/null +++ b/lib/runtime-c-api/src/trampoline.rs @@ -0,0 +1,84 @@ +//! Trampoline emitter for transforming function calls. + +use std::ffi::c_void; +use std::mem; +use wasmer_runtime_core::trampoline::*; + +#[repr(C)] +pub struct wasmer_trampoline_buffer_builder_t; + +#[repr(C)] +pub struct wasmer_trampoline_buffer_t; + +#[repr(C)] +pub struct wasmer_trampoline_callable_t; + +/// Creates a new trampoline builder. +#[no_mangle] +#[allow(clippy::cast_ptr_alignment)] +pub extern "C" fn wasmer_trampoline_buffer_builder_new() -> *mut wasmer_trampoline_buffer_builder_t +{ + Box::into_raw(Box::new(TrampolineBufferBuilder::new())) as *mut _ +} + +/// Adds a context trampoline to the builder. +#[no_mangle] +#[allow(clippy::cast_ptr_alignment)] +pub unsafe extern "C" fn wasmer_trampoline_buffer_builder_add_context_trampoline( + builder: *mut wasmer_trampoline_buffer_builder_t, + func: *const wasmer_trampoline_callable_t, + ctx: *const c_void, +) -> usize { + let builder = &mut *(builder as *mut TrampolineBufferBuilder); + builder.add_context_trampoline(func as *const CallTarget, ctx as *const CallContext) +} + +/// Adds a callinfo trampoline to the builder. +#[no_mangle] +#[allow(clippy::cast_ptr_alignment)] +pub unsafe extern "C" fn wasmer_trampoline_buffer_builder_add_callinfo_trampoline( + builder: *mut wasmer_trampoline_buffer_builder_t, + func: *const wasmer_trampoline_callable_t, + ctx: *const c_void, + num_params: u32, +) -> usize { + let builder = &mut *(builder as *mut TrampolineBufferBuilder); + builder.add_callinfo_trampoline(mem::transmute(func), ctx as *const CallContext, num_params) +} + +/// Finalizes the trampoline builder into an executable buffer. +#[no_mangle] +#[allow(clippy::cast_ptr_alignment)] +pub unsafe extern "C" fn wasmer_trampoline_buffer_builder_build( + builder: *mut wasmer_trampoline_buffer_builder_t, +) -> *mut wasmer_trampoline_buffer_t { + let builder = Box::from_raw(builder as *mut TrampolineBufferBuilder); + Box::into_raw(Box::new(builder.build())) as *mut _ +} + +/// Destroys the trampoline buffer if not null. +#[no_mangle] +#[allow(clippy::cast_ptr_alignment)] +pub unsafe extern "C" fn wasmer_trampoline_buffer_destroy(buffer: *mut wasmer_trampoline_buffer_t) { + if !buffer.is_null() { + Box::from_raw(buffer); + } +} + +/// Returns the callable pointer for the trampoline with index `idx`. +#[no_mangle] +#[allow(clippy::cast_ptr_alignment)] +pub unsafe extern "C" fn wasmer_trampoline_buffer_get_trampoline( + buffer: *const wasmer_trampoline_buffer_t, + idx: usize, +) -> *const wasmer_trampoline_callable_t { + let buffer = &*(buffer as *const TrampolineBuffer); + buffer.get_trampoline(idx) as _ +} + +/// Returns the context added by `add_context_trampoline`, from within the callee function. +#[no_mangle] +#[allow(clippy::cast_ptr_alignment)] +pub unsafe extern "C" fn wasmer_trampoline_get_context() -> *mut c_void { + get_context() as *const c_void as *mut c_void +} diff --git a/lib/runtime-c-api/tests/test-import-function-callinfo.c b/lib/runtime-c-api/tests/test-import-function-callinfo.c new file mode 100644 index 000000000..371f9eeba --- /dev/null +++ b/lib/runtime-c-api/tests/test-import-function-callinfo.c @@ -0,0 +1,132 @@ +#include +#include "../wasmer.h" +#include +#include +#include + +static bool print_str_called = false; +static int memory_len = 0; +static int ptr_len = 0; +static char actual_str[14] = {}; +static int actual_context_data_value = 0; + +typedef struct { + int value; +} context_data; + +struct print_str_context { + int call_count; +}; + +void print_str(struct print_str_context *local_context, uint64_t *args) +{ + local_context->call_count++; + + wasmer_instance_context_t *ctx = (void *) args[0]; + int32_t ptr = args[1]; + int32_t len = args[2]; + + const wasmer_memory_t *memory = wasmer_instance_context_memory(ctx, 0); + uint32_t mem_len = wasmer_memory_length(memory); + uint8_t *mem_bytes = wasmer_memory_data(memory); + for (int32_t idx = 0; idx < len; idx++) + { + actual_str[idx] = mem_bytes[ptr + idx]; + } + actual_str[13] = '\0'; + printf("In print_str, memory len: %d, ptr_len: %d\n, str %s", mem_len, len, actual_str); + print_str_called = true; + memory_len = mem_len; + ptr_len = len; + + actual_context_data_value = ((context_data *) wasmer_instance_context_data_get(ctx))->value; +} + +int main() +{ + wasmer_value_tag params_sig[] = {WASM_I32, WASM_I32}; + wasmer_value_tag returns_sig[] = {}; + struct print_str_context local_context = { + .call_count = 0 + }; + + printf("Creating trampoline buffer\n"); + wasmer_trampoline_buffer_builder_t *tbb = wasmer_trampoline_buffer_builder_new(); + unsigned long print_str_idx = wasmer_trampoline_buffer_builder_add_callinfo_trampoline( + tbb, + (wasmer_trampoline_callable_t *) print_str, + (void *) &local_context, + 3 + ); + wasmer_trampoline_buffer_t *tb = wasmer_trampoline_buffer_builder_build(tbb); + const wasmer_trampoline_callable_t *print_str_callable = wasmer_trampoline_buffer_get_trampoline(tb, print_str_idx); + + printf("Creating new func\n"); + wasmer_import_func_t *func = wasmer_import_func_new((void (*)(void *)) print_str_callable, params_sig, 2, returns_sig, 0); + wasmer_import_t import; + + char *module_name = "env"; + wasmer_byte_array module_name_bytes; + module_name_bytes.bytes = (const uint8_t *) module_name; + module_name_bytes.bytes_len = strlen(module_name); + char *import_name = "print_str"; + wasmer_byte_array import_name_bytes; + import_name_bytes.bytes = (const uint8_t *) import_name; + import_name_bytes.bytes_len = strlen(import_name); + + import.module_name = module_name_bytes; + import.import_name = import_name_bytes; + import.tag = WASM_FUNCTION; + import.value.func = func; + wasmer_import_t imports[] = {import}; + + // Read the wasm file bytes + FILE *file = fopen("assets/wasm_sample_app.wasm", "r"); + fseek(file, 0, SEEK_END); + long len = ftell(file); + uint8_t *bytes = malloc(len); + fseek(file, 0, SEEK_SET); + fread(bytes, 1, len, file); + fclose(file); + + printf("Instantiating\n"); + wasmer_instance_t *instance = NULL; + wasmer_result_t compile_result = wasmer_instantiate(&instance, bytes, len, imports, 1); + printf("Compile result: %d\n", compile_result); + + assert(compile_result == WASMER_OK); + + context_data* context_data = malloc(sizeof(context_data)); + int context_data_value = 42; + context_data->value = context_data_value; + wasmer_instance_context_data_set(instance, context_data); + + wasmer_value_t params[] = {}; + wasmer_value_t results[] = {}; + wasmer_result_t call_result = wasmer_instance_call(instance, "hello_wasm", params, 0, results, 0); + printf("Call result: %d\n", call_result); + + int error_len = wasmer_last_error_length(); + printf("Error len: `%d`\n", error_len); + char *error_str = malloc(error_len); + wasmer_last_error_message(error_str, error_len); + printf("Error str: `%s`\n", error_str); + + assert(call_result == WASMER_OK); + + assert(print_str_called); + assert(memory_len == 17); + assert(ptr_len == 13); + assert(0 == strcmp(actual_str, "Hello, World!")); + assert(context_data_value == actual_context_data_value); + assert(local_context.call_count == 1); + + printf("Destroying trampoline buffer\n"); + wasmer_trampoline_buffer_destroy(tb); + printf("Destroying func\n"); + wasmer_import_func_destroy(func); + printf("Destroy instance\n"); + wasmer_instance_destroy(instance); + free(context_data); + return 0; +} diff --git a/lib/runtime-c-api/tests/test-import-function.c b/lib/runtime-c-api/tests/test-import-function.c index 92e3e821d..a6bea2a54 100644 --- a/lib/runtime-c-api/tests/test-import-function.c +++ b/lib/runtime-c-api/tests/test-import-function.c @@ -14,8 +14,15 @@ typedef struct { int value; } context_data; +struct print_str_context { + int call_count; +}; + void print_str(wasmer_instance_context_t *ctx, int32_t ptr, int32_t len) { + struct print_str_context *local_context = wasmer_trampoline_get_context(); + local_context->call_count++; + const wasmer_memory_t *memory = wasmer_instance_context_memory(ctx, 0); uint32_t mem_len = wasmer_memory_length(memory); uint8_t *mem_bytes = wasmer_memory_data(memory); @@ -36,9 +43,22 @@ int main() { wasmer_value_tag params_sig[] = {WASM_I32, WASM_I32}; wasmer_value_tag returns_sig[] = {}; + struct print_str_context local_context = { + .call_count = 0 + }; + + printf("Creating trampoline buffer\n"); + wasmer_trampoline_buffer_builder_t *tbb = wasmer_trampoline_buffer_builder_new(); + unsigned long print_str_idx = wasmer_trampoline_buffer_builder_add_context_trampoline( + tbb, + (wasmer_trampoline_callable_t *) print_str, + (void *) &local_context + ); + wasmer_trampoline_buffer_t *tb = wasmer_trampoline_buffer_builder_build(tbb); + const wasmer_trampoline_callable_t *print_str_callable = wasmer_trampoline_buffer_get_trampoline(tb, print_str_idx); printf("Creating new func\n"); - wasmer_import_func_t *func = wasmer_import_func_new((void (*)(void *)) print_str, params_sig, 2, returns_sig, 0); + wasmer_import_func_t *func = wasmer_import_func_new((void (*)(void *)) print_str_callable, params_sig, 2, returns_sig, 0); wasmer_import_t import; char *module_name = "env"; @@ -95,7 +115,10 @@ int main() assert(ptr_len == 13); assert(0 == strcmp(actual_str, "Hello, World!")); assert(context_data_value == actual_context_data_value); + assert(local_context.call_count == 1); + printf("Destroying trampoline buffer\n"); + wasmer_trampoline_buffer_destroy(tb); printf("Destroying func\n"); wasmer_import_func_destroy(func); printf("Destroy instance\n"); diff --git a/lib/runtime-c-api/wasmer.h b/lib/runtime-c-api/wasmer.h index 6752f71a1..7c0f5249a 100644 --- a/lib/runtime-c-api/wasmer.h +++ b/lib/runtime-c-api/wasmer.h @@ -133,6 +133,18 @@ typedef struct { } wasmer_serialized_module_t; +typedef struct { + +} wasmer_trampoline_buffer_builder_t; + +typedef struct { + +} wasmer_trampoline_callable_t; + +typedef struct { + +} wasmer_trampoline_buffer_t; + /** * Creates a new Module from the given wasm bytes. * Returns `wasmer_result_t::WASMER_OK` upon success. @@ -584,6 +596,47 @@ uint32_t wasmer_table_length(wasmer_table_t *table); */ wasmer_result_t wasmer_table_new(wasmer_table_t **table, wasmer_limits_t limits); +/** + * Adds a callinfo trampoline to the builder. + */ +uintptr_t wasmer_trampoline_buffer_builder_add_callinfo_trampoline(wasmer_trampoline_buffer_builder_t *builder, + const wasmer_trampoline_callable_t *func, + const void *ctx, + uint32_t num_params); + +/** + * Adds a context trampoline to the builder. + */ +uintptr_t wasmer_trampoline_buffer_builder_add_context_trampoline(wasmer_trampoline_buffer_builder_t *builder, + const wasmer_trampoline_callable_t *func, + const void *ctx); + +/** + * Finalizes the trampoline builder into an executable buffer. + */ +wasmer_trampoline_buffer_t *wasmer_trampoline_buffer_builder_build(wasmer_trampoline_buffer_builder_t *builder); + +/** + * Creates a new trampoline builder. + */ +wasmer_trampoline_buffer_builder_t *wasmer_trampoline_buffer_builder_new(void); + +/** + * Destroys the trampoline buffer if not null. + */ +void wasmer_trampoline_buffer_destroy(wasmer_trampoline_buffer_t *buffer); + +/** + * Returns the callable pointer for the trampoline with index `idx`. + */ +const wasmer_trampoline_callable_t *wasmer_trampoline_buffer_get_trampoline(const wasmer_trampoline_buffer_t *buffer, + uintptr_t idx); + +/** + * Returns the context added by `add_context_trampoline`, from within the callee function. + */ +void *wasmer_trampoline_get_context(void); + /** * Returns true for valid wasm bytes and false for invalid bytes */ diff --git a/lib/runtime-c-api/wasmer.hh b/lib/runtime-c-api/wasmer.hh index 99e21fcc8..1c00b74c3 100644 --- a/lib/runtime-c-api/wasmer.hh +++ b/lib/runtime-c-api/wasmer.hh @@ -131,6 +131,18 @@ struct wasmer_serialized_module_t { }; +struct wasmer_trampoline_buffer_builder_t { + +}; + +struct wasmer_trampoline_callable_t { + +}; + +struct wasmer_trampoline_buffer_t { + +}; + extern "C" { /// Creates a new Module from the given wasm bytes. @@ -458,6 +470,33 @@ uint32_t wasmer_table_length(wasmer_table_t *table); /// and `wasmer_last_error_message` to get an error message. wasmer_result_t wasmer_table_new(wasmer_table_t **table, wasmer_limits_t limits); +/// Adds a callinfo trampoline to the builder. +uintptr_t wasmer_trampoline_buffer_builder_add_callinfo_trampoline(wasmer_trampoline_buffer_builder_t *builder, + const wasmer_trampoline_callable_t *func, + const void *ctx, + uint32_t num_params); + +/// Adds a context trampoline to the builder. +uintptr_t wasmer_trampoline_buffer_builder_add_context_trampoline(wasmer_trampoline_buffer_builder_t *builder, + const wasmer_trampoline_callable_t *func, + const void *ctx); + +/// Finalizes the trampoline builder into an executable buffer. +wasmer_trampoline_buffer_t *wasmer_trampoline_buffer_builder_build(wasmer_trampoline_buffer_builder_t *builder); + +/// Creates a new trampoline builder. +wasmer_trampoline_buffer_builder_t *wasmer_trampoline_buffer_builder_new(); + +/// Destroys the trampoline buffer if not null. +void wasmer_trampoline_buffer_destroy(wasmer_trampoline_buffer_t *buffer); + +/// Returns the callable pointer for the trampoline with index `idx`. +const wasmer_trampoline_callable_t *wasmer_trampoline_buffer_get_trampoline(const wasmer_trampoline_buffer_t *buffer, + uintptr_t idx); + +/// Returns the context added by `add_context_trampoline`, from within the callee function. +void *wasmer_trampoline_get_context(); + /// Returns true for valid wasm bytes and false for invalid bytes bool wasmer_validate(const uint8_t *wasm_bytes, uint32_t wasm_bytes_len); diff --git a/lib/runtime-core/src/lib.rs b/lib/runtime-core/src/lib.rs index 20708c17a..662f5c970 100644 --- a/lib/runtime-core/src/lib.rs +++ b/lib/runtime-core/src/lib.rs @@ -8,6 +8,10 @@ extern crate field_offset; #[macro_use] extern crate serde_derive; +#[allow(unused_imports)] +#[macro_use] +extern crate lazy_static; + #[macro_use] mod macros; #[doc(hidden)] @@ -29,12 +33,16 @@ mod sig_registry; pub mod structures; mod sys; pub mod table; +#[cfg(all(unix, target_arch = "x86_64"))] +pub mod trampoline_x64; pub mod typed_func; pub mod types; pub mod units; pub mod vm; #[doc(hidden)] pub mod vmcalls; +#[cfg(all(unix, target_arch = "x86_64"))] +pub use trampoline_x64 as trampoline; use self::error::CompileResult; #[doc(inline)] diff --git a/lib/runtime-core/src/trampoline_x64.rs b/lib/runtime-core/src/trampoline_x64.rs new file mode 100644 index 000000000..4defd1fa7 --- /dev/null +++ b/lib/runtime-core/src/trampoline_x64.rs @@ -0,0 +1,245 @@ +//! Trampoline generator for carrying context with function pointer. +//! +//! This makes use of the `mm0` register to pass the context as an implicit "parameter" because `mm0` is +//! not used to pass parameters and is almost never used by modern compilers. It's still better to call +//! `get_context()` as early as possible in the callee function though, as a good practice. +//! +//! Variadic functions are not supported because `rax` is used by the trampoline code. + +use crate::loader::CodeMemory; +use std::{mem, slice}; + +lazy_static! { + /// Reads the context pointer from `mm0`. + /// + /// This function generates code at runtime since `asm!` macro is not yet stable. + static ref GET_CONTEXT: extern "C" fn () -> *const CallContext = { + static CODE: &'static [u8] = &[ + 0x48, 0x0f, 0x7e, 0xc0, // movq %mm0, %rax + 0xc3, // retq + ]; + let mut mem = CodeMemory::new(4096); + mem[..CODE.len()].copy_from_slice(CODE); + mem.make_executable(); + let ptr = mem.as_ptr(); + mem::forget(mem); + unsafe { + mem::transmute(ptr) + } + }; +} + +/// An opaque type for pointers to a callable memory location. +pub enum CallTarget {} + +/// An opaque type for context pointers. +pub enum CallContext {} + +/// An opaque type for generated trampolines' call entries. +pub enum Trampoline {} + +/// Trampoline Buffer Builder. +pub struct TrampolineBufferBuilder { + code: Vec, + offsets: Vec, +} + +/// Trampoline Buffer. +pub struct TrampolineBuffer { + code: CodeMemory, + offsets: Vec, +} + +fn value_to_bytes(ptr: &T) -> &[u8] { + unsafe { slice::from_raw_parts(ptr as *const T as *const u8, mem::size_of::()) } +} + +/// Calls `GET_CONTEXT` and returns the current context. +pub fn get_context() -> *const CallContext { + GET_CONTEXT() +} + +impl TrampolineBufferBuilder { + pub fn new() -> TrampolineBufferBuilder { + TrampolineBufferBuilder { + code: vec![], + offsets: vec![], + } + } + + /// Adds a context trampoline. + /// + /// This generates a transparent trampoline function that forwards any call to `target` with + /// unmodified params/returns. When called from the trampoline, `target` will have access to + /// the `context` specified here through `get_context()`. + /// + /// Note that since `rax` is overwritten internally, variadic functions are not supported as `target`. + pub fn add_context_trampoline( + &mut self, + target: *const CallTarget, + context: *const CallContext, + ) -> usize { + let idx = self.offsets.len(); + self.offsets.push(self.code.len()); + self.code.extend_from_slice(&[ + 0x48, 0xb8, // movabsq ?, %rax + ]); + self.code.extend_from_slice(value_to_bytes(&context)); + self.code.extend_from_slice(&[ + 0x48, 0x0f, 0x6e, 0xc0, // movq %rax, %mm0 + ]); + self.code.extend_from_slice(&[ + 0x48, 0xb8, // movabsq ?, %rax + ]); + self.code.extend_from_slice(value_to_bytes(&target)); + self.code.extend_from_slice(&[ + 0xff, 0xe0, // jmpq *%rax + ]); + idx + } + + /// Adds a callinfo trampoline. + /// + /// This generates a trampoline function that collects `num_params` parameters into an array + /// and passes the array into `target` as the second argument when called. The first argument + /// of `target` is the `context` specified here. + /// + /// Note that non-integer parameters/variadic functions are not supported. + pub fn add_callinfo_trampoline( + &mut self, + target: unsafe extern "C" fn(*const CallContext, *const u64) -> u64, + context: *const CallContext, + num_params: u32, + ) -> usize { + let idx = self.offsets.len(); + self.offsets.push(self.code.len()); + + let mut stack_offset: u32 = num_params.checked_mul(8).unwrap(); + if stack_offset % 16 == 0 { + stack_offset += 8; + } + + self.code.extend_from_slice(&[0x48, 0x81, 0xec]); // sub ?, %rsp + self.code.extend_from_slice(value_to_bytes(&stack_offset)); + for i in 0..num_params { + match i { + 0..=5 => { + // mov %?, ?(%rsp) + let prefix: &[u8] = match i { + 0 => &[0x48, 0x89, 0xbc, 0x24], // rdi + 1 => &[0x48, 0x89, 0xb4, 0x24], // rsi + 2 => &[0x48, 0x89, 0x94, 0x24], // rdx + 3 => &[0x48, 0x89, 0x8c, 0x24], // rcx + 4 => &[0x4c, 0x89, 0x84, 0x24], // r8 + 5 => &[0x4c, 0x89, 0x8c, 0x24], // r9 + _ => unreachable!(), + }; + self.code.extend_from_slice(prefix); + self.code.extend_from_slice(value_to_bytes(&(i * 8u32))); + } + _ => { + self.code.extend_from_slice(&[ + 0x48, 0x8b, 0x84, 0x24, // mov ?(%rsp), %rax + ]); + self.code.extend_from_slice(value_to_bytes( + &((i - 6) * 8u32 + stack_offset + 8/* ret addr */), + )); + // mov %rax, ?(%rsp) + self.code.extend_from_slice(&[0x48, 0x89, 0x84, 0x24]); + self.code.extend_from_slice(value_to_bytes(&(i * 8u32))); + } + } + } + self.code.extend_from_slice(&[ + 0x48, 0xbf, // movabsq ?, %rdi + ]); + self.code.extend_from_slice(value_to_bytes(&context)); + self.code.extend_from_slice(&[ + 0x48, 0x89, 0xe6, // mov %rsp, %rsi + ]); + + self.code.extend_from_slice(&[ + 0x48, 0xb8, // movabsq ?, %rax + ]); + self.code.extend_from_slice(value_to_bytes(&target)); + self.code.extend_from_slice(&[ + 0xff, 0xd0, // callq *%rax + ]); + self.code.extend_from_slice(&[ + 0x48, 0x81, 0xc4, // add ?, %rsp + ]); + self.code.extend_from_slice(value_to_bytes(&stack_offset)); + self.code.extend_from_slice(&[ + 0xc3, //retq + ]); + idx + } + + /// Consumes the builder and builds the trampoline buffer. + pub fn build(self) -> TrampolineBuffer { + get_context(); // ensure lazy initialization is completed + + let mut code = CodeMemory::new(self.code.len()); + code[..self.code.len()].copy_from_slice(&self.code); + code.make_executable(); + TrampolineBuffer { + code, + offsets: self.offsets, + } + } +} + +impl TrampolineBuffer { + /// Returns the trampoline pointer at index `idx`. + pub fn get_trampoline(&self, idx: usize) -> *const Trampoline { + &self.code[self.offsets[idx]] as *const u8 as *const Trampoline + } +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_context_trampoline() { + struct TestContext { + value: i32, + } + extern "C" fn do_add(a: i32, b: f32) -> f32 { + let ctx = unsafe { &*(get_context() as *const TestContext) }; + a as f32 + b + ctx.value as f32 + } + let mut builder = TrampolineBufferBuilder::new(); + let ctx = TestContext { value: 3 }; + let idx = builder.add_context_trampoline( + do_add as usize as *const _, + &ctx as *const TestContext as *const _, + ); + let buf = builder.build(); + let t = buf.get_trampoline(idx); + let ret = unsafe { mem::transmute::<_, extern "C" fn(i32, f32) -> f32>(t)(1, 2.0) as i32 }; + assert_eq!(ret, 6); + } + #[test] + fn test_callinfo_trampoline() { + struct TestContext { + value: i32, + } + unsafe extern "C" fn do_add(ctx: *const CallContext, args: *const u64) -> u64 { + let ctx = &*(ctx as *const TestContext); + let args: &[u64] = slice::from_raw_parts(args, 8); + (args.iter().map(|x| *x as i32).fold(0, |a, b| a + b) + ctx.value) as u64 + } + let mut builder = TrampolineBufferBuilder::new(); + let ctx = TestContext { value: 100 }; + let idx = + builder.add_callinfo_trampoline(do_add, &ctx as *const TestContext as *const _, 8); + let buf = builder.build(); + let t = buf.get_trampoline(idx); + let ret = unsafe { + mem::transmute::<_, extern "C" fn(i32, i32, i32, i32, i32, i32, i32, i32) -> i32>(t)( + 1, 2, 3, 4, 5, 6, 7, 8, + ) as i32 + }; + assert_eq!(ret, 136); + } +}