diff --git a/CHANGELOG.md b/CHANGELOG.md index 4052797e53a..30388c12a86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ ## **[Unreleased]** ### Added +- [#2053](https://github.com/wasmerio/wasmer/pull/2053) Implement the non-standard `wasi_get_unordered_imports` function in the C API. - [#2072](https://github.com/wasmerio/wasmer/pull/2072) Add `wasm_config_set_target`, along with `wasm_target_t`, `wasm_triple_t` and `wasm_cpu_features_t` in the unstable C API. - [#2059](https://github.com/wasmerio/wasmer/pull/2059) Ability to capture `stdout` and `stderr` with WASI in the C API. - [#2040](https://github.com/wasmerio/wasmer/pull/2040) Add `InstanceHandle::vmoffsets` to expose the offsets of the `vmctx` region. diff --git a/lib/c-api/build.rs b/lib/c-api/build.rs index 8e9c17f259c..e7f876690bb 100644 --- a/lib/c-api/build.rs +++ b/lib/c-api/build.rs @@ -61,23 +61,21 @@ macro_rules! map_feature_as_c_define { } fn main() { + if !running_self() { + return; + } + let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); let out_dir = env::var("OUT_DIR").unwrap(); - if building_c_api_headers() { - build_wasm_c_api_headers(&crate_dir, &out_dir); - build_wasmer_c_api_headers(&crate_dir, &out_dir); - } - + build_wasm_c_api_headers(&crate_dir, &out_dir); + build_wasmer_c_api_headers(&crate_dir, &out_dir); build_inline_c_env_vars(); } -/// Check whether we should build the C API headers. -/// -/// For the moment, it's always enabled, unless if the `DOCS_RS` -/// environment variable is present. -fn building_c_api_headers() -> bool { - env::var("DOCS_RS").is_err() +/// Check whether we should build the C API headers or set `inline-c` up. +fn running_self() -> bool { + env::var("DOCS_RS").is_err() && env::var("_CBINDGEN_IS_RUNNING").is_err() } /// Build the header files for the `wasm_c_api` API. @@ -274,7 +272,7 @@ fn add_wasmer_version(pre_header: &mut String) { /// Create a fresh new `Builder`, already pre-configured. fn new_builder(language: Language, crate_dir: &str, include_guard: &str, header: &str) -> Builder { - Builder::new() + let builder = Builder::new() .with_config(cbindgen::Config { sort_by: cbindgen::SortKey::Name, cpp_compat: true, @@ -290,7 +288,12 @@ fn new_builder(language: Language, crate_dir: &str, include_guard: &str, header: .with_define("feature", "jit", JIT_FEATURE_AS_C_DEFINE) .with_define("feature", "compiler", COMPILER_FEATURE_AS_C_DEFINE) .with_define("feature", "wasi", WASI_FEATURE_AS_C_DEFINE) - .with_define("feature", "emscripten", EMSCRIPTEN_FEATURE_AS_C_DEFINE) + .with_define("feature", "emscripten", EMSCRIPTEN_FEATURE_AS_C_DEFINE); + + #[cfg(feature = "system-libffi")] + let builder = builder.with_parse_expand_features(&["system-libffi"]); + + builder } /// Exclude types and functions from the `deprecated` API. @@ -458,8 +461,8 @@ fn exclude_items_from_wasm_c_api(builder: Builder) -> Builder { .exclude_item("wasi_env_set_memory") .exclude_item("wasi_env_t") .exclude_item("wasi_get_imports") - .exclude_item("wasi_get_imports_inner") .exclude_item("wasi_get_start_function") + .exclude_item("wasi_get_unordered_imports") .exclude_item("wasi_get_wasi_version") .exclude_item("wasi_version_t") .exclude_item("wasm_config_set_compiler") @@ -471,6 +474,15 @@ fn exclude_items_from_wasm_c_api(builder: Builder) -> Builder { .exclude_item("wasm_cpu_features_t") .exclude_item("wasm_module_name") .exclude_item("wasm_module_set_name") + .exclude_item("wasm_named_extern_module") + .exclude_item("wasm_named_extern_name") + .exclude_item("wasm_named_extern_t") + .exclude_item("wasm_named_extern_unwrap") + .exclude_item("wasm_named_extern_vec_copy") + .exclude_item("wasm_named_extern_vec_delete") + .exclude_item("wasm_named_extern_vec_new") + .exclude_item("wasm_named_extern_vec_new_empty") + .exclude_item("wasm_named_extern_vec_new_uninitialized") .exclude_item("wasm_target_delete") .exclude_item("wasm_target_new") .exclude_item("wasm_target_t") diff --git a/lib/c-api/src/wasm_c_api/externals/mod.rs b/lib/c-api/src/wasm_c_api/externals/mod.rs index 05fc8043543..23dff955ef1 100644 --- a/lib/c-api/src/wasm_c_api/externals/mod.rs +++ b/lib/c-api/src/wasm_c_api/externals/mod.rs @@ -20,6 +20,16 @@ pub struct wasm_extern_t { wasm_declare_boxed_vec!(extern); +/// Copy a `wasm_extern_t`. +#[no_mangle] +pub unsafe extern "C" fn wasm_extern_copy(r#extern: &wasm_extern_t) -> Box { + Box::new(r#extern.clone()) +} + +/// Delete an extern. +#[no_mangle] +pub unsafe extern "C" fn wasm_extern_delete(_extern: Option>) {} + #[no_mangle] pub unsafe extern "C" fn wasm_func_as_extern( func: Option<&wasm_func_t>, @@ -125,3 +135,60 @@ pub unsafe extern "C" fn wasm_extern_as_table( None } } + +#[cfg(test)] +mod tests { + use inline_c::assert_c; + + #[test] + fn test_extern_copy() { + (assert_c! { + #include "tests/wasmer_wasm.h" + + int main() { + wasm_engine_t* engine = wasm_engine_new(); + wasm_store_t* store = wasm_store_new(engine); + + wasm_byte_vec_t wat; + wasmer_byte_vec_new_from_string( + &wat, + "(module\n" + " (func (export \"function\")))" + ); + wasm_byte_vec_t wasm; + wat2wasm(&wat, &wasm); + + wasm_module_t* module = wasm_module_new(store, &wasm); + assert(module); + + wasm_extern_vec_t imports = WASM_EMPTY_VEC; + wasm_trap_t* traps = NULL; + + wasm_instance_t* instance = wasm_instance_new(store, module, &imports, &traps); + assert(instance); + + wasm_extern_vec_t exports; + wasm_instance_exports(instance, &exports); + + assert(exports.size == 1); + + wasm_extern_t* function = exports.data[0]; + assert(wasm_extern_kind(function) == WASM_EXTERN_FUNC); + + wasm_extern_t* function_copy = wasm_extern_copy(function); + assert(wasm_extern_kind(function_copy) == WASM_EXTERN_FUNC); + + wasm_extern_delete(function_copy); + wasm_instance_delete(instance); + wasm_module_delete(module); + wasm_byte_vec_delete(&wasm); + wasm_byte_vec_delete(&wat); + wasm_store_delete(store); + wasm_engine_delete(engine); + + return 0; + } + }) + .success(); + } +} diff --git a/lib/c-api/src/wasm_c_api/macros.rs b/lib/c-api/src/wasm_c_api/macros.rs index dc85f6a9662..85ce2a8ca95 100644 --- a/lib/c-api/src/wasm_c_api/macros.rs +++ b/lib/c-api/src/wasm_c_api/macros.rs @@ -403,7 +403,7 @@ int main() { #[no_mangle] pub unsafe extern "C" fn []( out_ptr: &mut [], - in_ptr: & []) + in_ptr: & []) { *out_ptr = in_ptr.clone(); } diff --git a/lib/c-api/src/wasm_c_api/types/export.rs b/lib/c-api/src/wasm_c_api/types/export.rs index 1654de8193e..6a78805cb77 100644 --- a/lib/c-api/src/wasm_c_api/types/export.rs +++ b/lib/c-api/src/wasm_c_api/types/export.rs @@ -42,20 +42,8 @@ impl From for wasm_exporttype_t { impl From<&ExportType> for wasm_exporttype_t { fn from(other: &ExportType) -> Self { - let name = { - let mut heap_str: Box = other.name().to_string().into_boxed_str(); - let char_ptr = heap_str.as_mut_ptr(); - let str_len = heap_str.bytes().len(); - let name_inner = wasm_name_t { - size: str_len, - data: char_ptr, - }; - Box::leak(heap_str); - - Box::new(name_inner) - }; - - let extern_type = Box::new(other.ty().into()); + let name: Box = Box::new(other.name().to_string().into()); + let extern_type: Box = Box::new(other.ty().into()); wasm_exporttype_t { name, extern_type } } diff --git a/lib/c-api/src/wasm_c_api/types/import.rs b/lib/c-api/src/wasm_c_api/types/import.rs index a1f94f800a3..9d060584dd3 100644 --- a/lib/c-api/src/wasm_c_api/types/import.rs +++ b/lib/c-api/src/wasm_c_api/types/import.rs @@ -50,37 +50,13 @@ impl From for wasm_importtype_t { impl From<&ImportType> for wasm_importtype_t { fn from(other: &ImportType) -> Self { - let module = { - let mut heap_str: Box = other.module().to_string().into_boxed_str(); - let char_ptr = heap_str.as_mut_ptr(); - let str_len = heap_str.bytes().len(); - let module_inner = wasm_name_t { - size: str_len, - data: char_ptr, - }; - Box::leak(heap_str); - - Box::new(module_inner) - }; - - let name = { - let mut heap_str: Box = other.name().to_string().into_boxed_str(); - let char_ptr = heap_str.as_mut_ptr(); - let str_len = heap_str.bytes().len(); - let name_inner = wasm_name_t { - size: str_len, - data: char_ptr, - }; - Box::leak(heap_str); - - Box::new(name_inner) - }; - - let extern_type = Box::new(other.ty().into()); + let module: Box = Box::new(other.module().to_string().into()); + let name: Box = Box::new(other.name().to_string().into()); + let extern_type: Box = Box::new(other.ty().into()); wasm_importtype_t { - name, module, + name, extern_type, } } diff --git a/lib/c-api/src/wasm_c_api/types/mod.rs b/lib/c-api/src/wasm_c_api/types/mod.rs index c518bce80a5..c022ba17ef1 100644 --- a/lib/c-api/src/wasm_c_api/types/mod.rs +++ b/lib/c-api/src/wasm_c_api/types/mod.rs @@ -28,6 +28,19 @@ wasm_declare_vec!(byte); #[allow(non_camel_case_types)] pub type wasm_name_t = wasm_byte_vec_t; +impl From for wasm_name_t { + fn from(string: String) -> Self { + let mut boxed_str: Box = string.into_boxed_str(); + let data = boxed_str.as_mut_ptr(); + let size = boxed_str.bytes().len(); + let wasm_name = Self { data, size }; + + Box::leak(boxed_str); + + wasm_name + } +} + // opaque type over `ExternRef`? #[allow(non_camel_case_types)] pub struct wasm_ref_t; diff --git a/lib/c-api/src/wasm_c_api/wasi/mod.rs b/lib/c-api/src/wasm_c_api/wasi/mod.rs index 9e9f3e8d72d..b3f8cda72d2 100644 --- a/lib/c-api/src/wasm_c_api/wasi/mod.rs +++ b/lib/c-api/src/wasm_c_api/wasi/mod.rs @@ -9,6 +9,7 @@ use super::{ instance::wasm_instance_t, module::wasm_module_t, store::wasm_store_t, + types::wasm_name_t, }; // required due to really weird Rust resolution rules for macros // https://github.com/rust-lang/rust/issues/57966 @@ -170,7 +171,9 @@ pub struct wasi_env_t { inner: WasiEnv, } -/// Takes ownership over the `wasi_config_t`. +/// Create a new WASI environment. +/// +/// It take ownership over the `wasi_config_t`. #[no_mangle] pub extern "C" fn wasi_env_new(mut config: Box) -> Option> { if !config.inherit_stdout { @@ -194,6 +197,7 @@ pub extern "C" fn wasi_env_new(mut config: Box) -> Option>) {} @@ -341,24 +345,219 @@ pub unsafe extern "C" fn wasi_get_wasi_version(module: &wasm_module_t) -> wasi_v .unwrap_or(wasi_version_t::INVALID_VERSION) } -/// Takes ownership of `wasi_env_t`. +/// Non-standard type wrapping `wasm_extern_t` with the addition of +/// two `wasm_name_t` respectively for the module name and the name of +/// the extern (very likely to be an import). This non-standard type +/// is used by the non-standard `wasi_get_unordered_imports` function. +/// +/// The `module`, `name` and `extern` fields are all owned by this type. +#[allow(non_camel_case_types)] +#[derive(Clone)] +pub struct wasm_named_extern_t { + module: Box, + name: Box, + r#extern: Box, +} + +wasm_declare_boxed_vec!(named_extern); + +/// So. Let's explain a dirty hack. `cbindgen` reads the code and +/// collects symbols. What symbols do we need? None of the one +/// declared in `wasm.h`, but for non-standard API, we need to collect +/// all of them. The problem is that `wasm_named_extern_t` is the only +/// non-standard type where extra symbols are generated by a macro +/// (`wasm_declare_boxed_vec!`). If we want those macro-generated +/// symbols to be collected by `cbindgen`, we need to _expand_ the +/// crate (i.e. running something like `rustc -- -Zunstable-options +/// --pretty=expanded`). Expanding code is unstable and available only +/// on nightly compiler. We _don't want_ to use a nightly compiler +/// only for that. So how can we help `cbindgen` to _see_ those +/// symbols? +/// +/// First solution: We write the C code directly in a file, which is +/// then included in the generated header file with the `cbindgen` +/// API. Problem, it's super easy to get it outdated, and it makes the +/// build process more complex. +/// +/// Second solution: We write those symbols in a custom module, that +/// is just here for `cbindgen`, never used by our Rust code +/// (otherwise it's duplicated code), with no particular +/// implementation. +/// +/// And that's why we have the following `cbindgen_hack` +/// module. +/// +/// But this module must not be compiled by `rustc`. How to force +/// `rustc` to ignore a module? With conditional compilation. Because +/// `cbindgen` does not support conditional compilation, it will +/// always _ignore_ the `#[cfg]` attribute, and will always read the +/// content of the module. +/// +/// Sorry. +#[doc(hidden)] +#[cfg(__cbindgen_hack__ = "yes")] +mod __cbindgen_hack__ { + use super::*; + + #[repr(C)] + pub struct wasm_named_extern_vec_t { + pub size: usize, + pub data: *mut *mut wasm_named_extern_t, + } + + #[no_mangle] + pub unsafe extern "C" fn wasm_named_extern_vec_new( + out: *mut wasm_named_extern_vec_t, + length: usize, + init: *const *mut wasm_named_extern_t, + ) { + unimplemented!() + } + + #[no_mangle] + pub unsafe extern "C" fn wasm_named_extern_vec_new_uninitialized( + out: *mut wasm_named_extern_vec_t, + length: usize, + ) { + unimplemented!() + } + + #[no_mangle] + pub unsafe extern "C" fn wasm_named_extern_vec_copy( + out_ptr: &mut wasm_named_extern_vec_t, + in_ptr: &wasm_named_extern_vec_t, + ) { + unimplemented!() + } + + #[no_mangle] + pub unsafe extern "C" fn wasm_named_extern_vec_delete( + ptr: Option<&mut wasm_named_extern_vec_t>, + ) { + unimplemented!() + } + + #[no_mangle] + pub unsafe extern "C" fn wasm_named_extern_vec_new_empty(out: *mut wasm_named_extern_vec_t) { + unimplemented!() + } +} + +/// Non-standard function to get the module name of a +/// `wasm_named_extern_t`. +/// +/// The returned value isn't owned by the caller. +#[no_mangle] +pub extern "C" fn wasm_named_extern_module( + named_extern: Option<&wasm_named_extern_t>, +) -> Option<&wasm_name_t> { + Some(named_extern?.module.as_ref()) +} + +/// Non-standard function to get the name of a `wasm_named_extern_t`. +/// +/// The returned value isn't owned by the caller. +#[no_mangle] +pub extern "C" fn wasm_named_extern_name( + named_extern: Option<&wasm_named_extern_t>, +) -> Option<&wasm_name_t> { + Some(named_extern?.name.as_ref()) +} + +/// Non-standard function to get the wrapped extern of a +/// `wasm_named_extern_t`. +/// +/// The returned value isn't owned by the caller. +#[no_mangle] +pub extern "C" fn wasm_named_extern_unwrap( + named_extern: Option<&wasm_named_extern_t>, +) -> Option<&wasm_extern_t> { + Some(named_extern?.r#extern.as_ref()) +} + +/// Non-standard function to get the imports needed for the WASI +/// implementation with no particular order. Each import has its +/// associated module name and name, so that it can be re-order later +/// based on the `wasm_module_t` requirements. +/// +/// This function takes ownership of `wasm_env_t`. +#[no_mangle] +pub unsafe extern "C" fn wasi_get_unordered_imports( + store: Option<&wasm_store_t>, + module: Option<&wasm_module_t>, + wasi_env: Option>, + imports: &mut wasm_named_extern_vec_t, +) -> bool { + wasi_get_unordered_imports_inner(store, module, wasi_env, imports).is_some() +} + +fn wasi_get_unordered_imports_inner( + store: Option<&wasm_store_t>, + module: Option<&wasm_module_t>, + wasi_env: Option>, + imports: &mut wasm_named_extern_vec_t, +) -> Option<()> { + let store = store?; + let module = module?; + let wasi_env = wasi_env?; + + let store = &store.inner; + + let version = c_try!( + get_wasi_version(&module.inner, false).ok_or_else(|| CApiError { + msg: "could not detect a WASI version on the given module".to_string(), + }) + ); + + let import_object = generate_import_object_from_env(store, wasi_env.inner.clone(), version); + + *imports = import_object + .into_iter() + .map(|((module, name), export)| { + let module = Box::new(module.into()); + let name = Box::new(name.into()); + let extern_inner = Extern::from_vm_export(store, export); + + Box::new(wasm_named_extern_t { + module, + name, + r#extern: Box::new(wasm_extern_t { + instance: None, + inner: extern_inner, + }), + }) + }) + .collect::>() + .into(); + + Some(()) +} + +/// Non-standard function to get the imports needed for the WASI +/// implementation ordered as expected by the `wasm_module_t`. +/// +/// This function takes ownership of `wasm_env_t`. #[no_mangle] pub unsafe extern "C" fn wasi_get_imports( - store: &wasm_store_t, - module: &wasm_module_t, - wasi_env: &wasi_env_t, + store: Option<&wasm_store_t>, + module: Option<&wasm_module_t>, + wasi_env: Option>, imports: &mut wasm_extern_vec_t, ) -> bool { wasi_get_imports_inner(store, module, wasi_env, imports).is_some() } /// Takes ownership of `wasi_env_t`. -unsafe fn wasi_get_imports_inner( - store: &wasm_store_t, - module: &wasm_module_t, - wasi_env: &wasi_env_t, +fn wasi_get_imports_inner( + store: Option<&wasm_store_t>, + module: Option<&wasm_module_t>, + wasi_env: Option>, imports: &mut wasm_extern_vec_t, ) -> Option<()> { + let store = store?; + let module = module?; + let wasi_env = wasi_env?; + let store = &store.inner; let version = c_try!( @@ -399,19 +598,14 @@ unsafe fn wasi_get_imports_inner( pub unsafe extern "C" fn wasi_get_start_function( instance: &mut wasm_instance_t, ) -> Option> { - let f = c_try!(instance.inner.exports.get_function("_start")); + let start = c_try!(instance.inner.exports.get_function("_start")); + Some(Box::new(wasm_func_t { - inner: f.clone(), + inner: start.clone(), instance: Some(instance.inner.clone()), })) } -/// Delete a `wasm_extern_t` allocated by the API. -/// -/// cbindgen:ignore -#[no_mangle] -pub unsafe extern "C" fn wasm_extern_delete(_item: Option>) {} - #[cfg(test)] mod tests { use inline_c::assert_c; diff --git a/lib/c-api/wasmer_wasm.h b/lib/c-api/wasmer_wasm.h index 5dd1584a642..0b4ed32bf8f 100644 --- a/lib/c-api/wasmer_wasm.h +++ b/lib/c-api/wasmer_wasm.h @@ -136,10 +136,21 @@ typedef struct wasi_env_t wasi_env_t; typedef struct wasm_cpu_features_t wasm_cpu_features_t; +#if defined(WASMER_WASI_ENABLED) +typedef struct wasm_named_extern_t wasm_named_extern_t; +#endif + typedef struct wasm_target_t wasm_target_t; typedef struct wasm_triple_t wasm_triple_t; +#if defined(WASMER_WASI_ENABLED) +typedef struct { + uintptr_t size; + wasm_named_extern_t **data; +} wasm_named_extern_vec_t; +#endif + #ifdef __cplusplus extern "C" { #endif // __cplusplus @@ -215,7 +226,7 @@ void wasi_env_set_memory(wasi_env_t *_env, #if defined(WASMER_WASI_ENABLED) bool wasi_get_imports(const wasm_store_t *store, const wasm_module_t *module, - const wasi_env_t *wasi_env, + wasi_env_t *wasi_env, wasm_extern_vec_t *imports); #endif @@ -223,6 +234,13 @@ bool wasi_get_imports(const wasm_store_t *store, wasm_func_t *wasi_get_start_function(wasm_instance_t *instance); #endif +#if defined(WASMER_WASI_ENABLED) +bool wasi_get_unordered_imports(const wasm_store_t *store, + const wasm_module_t *module, + wasi_env_t *wasi_env, + wasm_named_extern_vec_t *imports); +#endif + #if defined(WASMER_WASI_ENABLED) wasi_version_t wasi_get_wasi_version(const wasm_module_t *module); #endif @@ -245,6 +263,41 @@ void wasm_module_name(const wasm_module_t *module, wasm_name_t *out); bool wasm_module_set_name(wasm_module_t *module, const wasm_name_t *name); +#if defined(WASMER_WASI_ENABLED) +const wasm_name_t *wasm_named_extern_module(const wasm_named_extern_t *named_extern); +#endif + +#if defined(WASMER_WASI_ENABLED) +const wasm_name_t *wasm_named_extern_name(const wasm_named_extern_t *named_extern); +#endif + +#if defined(WASMER_WASI_ENABLED) +const wasm_extern_t *wasm_named_extern_unwrap(const wasm_named_extern_t *named_extern); +#endif + +#if defined(WASMER_WASI_ENABLED) +void wasm_named_extern_vec_copy(wasm_named_extern_vec_t *out_ptr, + const wasm_named_extern_vec_t *in_ptr); +#endif + +#if defined(WASMER_WASI_ENABLED) +void wasm_named_extern_vec_delete(wasm_named_extern_vec_t *ptr); +#endif + +#if defined(WASMER_WASI_ENABLED) +void wasm_named_extern_vec_new(wasm_named_extern_vec_t *out, + uintptr_t length, + wasm_named_extern_t *const *init); +#endif + +#if defined(WASMER_WASI_ENABLED) +void wasm_named_extern_vec_new_empty(wasm_named_extern_vec_t *out); +#endif + +#if defined(WASMER_WASI_ENABLED) +void wasm_named_extern_vec_new_uninitialized(wasm_named_extern_vec_t *out, uintptr_t length); +#endif + void wasm_target_delete(wasm_target_t *_target); wasm_target_t *wasm_target_new(wasm_triple_t *triple, wasm_cpu_features_t *cpu_features);