Skip to content

Commit

Permalink
Merge #1133
Browse files Browse the repository at this point in the history
1133: feat(runtime-c-api) Implement `wasmer_trap` r=Hywan a=Hywan

Idea is to provide an API to get fallible host function by calling `wasmer_trap` to run the Wasmer trapping API.

This is probably the easiest solution to not break the existing API, and not add a lot of complexity in the code.

Co-authored-by: Ivan Enderlin <[email protected]>
  • Loading branch information
bors[bot] and Hywan authored Jan 15, 2020
2 parents d350613 + fb06ee3 commit 50acd8b
Show file tree
Hide file tree
Showing 10 changed files with 237 additions and 12 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## **[Unreleased]**

- [#1133](https://github.com/wasmerio/wasmer/pull/1133) New `wasmer_trap` function in the C API, to properly error from within a host function
- [#1147](https://github.com/wasmerio/wasmer/pull/1147) Remove `log` and `trace` macros from `wasmer-runtime-core`, remove `debug` and `trace` features from `wasmer-*` crates, use the `log` crate for logging and use `fern` in the Wasmer CLI binary to output log messages. Colorized output will be enabled automatically if printing to a terminal, to force colorization on or off, set the `WASMER_COLOR` environment variable to `true` or `false`.
- [#1128](https://github.com/wasmerio/wasmer/pull/1128) Fix a crash when a host function is missing and the `allow_missing_functions` flag is enabled
- [#1099](https://github.com/wasmerio/wasmer/pull/1099) Remove `backend::Backend` from `wasmer_runtime_core`
Expand Down
1 change: 1 addition & 0 deletions lib/runtime-c-api/src/export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,7 @@ pub unsafe extern "C" fn wasmer_export_func_call(

let instance = &*named_export.instance;
let result = instance.call(&named_export.name, &params[..]);

match result {
Ok(results_vec) => {
if !results_vec.is_empty() {
Expand Down
82 changes: 78 additions & 4 deletions lib/runtime-c-api/src/import/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,20 @@
use crate::{
error::{update_last_error, CApiError},
export::{wasmer_import_export_kind, wasmer_import_export_value},
instance::wasmer_instance_context_t,
module::wasmer_module_t,
value::wasmer_value_tag,
wasmer_byte_array, wasmer_result_t,
};
use libc::c_uint;
use std::{convert::TryFrom, ffi::c_void, ptr, slice, sync::Arc};
use wasmer_runtime::{Global, Memory, Module, Table};
use std::{
convert::TryFrom,
ffi::{c_void, CStr},
os::raw::c_char,
ptr, slice,
sync::Arc,
};
use wasmer_runtime::{Ctx, Global, Memory, Module, Table};
use wasmer_runtime_core::{
export::{Context, Export, FuncPointer},
import::{ImportObject, ImportObjectIterator},
Expand Down Expand Up @@ -636,9 +643,23 @@ pub unsafe extern "C" fn wasmer_import_func_params_arity(
}
}

/// Creates new func
/// Creates new host function, aka imported function. `func` is a
/// function pointer, where the first argument is the famous `vm::Ctx`
/// (in Rust), or `wasmer_instance_context_t` (in C). All arguments
/// must be typed with compatible WebAssembly native types:
///
/// | WebAssembly type | C/C++ type |
/// | ---------------- | ---------- |
/// | `i32` | `int32_t` |
/// | `i64` | `int64_t` |
/// | `f32` | `float` |
/// | `f64` | `double` |
///
/// The function pointer must have a lifetime greater than the
/// WebAssembly instance lifetime.
///
/// The caller owns the object and should call `wasmer_import_func_destroy` to free it.
/// The caller owns the object and should call
/// `wasmer_import_func_destroy` to free it.
#[no_mangle]
#[allow(clippy::cast_ptr_alignment)]
pub unsafe extern "C" fn wasmer_import_func_new(
Expand All @@ -661,6 +682,59 @@ pub unsafe extern "C" fn wasmer_import_func_new(
Box::into_raw(export) as *mut wasmer_import_func_t
}

/// Stop the execution of a host function, aka imported function. The
/// function must be used _only_ inside a host function.
///
/// The pointer to `wasmer_instance_context_t` is received by the host
/// function as its first argument. Just passing it to `ctx` is fine.
///
/// The error message must have a greater lifetime than the host
/// function itself since the error is read outside the host function
/// with `wasmer_last_error_message`.
///
/// This function returns `wasmer_result_t::WASMER_ERROR` if `ctx` or
/// `error_message` are null.
///
/// This function never returns otherwise.
#[no_mangle]
#[allow(clippy::cast_ptr_alignment)]
pub unsafe extern "C" fn wasmer_trap(
ctx: *const wasmer_instance_context_t,
error_message: *const c_char,
) -> wasmer_result_t {
if ctx.is_null() {
update_last_error(CApiError {
msg: "ctx ptr is null in wasmer_import_trap".to_string(),
});

return wasmer_result_t::WASMER_ERROR;
}

if error_message.is_null() {
update_last_error(CApiError {
msg: "error_message is null in wasmer_import_trap".to_string(),
});

return wasmer_result_t::WASMER_ERROR;
}

let ctx = &*(ctx as *const Ctx);
let error_message = CStr::from_ptr(error_message).to_str().unwrap();

(&*ctx.module)
.runnable_module
.do_early_trap(Box::new(error_message)); // never returns

// cbindgen does not generate a binding for a function that
// returns `!`. Since we also need to error in some cases, the
// output type of `wasmer_trap` is `wasmer_result_t`. But the OK
// case is not reachable because `do_early_trap` never
// returns. That's a compromise to satisfy the Rust type system,
// cbindgen, and get an acceptable clean code.
#[allow(unreachable_code)]
wasmer_result_t::WASMER_OK
}

/// Sets the params buffer to the parameter types of the given wasmer_import_func_t
///
/// Returns `wasmer_result_t::WASMER_OK` upon success.
Expand Down
1 change: 1 addition & 0 deletions lib/runtime-c-api/tests/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ test-exported-memory
test-exports
test-globals
test-import-function
test-import-trap
test-import-object
test-imports
test-instantiate
Expand Down
5 changes: 5 additions & 0 deletions lib/runtime-c-api/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ add_executable(test-exported-memory test-exported-memory.c)
add_executable(test-exports test-exports.c)
add_executable(test-globals test-globals.c)
add_executable(test-import-function test-import-function.c)
add_executable(test-import-trap test-import-trap.c)
add_executable(test-imports test-imports.c)
add_executable(test-import-object test-import-object.c)
add_executable(test-instantiate test-instantiate.c)
Expand Down Expand Up @@ -64,6 +65,10 @@ target_link_libraries(test-import-function general ${WASMER_LIB})
target_compile_options(test-import-function PRIVATE ${COMPILER_OPTIONS})
add_test(test-import-function test-import-function)

target_link_libraries(test-import-trap general ${WASMER_LIB})
target_compile_options(test-import-trap PRIVATE ${COMPILER_OPTIONS})
add_test(test-import-trap test-import-trap)

target_link_libraries(test-imports general ${WASMER_LIB})
target_compile_options(test-imports PRIVATE ${COMPILER_OPTIONS})
add_test(test-imports test-imports)
Expand Down
2 changes: 1 addition & 1 deletion lib/runtime-c-api/tests/test-import-function.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ void print_str(wasmer_instance_context_t *ctx, int32_t ptr, int32_t len)
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);
printf("In print_str, memory len: %d, ptr_len: %d, str %s\n", mem_len, len, actual_str);
print_str_called = true;
memory_len = mem_len;
ptr_len = len;
Expand Down
80 changes: 80 additions & 0 deletions lib/runtime-c-api/tests/test-import-trap.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
#include <stdio.h>
#include "../wasmer.h"
#include <assert.h>
#include <stdint.h>
#include <string.h>

static const char *trap_error_message = "Hello";

void print_str(wasmer_instance_context_t *ctx, int32_t _ptr, int32_t _len)
{
wasmer_trap(ctx, trap_error_message);
}

int main()
{
wasmer_value_tag params_sig[] = {WASM_I32, WASM_I32};
wasmer_value_tag returns_sig[] = {};

printf("Creating new func\n");
wasmer_import_func_t *func = wasmer_import_func_new((void (*)(void *)) print_str, params_sig, 2, returns_sig, 0);

char *module_name = "env";
wasmer_byte_array module_name_bytes = {
.bytes = (const uint8_t *) module_name,
.bytes_len = strlen(module_name),
};

char *import_name = "print_str";
wasmer_byte_array import_name_bytes = {
.bytes = (const uint8_t *) import_name,
.bytes_len = strlen(import_name),
};

wasmer_import_t import = {
.module_name = module_name_bytes,
.import_name = import_name_bytes,
.tag = WASM_FUNCTION,
.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);

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

assert(call_result == WASMER_ERROR);

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(0 == strcmp(error_str, "Call error: \"Hello\""));

printf("Destroying func\n");
wasmer_import_func_destroy(func);
printf("Destroy instance\n");
wasmer_instance_destroy(instance);

return 0;
}
38 changes: 35 additions & 3 deletions lib/runtime-c-api/wasmer.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ enum Version {
Unknown = 0,
/**
* Latest version. See `wasmer_wasi::WasiVersion::Latest` to
* leran more.
* learn more.
*/
Latest = 1,
/**
Expand Down Expand Up @@ -537,9 +537,23 @@ unsigned int wasmer_import_descriptors_len(wasmer_import_descriptors_t *exports)
void wasmer_import_func_destroy(wasmer_import_func_t *func);

/**
* Creates new func
* Creates new host function, aka imported function. `func` is a
* function pointer, where the first argument is the famous `vm::Ctx`
* (in Rust), or `wasmer_instance_context_t` (in C). All arguments
* must be typed with compatible WebAssembly native types:
*
* The caller owns the object and should call `wasmer_import_func_destroy` to free it.
* | WebAssembly type | C/C++ type |
* | ---------------- | ---------- |
* | `i32` | `int32_t` |
* | `i64` | `int64_t` |
* | `f32` | `float` |
* | `f64` | `double` |
*
* The function pointer must have a lifetime greater than the
* WebAssembly instance lifetime.
*
* The caller owns the object and should call
* `wasmer_import_func_destroy` to free it.
*/
wasmer_import_func_t *wasmer_import_func_new(void (*func)(void *data),
const wasmer_value_tag *params,
Expand Down Expand Up @@ -966,6 +980,24 @@ const wasmer_trampoline_callable_t *wasmer_trampoline_buffer_get_trampoline(cons
void *wasmer_trampoline_get_context(void);
#endif

/**
* Stop the execution of a host function, aka imported function. The
* function must be used _only_ inside a host function.
*
* The pointer to `wasmer_instance_context_t` is received by the host
* function as its first argument. Just passing it to `ctx` is fine.
*
* The error message must have a greater lifetime than the host
* function itself since the error is read outside the host function
* with `wasmer_last_error_message`.
*
* This function returns `wasmer_result_t::WASMER_ERROR` if `ctx` or
* `error_message` are null.
*
* This function never returns otherwise.
*/
wasmer_result_t wasmer_trap(const wasmer_instance_context_t *ctx, const char *error_message);

/**
* Returns true for valid wasm bytes and false for invalid bytes
*/
Expand Down
36 changes: 33 additions & 3 deletions lib/runtime-c-api/wasmer.hh
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ enum class Version : uint8_t {
/// Version cannot be detected or is unknown.
Unknown = 0,
/// Latest version. See `wasmer_wasi::WasiVersion::Latest` to
/// leran more.
/// learn more.
Latest = 1,
/// `wasi_unstable`.
Snapshot0 = 2,
Expand Down Expand Up @@ -431,9 +431,23 @@ unsigned int wasmer_import_descriptors_len(wasmer_import_descriptors_t *exports)
/// Frees memory for the given Func
void wasmer_import_func_destroy(wasmer_import_func_t *func);

/// Creates new func
/// Creates new host function, aka imported function. `func` is a
/// function pointer, where the first argument is the famous `vm::Ctx`
/// (in Rust), or `wasmer_instance_context_t` (in C). All arguments
/// must be typed with compatible WebAssembly native types:
///
/// The caller owns the object and should call `wasmer_import_func_destroy` to free it.
/// | WebAssembly type | C/C++ type |
/// | ---------------- | ---------- |
/// | `i32` | `int32_t` |
/// | `i64` | `int64_t` |
/// | `f32` | `float` |
/// | `f64` | `double` |
///
/// The function pointer must have a lifetime greater than the
/// WebAssembly instance lifetime.
///
/// The caller owns the object and should call
/// `wasmer_import_func_destroy` to free it.
wasmer_import_func_t *wasmer_import_func_new(void (*func)(void *data),
const wasmer_value_tag *params,
unsigned int params_len,
Expand Down Expand Up @@ -763,6 +777,22 @@ const wasmer_trampoline_callable_t *wasmer_trampoline_buffer_get_trampoline(cons
void *wasmer_trampoline_get_context();
#endif

/// Stop the execution of a host function, aka imported function. The
/// function must be used _only_ inside a host function.
///
/// The pointer to `wasmer_instance_context_t` is received by the host
/// function as its first argument. Just passing it to `ctx` is fine.
///
/// The error message must have a greater lifetime than the host
/// function itself since the error is read outside the host function
/// with `wasmer_last_error_message`.
///
/// This function returns `wasmer_result_t::WASMER_ERROR` if `ctx` or
/// `error_message` are null.
///
/// This function never returns otherwise.
wasmer_result_t wasmer_trap(const wasmer_instance_context_t *ctx, const char *error_message);

/// 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
3 changes: 2 additions & 1 deletion lib/runtime-core/src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,10 +149,11 @@ pub trait RunnableModule: Send + Sync {
}

/// A wasm trampoline contains the necessary data to dynamically call an exported wasm function.
/// Given a particular signature index, we are returned a trampoline that is matched with that
/// Given a particular signature index, we return a trampoline that is matched with that
/// signature and an invoke function that can call the trampoline.
fn get_trampoline(&self, info: &ModuleInfo, sig_index: SigIndex) -> Option<Wasm>;

/// Trap an error.
unsafe fn do_early_trap(&self, data: Box<dyn Any + Send>) -> !;

/// Returns the machine code associated with this module.
Expand Down

0 comments on commit 50acd8b

Please sign in to comment.