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

Added context trampoline into runtime #481

Merged
merged 11 commits into from
Jun 5, 2019
2 changes: 2 additions & 0 deletions lib/runtime-c-api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down
60 changes: 60 additions & 0 deletions lib/runtime-c-api/src/trampoline.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
use std::ffi::c_void;
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;

#[no_mangle]
#[allow(clippy::cast_ptr_alignment)]
pub extern "C" fn wasmer_trampoline_buffer_builder_new() -> *mut wasmer_trampoline_buffer_builder_t
losfair marked this conversation as resolved.
Show resolved Hide resolved
{
Box::into_raw(Box::new(TrampolineBufferBuilder::new())) as *mut _
}

#[no_mangle]
#[allow(clippy::cast_ptr_alignment)]
pub unsafe extern "C" fn wasmer_trampoline_buffer_builder_add_function(
b: *mut wasmer_trampoline_buffer_builder_t,
losfair marked this conversation as resolved.
Show resolved Hide resolved
f: *const wasmer_trampoline_callable_t,
ctx: *const c_void,
) -> usize {
let b = &mut *(b as *mut TrampolineBufferBuilder);
b.add_function(f as *const CallTarget, ctx as *const CallContext)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, check that ctx isn't a null pointer. Use update_last_error to raise an error accordingly.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, check that f and b aren't null.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really need to check null pointers explicitly that way?

It's easy to debug SIGSEGV's caused by null pointers.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is what we did in the other function, it's debatable :-).

}

#[no_mangle]
#[allow(clippy::cast_ptr_alignment)]
pub unsafe extern "C" fn wasmer_trampoline_buffer_builder_build(
b: *mut wasmer_trampoline_buffer_builder_t,
) -> *mut wasmer_trampoline_buffer_t {
let b = Box::from_raw(b as *mut TrampolineBufferBuilder);
Box::into_raw(Box::new(b.build())) as *mut _
}

#[no_mangle]
#[allow(clippy::cast_ptr_alignment)]
pub unsafe extern "C" fn wasmer_trampoline_buffer_destroy(b: *mut wasmer_trampoline_buffer_t) {
Box::from_raw(b);
losfair marked this conversation as resolved.
Show resolved Hide resolved
}

#[no_mangle]
#[allow(clippy::cast_ptr_alignment)]
pub unsafe extern "C" fn wasmer_trampoline_buffer_get_trampoline(
b: *const wasmer_trampoline_buffer_t,
idx: usize,
) -> *const wasmer_trampoline_callable_t {
let b = &*(b as *const TrampolineBuffer);
b.get_trampoline(idx) as _
}

#[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
}
25 changes: 24 additions & 1 deletion lib/runtime-c-api/tests/test-import-function.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
losfair marked this conversation as resolved.
Show resolved Hide resolved
{
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);
Expand All @@ -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_function(
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";
Expand Down Expand Up @@ -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");
Expand Down
27 changes: 27 additions & 0 deletions lib/runtime-c-api/wasmer.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -584,6 +596,21 @@ uint32_t wasmer_table_length(wasmer_table_t *table);
*/
wasmer_result_t wasmer_table_new(wasmer_table_t **table, wasmer_limits_t limits);

uintptr_t wasmer_trampoline_buffer_builder_add_function(wasmer_trampoline_buffer_builder_t *b,
const wasmer_trampoline_callable_t *f,
const void *ctx);

wasmer_trampoline_buffer_t *wasmer_trampoline_buffer_builder_build(wasmer_trampoline_buffer_builder_t *b);

wasmer_trampoline_buffer_builder_t *wasmer_trampoline_buffer_builder_new(void);

void wasmer_trampoline_buffer_destroy(wasmer_trampoline_buffer_t *b);

const wasmer_trampoline_callable_t *wasmer_trampoline_buffer_get_trampoline(const wasmer_trampoline_buffer_t *b,
uintptr_t idx);

void *wasmer_trampoline_get_context(void);

/**
* Returns true for valid wasm bytes and false for invalid bytes
*/
Expand Down
27 changes: 27 additions & 0 deletions lib/runtime-c-api/wasmer.hh
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -458,6 +470,21 @@ 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);

uintptr_t wasmer_trampoline_buffer_builder_add_function(wasmer_trampoline_buffer_builder_t *b,
const wasmer_trampoline_callable_t *f,
const void *ctx);

wasmer_trampoline_buffer_t *wasmer_trampoline_buffer_builder_build(wasmer_trampoline_buffer_builder_t *b);

wasmer_trampoline_buffer_builder_t *wasmer_trampoline_buffer_builder_new();

void wasmer_trampoline_buffer_destroy(wasmer_trampoline_buffer_t *b);

const wasmer_trampoline_callable_t *wasmer_trampoline_buffer_get_trampoline(const wasmer_trampoline_buffer_t *b,
uintptr_t idx);

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);

Expand Down
8 changes: 8 additions & 0 deletions lib/runtime-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand All @@ -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)]
Expand Down
130 changes: 130 additions & 0 deletions lib/runtime-core/src/trampoline_x64.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
//! 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;

lazy_static! {
losfair marked this conversation as resolved.
Show resolved Hide resolved
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();
::std::mem::forget(mem);
losfair marked this conversation as resolved.
Show resolved Hide resolved
unsafe {
::std::mem::transmute(ptr)
}
};
}

pub enum CallTarget {}
losfair marked this conversation as resolved.
Show resolved Hide resolved
pub enum CallContext {}
pub enum Trampoline {}

pub struct TrampolineBufferBuilder {
code: Vec<u8>,
offsets: Vec<usize>,
}

pub struct TrampolineBuffer {
code: CodeMemory,
offsets: Vec<usize>,
}

fn pointer_to_bytes<T>(ptr: &*const T) -> &[u8] {
unsafe {
::std::slice::from_raw_parts(
ptr as *const *const T as *const u8,
::std::mem::size_of::<*const T>(),
)
}
}

pub fn get_context() -> *const CallContext {
GET_CONTEXT()
}

impl TrampolineBufferBuilder {
pub fn new() -> TrampolineBufferBuilder {
TrampolineBufferBuilder {
code: vec![],
offsets: vec![],
}
}

pub fn add_function(
&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(pointer_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(pointer_to_bytes(&target));
self.code.extend_from_slice(&[
0xff, 0xe0, // jmpq *%rax
]);
idx
}

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 {
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_trampoline_call() {
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_function(
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 { ::std::mem::transmute::<_, extern "C" fn(i32, f32) -> f32>(t)(1, 2.0) as i32 };
assert_eq!(ret, 6);
}
}