856: Add WASI support to C API r=MarkMcCaskey a=MarkMcCaskey

This is an additive change (with one unrelated clean up of `*mut ptr -> *const ptr`).  It exposes the functions to get a WASI import object.  It also implements a function on import object to get an `import` with namespace and name.

These changes should be okay to ship now, we can follow up to finish adding methods to ImportObject on the C API side and start migrating away from  `*imports`.

# Review

- [x] Add a short description of the the change to the CHANGELOG.md file


Co-authored-by: Mark McCaskey <mark@wasmer.io>
Co-authored-by: Ivan Enderlin <ivan.enderlin@hoa-project.net>
Co-authored-by: Mark McCaskey <markmccaskey@users.noreply.github.com>
Co-authored-by: nlewycky <nick@wasmer.io>
This commit is contained in:
bors[bot] 2019-10-29 22:39:51 +00:00 committed by GitHub
commit 2dbe80ad34
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 1140 additions and 20 deletions

View File

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

1
Cargo.lock generated
View File

@ -1508,6 +1508,7 @@ dependencies = [
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"wasmer-runtime 0.9.0",
"wasmer-runtime-core 0.9.0",
"wasmer-wasi 0.9.0",
]
[[package]]

View File

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

View File

@ -24,12 +24,19 @@ default-features = false
path = "../runtime-core"
version = "0.9.0"
[dependencies.wasmer-wasi]
default-features = false
path = "../wasi"
version = "0.9.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"

View File

@ -85,12 +85,41 @@ 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<u32> for wasmer_import_export_kind {
type Error = ();
fn try_from(value: u32) -> Result<Self, Self::Error> {
Ok(match value {
0 => Self::WASM_FUNCTION,
1 => Self::WASM_GLOBAL,
2 => Self::WASM_MEMORY,
3 => Self::WASM_TABLE,
_ => return Err(()),
})
}
}
/// Gets export descriptors for the given module

View File

