diff --git a/CHANGELOG.md b/CHANGELOG.md index 266dd165d34..60ff42f8b9b 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) Introduce strict/non-strict modes for `get_wasi_version` - [#1029](https://github.com/wasmerio/wasmer/pull/1029) Add the “floating” `WasiVersion::Latest` version. - [#1006](https://github.com/wasmerio/wasmer/pull/1006) Fix minor panic issue when `wasmer::compile_with` called with llvm backend 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 diff --git a/lib/runtime-c-api/src/import/mod.rs b/lib/runtime-c-api/src/import/mod.rs index 5b44eb2b557..2bca1e3902c 100644 --- a/lib/runtime-c-api/src/import/mod.rs +++ b/lib/runtime-c-api/src/import/mod.rs @@ -8,7 +8,7 @@ use crate::{ value::wasmer_value_tag, wasmer_byte_array, wasmer_result_t, }; -use libc::c_uint; +use libc::{c_uchar, c_uint}; use std::{convert::TryFrom, ffi::c_void, ptr, slice, sync::Arc}; use wasmer_runtime::{Global, Memory, Module, Table}; use wasmer_runtime_core::{ diff --git a/lib/runtime-c-api/src/import/wasi.rs b/lib/runtime-c-api/src/import/wasi.rs index 3df3c7f700b..0ff833a61f5 100644 --- a/lib/runtime-c-api/src/import/wasi.rs +++ b/lib/runtime-c-api/src/import/wasi.rs @@ -1,6 +1,35 @@ use super::*; use crate::get_slice_checked; -use std::path::PathBuf; +use std::{path::PathBuf, ptr, str}; +use wasmer_wasi as wasi; + +#[derive(Debug, PartialEq)] +#[repr(u8)] +pub enum Version { + /// Version cannot be detected or is unknown. + Unknown = 0, + + /// Latest version. See `wasmer_wasi::WasiVersion::Latest` to + /// leran more. + Latest = 1, + + /// `wasi_unstable`. + Snapshot0 = 2, + + /// `wasi_snapshot_preview1`. + Snapshot1 = 3, +} + +impl From for Version { + fn from(value: c_uchar) -> Self { + match value { + 1 => Self::Latest, + 2 => Self::Snapshot0, + 3 => Self::Snapshot1, + _ => Self::Unknown, + } + } +} /// 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 +43,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 +73,77 @@ 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::Latest, 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 for a specific version. +/// +/// This function is similar to `wasmer_wasi_generate_import_object` +/// except that the first argument describes the WASI version. +/// +/// The version is expected to be of kind `Version`. +#[no_mangle] +pub unsafe extern "C" fn wasmer_wasi_generate_import_object_for_version( + version: c_uchar, + 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.into(), + 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, false) { + Some(version) => match version { + wasi::WasiVersion::Snapshot0 => Version::Snapshot0, + wasi::WasiVersion::Snapshot1 => Version::Snapshot1, + wasi::WasiVersion::Latest => Version::Latest, + }, + 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 +158,15 @@ 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::Latest => wasi::WasiVersion::Latest, + Version::Snapshot0 => wasi::WasiVersion::Snapshot0, + Version::Snapshot1 => wasi::WasiVersion::Snapshot1, + _ => panic!("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 +183,20 @@ 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 } + +#[cfg(test)] +mod tests { + use super::Version; + + #[test] + fn test_versions_from_uint() { + assert_eq!(Version::Unknown, 0.into()); + assert_eq!(Version::Latest, 1.into()); + assert_eq!(Version::Snapshot0, 2.into()); + assert_eq!(Version::Snapshot1, 3.into()); + } +} 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 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) 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(); diff --git a/lib/runtime-c-api/wasmer.h b/lib/runtime-c-api/wasmer.h index 40a265cff3b..de921e3b04d 100644 --- a/lib/runtime-c-api/wasmer.h +++ b/lib/runtime-c-api/wasmer.h @@ -25,6 +25,27 @@ #include #include +enum Version { + /** + * Version cannot be detected or is unknown. + */ + Unknown = 0, + /** + * Latest version. See `wasmer_wasi::WasiVersion::Latest` to + * leran more. + */ + Latest = 1, + /** + * `wasi_unstable`. + */ + Snapshot0 = 2, + /** + * `wasi_snapshot_preview1`. + */ + Snapshot1 = 3, +}; +typedef uint8_t Version; + /** * List of export/import kinds. */ @@ -890,4 +911,29 @@ 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 for a specific version. + * + * This function is similar to `wasmer_wasi_generate_import_object` + * except that the first argument describes the WASI version. + * + * The version is expected to be of kind `Version`. + */ +wasmer_import_object_t *wasmer_wasi_generate_import_object_for_version(unsigned char 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..09b58f86100 100644 --- a/lib/runtime-c-api/wasmer.hh +++ b/lib/runtime-c-api/wasmer.hh @@ -25,6 +25,18 @@ #include #include +enum class Version : uint8_t { + /// Version cannot be detected or is unknown. + Unknown = 0, + /// Latest version. See `wasmer_wasi::WasiVersion::Latest` to + /// leran more. + Latest = 1, + /// `wasi_unstable`. + Snapshot0 = 2, + /// `wasi_snapshot_preview1`. + Snapshot1 = 3, +}; + /// List of export/import kinds. enum class wasmer_import_export_kind : uint32_t { WASM_FUNCTION = 0, @@ -702,6 +714,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 for a specific version. +/// +/// This function is similar to `wasmer_wasi_generate_import_object` +/// except that the first argument describes the WASI version. +/// +/// The version is expected to be of kind `Version`. +wasmer_import_object_t *wasmer_wasi_generate_import_object_for_version(unsigned char 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