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

feat(runtime-c-api) Implement wasmer_trap #1133

Merged
merged 20 commits into from
Jan 15, 2020
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
ec37859
chore(runtime-c-api) Update headers.
Hywan Jan 10, 2020
e913e89
chore(runtime-c-api) Fix CS.
Hywan Jan 10, 2020
35459c2
feat(runtime-c-api) Implement `wasmer_import_trap`.
Hywan Jan 10, 2020
6846204
chore(runtime-c-api) Update headers.
Hywan Jan 10, 2020
c7a4825
doc(runtime-core) Update documentation.
Hywan Jan 10, 2020
4bf3d6d
test(runtime-c-api) Test `wasmer_import_trap`.
Hywan Jan 10, 2020
8790f6d
feat(runtime-c-api) Check pointers aren't null in `wasmer_import_trap`.
Hywan Jan 13, 2020
b45ead2
test(runtime-c-api) Test `wasmer_import_trap`.
Hywan Jan 13, 2020
a506411
doc(runtime-c-api) Improve documentation of `wasmer_import_trap`.
Hywan Jan 13, 2020
b494bd8
doc(runtime-c-api) Improve `wasmer_import_func_new`'s documentation.
Hywan Jan 13, 2020
6e7d5ba
chore(runtime-c-api) Update C/C++ headers.
Hywan Jan 13, 2020
b5e96b8
doc(changelog) Add #1133.
Hywan Jan 13, 2020
176152e
test(runtime-c-api) Remove a hardcoded value.
Hywan Jan 13, 2020
c0b439e
feat(runtime-c-api) Rename `wasmer_import_trap` to `wasmer_trap`.
Hywan Jan 15, 2020
4cdf868
doc(runtime-c-api) Explain why code is unreachable in `wasmer_trap`.
Hywan Jan 15, 2020
bcbde69
doc(runtime-core) Fix a typo.
Hywan Jan 15, 2020
cd16a7d
chore(runtime-c-api) Update C/C++ headers.
Hywan Jan 15, 2020
ce3fb49
Merge branch 'master' into feat-runtime-c-api-import-trap
Hywan Jan 15, 2020
f4ae606
doc(changelog) Update #1133.
Hywan Jan 15, 2020
fb06ee3
test(runtime-c-api) Rename `wasmer_import_trap` to `wasmer_trap`.
Hywan Jan 15, 2020
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
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_import_trap` function in the C API, to properly error from within a host function
- [#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
- [#1097](https://github.com/wasmerio/wasmer/pull/1097) Move inline breakpoint outside of runtime backend
- [#1095](https://github.com/wasmerio/wasmer/pull/1095) Update to cranelift 0.52.
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
76 changes: 72 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 caller owns the object and should call `wasmer_import_func_destroy` to free it.
/// 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.
#[no_mangle]
#[allow(clippy::cast_ptr_alignment)]
pub unsafe extern "C" fn wasmer_import_func_new(
Expand All @@ -661,6 +682,53 @@ 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_import_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();
Hywan marked this conversation as resolved.
Show resolved Hide resolved

(&*ctx.module)
Hywan marked this conversation as resolved.
Show resolved Hide resolved
.runnable_module
.do_early_trap(Box::new(error_message)); // never returns

#[allow(unreachable_code)]
wasmer_result_t::WASMER_OK
Copy link
Contributor

Choose a reason for hiding this comment

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

Perhaps we should panic here? Seems like a small thing, not sure what best practice is here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

cbindgen doesn't generate a C bindgen for a Rust function with ! as returned type.

Since I need to error before calling do_early_trap, I've decided to return a wasmer_result_t. And in the best scenario, I return WASMER_OK, even if it's unreachable. I'll add more documentation to explain.

}

/// 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_import_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 @@ -661,6 +675,24 @@ wasmer_import_object_iter_t *wasmer_import_object_iterate_functions(const wasmer
*/
wasmer_import_object_t *wasmer_import_object_new(void);

/**
* 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_import_trap(const wasmer_instance_context_t *ctx, const char *error_message);

/**
* Calls an instances exported function by `name` with the provided parameters.
* Results are set using the provided `results` pointer.
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 @@ -528,6 +542,22 @@ wasmer_import_object_iter_t *wasmer_import_object_iterate_functions(const wasmer
/// See also `wasmer_import_object_append`
wasmer_import_object_t *wasmer_import_object_new();

/// 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_import_trap(const wasmer_instance_context_t *ctx, const char *error_message);

/// Calls an instances exported function by `name` with the provided parameters.
/// Results are set using the provided `results` pointer.
///
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 @@ -219,10 +219,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 are returning a trampoline that is matched with that
Copy link
Contributor

Choose a reason for hiding this comment

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

I prefer returned to returning here;

, we return a trampoline... is probably best though.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I thought it was an English typo.

Copy link
Contributor

Choose a reason for hiding this comment

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

I believe it's grammatically correct and called the "passive voice", it's often discouraged in English though because it takes more words and ends up being less specific. For example, I generally prefer direct writing like, "This function takes a function signature and returns a corresponding trampoline".

/// 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