@ -9,11 +9,11 @@ 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},
import::ImportObject,
import::{ImportObject, ImportObjectIterator},
module::ImportName,
types::{FuncSig, Type},
};
@ -41,6 +41,10 @@ pub struct wasmer_import_descriptor_t;
#[derive(Clone)]
pub struct wasmer_import_descriptors_t;
#[repr(C)]
#[derive(Clone)]
pub struct wasmer_import_object_iter_t;
/// Creates a new empty import object.
/// See also `wasmer_import_object_append`
#[allow(clippy::cast_ptr_alignment)]
@ -51,12 +55,318 @@ 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;
#[cfg(feature = "wasi")]
pub use self::wasi::*;
/// Gets an entry from an ImportObject at the name and namespace.
/// Stores `name`, `namespace`, and `import_export_value` in `import`.
/// Thus these must remain valid for the lifetime of `import`.
///
/// The caller owns all data involved.
/// `import_export_value` will be written to based on `tag`.
#[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: "pointers 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
}
}
/// private wrapper data type used for casting
#[repr(C)]
struct WasmerImportObjectIterator(
std::iter::Peekable<Box<dyn Iterator<Item = <ImportObjectIterator as Iterator>::Item>>>,
);
/// Create an iterator over the functions in the import object.
/// Get the next import with `wasmer_import_object_iter_next`
/// Free the iterator with `wasmer_import_object_iter_destroy`
#[no_mangle]
pub unsafe extern "C" fn wasmer_import_object_iterate_functions(
import_object: *const wasmer_import_object_t,
) -> *mut wasmer_import_object_iter_t {
if import_object.is_null() {
update_last_error(CApiError {
msg: "import_object must not be null".to_owned(),
});
return std::ptr::null_mut();
}
let import_object: &ImportObject = &*(import_object as *const ImportObject);
let iter_inner = Box::new(import_object.clone_ref().into_iter().filter(|(_, _, e)| {
if let Export::Function { .. } = e {
true
} else {
false
}
})) as Box<dyn Iterator<Item = <ImportObjectIterator as Iterator>::Item>>;
let iterator = Box::new(WasmerImportObjectIterator(iter_inner.peekable()));
Box::into_raw(iterator) as *mut wasmer_import_object_iter_t
}
/// Writes the next value to `import`. `WASMER_ERROR` is returned if there
/// was an error or there's nothing left to return.
///
/// To free the memory allocated here, pass the import to `wasmer_import_object_imports_destroy`.
/// To check if the iterator is done, use `wasmer_import_object_iter_at_end`.
#[no_mangle]
pub unsafe extern "C" fn wasmer_import_object_iter_next(
import_object_iter: *mut wasmer_import_object_iter_t,
import: *mut wasmer_import_t,
) -> wasmer_result_t {
if import_object_iter.is_null() || import.is_null() {
update_last_error(CApiError {
msg: "import_object_iter and import must not be null".to_owned(),
});
return wasmer_result_t::WASMER_ERROR;
}
let iter = &mut *(import_object_iter as *mut WasmerImportObjectIterator);
let out = &mut *import;
// TODO: the copying here can be optimized away, we just need to use a different type of
// iterator internally
if let Some((namespace, name, export)) = iter.0.next() {
let ns = {
let mut n = namespace.clone();
n.shrink_to_fit();
n.into_bytes()
};
let ns_bytes = wasmer_byte_array {
bytes: ns.as_ptr(),
bytes_len: ns.len() as u32,
};
let name = {
let mut n = name.clone();
n.shrink_to_fit();
n.into_bytes()
};
let name_bytes = wasmer_byte_array {
bytes: name.as_ptr(),
bytes_len: name.len() as u32,
};
out.module_name = ns_bytes;
out.import_name = name_bytes;
std::mem::forget(ns);
std::mem::forget(name);
match export {
Export::Function { .. } => {
let func = Box::new(export.clone());
out.tag = wasmer_import_export_kind::WASM_FUNCTION;
out.value = wasmer_import_export_value {
func: Box::into_raw(func) as *mut _ as *const _,
};
}
Export::Global(global) => {
let glbl = Box::new(global.clone());
out.tag = wasmer_import_export_kind::WASM_GLOBAL;
out.value = wasmer_import_export_value {
global: Box::into_raw(glbl) as *mut _ as *const _,
};
}
Export::Memory(memory) => {
let mem = Box::new(memory.clone());
out.tag = wasmer_import_export_kind::WASM_MEMORY;
out.value = wasmer_import_export_value {
memory: Box::into_raw(mem) as *mut _ as *const _,
};
}
Export::Table(table) => {
let tbl = Box::new(table.clone());
out.tag = wasmer_import_export_kind::WASM_TABLE;
out.value = wasmer_import_export_value {
memory: Box::into_raw(tbl) as *mut _ as *const _,
};
}
}
wasmer_result_t::WASMER_OK
} else {
wasmer_result_t::WASMER_ERROR
}
}
/// Returns true if further calls to `wasmer_import_object_iter_next` will
/// not return any new data
#[no_mangle]
pub unsafe extern "C" fn wasmer_import_object_iter_at_end(
import_object_iter: *mut wasmer_import_object_iter_t,
) -> bool {
if import_object_iter.is_null() {
update_last_error(CApiError {
msg: "import_object_iter must not be null".to_owned(),
});
return true;
}
let iter = &mut *(import_object_iter as *mut WasmerImportObjectIterator);
iter.0.peek().is_none()
}
/// Frees the memory allocated by `wasmer_import_object_iterate_functions`
#[no_mangle]
pub unsafe extern "C" fn wasmer_import_object_iter_destroy(
import_object_iter: *mut wasmer_import_object_iter_t,
) {
if !import_object_iter.is_null() {
let _ = Box::from_raw(import_object_iter as *mut WasmerImportObjectIterator);
}
}
/// Frees the memory allocated in `wasmer_import_object_iter_next`
///
/// This function does not free the memory in `wasmer_import_object_t`;
/// it only frees memory allocated while querying a `wasmer_import_object_t`.
#[no_mangle]
pub unsafe extern "C" fn wasmer_import_object_imports_destroy(
imports: *mut wasmer_import_t,
imports_len: u32,
) {
if imports.is_null() {
return;
}
let imports: &[wasmer_import_t] = &*slice::from_raw_parts_mut(imports, imports_len as usize);
for import in imports {
let _namespace: Vec<u8> = 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<u8> = 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<Export> = Box::from_raw(import.value.func as *mut _);
}
wasmer_import_export_kind::WASM_GLOBAL => {
let _: Box<Global> = Box::from_raw(import.value.global as *mut _);
}
wasmer_import_export_kind::WASM_MEMORY => {
let _: Box<Memory> = Box::from_raw(import.value.memory as *mut _);
}
wasmer_import_export_kind::WASM_TABLE => {
let _: Box<Table> = Box::from_raw(import.value.table as *mut _);
}
}
}
}
/// Extends an existing import object with new imports
#[allow(clippy::cast_ptr_alignment)]
#[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);

