From 7f3680c27a3f7e4c41e85ee5e4d289bfd921ea87 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 2 Dec 2019 15:36:45 +0100 Subject: [PATCH 1/9] fix(wasi) `get_wasi_version` is broken with multiple namespaces. If a module has multiple import namespaces, `get_wasi_version` is broken because it assumes a module must only have a single namespace. This patch fixes it by a slower `get_wasi_version` function, but a correct one. As soon as the `wasi_unstable` or `wasi_snapshot_preview1` namespace is met, `get_wasi_version` maps it to the respective `WasiVersion` variant. It assumes however that a module must hold a unique WASI version. --- lib/wasi/src/utils.rs | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/lib/wasi/src/utils.rs b/lib/wasi/src/utils.rs index b19a404884b..e7c24aff48f 100644 --- a/lib/wasi/src/utils.rs +++ b/lib/wasi/src/utils.rs @@ -18,24 +18,19 @@ pub enum WasiVersion { /// Detect the version of WASI being used from the namespace pub fn get_wasi_version(module: &Module) -> Option { - let mut import_iter = module + let namespace_table = &module.info().namespace_table; + + module .info() .imported_functions .iter() - .map(|(_, import_name)| import_name.namespace_index); + .find_map(|(_, import_name)| { + let namespace_index = import_name.namespace_index; - // returns None if empty - let first = import_iter.next()?; - if import_iter.all(|idx| idx == first) { - // once we know that all the namespaces are the same, we can use it to - // detect which version of WASI this is - match module.info().namespace_table.get(first) { - "wasi_unstable" => Some(WasiVersion::Snapshot0), - "wasi_snapshot_preview1" => Some(WasiVersion::Snapshot1), - _ => None, - } - } else { - // not all funcs have the same namespace, therefore it's not WASI - None - } + match namespace_table.get(namespace_index) { + "wasi_unstable" => Some(WasiVersion::Snapshot0), + "wasi_snapshot_preview1" => Some(WasiVersion::Snapshot1), + _ => None, + } + }) } From 8df0591ee57ef8bacbacbcb13a6c41129129ede3 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 2 Dec 2019 15:41:46 +0100 Subject: [PATCH 2/9] doc(changelog) Add #1028. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c1de67bf893..79f9da5aa5c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## **[Unreleased]** +- [#1028](https://github.com/wasmerio/wasmer/pull/1028) Fix `get_wasi_version` when a module has multiple import namespaces. - [#1006](https://github.com/wasmerio/wasmer/pull/1006) Fix minor panic issue when `wasmer::compile_with` called with llvm backend - [#1009](https://github.com/wasmerio/wasmer/pull/1009) Enable LLVM verifier for all tests, add new llvm-backend-tests crate. - [#1004](https://github.com/wasmerio/wasmer/pull/1004) Add the Auto backend to enable to adapt backend usage depending on wasm file executed. From 62b33386300f4cf618c630c379403ab31af44657 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 2 Dec 2019 15:58:23 +0100 Subject: [PATCH 3/9] doc(runtime-c-api) Suggest to test in release mode. --- lib/runtime-c-api/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/runtime-c-api/README.md b/lib/runtime-c-api/README.md index 06e545bf739..39e2c21a9fa 100644 --- a/lib/runtime-c-api/README.md +++ b/lib/runtime-c-api/README.md @@ -112,7 +112,7 @@ rebuild in release mode for the tests to see the changes. The tests can be run via `cargo test`, such as: ```sh -$ cargo test -- --nocapture +$ cargo test --release -- --nocapture ``` To run tests manually, enter the `lib/runtime-c-api/tests` directory From 281fd9d08dd29807effddfed6fb355facc7bd19d Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 2 Dec 2019 15:59:20 +0100 Subject: [PATCH 4/9] feat(runtime-c-api) Ability to generate `ImportObject` for a specific WASI version. This patch introduces 2 new functions: * `wasmer_wasi_generate_import_object_for_version` and * `wasmer_wasi_get_version`. It mimics the current API provided by `wasmer_wasi`, nothing fancy here. It's just a regular port to C/C++. Because `wasmer_wasi::get_wasi_version` returns an option, and in order to simplify the C/C++ API, `wasmer_wasi_get_version` can return `Version::Unknown` in case of an error. It's up to the user to check the version is valid (i.e. not unknown). --- lib/runtime-c-api/src/import/wasi.rs | 91 ++++++++++++++++++++++++---- 1 file changed, 79 insertions(+), 12 deletions(-) diff --git a/lib/runtime-c-api/src/import/wasi.rs b/lib/runtime-c-api/src/import/wasi.rs index 3df3c7f700b..ec362fdf2ec 100644 --- a/lib/runtime-c-api/src/import/wasi.rs +++ b/lib/runtime-c-api/src/import/wasi.rs @@ -1,6 +1,18 @@ use super::*; use crate::get_slice_checked; -use std::path::PathBuf; +use std::{path::PathBuf, ptr, str}; +use wasmer_wasi as wasi; + +#[derive(Debug)] +#[repr(u8)] +pub enum Version { + /// Version cannot be detected or is unknown. + Unknown, + /// `wasi_unstable`. + Snapshot0, + /// `wasi_snapshot_preview1`. + Snapshot1, +} /// Opens a directory that's visible to the WASI module as `alias` but /// is backed by the host file at `host_file_path` @@ -14,9 +26,9 @@ pub struct wasmer_wasi_map_dir_entry_t { impl wasmer_wasi_map_dir_entry_t { /// Converts the data into owned, Rust types - pub unsafe fn as_tuple(&self) -> Result<(String, PathBuf), std::str::Utf8Error> { + pub unsafe fn as_tuple(&self) -> Result<(String, PathBuf), str::Utf8Error> { let alias = self.alias.as_str()?.to_owned(); - let host_path = std::path::PathBuf::from(self.host_file_path.as_str()?); + let host_path = PathBuf::from(self.host_file_path.as_str()?); Ok((alias, host_path)) } @@ -44,21 +56,74 @@ pub unsafe extern "C" fn wasmer_wasi_generate_import_object( let mapped_dir_list = get_slice_checked(mapped_dirs, mapped_dirs_len as usize); wasmer_wasi_generate_import_object_inner( + Version::Snapshot1, arg_list, env_list, preopened_file_list, mapped_dir_list, ) - .unwrap_or(std::ptr::null_mut()) + .unwrap_or(ptr::null_mut()) +} + +/// Creates a WASI import object. +/// +/// This function is similar to `wasmer_wasi_generate_import_object` +/// except that the first argument describes the WASI version. +#[no_mangle] +pub unsafe extern "C" fn wasmer_wasi_generate_import_object_for_version( + version: Version, + args: *const wasmer_byte_array, + args_len: c_uint, + envs: *const wasmer_byte_array, + envs_len: c_uint, + preopened_files: *const wasmer_byte_array, + preopened_files_len: c_uint, + mapped_dirs: *const wasmer_wasi_map_dir_entry_t, + mapped_dirs_len: c_uint, +) -> *mut wasmer_import_object_t { + let arg_list = get_slice_checked(args, args_len as usize); + let env_list = get_slice_checked(envs, envs_len as usize); + let preopened_file_list = get_slice_checked(preopened_files, preopened_files_len as usize); + let mapped_dir_list = get_slice_checked(mapped_dirs, mapped_dirs_len as usize); + + wasmer_wasi_generate_import_object_inner( + version, + arg_list, + env_list, + preopened_file_list, + mapped_dir_list, + ) + .unwrap_or(ptr::null_mut()) +} + +/// Find the version of WASI used by the module. +/// +/// In case of error, the returned version is `Version::Unknown`. +#[no_mangle] +pub unsafe extern "C" fn wasmer_wasi_get_version(module: *const wasmer_module_t) -> Version { + if module.is_null() { + return Version::Unknown; + } + + let module = &*(module as *const Module); + + match wasi::get_wasi_version(module) { + Some(version) => match version { + wasi::WasiVersion::Snapshot0 => Version::Snapshot0, + wasi::WasiVersion::Snapshot1 => Version::Snapshot1, + }, + None => Version::Unknown, + } } /// Inner function that wraps error handling fn wasmer_wasi_generate_import_object_inner( + version: Version, arg_list: &[wasmer_byte_array], env_list: &[wasmer_byte_array], preopened_file_list: &[wasmer_byte_array], mapped_dir_list: &[wasmer_wasi_map_dir_entry_t], -) -> Result<*mut wasmer_import_object_t, std::str::Utf8Error> { +) -> Result<*mut wasmer_import_object_t, str::Utf8Error> { let arg_vec = arg_list.iter().map(|arg| unsafe { arg.as_vec() }).collect(); let env_vec = env_list .iter() @@ -73,7 +138,14 @@ fn wasmer_wasi_generate_import_object_inner( .map(|entry| unsafe { entry.as_tuple() }) .collect::, _>>()?; - let import_object = Box::new(wasmer_wasi::generate_import_object( + let version = match version { + Version::Snapshot0 => wasi::WasiVersion::Snapshot0, + Version::Snapshot1 => wasi::WasiVersion::Snapshot1, + _ => panic!(format!("Version {:?} is invalid.", version)), + }; + + let import_object = Box::new(wasi::generate_import_object_for_version( + version, arg_vec, env_vec, po_file_vec, @@ -90,12 +162,7 @@ fn wasmer_wasi_generate_import_object_inner( #[no_mangle] pub unsafe extern "C" fn wasmer_wasi_generate_default_import_object() -> *mut wasmer_import_object_t { - let import_object = Box::new(wasmer_wasi::generate_import_object( - vec![], - vec![], - vec![], - vec![], - )); + let import_object = Box::new(wasi::generate_import_object(vec![], vec![], vec![], vec![])); Box::into_raw(import_object) as *mut wasmer_import_object_t } From 2a2c73a171ab498c7e6e09729ee2c3cbbbfe3f47 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 2 Dec 2019 16:05:01 +0100 Subject: [PATCH 5/9] chore(runtime-c-api) Update C/C++ headers. --- lib/runtime-c-api/wasmer.h | 39 +++++++++++++++++++++++++++++++++++++ lib/runtime-c-api/wasmer.hh | 28 ++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/lib/runtime-c-api/wasmer.h b/lib/runtime-c-api/wasmer.h index 40a265cff3b..672a1704cd5 100644 --- a/lib/runtime-c-api/wasmer.h +++ b/lib/runtime-c-api/wasmer.h @@ -25,6 +25,22 @@ #include #include +enum Version { + /** + * Version cannot be detected or is unknown. + */ + Unknown, + /** + * `wasi_unstable`. + */ + Snapshot0, + /** + * `wasi_snapshot_preview1`. + */ + Snapshot1, +}; +typedef uint8_t Version; + /** * List of export/import kinds. */ @@ -890,4 +906,27 @@ wasmer_import_object_t *wasmer_wasi_generate_import_object(const wasmer_byte_arr const wasmer_wasi_map_dir_entry_t *mapped_dirs, unsigned int mapped_dirs_len); +/** + * Creates a WASI import object. + * + * This function is similar to `wasmer_wasi_generate_import_object` + * except that the first argument describes the WASI version. + */ +wasmer_import_object_t *wasmer_wasi_generate_import_object_for_version(Version version, + const wasmer_byte_array *args, + unsigned int args_len, + const wasmer_byte_array *envs, + unsigned int envs_len, + const wasmer_byte_array *preopened_files, + unsigned int preopened_files_len, + const wasmer_wasi_map_dir_entry_t *mapped_dirs, + unsigned int mapped_dirs_len); + +/** + * Find the version of WASI used by the module. + * + * In case of error, the returned version is `Version::Unknown`. + */ +Version wasmer_wasi_get_version(const wasmer_module_t *module); + #endif /* WASMER_H */ diff --git a/lib/runtime-c-api/wasmer.hh b/lib/runtime-c-api/wasmer.hh index b437edd4be0..d293c817ebc 100644 --- a/lib/runtime-c-api/wasmer.hh +++ b/lib/runtime-c-api/wasmer.hh @@ -25,6 +25,15 @@ #include #include +enum class Version : uint8_t { + /// Version cannot be detected or is unknown. + Unknown, + /// `wasi_unstable`. + Snapshot0, + /// `wasi_snapshot_preview1`. + Snapshot1, +}; + /// List of export/import kinds. enum class wasmer_import_export_kind : uint32_t { WASM_FUNCTION = 0, @@ -702,6 +711,25 @@ wasmer_import_object_t *wasmer_wasi_generate_import_object(const wasmer_byte_arr const wasmer_wasi_map_dir_entry_t *mapped_dirs, unsigned int mapped_dirs_len); +/// Creates a WASI import object. +/// +/// This function is similar to `wasmer_wasi_generate_import_object` +/// except that the first argument describes the WASI version. +wasmer_import_object_t *wasmer_wasi_generate_import_object_for_version(Version version, + const wasmer_byte_array *args, + unsigned int args_len, + const wasmer_byte_array *envs, + unsigned int envs_len, + const wasmer_byte_array *preopened_files, + unsigned int preopened_files_len, + const wasmer_wasi_map_dir_entry_t *mapped_dirs, + unsigned int mapped_dirs_len); + +/// Find the version of WASI used by the module. +/// +/// In case of error, the returned version is `Version::Unknown`. +Version wasmer_wasi_get_version(const wasmer_module_t *module); + } // extern "C" #endif // WASMER_H From cbbc6ed4212ab14d1d9d3056e346b328f93584d7 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 2 Dec 2019 16:05:34 +0100 Subject: [PATCH 6/9] chore(runtime-c-api) Fix CS in `CMakeLists.txt`. --- lib/runtime-c-api/tests/CMakeLists.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/runtime-c-api/tests/CMakeLists.txt b/lib/runtime-c-api/tests/CMakeLists.txt index e4a573e6386..20f7dea13bd 100644 --- a/lib/runtime-c-api/tests/CMakeLists.txt +++ b/lib/runtime-c-api/tests/CMakeLists.txt @@ -19,12 +19,12 @@ add_executable(test-context test-context.c) add_executable(test-module-import-instantiate test-module-import-instantiate.c) if (DEFINED WASI_TESTS) - add_executable(test-wasi-import-object test-wasi-import-object.c) + add_executable(test-wasi-import-object test-wasi-import-object.c) endif() find_library( - WASMER_LIB NAMES libwasmer_runtime_c_api.dylib libwasmer_runtime_c_api.so wasmer_runtime_c_api.dll - PATHS ${CMAKE_SOURCE_DIR}/../../../target/release/ + WASMER_LIB NAMES libwasmer_runtime_c_api.dylib libwasmer_runtime_c_api.so wasmer_runtime_c_api.dll + PATHS ${CMAKE_SOURCE_DIR}/../../../target/release/ ) if(NOT WASMER_LIB) From d4ff772709b9b9996195fe34712adf5989e65d74 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 2 Dec 2019 16:07:20 +0100 Subject: [PATCH 7/9] chore(git) Ignore the object file `test-import-object`. --- lib/runtime-c-api/tests/.gitignore | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/runtime-c-api/tests/.gitignore b/lib/runtime-c-api/tests/.gitignore index b64fa610c7e..cbc7f3b1fcf 100644 --- a/lib/runtime-c-api/tests/.gitignore +++ b/lib/runtime-c-api/tests/.gitignore @@ -10,20 +10,20 @@ compile_commands.json CTestTestfile.cmake _deps rust-build +test-context test-exported-memory test-exports test-globals test-import-function +test-import-object test-imports test-instantiate test-memory test-module test-module-exports +test-module-import-instantiate test-module-imports test-module-serialize test-tables test-validate -test-context -test-module-import-instantiate -test-wasi-import-object - +test-wasi-import-object \ No newline at end of file From 2223a198ee820f7aeaa256e302e0702c20085c2c Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 2 Dec 2019 16:09:14 +0100 Subject: [PATCH 8/9] test(runtime-c-api) Test the new WASI version API. This patch updates `test-wasi-import-object` to test the new `wasmer_wasi_get_version` & `wasmer_wasi_generate_import_object_for_version` functions, and the new `Version` type. --- .../tests/test-wasi-import-object.c | 44 ++++++++++++------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/lib/runtime-c-api/tests/test-wasi-import-object.c b/lib/runtime-c-api/tests/test-wasi-import-object.c index bd1478dc137..da94849963e 100644 --- a/lib/runtime-c-api/tests/test-wasi-import-object.c +++ b/lib/runtime-c-api/tests/test-wasi-import-object.c @@ -28,9 +28,9 @@ void print_wasmer_error() // helper function to print byte array to stdout void print_byte_array(wasmer_byte_array *arr) { - for (int i = 0; i < arr->bytes_len; ++i) { - putchar(arr->bytes[i]); - } + for (int i = 0; i < arr->bytes_len; ++i) { + putchar(arr->bytes[i]); + } } int main() @@ -164,20 +164,7 @@ int main() }; int mapped_dir_len = sizeof(mapped_dirs) / sizeof(mapped_dirs[0]); - // Create the WASI import object - wasmer_import_object_t *import_object = - wasmer_wasi_generate_import_object(args, wasi_argc, - envs, wasi_env_len, - NULL, 0, - mapped_dirs, mapped_dir_len); - - // Create our imports - wasmer_import_t imports[] = {func_import, global_import, memory_import, table_import}; - int imports_len = sizeof(imports) / sizeof(imports[0]); - // Add our imports to the import object - wasmer_import_object_extend(import_object, imports, imports_len); - - // Read the wasm file bytes + // Read the Wasm file bytes. FILE *file = fopen("assets/extended_wasi.wasm", "r"); assert(file); fseek(file, 0, SEEK_END); @@ -191,16 +178,39 @@ int main() // Compile the WebAssembly module wasmer_result_t compile_result = wasmer_compile(&module, bytes, len); printf("Compile result: %d\n", compile_result); + if (compile_result != WASMER_OK) { print_wasmer_error(); } + assert(compile_result == WASMER_OK); + // Detect the WASI version if any. This step is not mandatory, we + // use it to test the WASI version API. + Version wasi_version = wasmer_wasi_get_version(module); + + printf("WASI version: %d\n", wasi_version); + + // Create the WASI import object + wasmer_import_object_t *import_object = + wasmer_wasi_generate_import_object_for_version(wasi_version, + args, wasi_argc, + envs, wasi_env_len, + NULL, 0, + mapped_dirs, mapped_dir_len); + + // Create our imports + wasmer_import_t imports[] = {func_import, global_import, memory_import, table_import}; + int imports_len = sizeof(imports) / sizeof(imports[0]); + // Add our imports to the import object + wasmer_import_object_extend(import_object, imports, imports_len); + // Instantiatoe the module with our import_object wasmer_instance_t *instance = NULL; wasmer_result_t instantiate_result = wasmer_module_import_instantiate(&instance, module, import_object); printf("Instantiate result: %d\n", instantiate_result); + if (instantiate_result != WASMER_OK) { print_wasmer_error(); From cbf29eef2d855da5f214b356642900ccea1f397d Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 2 Dec 2019 16:19:19 +0100 Subject: [PATCH 9/9] doc(changelog) Add #1030. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 79f9da5aa5c..6d400b37ddf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## **[Unreleased]** +- [#1030](https://github.com/wasmerio/wasmer/pull/1030) Ability to generate `ImportObject` for a specific version WASI version with the C API. - [#1028](https://github.com/wasmerio/wasmer/pull/1028) Fix `get_wasi_version` when a module has multiple import namespaces. - [#1006](https://github.com/wasmerio/wasmer/pull/1006) Fix minor panic issue when `wasmer::compile_with` called with llvm backend - [#1009](https://github.com/wasmerio/wasmer/pull/1009) Enable LLVM verifier for all tests, add new llvm-backend-tests crate.