From b45b1c12f14b05af43a8618049283e61a2d36796 Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Tue, 1 Oct 2019 12:08:45 -0700 Subject: [PATCH 01/11] Add WASI support to runtime-c-api --- Cargo.lock | 1 + lib/runtime-c-api/Cargo.toml | 9 ++- lib/runtime-c-api/src/import.rs | 104 ++++++++++++++++++++++++++++++++ lib/runtime-c-api/src/lib.rs | 20 ++++++ lib/runtime-c-api/wasmer.h | 36 +++++++++++ lib/runtime-c-api/wasmer.hh | 26 ++++++++ 6 files changed, 195 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index fb642644e59..c104006bdf8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1639,6 +1639,7 @@ dependencies = [ "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", "wasmer-runtime 0.7.0", "wasmer-runtime-core 0.7.0", + "wasmer-wasi 0.7.0", ] [[package]] diff --git a/lib/runtime-c-api/Cargo.toml b/lib/runtime-c-api/Cargo.toml index bcfa4bec5d1..75d421414b5 100644 --- a/lib/runtime-c-api/Cargo.toml +++ b/lib/runtime-c-api/Cargo.toml @@ -24,12 +24,19 @@ default-features = false path = "../runtime-core" version = "0.7.0" +[dependencies.wasmer-wasi] +default-features = false +path = "../wasi" +version = "0.7.0" +optional = true + [features] -default = ["cranelift-backend"] +default = ["cranelift-backend", "wasi"] debug = ["wasmer-runtime/debug"] cranelift-backend = ["wasmer-runtime/cranelift", "wasmer-runtime/default-backend-cranelift"] llvm-backend = ["wasmer-runtime/llvm", "wasmer-runtime/default-backend-llvm"] singlepass-backend = ["wasmer-runtime/singlepass", "wasmer-runtime/default-backend-singlepass"] +wasi = ["wasmer-wasi"] [build-dependencies] cbindgen = "0.9" diff --git a/lib/runtime-c-api/src/import.rs b/lib/runtime-c-api/src/import.rs index 2d6f9e99d31..abd35c9d075 100644 --- a/lib/runtime-c-api/src/import.rs +++ b/lib/runtime-c-api/src/import.rs @@ -51,6 +51,110 @@ pub unsafe extern "C" fn wasmer_import_object_new() -> *mut wasmer_import_object Box::into_raw(import_object) as *mut wasmer_import_object_t } +#[cfg(feature = "wasi")] +mod wasi { + use super::*; + use std::path::PathBuf; + + /// Opens a directory that's visible to the WASI module as `alias` but + /// is backed by the host file at `host_file_path` + #[repr(C)] + pub struct wasmer_wasi_map_dir_entry_t { + /// What the WASI module will see in its virtual root + pub alias: wasmer_byte_array, + /// The backing file that the WASI module will interact with via the alias + pub host_file_path: wasmer_byte_array, + } + + 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> { + let alias = self.alias.as_str()?.to_owned(); + let host_path = std::path::PathBuf::from(self.host_file_path.as_str()?); + + Ok((alias, host_path)) + } + } + + /// Creates a WASI import object + #[no_mangle] + pub unsafe extern "C" fn wasmer_wasi_generate_import_object( + 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 = std::slice::from_raw_parts(args, args_len as usize); + let env_list = std::slice::from_raw_parts(envs, envs_len as usize); + let preopened_file_list = + std::slice::from_raw_parts(preopened_files, preopened_files_len as usize); + let mapped_dir_list = std::slice::from_raw_parts(mapped_dirs, mapped_dirs_len as usize); + + wasmer_wasi_generate_import_object_inner( + arg_list, + env_list, + preopened_file_list, + mapped_dir_list, + ) + .unwrap_or(std::ptr::null_mut()) + } + + /// Inner function that wraps error handling + fn wasmer_wasi_generate_import_object_inner( + 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> { + let arg_vec = arg_list.iter().map(|arg| unsafe { arg.as_vec() }).collect(); + let env_vec = env_list + .iter() + .map(|env_var| unsafe { env_var.as_vec() }) + .collect(); + let po_file_vec = preopened_file_list + .iter() + .map(|po_file| Ok(unsafe { po_file.as_str()? }.to_owned())) + .collect::, _>>()?; + let mapped_dir_vec = mapped_dir_list + .iter() + .map(|entry| unsafe { entry.as_tuple() }) + .collect::, _>>()?; + + let import_object = Box::new(wasmer_wasi::generate_import_object( + arg_vec, + env_vec, + po_file_vec, + mapped_dir_vec, + )); + Ok(Box::into_raw(import_object) as *mut wasmer_import_object_t) + } + + /// Convenience function that creates a WASI import object with no arguments, + /// environment variables, preopened files, or mapped directories. + /// + /// This function is the same as calling [`wasmer_wasi_generate_import_object`] with all + /// empty values. + #[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![], + )); + + Box::into_raw(import_object) as *mut wasmer_import_object_t + } +} + +#[cfg(feature = "wasi")] +pub use self::wasi::*; + /// Extends an existing import object with new imports #[allow(clippy::cast_ptr_alignment)] #[no_mangle] diff --git a/lib/runtime-c-api/src/lib.rs b/lib/runtime-c-api/src/lib.rs index 4dae18cd590..a2433afa20a 100644 --- a/lib/runtime-c-api/src/lib.rs +++ b/lib/runtime-c-api/src/lib.rs @@ -129,3 +129,23 @@ pub struct wasmer_byte_array { pub bytes: *const u8, pub bytes_len: u32, } + +impl wasmer_byte_array { + /// Get the data as a slice + pub unsafe fn as_slice<'a>(&self) -> &'a [u8] { + std::slice::from_raw_parts(self.bytes, self.bytes_len as usize) + } + + /// Copy the data into an owned Vec + pub unsafe fn as_vec(&self) -> Vec { + let mut out = Vec::with_capacity(self.bytes_len as usize); + out.extend_from_slice(self.as_slice()); + + out + } + + /// Read the data as a &str, returns an error if the string is not valid UTF8 + pub unsafe fn as_str<'a>(&self) -> Result<&'a str, std::str::Utf8Error> { + std::str::from_utf8(self.as_slice()) + } +} diff --git a/lib/runtime-c-api/wasmer.h b/lib/runtime-c-api/wasmer.h index 447d3107597..a13a40c92a9 100644 --- a/lib/runtime-c-api/wasmer.h +++ b/lib/runtime-c-api/wasmer.h @@ -170,6 +170,21 @@ typedef struct { } wasmer_trampoline_buffer_t; +/** + * Opens a directory that's visible to the WASI module as `alias` but + * is backed by the host file at `host_file_path` + */ +typedef struct { + /** + * What the WASI module will see in its virtual root + */ + wasmer_byte_array alias; + /** + * The backing file that the WASI module will interact with via the alias + */ + wasmer_byte_array host_file_path; +} wasmer_wasi_map_dir_entry_t; + /** * Creates a new Module from the given wasm bytes. * @@ -756,4 +771,25 @@ void *wasmer_trampoline_get_context(void); */ bool wasmer_validate(const uint8_t *wasm_bytes, uint32_t wasm_bytes_len); +/** + * Convenience function that creates a WASI import object with no arguments, + * environment variables, preopened files, or mapped directories. + * + * This function is the same as calling [`wasmer_wasi_generate_import_object`] with all + * empty values. + */ +wasmer_import_object_t *wasmer_wasi_generate_default_import_object(void); + +/** + * Creates a WASI import object + */ +wasmer_import_object_t *wasmer_wasi_generate_import_object(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); + #endif /* WASMER_H */ diff --git a/lib/runtime-c-api/wasmer.hh b/lib/runtime-c-api/wasmer.hh index d372d8eb3e2..51cdaa5c959 100644 --- a/lib/runtime-c-api/wasmer.hh +++ b/lib/runtime-c-api/wasmer.hh @@ -154,6 +154,15 @@ struct wasmer_trampoline_buffer_t { }; +/// Opens a directory that's visible to the WASI module as `alias` but +/// is backed by the host file at `host_file_path` +struct wasmer_wasi_map_dir_entry_t { + /// What the WASI module will see in its virtual root + wasmer_byte_array alias; + /// The backing file that the WASI module will interact with via the alias + wasmer_byte_array host_file_path; +}; + extern "C" { /// Creates a new Module from the given wasm bytes. @@ -590,6 +599,23 @@ void *wasmer_trampoline_get_context(); /// Returns true for valid wasm bytes and false for invalid bytes bool wasmer_validate(const uint8_t *wasm_bytes, uint32_t wasm_bytes_len); +/// Convenience function that creates a WASI import object with no arguments, +/// environment variables, preopened files, or mapped directories. +/// +/// This function is the same as calling [`wasmer_wasi_generate_import_object`] with all +/// empty values. +wasmer_import_object_t *wasmer_wasi_generate_default_import_object(); + +/// Creates a WASI import object +wasmer_import_object_t *wasmer_wasi_generate_import_object(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); + } // extern "C" #endif // WASMER_H From 70b55b801d4990f9931fd748856bb3e490e1dfb8 Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Thu, 3 Oct 2019 10:50:07 -0700 Subject: [PATCH 02/11] Check argument pointers for null to WASI calls --- lib/runtime-c-api/src/import.rs | 16 ++++++++++------ lib/runtime-c-api/src/lib.rs | 13 ++++++++++++- lib/runtime-c-api/wasmer.h | 6 +++++- lib/runtime-c-api/wasmer.hh | 6 +++++- 4 files changed, 32 insertions(+), 9 deletions(-) diff --git a/lib/runtime-c-api/src/import.rs b/lib/runtime-c-api/src/import.rs index abd35c9d075..bdbc1bea303 100644 --- a/lib/runtime-c-api/src/import.rs +++ b/lib/runtime-c-api/src/import.rs @@ -54,6 +54,7 @@ pub unsafe extern "C" fn wasmer_import_object_new() -> *mut wasmer_import_object #[cfg(feature = "wasi")] mod wasi { use super::*; + use crate::get_slice_checked; use std::path::PathBuf; /// Opens a directory that's visible to the WASI module as `alias` but @@ -76,7 +77,11 @@ mod wasi { } } - /// Creates a WASI import object + /// Creates a WASI import object. + /// + /// This function treats null pointers as empty collections. + /// For example, passing null for a string in `args`, will lead to a zero + /// length argument in that position. #[no_mangle] pub unsafe extern "C" fn wasmer_wasi_generate_import_object( args: *const wasmer_byte_array, @@ -88,11 +93,10 @@ mod wasi { mapped_dirs: *const wasmer_wasi_map_dir_entry_t, mapped_dirs_len: c_uint, ) -> *mut wasmer_import_object_t { - let arg_list = std::slice::from_raw_parts(args, args_len as usize); - let env_list = std::slice::from_raw_parts(envs, envs_len as usize); - let preopened_file_list = - std::slice::from_raw_parts(preopened_files, preopened_files_len as usize); - let mapped_dir_list = std::slice::from_raw_parts(mapped_dirs, mapped_dirs_len as usize); + 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( arg_list, diff --git a/lib/runtime-c-api/src/lib.rs b/lib/runtime-c-api/src/lib.rs index a2433afa20a..17e96a4c343 100644 --- a/lib/runtime-c-api/src/lib.rs +++ b/lib/runtime-c-api/src/lib.rs @@ -133,7 +133,7 @@ pub struct wasmer_byte_array { impl wasmer_byte_array { /// Get the data as a slice pub unsafe fn as_slice<'a>(&self) -> &'a [u8] { - std::slice::from_raw_parts(self.bytes, self.bytes_len as usize) + get_slice_checked(self.bytes, self.bytes_len as usize) } /// Copy the data into an owned Vec @@ -149,3 +149,14 @@ impl wasmer_byte_array { std::str::from_utf8(self.as_slice()) } } + +/// Gets a slice from a pointer and a length, returning an empty slice if the +/// pointer is null +#[inline] +pub(crate) unsafe fn get_slice_checked<'a, T>(ptr: *const T, len: usize) -> &'a [T] { + if ptr.is_null() { + &[] + } else { + std::slice::from_raw_parts(ptr, len) + } +} diff --git a/lib/runtime-c-api/wasmer.h b/lib/runtime-c-api/wasmer.h index a13a40c92a9..23caa54ccd0 100644 --- a/lib/runtime-c-api/wasmer.h +++ b/lib/runtime-c-api/wasmer.h @@ -781,7 +781,11 @@ bool wasmer_validate(const uint8_t *wasm_bytes, uint32_t wasm_bytes_len); wasmer_import_object_t *wasmer_wasi_generate_default_import_object(void); /** - * Creates a WASI import object + * Creates a WASI import object. + * + * This function treats null pointers as empty collections. + * For example, passing null for a string in `args`, will lead to a zero + * length argument in that position. */ wasmer_import_object_t *wasmer_wasi_generate_import_object(const wasmer_byte_array *args, unsigned int args_len, diff --git a/lib/runtime-c-api/wasmer.hh b/lib/runtime-c-api/wasmer.hh index 51cdaa5c959..da8e391180e 100644 --- a/lib/runtime-c-api/wasmer.hh +++ b/lib/runtime-c-api/wasmer.hh @@ -606,7 +606,11 @@ bool wasmer_validate(const uint8_t *wasm_bytes, uint32_t wasm_bytes_len); /// empty values. wasmer_import_object_t *wasmer_wasi_generate_default_import_object(); -/// Creates a WASI import object +/// Creates a WASI import object. +/// +/// This function treats null pointers as empty collections. +/// For example, passing null for a string in `args`, will lead to a zero +/// length argument in that position. wasmer_import_object_t *wasmer_wasi_generate_import_object(const wasmer_byte_array *args, unsigned int args_len, const wasmer_byte_array *envs, From bfb9d3849c7cf6b776adade45cbdb943174a4528 Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Thu, 3 Oct 2019 11:58:06 -0700 Subject: [PATCH 03/11] Fix merge --- lib/runtime-c-api/Cargo.toml | 2 +- lib/runtime-c-api/src/import.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/runtime-c-api/Cargo.toml b/lib/runtime-c-api/Cargo.toml index 05660a8b980..91815e7a70d 100644 --- a/lib/runtime-c-api/Cargo.toml +++ b/lib/runtime-c-api/Cargo.toml @@ -27,7 +27,7 @@ version = "0.8.0" [dependencies.wasmer-wasi] default-features = false path = "../wasi" -version = "0.7.0" +version = "0.8.0" optional = true [features] diff --git a/lib/runtime-c-api/src/import.rs b/lib/runtime-c-api/src/import.rs index bdbc1bea303..19f4157223f 100644 --- a/lib/runtime-c-api/src/import.rs +++ b/lib/runtime-c-api/src/import.rs @@ -121,7 +121,7 @@ mod wasi { .collect(); let po_file_vec = preopened_file_list .iter() - .map(|po_file| Ok(unsafe { po_file.as_str()? }.to_owned())) + .map(|po_file| Ok(unsafe { PathBuf::from(po_file.as_str()?) }.to_owned())) .collect::, _>>()?; let mapped_dir_vec = mapped_dir_list .iter() From 913354adb3b38f829db49e36638540bb5e5b19b2 Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Wed, 9 Oct 2019 17:29:27 -0700 Subject: [PATCH 04/11] Add function to get export from ns/name via ImportObject to C API --- lib/runtime-c-api/src/export.rs | 37 ++++++++-- lib/runtime-c-api/src/import.rs | 114 +++++++++++++++++++++++++++++- lib/runtime-c-api/src/instance.rs | 1 + lib/runtime-c-api/wasmer.h | 24 +++++-- lib/runtime-c-api/wasmer.hh | 22 ++++-- 5 files changed, 184 insertions(+), 14 deletions(-) diff --git a/lib/runtime-c-api/src/export.rs b/lib/runtime-c-api/src/export.rs index d6dfa5d973b..3112b6178d8 100644 --- a/lib/runtime-c-api/src/export.rs +++ b/lib/runtime-c-api/src/export.rs @@ -85,12 +85,39 @@ pub union wasmer_import_export_value { /// List of export/import kinds. #[allow(non_camel_case_types)] #[repr(u32)] -#[derive(Clone)] +#[derive(Clone, PartialEq, Eq)] +// ================ +// ! DANGER ! +// ================ +// Do not modify these values without updating the `TryFrom` implementation below pub enum wasmer_import_export_kind { - WASM_FUNCTION, - WASM_GLOBAL, - WASM_MEMORY, - WASM_TABLE, + WASM_FUNCTION = 0, + WASM_GLOBAL = 1, + WASM_MEMORY = 2, + WASM_TABLE = 3, +} + +impl wasmer_import_export_kind { + pub fn to_str(&self) -> &'static str { + match self { + Self::WASM_FUNCTION => "function", + Self::WASM_GLOBAL => "global", + Self::WASM_MEMORY => "memory", + Self::WASM_TABLE => "table", + } + } +} + +impl std::convert::TryFrom for wasmer_import_export_kind { + type Error = (); + + fn try_from(value: u32) -> Result { + if value > 3 { + Err(()) + } else { + Ok(unsafe { std::mem::transmute(value) }) + } + } } /// Gets export descriptors for the given module diff --git a/lib/runtime-c-api/src/import.rs b/lib/runtime-c-api/src/import.rs index 19f4157223f..52d196dd0f7 100644 --- a/lib/runtime-c-api/src/import.rs +++ b/lib/runtime-c-api/src/import.rs @@ -9,7 +9,7 @@ use crate::{ wasmer_byte_array, wasmer_result_t, }; use libc::c_uint; -use std::{ffi::c_void, ptr, slice, sync::Arc}; +use std::{convert::TryFrom, ffi::c_void, ptr, slice, sync::Arc}; use wasmer_runtime::{Global, Memory, Module, Table}; use wasmer_runtime_core::{ export::{Context, Export, FuncPointer}, @@ -159,6 +159,118 @@ mod wasi { #[cfg(feature = "wasi")] pub use self::wasi::*; +/// Gets an entry from an ImportObject at the name and namespace. +/// Stores an immutable reference to `name` and `namespace` in `import`. +/// +/// The caller owns all data involved. +/// `import_export_value` will be written to based on `tag`, `import_export_value` must be +/// initialized to point to the type specified by `tag`. Failure to do so may result +/// in data corruption or undefined behavior. +#[no_mangle] +pub unsafe extern "C" fn wasmer_import_object_get_import( + import_object: *const wasmer_import_object_t, + namespace: wasmer_byte_array, + name: wasmer_byte_array, + import: *mut wasmer_import_t, + import_export_value: *mut wasmer_import_export_value, + tag: u32, +) -> wasmer_result_t { + let tag: wasmer_import_export_kind = if let Ok(t) = TryFrom::try_from(tag) { + t + } else { + update_last_error(CApiError { + msg: "wasmer_import_export_tag out of range".to_string(), + }); + return wasmer_result_t::WASMER_ERROR; + }; + let import_object: &mut ImportObject = &mut *(import_object as *mut ImportObject); + let namespace_str = if let Ok(ns) = namespace.as_str() { + ns + } else { + update_last_error(CApiError { + msg: "error converting namespace to UTF-8 string".to_string(), + }); + return wasmer_result_t::WASMER_ERROR; + }; + let name_str = if let Ok(name) = name.as_str() { + name + } else { + update_last_error(CApiError { + msg: "error converting name to UTF-8 string".to_string(), + }); + return wasmer_result_t::WASMER_ERROR; + }; + if import.is_null() || import_export_value.is_null() { + update_last_error(CApiError { + msg: "pointer to import and import_export_value must not be null".to_string(), + }); + return wasmer_result_t::WASMER_ERROR; + } + let import_out = &mut *import; + let import_export_value_out = &mut *import_export_value; + if let Some(export) = + import_object.maybe_with_namespace(namespace_str, |ns| ns.get_export(name_str)) + { + match export { + Export::Function { .. } => { + if tag != wasmer_import_export_kind::WASM_FUNCTION { + update_last_error(CApiError { + msg: format!("Found function, expected {}", tag.to_str()), + }); + return wasmer_result_t::WASMER_ERROR; + } + import_out.tag = wasmer_import_export_kind::WASM_FUNCTION; + let writer = import_export_value_out.func as *mut Export; + *writer = export.clone(); + } + Export::Memory(memory) => { + if tag != wasmer_import_export_kind::WASM_MEMORY { + update_last_error(CApiError { + msg: format!("Found memory, expected {}", tag.to_str()), + }); + return wasmer_result_t::WASMER_ERROR; + } + import_out.tag = wasmer_import_export_kind::WASM_MEMORY; + let writer = import_export_value_out.func as *mut Memory; + *writer = memory.clone(); + } + Export::Table(table) => { + if tag != wasmer_import_export_kind::WASM_TABLE { + update_last_error(CApiError { + msg: format!("Found table, expected {}", tag.to_str()), + }); + return wasmer_result_t::WASMER_ERROR; + } + import_out.tag = wasmer_import_export_kind::WASM_TABLE; + let writer = import_export_value_out.func as *mut Table; + *writer = table.clone(); + } + Export::Global(global) => { + if tag != wasmer_import_export_kind::WASM_GLOBAL { + update_last_error(CApiError { + msg: format!("Found global, expected {}", tag.to_str()), + }); + return wasmer_result_t::WASMER_ERROR; + } + import_out.tag = wasmer_import_export_kind::WASM_GLOBAL; + let writer = import_export_value_out.func as *mut Global; + *writer = global.clone(); + } + } + + import_out.value = *import_export_value; + import_out.module_name = namespace; + import_out.import_name = name; + + wasmer_result_t::WASMER_OK + } else { + update_last_error(CApiError { + msg: format!("Export {} {} not found", namespace_str, name_str), + }); + wasmer_result_t::WASMER_ERROR + } +} + /// Extends an existing import object with new imports #[allow(clippy::cast_ptr_alignment)] #[no_mangle] diff --git a/lib/runtime-c-api/src/instance.rs b/lib/runtime-c-api/src/instance.rs index 69fa0131a27..b39e52d7eed 100644 --- a/lib/runtime-c-api/src/instance.rs +++ b/lib/runtime-c-api/src/instance.rs @@ -75,6 +75,7 @@ pub unsafe extern "C" fn wasmer_instantiate( let namespace = namespaces.entry(module_name).or_insert_with(Namespace::new); + // TODO check that tag is actually in bounds here let export = match import.tag { wasmer_import_export_kind::WASM_MEMORY => { let mem = import.value.memory as *mut Memory; diff --git a/lib/runtime-c-api/wasmer.h b/lib/runtime-c-api/wasmer.h index 23caa54ccd0..dda77d7c49d 100644 --- a/lib/runtime-c-api/wasmer.h +++ b/lib/runtime-c-api/wasmer.h @@ -10,10 +10,10 @@ * List of export/import kinds. */ enum wasmer_import_export_kind { - WASM_FUNCTION, - WASM_GLOBAL, - WASM_MEMORY, - WASM_TABLE, + WASM_FUNCTION = 0, + WASM_GLOBAL = 1, + WASM_MEMORY = 2, + WASM_TABLE = 3, }; typedef uint32_t wasmer_import_export_kind; @@ -469,6 +469,22 @@ wasmer_result_t wasmer_import_object_extend(wasmer_import_object_t *import_objec wasmer_import_t *imports, unsigned int imports_len); +/** + * Gets an entry from an ImportObject at the name and namespace. + * Stores an immutable reference to `name` and `namespace` in `import`. + * + * The caller owns all data involved. + * `import_export_value` will be written to based on `tag`, `import_export_value` must be + * initialized to point to the type specified by `tag`. Failure to do so may result + * in data corruption or undefined behavior. + */ +wasmer_result_t wasmer_import_object_get_import(const wasmer_import_object_t *import_object, + wasmer_byte_array namespace_, + wasmer_byte_array name, + wasmer_import_t *import, + wasmer_import_export_value *import_export_value, + uint32_t tag); + /** * Creates a new empty import object. * See also `wasmer_import_object_append` diff --git a/lib/runtime-c-api/wasmer.hh b/lib/runtime-c-api/wasmer.hh index da8e391180e..39bc12c69eb 100644 --- a/lib/runtime-c-api/wasmer.hh +++ b/lib/runtime-c-api/wasmer.hh @@ -8,10 +8,10 @@ /// List of export/import kinds. enum class wasmer_import_export_kind : uint32_t { - WASM_FUNCTION, - WASM_GLOBAL, - WASM_MEMORY, - WASM_TABLE, + WASM_FUNCTION = 0, + WASM_GLOBAL = 1, + WASM_MEMORY = 2, + WASM_TABLE = 3, }; enum class wasmer_result_t { @@ -371,6 +371,20 @@ wasmer_result_t wasmer_import_object_extend(wasmer_import_object_t *import_objec wasmer_import_t *imports, unsigned int imports_len); +/// Gets an entry from an ImportObject at the name and namespace. +/// Stores an immutable reference to `name` and `namespace` in `import`. +/// +/// The caller owns all data involved. +/// `import_export_value` will be written to based on `tag`, `import_export_value` must be +/// initialized to point to the type specified by `tag`. Failure to do so may result +/// in data corruption or undefined behavior. +wasmer_result_t wasmer_import_object_get_import(const wasmer_import_object_t *import_object, + wasmer_byte_array namespace_, + wasmer_byte_array name, + wasmer_import_t *import, + wasmer_import_export_value *import_export_value, + uint32_t tag); + /// Creates a new empty import object. /// See also `wasmer_import_object_append` wasmer_import_object_t *wasmer_import_object_new(); From 51f619a132f01b70eed80d81614433047ec16e21 Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Thu, 10 Oct 2019 11:22:45 -0700 Subject: [PATCH 05/11] Change pointer that's not modified to be const in C API --- CHANGELOG.md | 1 + lib/runtime-c-api/src/import.rs | 2 +- lib/runtime-c-api/wasmer.h | 2 +- lib/runtime-c-api/wasmer.hh | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a11365bb9c2..ce50507f106 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ Blocks of changes will separated by version increments. Special thanks to @jdanford for their contributions! +- [#856](https://github.com/wasmerio/wasmer/pull/856) Expose methods in the runtime C API to get a WASI import object - [#850](https://github.com/wasmerio/wasmer/pull/850) New `WasiStateBuilder` API. small, add misc. breaking changes to existing API (for example, changing the preopen dirs arg on `wasi::generate_import_object` from `Vec` to `Vec`) - [#852](https://github.com/wasmerio/wasmer/pull/852) Make minor grammar/capitalization fixes to README.md - [#841](https://github.com/wasmerio/wasmer/pull/841) Slightly improve rustdoc documentation and small updates to outdated info in readme files diff --git a/lib/runtime-c-api/src/import.rs b/lib/runtime-c-api/src/import.rs index 52d196dd0f7..bc7232b5015 100644 --- a/lib/runtime-c-api/src/import.rs +++ b/lib/runtime-c-api/src/import.rs @@ -276,7 +276,7 @@ pub unsafe extern "C" fn wasmer_import_object_get_import( #[no_mangle] pub unsafe extern "C" fn wasmer_import_object_extend( import_object: *mut wasmer_import_object_t, - imports: *mut wasmer_import_t, + imports: *const wasmer_import_t, imports_len: c_uint, ) -> wasmer_result_t { let import_object: &mut ImportObject = &mut *(import_object as *mut ImportObject); diff --git a/lib/runtime-c-api/wasmer.h b/lib/runtime-c-api/wasmer.h index dda77d7c49d..862f3580ce1 100644 --- a/lib/runtime-c-api/wasmer.h +++ b/lib/runtime-c-api/wasmer.h @@ -466,7 +466,7 @@ void wasmer_import_object_destroy(wasmer_import_object_t *import_object); * Extends an existing import object with new imports */ wasmer_result_t wasmer_import_object_extend(wasmer_import_object_t *import_object, - wasmer_import_t *imports, + const wasmer_import_t *imports, unsigned int imports_len); /** diff --git a/lib/runtime-c-api/wasmer.hh b/lib/runtime-c-api/wasmer.hh index 39bc12c69eb..c7c796ae463 100644 --- a/lib/runtime-c-api/wasmer.hh +++ b/lib/runtime-c-api/wasmer.hh @@ -368,7 +368,7 @@ void wasmer_import_object_destroy(wasmer_import_object_t *import_object); /// Extends an existing import object with new imports wasmer_result_t wasmer_import_object_extend(wasmer_import_object_t *import_object, - wasmer_import_t *imports, + const wasmer_import_t *imports, unsigned int imports_len); /// Gets an entry from an ImportObject at the name and namespace. From bd8e8646565b6a0815bd77c5a46cd9c6e067cbbf Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Thu, 10 Oct 2019 16:07:56 -0700 Subject: [PATCH 06/11] Add functions import object to get import fns and free them --- lib/runtime-c-api/src/import.rs | 92 +++++++++++++++++++++++++++++++++ lib/runtime-c-api/wasmer.h | 12 +++++ lib/runtime-c-api/wasmer.hh | 8 +++ 3 files changed, 112 insertions(+) diff --git a/lib/runtime-c-api/src/import.rs b/lib/runtime-c-api/src/import.rs index bc7232b5015..c39a7d121aa 100644 --- a/lib/runtime-c-api/src/import.rs +++ b/lib/runtime-c-api/src/import.rs @@ -271,6 +271,98 @@ pub unsafe extern "C" fn wasmer_import_object_get_import( } } +#[no_mangle] +/// Call `wasmer_import_object_imports_destroy` to free the memory allocated by this function +pub unsafe extern "C" fn wasmer_import_object_get_functions( + import_object: *const wasmer_import_object_t, + imports: *mut wasmer_import_t, + imports_len: u32, +) -> i32 { + if import_object.is_null() || imports.is_null() { + update_last_error(CApiError { + msg: format!("import_object and imports must not be null"), + }); + return -1; + } + let import_object: &mut ImportObject = &mut *(import_object as *mut ImportObject); + + let mut i = 0; + for (namespace, name, export) in import_object.clone_ref().into_iter() { + if i + 1 > imports_len { + return i as i32; + } + match export { + Export::Function { .. } => { + let ns = namespace.clone().into_bytes(); + let ns_bytes = wasmer_byte_array { + bytes: ns.as_ptr(), + bytes_len: ns.len() as u32, + }; + std::mem::forget(ns); + + let name = name.clone().into_bytes(); + let name_bytes = wasmer_byte_array { + bytes: name.as_ptr(), + bytes_len: name.len() as u32, + }; + std::mem::forget(name); + + let func = Box::new(export.clone()); + + let new_entry = wasmer_import_t { + module_name: ns_bytes, + import_name: name_bytes, + tag: wasmer_import_export_kind::WASM_FUNCTION, + value: wasmer_import_export_value { + func: Box::into_raw(func) as *mut _ as *const _, + }, + }; + *imports.add(i as usize) = new_entry; + i += 1; + } + _ => (), + } + } + + return i as i32; +} + +#[no_mangle] +/// Frees the memory acquired in `wasmer_import_object_get_functions` +pub unsafe extern "C" fn wasmer_import_object_imports_destroy( + imports: *mut wasmer_import_t, + imports_len: u32, +) { + // what's our null check policy here? + let imports: &[wasmer_import_t] = &*slice::from_raw_parts_mut(imports, imports_len as usize); + for import in imports { + let _namespace: Vec = Vec::from_raw_parts( + import.module_name.bytes as *mut u8, + import.module_name.bytes_len as usize, + import.module_name.bytes_len as usize, + ); + let _name: Vec = Vec::from_raw_parts( + import.import_name.bytes as *mut u8, + import.import_name.bytes_len as usize, + import.import_name.bytes_len as usize, + ); + match import.tag { + wasmer_import_export_kind::WASM_FUNCTION => { + let _: Box = Box::from_raw(import.value.func as *mut _); + } + wasmer_import_export_kind::WASM_GLOBAL => { + let _: Box = Box::from_raw(import.value.global as *mut _); + } + wasmer_import_export_kind::WASM_MEMORY => { + let _: Box = Box::from_raw(import.value.memory as *mut _); + } + wasmer_import_export_kind::WASM_TABLE => { + let _: Box = Box::from_raw(import.value.table as *mut _); + } + } + } +} + /// Extends an existing import object with new imports #[allow(clippy::cast_ptr_alignment)] #[no_mangle] diff --git a/lib/runtime-c-api/wasmer.h b/lib/runtime-c-api/wasmer.h index 862f3580ce1..582a092c14e 100644 --- a/lib/runtime-c-api/wasmer.h +++ b/lib/runtime-c-api/wasmer.h @@ -469,6 +469,13 @@ wasmer_result_t wasmer_import_object_extend(wasmer_import_object_t *import_objec const wasmer_import_t *imports, unsigned int imports_len); +/** + * Call `wasmer_import_object_imports_destroy` to free the memory allocated by this function + */ +int32_t wasmer_import_object_get_functions(const wasmer_import_object_t *import_object, + wasmer_import_t *imports, + uint32_t imports_len); + /** * Gets an entry from an ImportObject at the name and namespace. * Stores an immutable reference to `name` and `namespace` in `import`. @@ -485,6 +492,11 @@ wasmer_result_t wasmer_import_object_get_import(const wasmer_import_object_t *im wasmer_import_export_value *import_export_value, uint32_t tag); +/** + * Frees the memory acquired in `wasmer_import_object_get_functions` + */ +void wasmer_import_object_imports_destroy(wasmer_import_t *imports, uint32_t imports_len); + /** * Creates a new empty import object. * See also `wasmer_import_object_append` diff --git a/lib/runtime-c-api/wasmer.hh b/lib/runtime-c-api/wasmer.hh index c7c796ae463..0dae80aa58e 100644 --- a/lib/runtime-c-api/wasmer.hh +++ b/lib/runtime-c-api/wasmer.hh @@ -371,6 +371,11 @@ wasmer_result_t wasmer_import_object_extend(wasmer_import_object_t *import_objec const wasmer_import_t *imports, unsigned int imports_len); +/// Call `wasmer_import_object_imports_destroy` to free the memory allocated by this function +int32_t wasmer_import_object_get_functions(const wasmer_import_object_t *import_object, + wasmer_import_t *imports, + uint32_t imports_len); + /// Gets an entry from an ImportObject at the name and namespace. /// Stores an immutable reference to `name` and `namespace` in `import`. /// @@ -385,6 +390,9 @@ wasmer_result_t wasmer_import_object_get_import(const wasmer_import_object_t *im wasmer_import_export_value *import_export_value, uint32_t tag); +/// Frees the memory acquired in `wasmer_import_object_get_functions` +void wasmer_import_object_imports_destroy(wasmer_import_t *imports, uint32_t imports_len); + /// Creates a new empty import object. /// See also `wasmer_import_object_append` wasmer_import_object_t *wasmer_import_object_new(); From 01c209fe96e3d3651b84ba8c63e5cd5d06d892a2 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Fri, 11 Oct 2019 09:19:46 +0200 Subject: [PATCH 07/11] feat(runtime-c-api) Move the `wasi` module into its own file. --- .../src/{import.rs => import/mod.rs} | 104 +----------------- lib/runtime-c-api/src/import/wasi.rs | 101 +++++++++++++++++ 2 files changed, 102 insertions(+), 103 deletions(-) rename lib/runtime-c-api/src/{import.rs => import/mod.rs} (85%) create mode 100644 lib/runtime-c-api/src/import/wasi.rs diff --git a/lib/runtime-c-api/src/import.rs b/lib/runtime-c-api/src/import/mod.rs similarity index 85% rename from lib/runtime-c-api/src/import.rs rename to lib/runtime-c-api/src/import/mod.rs index c39a7d121aa..87b6b7447af 100644 --- a/lib/runtime-c-api/src/import.rs +++ b/lib/runtime-c-api/src/import/mod.rs @@ -52,109 +52,7 @@ pub unsafe extern "C" fn wasmer_import_object_new() -> *mut wasmer_import_object } #[cfg(feature = "wasi")] -mod wasi { - use super::*; - use crate::get_slice_checked; - use std::path::PathBuf; - - /// Opens a directory that's visible to the WASI module as `alias` but - /// is backed by the host file at `host_file_path` - #[repr(C)] - pub struct wasmer_wasi_map_dir_entry_t { - /// What the WASI module will see in its virtual root - pub alias: wasmer_byte_array, - /// The backing file that the WASI module will interact with via the alias - pub host_file_path: wasmer_byte_array, - } - - 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> { - let alias = self.alias.as_str()?.to_owned(); - let host_path = std::path::PathBuf::from(self.host_file_path.as_str()?); - - Ok((alias, host_path)) - } - } - - /// Creates a WASI import object. - /// - /// This function treats null pointers as empty collections. - /// For example, passing null for a string in `args`, will lead to a zero - /// length argument in that position. - #[no_mangle] - pub unsafe extern "C" fn wasmer_wasi_generate_import_object( - 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( - arg_list, - env_list, - preopened_file_list, - mapped_dir_list, - ) - .unwrap_or(std::ptr::null_mut()) - } - - /// Inner function that wraps error handling - fn wasmer_wasi_generate_import_object_inner( - 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> { - let arg_vec = arg_list.iter().map(|arg| unsafe { arg.as_vec() }).collect(); - let env_vec = env_list - .iter() - .map(|env_var| unsafe { env_var.as_vec() }) - .collect(); - let po_file_vec = preopened_file_list - .iter() - .map(|po_file| Ok(unsafe { PathBuf::from(po_file.as_str()?) }.to_owned())) - .collect::, _>>()?; - let mapped_dir_vec = mapped_dir_list - .iter() - .map(|entry| unsafe { entry.as_tuple() }) - .collect::, _>>()?; - - let import_object = Box::new(wasmer_wasi::generate_import_object( - arg_vec, - env_vec, - po_file_vec, - mapped_dir_vec, - )); - Ok(Box::into_raw(import_object) as *mut wasmer_import_object_t) - } - - /// Convenience function that creates a WASI import object with no arguments, - /// environment variables, preopened files, or mapped directories. - /// - /// This function is the same as calling [`wasmer_wasi_generate_import_object`] with all - /// empty values. - #[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![], - )); - - Box::into_raw(import_object) as *mut wasmer_import_object_t - } -} +mod wasi; #[cfg(feature = "wasi")] pub use self::wasi::*; diff --git a/lib/runtime-c-api/src/import/wasi.rs b/lib/runtime-c-api/src/import/wasi.rs new file mode 100644 index 00000000000..3df3c7f700b --- /dev/null +++ b/lib/runtime-c-api/src/import/wasi.rs @@ -0,0 +1,101 @@ +use super::*; +use crate::get_slice_checked; +use std::path::PathBuf; + +/// Opens a directory that's visible to the WASI module as `alias` but +/// is backed by the host file at `host_file_path` +#[repr(C)] +pub struct wasmer_wasi_map_dir_entry_t { + /// What the WASI module will see in its virtual root + pub alias: wasmer_byte_array, + /// The backing file that the WASI module will interact with via the alias + pub host_file_path: wasmer_byte_array, +} + +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> { + let alias = self.alias.as_str()?.to_owned(); + let host_path = std::path::PathBuf::from(self.host_file_path.as_str()?); + + Ok((alias, host_path)) + } +} + +/// Creates a WASI import object. +/// +/// This function treats null pointers as empty collections. +/// For example, passing null for a string in `args`, will lead to a zero +/// length argument in that position. +#[no_mangle] +pub unsafe extern "C" fn wasmer_wasi_generate_import_object( + 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( + arg_list, + env_list, + preopened_file_list, + mapped_dir_list, + ) + .unwrap_or(std::ptr::null_mut()) +} + +/// Inner function that wraps error handling +fn wasmer_wasi_generate_import_object_inner( + 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> { + let arg_vec = arg_list.iter().map(|arg| unsafe { arg.as_vec() }).collect(); + let env_vec = env_list + .iter() + .map(|env_var| unsafe { env_var.as_vec() }) + .collect(); + let po_file_vec = preopened_file_list + .iter() + .map(|po_file| Ok(unsafe { PathBuf::from(po_file.as_str()?) }.to_owned())) + .collect::, _>>()?; + let mapped_dir_vec = mapped_dir_list + .iter() + .map(|entry| unsafe { entry.as_tuple() }) + .collect::, _>>()?; + + let import_object = Box::new(wasmer_wasi::generate_import_object( + arg_vec, + env_vec, + po_file_vec, + mapped_dir_vec, + )); + Ok(Box::into_raw(import_object) as *mut wasmer_import_object_t) +} + +/// Convenience function that creates a WASI import object with no arguments, +/// environment variables, preopened files, or mapped directories. +/// +/// This function is the same as calling [`wasmer_wasi_generate_import_object`] with all +/// empty values. +#[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![], + )); + + Box::into_raw(import_object) as *mut wasmer_import_object_t +} From c3ff8eb5401e99d807cd389d4690ef4765be8f54 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Fri, 11 Oct 2019 09:26:05 +0200 Subject: [PATCH 08/11] fix(runtime-c-api) Replace unsafe code by safe code. --- lib/runtime-c-api/src/export.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/runtime-c-api/src/export.rs b/lib/runtime-c-api/src/export.rs index 3112b6178d8..acc3d122e11 100644 --- a/lib/runtime-c-api/src/export.rs +++ b/lib/runtime-c-api/src/export.rs @@ -112,11 +112,13 @@ impl std::convert::TryFrom for wasmer_import_export_kind { type Error = (); fn try_from(value: u32) -> Result { - if value > 3 { - Err(()) - } else { - Ok(unsafe { std::mem::transmute(value) }) - } + Ok(match value { + 0 => Self::WASM_FUNCTION, + 1 => Self::WASM_GLOBAL, + 2 => Self::WASM_MEMORY, + 3 => Self::WASM_TABLE, + _ => return Err(()), + }) } } From 80cfeb590e33e5a031d6eddd6f6a65ab83cf9082 Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Thu, 24 Oct 2019 11:50:43 -0700 Subject: [PATCH 09/11] Clean up comments add headers too --- lib/runtime-c-api/src/import/mod.rs | 6 ++++-- lib/runtime-c-api/wasmer.h | 3 +++ lib/runtime-c-api/wasmer.hh | 3 +++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/runtime-c-api/src/import/mod.rs b/lib/runtime-c-api/src/import/mod.rs index 87b6b7447af..3ef5a487cc4 100644 --- a/lib/runtime-c-api/src/import/mod.rs +++ b/lib/runtime-c-api/src/import/mod.rs @@ -100,7 +100,7 @@ pub unsafe extern "C" fn wasmer_import_object_get_import( }; if import.is_null() || import_export_value.is_null() { update_last_error(CApiError { - msg: "pointer to import and import_export_value must not be null".to_string(), + msg: "pointers to import and import_export_value must not be null".to_string(), }); return wasmer_result_t::WASMER_ERROR; } @@ -227,11 +227,13 @@ pub unsafe extern "C" fn wasmer_import_object_get_functions( #[no_mangle] /// Frees the memory acquired in `wasmer_import_object_get_functions` +/// +/// This function does not free the memory in `wasmer_import_object_t`; +/// it only frees memory allocated while querying a `wasmer_import_object_t`. pub unsafe extern "C" fn wasmer_import_object_imports_destroy( imports: *mut wasmer_import_t, imports_len: u32, ) { - // what's our null check policy here? let imports: &[wasmer_import_t] = &*slice::from_raw_parts_mut(imports, imports_len as usize); for import in imports { let _namespace: Vec = Vec::from_raw_parts( diff --git a/lib/runtime-c-api/wasmer.h b/lib/runtime-c-api/wasmer.h index 582a092c14e..57338a5bc51 100644 --- a/lib/runtime-c-api/wasmer.h +++ b/lib/runtime-c-api/wasmer.h @@ -494,6 +494,9 @@ wasmer_result_t wasmer_import_object_get_import(const wasmer_import_object_t *im /** * Frees the memory acquired in `wasmer_import_object_get_functions` + * + * This function does not free the memory in `wasmer_import_object_t`; + * it only frees memory allocated while querying a `wasmer_import_object_t`. */ void wasmer_import_object_imports_destroy(wasmer_import_t *imports, uint32_t imports_len); diff --git a/lib/runtime-c-api/wasmer.hh b/lib/runtime-c-api/wasmer.hh index 0dae80aa58e..521c6fa6cf2 100644 --- a/lib/runtime-c-api/wasmer.hh +++ b/lib/runtime-c-api/wasmer.hh @@ -391,6 +391,9 @@ wasmer_result_t wasmer_import_object_get_import(const wasmer_import_object_t *im uint32_t tag); /// Frees the memory acquired in `wasmer_import_object_get_functions` +/// +/// This function does not free the memory in `wasmer_import_object_t`; +/// it only frees memory allocated while querying a `wasmer_import_object_t`. void wasmer_import_object_imports_destroy(wasmer_import_t *imports, uint32_t imports_len); /// Creates a new empty import object. From 2a532b8ce5cc3be69f62fb66916ee57fb03faa2b Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Fri, 25 Oct 2019 14:24:22 -0700 Subject: [PATCH 10/11] Add tests for C API import_object and WASI updates --- Makefile | 2 + lib/runtime-c-api/src/import/mod.rs | 34 ++- lib/runtime-c-api/tests/CMakeLists.txt | 10 + lib/runtime-c-api/tests/assets/README.md | 3 + .../tests/assets/extended_wasi.rs | 31 +++ .../tests/assets/extended_wasi.wasm | Bin 0 -> 77757 bytes lib/runtime-c-api/tests/test-exports.c | 2 +- lib/runtime-c-api/tests/test-import-object | Bin 0 -> 14932 bytes lib/runtime-c-api/tests/test-import-object.c | 172 ++++++++++++ .../tests/test-wasi-import-object | Bin 0 -> 19508 bytes .../tests/test-wasi-import-object.c | 258 ++++++++++++++++++ lib/runtime-c-api/wasmer.h | 10 +- lib/runtime-c-api/wasmer.hh | 8 +- 13 files changed, 524 insertions(+), 6 deletions(-) create mode 100644 lib/runtime-c-api/tests/assets/README.md create mode 100644 lib/runtime-c-api/tests/assets/extended_wasi.rs create mode 100755 lib/runtime-c-api/tests/assets/extended_wasi.wasm create mode 100755 lib/runtime-c-api/tests/test-import-object create mode 100644 lib/runtime-c-api/tests/test-import-object.c create mode 100755 lib/runtime-c-api/tests/test-wasi-import-object create mode 100644 lib/runtime-c-api/tests/test-wasi-import-object.c diff --git a/Makefile b/Makefile index 58b85dbb1a5..0f3560e6762 100644 --- a/Makefile +++ b/Makefile @@ -105,6 +105,8 @@ capi: test-capi: capi cargo test -p wasmer-runtime-c-api --release +capi-test: test-capi + test-rest: cargo test --release --all --exclude wasmer-runtime-c-api --exclude wasmer-emscripten --exclude wasmer-spectests --exclude wasmer-wasi --exclude wasmer-middleware-common --exclude wasmer-middleware-common-tests --exclude wasmer-singlepass-backend --exclude wasmer-clif-backend --exclude wasmer-llvm-backend --exclude wasmer-wasi-tests --exclude wasmer-emscripten-tests diff --git a/lib/runtime-c-api/src/import/mod.rs b/lib/runtime-c-api/src/import/mod.rs index 3ef5a487cc4..ad0470af643 100644 --- a/lib/runtime-c-api/src/import/mod.rs +++ b/lib/runtime-c-api/src/import/mod.rs @@ -170,7 +170,35 @@ pub unsafe extern "C" fn wasmer_import_object_get_import( } #[no_mangle] -/// Call `wasmer_import_object_imports_destroy` to free the memory allocated by this function +/// Get the number of functions that an import object contains. +/// The result of this is useful as an argument to `wasmer_import_object_get_functions`. +/// This function returns -1 on error. +pub unsafe extern "C" fn wasmer_import_object_get_num_functions( + import_object: *const wasmer_import_object_t, +) -> i32 { + if import_object.is_null() { + update_last_error(CApiError { + msg: "import_object must not be null".to_owned(), + }); + return -1; + } + let import_object: &ImportObject = &*(import_object as *const ImportObject); + import_object + .clone_ref() + .into_iter() + .filter(|(_, _, e)| { + if let Export::Function { .. } = e { + true + } else { + false + } + }) + .count() as i32 +} + +#[no_mangle] +/// Call `wasmer_import_object_imports_destroy` to free the memory allocated by this function. +/// This function return -1 on error. pub unsafe extern "C" fn wasmer_import_object_get_functions( import_object: *const wasmer_import_object_t, imports: *mut wasmer_import_t, @@ -178,11 +206,11 @@ pub unsafe extern "C" fn wasmer_import_object_get_functions( ) -> i32 { if import_object.is_null() || imports.is_null() { update_last_error(CApiError { - msg: format!("import_object and imports must not be null"), + msg: "import_object and imports must not be null".to_owned(), }); return -1; } - let import_object: &mut ImportObject = &mut *(import_object as *mut ImportObject); + let import_object: &ImportObject = &*(import_object as *const ImportObject); let mut i = 0; for (namespace, name, export) in import_object.clone_ref().into_iter() { diff --git a/lib/runtime-c-api/tests/CMakeLists.txt b/lib/runtime-c-api/tests/CMakeLists.txt index 7db3cbcb06d..d8f206c4939 100644 --- a/lib/runtime-c-api/tests/CMakeLists.txt +++ b/lib/runtime-c-api/tests/CMakeLists.txt @@ -6,6 +6,8 @@ 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-imports test-imports.c) +add_executable(test-import-object test-import-object.c) +add_executable(test-wasi-import-object test-wasi-import-object.c) add_executable(test-instantiate test-instantiate.c) add_executable(test-memory test-memory.c) add_executable(test-module test-module.c) @@ -58,6 +60,14 @@ target_link_libraries(test-imports general ${WASMER_LIB}) target_compile_options(test-imports PRIVATE ${COMPILER_OPTIONS}) add_test(test-imports test-imports) +target_link_libraries(test-import-object general ${WASMER_LIB}) +target_compile_options(test-import-object PRIVATE ${COMPILER_OPTIONS}) +add_test(test-import-object test-import-object) + +target_link_libraries(test-wasi-import-object general ${WASMER_LIB}) +target_compile_options(test-wasi-import-object PRIVATE ${COMPILER_OPTIONS}) +add_test(test-wasi-import-object test-wasi-import-object) + target_link_libraries(test-instantiate general ${WASMER_LIB}) target_compile_options(test-instantiate PRIVATE ${COMPILER_OPTIONS}) add_test(test-instantiate test-instantiate) diff --git a/lib/runtime-c-api/tests/assets/README.md b/lib/runtime-c-api/tests/assets/README.md new file mode 100644 index 00000000000..52e92c052c8 --- /dev/null +++ b/lib/runtime-c-api/tests/assets/README.md @@ -0,0 +1,3 @@ +These are used in tests in the parent directory. + +To keep the generated wasm small, use `wasm-opt` and `wasm-strip` from wabt-tools (can be installed via wapm). Addtionally, consider passing the `-C opt-level=z` flag to `rustc` to optimize for size. diff --git a/lib/runtime-c-api/tests/assets/extended_wasi.rs b/lib/runtime-c-api/tests/assets/extended_wasi.rs new file mode 100644 index 00000000000..31c659f8ebc --- /dev/null +++ b/lib/runtime-c-api/tests/assets/extended_wasi.rs @@ -0,0 +1,31 @@ +extern "C" { + fn host_print(ptr: u32, len: u32); +} + +fn main() { + let args = std::env::args().collect::>(); + + println!("Found {} args on program {}", args.len(), args[0]); + + let env_vars = std::env::vars() + .map(|(arg, val)| format!("{}={}", arg, val)) + .collect::>(); + let env_var_list = env_vars.join(", "); + + println!("Found {} env vars: {}", env_vars.len(), env_var_list); + + let dirs_in_root = std::fs::read_dir("/") + .unwrap() + .map(|e| e.map(|inner| format!("{:?}", inner))) + .collect::, _>>() + .unwrap(); + + println!( + "Found {} pre opened dirs: {}", + dirs_in_root.len(), + dirs_in_root.join(", ") + ); + + const HOST_STR: &str = "This string came from a WASI module"; + unsafe { host_print(HOST_STR.as_ptr() as u32, HOST_STR.len() as u32) }; +} diff --git a/lib/runtime-c-api/tests/assets/extended_wasi.wasm b/lib/runtime-c-api/tests/assets/extended_wasi.wasm new file mode 100755 index 0000000000000000000000000000000000000000..b8eedd722160b2f70e0b4215d5e71f44ab5cc207 GIT binary patch literal 77757 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 P)_uo8SU8%8LYXMEu&j*TlbN*j`)l|L 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 literal 0 HcmV?d00001 diff --git a/lib/runtime-c-api/tests/test-import-object.c b/lib/runtime-c-api/tests/test-import-object.c new file mode 100644 index 00000000000..640f1664080 --- /dev/null +++ b/lib/runtime-c-api/tests/test-import-object.c @@ -0,0 +1,172 @@ +#include +#include "../wasmer.h" +#include +#include +#include + +bool static print_str_called = false; + +// Host function that will be imported into the Web Assembly Instance +void print_str(const wasmer_instance_context_t *ctx, int32_t ptr, int32_t len) +{ + print_str_called = true; + const wasmer_memory_t *memory = wasmer_instance_context_memory(ctx, 0); + uint32_t mem_len = wasmer_memory_length(memory); + uint8_t *mem_bytes = wasmer_memory_data(memory); + printf("%.*s", len, mem_bytes + ptr); +} + +// Use the last_error API to retrieve error messages +void print_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); +} + +int main() +{ + // Create a new func to hold the parameter and signature + // of our `print_str` host function + wasmer_value_tag params_sig[] = {WASM_I32, WASM_I32}; + wasmer_value_tag returns_sig[] = {}; + wasmer_import_func_t *func = wasmer_import_func_new((void (*)(void *)) print_str, params_sig, 2, returns_sig, 0); + + // Create module name for our imports + // represented in bytes for UTF-8 compatability + const char *module_name = "env"; + wasmer_byte_array module_name_bytes; + module_name_bytes.bytes = (const uint8_t *) module_name; + module_name_bytes.bytes_len = strlen(module_name); + + // Define a function import + const char *import_name = "_print_str"; + wasmer_byte_array import_name_bytes; + import_name_bytes.bytes = (const uint8_t *) import_name; + import_name_bytes.bytes_len = strlen(import_name); + wasmer_import_t func_import; + func_import.module_name = module_name_bytes; + func_import.import_name = import_name_bytes; + func_import.tag = WASM_FUNCTION; + func_import.value.func = func; + + // Define a memory import + const char *import_memory_name = "memory"; + wasmer_byte_array import_memory_name_bytes; + import_memory_name_bytes.bytes = (const uint8_t *) import_memory_name; + import_memory_name_bytes.bytes_len = strlen(import_memory_name); + wasmer_import_t memory_import; + memory_import.module_name = module_name_bytes; + memory_import.import_name = import_memory_name_bytes; + memory_import.tag = WASM_MEMORY; + wasmer_memory_t *memory = NULL; + wasmer_limits_t descriptor; + descriptor.min = 256; + wasmer_limit_option_t max; + max.has_some = true; + max.some = 256; + descriptor.max = max; + wasmer_result_t memory_result = wasmer_memory_new(&memory, descriptor); + if (memory_result != WASMER_OK) + { + print_wasmer_error(); + } + memory_import.value.memory = memory; + + // Define a global import + const char *import_global_name = "__memory_base"; + wasmer_byte_array import_global_name_bytes; + import_global_name_bytes.bytes = (const uint8_t *) import_global_name; + import_global_name_bytes.bytes_len = strlen(import_global_name); + wasmer_import_t global_import; + global_import.module_name = module_name_bytes; + global_import.import_name = import_global_name_bytes; + global_import.tag = WASM_GLOBAL; + wasmer_value_t val; + val.tag = WASM_I32; + val.value.I32 = 1024; + wasmer_global_t *global = wasmer_global_new(val, false); + global_import.value.global = global; + + // Define a table import + const char *import_table_name = "table"; + wasmer_byte_array import_table_name_bytes; + import_table_name_bytes.bytes = (const uint8_t *) import_table_name; + import_table_name_bytes.bytes_len = strlen(import_table_name); + wasmer_import_t table_import; + table_import.module_name = module_name_bytes; + table_import.import_name = import_table_name_bytes; + table_import.tag = WASM_TABLE; + wasmer_table_t *table = NULL; + wasmer_limits_t table_descriptor; + table_descriptor.min = 256; + wasmer_limit_option_t table_max; + table_max.has_some = true; + table_max.some = 256; + table_descriptor.max = table_max; + wasmer_result_t table_result = wasmer_table_new(&table, table_descriptor); + if (table_result != WASMER_OK) + { + print_wasmer_error(); + } + table_import.value.table = table; + + // Define an empty import object + wasmer_import_object_t *import_object = wasmer_import_object_new(); + // 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 + FILE *file = fopen("assets/hello_wasm.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); + + wasmer_module_t *module = NULL; + // 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); + + // 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(); + } + assert(instantiate_result == WASMER_OK); + + // Call the exported "hello_wasm" function of our instance + wasmer_value_t params[] = {}; + wasmer_value_t result_one; + wasmer_value_t results[] = {result_one}; + wasmer_result_t call_result = wasmer_instance_call(instance, "_hello_wasm", params, 0, results, 1); + printf("Call result: %d\n", call_result); + assert(call_result == WASMER_OK); + assert(print_str_called); + + // Use *_destroy methods to cleanup as specified in the header documentation + wasmer_import_func_destroy(func); + wasmer_global_destroy(global); + wasmer_memory_destroy(memory); + wasmer_table_destroy(table); + wasmer_instance_destroy(instance); + wasmer_import_object_destroy(import_object); + wasmer_module_destroy(module); + + return 0; +} diff --git a/lib/runtime-c-api/tests/test-wasi-import-object b/lib/runtime-c-api/tests/test-wasi-import-object new file mode 100755 index 0000000000000000000000000000000000000000..70e24d17d8b27c47e5810e79983e2fe5e4b8779d GIT binary patch literal 19508 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 literal 0 HcmV?d00001 diff --git a/lib/runtime-c-api/tests/test-wasi-import-object.c b/lib/runtime-c-api/tests/test-wasi-import-object.c new file mode 100644 index 00000000000..cf4a145d5c3 --- /dev/null +++ b/lib/runtime-c-api/tests/test-wasi-import-object.c @@ -0,0 +1,258 @@ +#include +#include "../wasmer.h" +#include +#include +#include + +bool static host_print_called = false; + +// Host function that will be imported into the Web Assembly Instance +void host_print(const wasmer_instance_context_t *ctx, int32_t ptr, int32_t len) +{ + host_print_called = true; + const wasmer_memory_t *memory = wasmer_instance_context_memory(ctx, 0); + uint32_t mem_len = wasmer_memory_length(memory); + uint8_t *mem_bytes = wasmer_memory_data(memory); + printf("%.*s", len, mem_bytes + ptr); +} + +// Use the last_error API to retrieve error messages +void print_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); +} + +// 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]); + } +} + +int main() +{ + // Create a new func to hold the parameter and signature + // of our `host_print` host function + wasmer_value_tag params_sig[] = {WASM_I32, WASM_I32}; + wasmer_value_tag returns_sig[] = {}; + wasmer_import_func_t *func = wasmer_import_func_new((void (*)(void *)) host_print, params_sig, 2, returns_sig, 0); + + // Create module name for our imports + // represented in bytes for UTF-8 compatability + const char *module_name = "env"; + wasmer_byte_array module_name_bytes; + module_name_bytes.bytes = (const uint8_t *) module_name; + module_name_bytes.bytes_len = strlen(module_name); + + // Define a function import + const char *import_name = "host_print"; + wasmer_byte_array import_name_bytes; + import_name_bytes.bytes = (const uint8_t *) import_name; + import_name_bytes.bytes_len = strlen(import_name); + wasmer_import_t func_import; + func_import.module_name = module_name_bytes; + func_import.import_name = import_name_bytes; + func_import.tag = WASM_FUNCTION; + func_import.value.func = func; + + // Define a memory import + const char *import_memory_name = "memory"; + wasmer_byte_array import_memory_name_bytes; + import_memory_name_bytes.bytes = (const uint8_t *) import_memory_name; + import_memory_name_bytes.bytes_len = strlen(import_memory_name); + wasmer_import_t memory_import; + memory_import.module_name = module_name_bytes; + memory_import.import_name = import_memory_name_bytes; + memory_import.tag = WASM_MEMORY; + wasmer_memory_t *memory = NULL; + wasmer_limits_t descriptor; + descriptor.min = 256; + wasmer_limit_option_t max; + max.has_some = true; + max.some = 256; + descriptor.max = max; + wasmer_result_t memory_result = wasmer_memory_new(&memory, descriptor); + if (memory_result != WASMER_OK) + { + print_wasmer_error(); + } + memory_import.value.memory = memory; + + // Define a global import + const char *import_global_name = "__memory_base"; + wasmer_byte_array import_global_name_bytes; + import_global_name_bytes.bytes = (const uint8_t *) import_global_name; + import_global_name_bytes.bytes_len = strlen(import_global_name); + wasmer_import_t global_import; + global_import.module_name = module_name_bytes; + global_import.import_name = import_global_name_bytes; + global_import.tag = WASM_GLOBAL; + wasmer_value_t val; + val.tag = WASM_I32; + val.value.I32 = 1024; + wasmer_global_t *global = wasmer_global_new(val, false); + global_import.value.global = global; + + // Define a table import + const char *import_table_name = "table"; + wasmer_byte_array import_table_name_bytes; + import_table_name_bytes.bytes = (const uint8_t *) import_table_name; + import_table_name_bytes.bytes_len = strlen(import_table_name); + wasmer_import_t table_import; + table_import.module_name = module_name_bytes; + table_import.import_name = import_table_name_bytes; + table_import.tag = WASM_TABLE; + wasmer_table_t *table = NULL; + wasmer_limits_t table_descriptor; + table_descriptor.min = 256; + wasmer_limit_option_t table_max; + table_max.has_some = true; + table_max.some = 256; + table_descriptor.max = table_max; + wasmer_result_t table_result = wasmer_table_new(&table, table_descriptor); + if (table_result != WASMER_OK) + { + print_wasmer_error(); + } + table_import.value.table = table; + + + // Create arbitrary arguments for our program + + // Set up data for our WASI import object + // + // Environment variables and program arguments are processed by the WASI + // program. They will not have any effects unless the program includes + // logic to process them. + const char *wasi_prog_name = "wasi_test_program"; + const char *wasi_first_arg = "--help"; + wasmer_byte_array args[] = { + { .bytes = (const uint8_t *) wasi_prog_name, + .bytes_len = strlen(wasi_prog_name) }, + { .bytes = (const uint8_t *) wasi_first_arg, + .bytes_len = strlen(wasi_first_arg) } + }; + int wasi_argc = sizeof(args) / sizeof(args[0]); + + // Create arbitrary environment variables for our program; + const char *wasi_color_env = "COLOR=TRUE"; + const char *wasi_app_should_log = "APP_SHOULD_LOG=FALSE"; + wasmer_byte_array envs[] = { + { .bytes = (const uint8_t *) wasi_color_env, + .bytes_len = strlen(wasi_color_env) }, + { .bytes = (const uint8_t *) wasi_app_should_log, + .bytes_len = strlen(wasi_app_should_log) } + }; + int wasi_env_len = sizeof(args) / sizeof(args[0]); + + // Open the host's current directory under a different name. + // WARNING: this gives the WASI module limited access to your host's file system, + // use caution when granting these permissions to untrusted Wasm modules. + const char *wasi_map_dir_alias = "the_host_current_dir"; + const char *wasi_map_dir_host_path = "."; + wasmer_wasi_map_dir_entry_t mapped_dirs[] = { + { .alias = + { .bytes = (const uint8_t *) wasi_map_dir_alias, + .bytes_len = strlen(wasi_map_dir_alias) }, + .host_file_path = + { .bytes = (const uint8_t *) wasi_map_dir_host_path, + .bytes_len = strlen(wasi_map_dir_host_path) } } + }; + 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 + FILE *file = fopen("assets/extended_wasi.wasm", "r"); + assert(file); + 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); + + wasmer_module_t *module = NULL; + // 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); + + // 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(); + } + assert(instantiate_result == WASMER_OK); + + // Call the exported "hello_wasm" function of our instance + wasmer_value_t params[] = {}; + wasmer_value_t result_one; + wasmer_value_t results[] = {result_one}; + // _start runs before main for WASI programs + wasmer_result_t call_result = wasmer_instance_call(instance, "_start", params, 0, results, 1); + printf("Call result: %d\n", call_result); + assert(call_result == WASMER_OK); + assert(host_print_called); + + int32_t num_functions = wasmer_import_object_get_num_functions(import_object); + if (num_functions == -1) { + print_wasmer_error(); + return -1; + } + wasmer_import_t *func_array = malloc(sizeof(wasmer_import_t) * num_functions); + assert(func_array); + + int32_t num_returned = wasmer_import_object_get_functions(import_object, func_array, num_functions); + if (num_functions == -1) { + print_wasmer_error(); + return -1; + } + assert(num_functions == num_returned); + + printf("Found %d functions in import object:\n", num_returned); + for (int i = 0; i < num_returned; ++i) { + print_byte_array(&func_array[i].module_name); + putchar(' '); + print_byte_array(&func_array[i].import_name); + putchar('\n'); + assert(func_array[i].tag == WASM_FUNCTION); + assert(func_array[i].value.func); + } + + // Use *_destroy methods to cleanup as specified in the header documentation + wasmer_import_object_imports_destroy(func_array, num_returned); + free(func_array); + wasmer_import_func_destroy(func); + wasmer_global_destroy(global); + wasmer_memory_destroy(memory); + wasmer_table_destroy(table); + wasmer_instance_destroy(instance); + wasmer_import_object_destroy(import_object); + wasmer_module_destroy(module); + + return 0; +} + diff --git a/lib/runtime-c-api/wasmer.h b/lib/runtime-c-api/wasmer.h index 57338a5bc51..8bb9fb254ac 100644 --- a/lib/runtime-c-api/wasmer.h +++ b/lib/runtime-c-api/wasmer.h @@ -470,7 +470,8 @@ wasmer_result_t wasmer_import_object_extend(wasmer_import_object_t *import_objec unsigned int imports_len); /** - * Call `wasmer_import_object_imports_destroy` to free the memory allocated by this function + * Call `wasmer_import_object_imports_destroy` to free the memory allocated by this function. + * This function return -1 on error. */ int32_t wasmer_import_object_get_functions(const wasmer_import_object_t *import_object, wasmer_import_t *imports, @@ -492,6 +493,13 @@ wasmer_result_t wasmer_import_object_get_import(const wasmer_import_object_t *im wasmer_import_export_value *import_export_value, uint32_t tag); +/** + * Get the number of functions that an import object contains. + * The result of this is useful as an argument to `wasmer_import_object_get_functions`. + * This function returns -1 on error. + */ +int32_t wasmer_import_object_get_num_functions(const wasmer_import_object_t *import_object); + /** * Frees the memory acquired in `wasmer_import_object_get_functions` * diff --git a/lib/runtime-c-api/wasmer.hh b/lib/runtime-c-api/wasmer.hh index 521c6fa6cf2..21426f27806 100644 --- a/lib/runtime-c-api/wasmer.hh +++ b/lib/runtime-c-api/wasmer.hh @@ -371,7 +371,8 @@ wasmer_result_t wasmer_import_object_extend(wasmer_import_object_t *import_objec const wasmer_import_t *imports, unsigned int imports_len); -/// Call `wasmer_import_object_imports_destroy` to free the memory allocated by this function +/// Call `wasmer_import_object_imports_destroy` to free the memory allocated by this function. +/// This function return -1 on error. int32_t wasmer_import_object_get_functions(const wasmer_import_object_t *import_object, wasmer_import_t *imports, uint32_t imports_len); @@ -390,6 +391,11 @@ wasmer_result_t wasmer_import_object_get_import(const wasmer_import_object_t *im wasmer_import_export_value *import_export_value, uint32_t tag); +/// Get the number of functions that an import object contains. +/// The result of this is useful as an argument to `wasmer_import_object_get_functions`. +/// This function returns -1 on error. +int32_t wasmer_import_object_get_num_functions(const wasmer_import_object_t *import_object); + /// Frees the memory acquired in `wasmer_import_object_get_functions` /// /// This function does not free the memory in `wasmer_import_object_t`; From aa82df7bc526c9453c14802cfee1b56777269d2a Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Fri, 25 Oct 2019 14:42:39 -0700 Subject: [PATCH 11/11] Fix github merge move entry in changelog due to release --- CHANGELOG.md | 2 +- lib/runtime-c-api/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ef174e90283..b4b97be0268 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## **[Unreleased]** - [#883](https://github.com/wasmerio/wasmer/pull/883) Allow floating point operations to have arbitrary inputs, even including SNaNs. +- [#856](https://github.com/wasmerio/wasmer/pull/856) Expose methods in the runtime C API to get a WASI import object ## 0.9.0 - 2019-10-23 @@ -17,7 +18,6 @@ Special thanks to @alocquet for their contributions! Special thanks to @jdanford for their contributions! -- [#856](https://github.com/wasmerio/wasmer/pull/856) Expose methods in the runtime C API to get a WASI import object - [#850](https://github.com/wasmerio/wasmer/pull/850) New `WasiStateBuilder` API. small, add misc. breaking changes to existing API (for example, changing the preopen dirs arg on `wasi::generate_import_object` from `Vec` to `Vec`) - [#852](https://github.com/wasmerio/wasmer/pull/852) Make minor grammar/capitalization fixes to README.md - [#841](https://github.com/wasmerio/wasmer/pull/841) Slightly improve rustdoc documentation and small updates to outdated info in readme files diff --git a/lib/runtime-c-api/Cargo.toml b/lib/runtime-c-api/Cargo.toml index 216975bd64a..8d5dbb61401 100644 --- a/lib/runtime-c-api/Cargo.toml +++ b/lib/runtime-c-api/Cargo.toml @@ -27,7 +27,7 @@ version = "0.9.0" [dependencies.wasmer-wasi] default-features = false path = "../wasi" -version = "0.8.0" +version = "0.9.0" optional = true [features]