View File

@ -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::<Result<Vec<_>, _>>()?;
let mapped_dir_vec = mapped_dir_list
.iter()
.map(|entry| unsafe { entry.as_tuple() })
.collect::<Result<Vec<_>, _>>()?;
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
}

View File

@ -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;

View File

@ -129,3 +129,34 @@ 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] {
get_slice_checked(self.bytes, self.bytes_len as usize)
}
/// Copy the data into an owned Vec
pub unsafe fn as_vec(&self) -> Vec<u8> {
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())
}
}
/// 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)
}
}

View File

@ -25,3 +25,5 @@ test-tables
test-validate
test-context
test-module-import-instantiate
test-wasi-import-object

View File

@ -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)

View File

@ -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.

View File

@ -0,0 +1,31 @@
extern "C" {
fn host_print(ptr: u32, len: u32);
}
fn main() {
let args = std::env::args().collect::<Vec<String>>();
println!("Found {} args on program {}", args.len(), args[0]);
let env_vars = std::env::vars()
.map(|(arg, val)| format!("{}={}", arg, val))
.collect::<Vec<String>>();
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::<Result<Vec<String>, _>>()
.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) };
}

Binary file not shown.

View File

@ -1,3 +1,4 @@
#include <inttypes.h>
#include <stdio.h>
#include "../wasmer.h"
#include <assert.h>
@ -242,7 +243,7 @@ int main()
wasmer_result_t call_result = wasmer_export_func_call(exported_function, inputs, inputs_arity, outputs, outputs_arity);
printf("Result: %ld\n", outputs[0].value.I64);
printf("Result: %" PRId64 "\n", outputs[0].value.I64);
assert(outputs[0].value.I64 == 7);
assert(call_result == WASMER_OK);

Binary file not shown.

View File

@ -0,0 +1,172 @@
#include <stdio.h>
#include "../wasmer.h"
#include <assert.h>
#include <stdint.h>
#include <string.h>
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;
}

Binary file not shown.

View File

@ -0,0 +1,250 @@
#include <stdio.h>
#include "../wasmer.h"
#include <assert.h>
#include <stdint.h>
#include <string.h>
static bool 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);
wasmer_import_object_iter_t *func_iter = wasmer_import_object_iterate_functions(import_object);
puts("Functions in import object:");
while ( !wasmer_import_object_iter_at_end(func_iter) ) {
wasmer_import_t import;
wasmer_result_t result = wasmer_import_object_iter_next(func_iter, &import);
assert(result == WASMER_OK);
print_byte_array(&import.module_name);
putchar(' ');
print_byte_array(&import.import_name);
putchar('\n');
assert(import.tag == WASM_FUNCTION);
assert(import.value.func);
wasmer_import_object_imports_destroy(&import, 1);
}
wasmer_import_object_iter_destroy(func_iter);
// 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;
}

View File

@ -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;
@ -138,6 +138,10 @@ typedef struct {
typedef struct {
} wasmer_import_object_iter_t;
typedef struct {
} wasmer_instance_t;
typedef struct {
@ -170,6 +174,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.
*
@ -451,9 +470,60 @@ 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.
* Stores `name`, `namespace`, and `import_export_value` in `import`.
* Thus these must remain valid for the lifetime of `import`.
*
* The caller owns all data involved.
* `import_export_value` will be written to based on `tag`.
*/
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);
/**
* Frees the memory allocated in `wasmer_import_object_iter_next`
*
* 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);
/**
* Returns true if further calls to `wasmer_import_object_iter_next` will
* not return any new data
*/
bool wasmer_import_object_iter_at_end(wasmer_import_object_iter_t *import_object_iter);
/**
* Frees the memory allocated by `wasmer_import_object_iterate_functions`
*/
void wasmer_import_object_iter_destroy(wasmer_import_object_iter_t *import_object_iter);
/**
* Writes the next value to `import`. `WASMER_ERROR` is returned if there
* was an error or there's nothing left to return.
*
* To free the memory allocated here, pass the import to `wasmer_import_object_imports_destroy`.
* To check if the iterator is done, use `wasmer_import_object_iter_at_end`.
*/
wasmer_result_t wasmer_import_object_iter_next(wasmer_import_object_iter_t *import_object_iter,
wasmer_import_t *import);
/**
* Create an iterator over the functions in the import object.
* Get the next import with `wasmer_import_object_iter_next`
* Free the iterator with `wasmer_import_object_iter_destroy`
*/
wasmer_import_object_iter_t *wasmer_import_object_iterate_functions(const wasmer_import_object_t *import_object);
/**
* Creates a new empty import object.
* See also `wasmer_import_object_append`
@ -756,4 +826,29 @@ 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.
*
* 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,
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 */

View File

@ -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 {
@ -120,6 +120,10 @@ struct wasmer_import_t {
wasmer_import_export_value value;
};
struct wasmer_import_object_iter_t {
};
struct wasmer_instance_t {
};
@ -154,6 +158,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.
@ -359,9 +372,48 @@ 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.
/// Stores `name`, `namespace`, and `import_export_value` in `import`.
/// Thus these must remain valid for the lifetime of `import`.
///
/// The caller owns all data involved.
/// `import_export_value` will be written to based on `tag`.
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);
/// Frees the memory allocated in `wasmer_import_object_iter_next`
///
/// 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);
/// Returns true if further calls to `wasmer_import_object_iter_next` will
/// not return any new data
bool wasmer_import_object_iter_at_end(wasmer_import_object_iter_t *import_object_iter);
/// Frees the memory allocated by `wasmer_import_object_iterate_functions`
void wasmer_import_object_iter_destroy(wasmer_import_object_iter_t *import_object_iter);
/// Writes the next value to `import`. `WASMER_ERROR` is returned if there
/// was an error or there's nothing left to return.
///
/// To free the memory allocated here, pass the import to `wasmer_import_object_imports_destroy`.
/// To check if the iterator is done, use `wasmer_import_object_iter_at_end`.
wasmer_result_t wasmer_import_object_iter_next(wasmer_import_object_iter_t *import_object_iter,
wasmer_import_t *import);
/// Create an iterator over the functions in the import object.
/// Get the next import with `wasmer_import_object_iter_next`
/// Free the iterator with `wasmer_import_object_iter_destroy`
wasmer_import_object_iter_t *wasmer_import_object_iterate_functions(const wasmer_import_object_t *import_object);
/// Creates a new empty import object.
/// See also `wasmer_import_object_append`
wasmer_import_object_t *wasmer_import_object_new();
@ -590,6 +642,27 @@ 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.
///
/// 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,
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