diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 30a49f0a8..c6577b392 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -43,3 +43,13 @@ jobs: - run: cargo test --release env: RUST_BACKTRACE: 1 + - name: Build and Test C API + run: | + make capi + make test-capi-cranelift + if: matrix.os != 'windows-latest' + - name: Build C API on Windows + run: make capi + if: matrix.os == 'windows-latest' + env: + RUST_BACKTRACE: 1 diff --git a/Cargo.lock b/Cargo.lock index 48448034a..0d1d8f77e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -139,6 +139,23 @@ dependencies = [ "rustc_version", ] +[[package]] +name = "cbindgen" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15be43e426e5133330fffd63026e178e70ecc74df7a4a844a8ff6e6ffc2fcde" +dependencies = [ + "clap", + "log", + "proc-macro2", + "quote", + "serde", + "serde_json", + "syn", + "tempfile", + "toml", +] + [[package]] name = "cc" version = "1.0.50" @@ -604,6 +621,12 @@ dependencies = [ "either", ] +[[package]] +name = "itoa" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e" + [[package]] name = "lazy_static" version = "1.4.0" @@ -1123,6 +1146,12 @@ dependencies = [ "semver", ] +[[package]] +name = "ryu" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3d612bc64430efeb3f7ee6ef26d590dce0c43249217bddc62112540c7941e1" + [[package]] name = "scopeguard" version = "1.1.0" @@ -1217,6 +1246,17 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_json" +version = "1.0.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7894c8ed05b7a3a279aeb79025fdec1d3158080b75b98a08faf2806bb799edd" +dependencies = [ + "itoa", + "ryu", + "serde", +] + [[package]] name = "smallvec" version = "1.4.0" @@ -1373,6 +1413,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "toml" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffc92d160b1eef40665be3a05630d003936a3bc7da7421277846c2613e92c71a" +dependencies = [ + "serde", +] + [[package]] name = "tracing" version = "0.1.13" @@ -1662,6 +1711,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "wasmer-runtime-c-api" +version = "0.16.2" +dependencies = [ + "cbindgen", + "lazy_static", + "libc", + "wasmer", + "wasmer-wasi", +] + [[package]] name = "wasmer-wasi" version = "0.16.2" diff --git a/Cargo.toml b/Cargo.toml index 9f6995d30..abe9ed1bd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,6 +42,7 @@ bytesize = "1.0.0" [workspace] members = [ + "lib/c-api", ] [build-dependencies] diff --git a/Makefile b/Makefile index d7760d7c2..d502f47a1 100644 --- a/Makefile +++ b/Makefile @@ -6,3 +6,32 @@ test: doc: cargo doc --all-features --document-private-items + +capi-singlepass: + cargo build --manifest-path lib/c-api/Cargo.toml --release \ + --no-default-features --features singlepass-backend,wasi + +capi-cranelift: + cargo build --manifest-path lib/c-api/Cargo.toml --release \ + --no-default-features --features cranelift-backend,wasi + +capi-llvm: + cargo build --manifest-path lib/c-api/Cargo.toml --release \ + --no-default-features --features llvm-backend,wasi + +# We use cranelift as the default backend for the capi for now +capi: capi-cranelift + +test-capi-singlepass: capi-singlepass + cargo test --manifest-path lib/c-api/Cargo.toml --release \ + --no-default-features --features singlepass-backend,wasi + +test-capi-cranelift: capi-cranelift + cargo test --manifest-path lib/c-api/Cargo.toml --release \ + --no-default-features --features cranelift-backend,wasi -- --nocapture --test-threads=1 + +test-capi-llvm: capi-llvm + cargo test --manifest-path lib/c-api/Cargo.toml --release \ + --no-default-features --features llvm-backend,wasi + +test-capi: test-capi-singlepass test-capi-cranelift test-capi-llvm test-capi-emscripten diff --git a/lib/api/src/externals.rs b/lib/api/src/externals.rs index 605288458..3cbbc53e0 100644 --- a/lib/api/src/externals.rs +++ b/lib/api/src/externals.rs @@ -229,7 +229,9 @@ impl Table { let item = init.into_checked_anyfunc(store)?; let tunables = store.engine().tunables(); let table_plan = tunables.table_plan(ty); - let table = tunables.create_table(table_plan).unwrap(); + let table = tunables + .create_table(table_plan) + .map_err(RuntimeError::new)?; let definition = table.vmtable(); for i in 0..definition.current_elements { @@ -582,19 +584,30 @@ impl Function { params: &[Val], results: &mut [Val], ) -> Result<(), RuntimeError> { + let format_types_for_error_message = |items: &[Val]| { + items + .iter() + .map(|param| param.ty().to_string()) + .collect::>() + .join(", ") + }; let signature = self.ty(); if signature.params().len() != params.len() { return Err(RuntimeError::new(format!( - "expected {} arguments, got {}", + "expected {} arguments, got {}: Parameters of type [{}] did not match signature {}", signature.params().len(), - params.len() + params.len(), + format_types_for_error_message(params), + &signature ))); } if signature.results().len() != results.len() { return Err(RuntimeError::new(format!( - "expected {} results, got {}", + "expected {} results, got {}: Results of type [{}] did not match signature {}", signature.results().len(), - results.len() + results.len(), + format_types_for_error_message(results), + &signature, ))); } @@ -604,7 +617,11 @@ impl Function { let param_tys = signature.params().iter(); for ((arg, slot), ty) in params.iter().zip(&mut values_vec).zip(param_tys) { if arg.ty() != ty.clone() { - return Err(RuntimeError::new("argument type mismatch")); + let param_types = format_types_for_error_message(params); + return Err(RuntimeError::new(format!( + "Parameters of type [{}] did not match signature {}", + param_types, &signature, + ))); } unsafe { arg.write_value_to(slot); diff --git a/lib/api/src/lib.rs b/lib/api/src/lib.rs index 93f338d59..39146b8ff 100644 --- a/lib/api/src/lib.rs +++ b/lib/api/src/lib.rs @@ -26,7 +26,7 @@ pub use crate::types::{ MemoryType, Mutability, TableType, Val, ValType, }; -pub use wasm_common::{ValueType, WasmExternType, WasmTypeList}; +pub use wasm_common::{Bytes, Pages, ValueType, WasmExternType, WasmTypeList}; #[cfg(feature = "compiler")] pub use wasmer_compiler::CompilerConfig; pub use wasmer_compiler::{Features, Target}; diff --git a/lib/api/src/store.rs b/lib/api/src/store.rs index b83635e77..e4eb6af87 100644 --- a/lib/api/src/store.rs +++ b/lib/api/src/store.rs @@ -6,15 +6,15 @@ use wasmer_engine::Engine; #[derive(Clone)] pub struct Store { - engine: Arc, + engine: Arc, } impl Store { - pub fn new(engine: Arc) -> Store { + pub fn new(engine: Arc) -> Store { Store { engine } } - pub fn engine(&self) -> &Arc { + pub fn engine(&self) -> &Arc { &self.engine } @@ -37,28 +37,30 @@ impl Default for Store { // sure this function doesn't emit a compile error even if // more than one compiler is enabled. #[allow(unreachable_code)] - fn get_config() -> Box { + fn get_config() -> Arc { #[cfg(feature = "cranelift")] - return Box::new(wasmer_compiler_cranelift::CraneliftConfig::default()); + return Arc::new(wasmer_compiler_cranelift::CraneliftConfig::default()); #[cfg(feature = "llvm")] - return Box::new(wasmer_compiler_llvm::LLVMConfig::default()); + return Arc::new(wasmer_compiler_llvm::LLVMConfig::default()); #[cfg(feature = "singlepass")] - return Box::new(wasmer_compiler_singlepass::SinglepassConfig::default()); + return Arc::new(wasmer_compiler_singlepass::SinglepassConfig::default()); } #[allow(unreachable_code)] - fn get_engine(config: Box) -> Arc { + fn get_engine( + config: Arc, + ) -> Arc { let tunables = Tunables::for_target(config.target().triple()); #[cfg(feature = "jit")] - return Arc::new(wasmer_engine_jit::JITEngine::new(&config, tunables)); + return Arc::new(wasmer_engine_jit::JITEngine::new(&*config, tunables)); } let config = get_config(); let engine = get_engine(config); - Store::new(config) + Store::new(engine) } } diff --git a/lib/c-api/.gitignore b/lib/c-api/.gitignore new file mode 100644 index 000000000..baf7545df --- /dev/null +++ b/lib/c-api/.gitignore @@ -0,0 +1 @@ +doc/html/ \ No newline at end of file diff --git a/lib/c-api/Cargo.toml b/lib/c-api/Cargo.toml new file mode 100644 index 000000000..ffbcbaf4b --- /dev/null +++ b/lib/c-api/Cargo.toml @@ -0,0 +1,52 @@ +[package] +name = "wasmer-runtime-c-api" +version = "0.16.2" +description = "Wasmer C API library" +documentation = "https://wasmerio.github.io/wasmer/c/runtime-c-api/" +license = "MIT" +authors = ["The Wasmer Engineering Team "] +repository = "https://github.com/wasmerio/wasmer" +keywords = ["wasm", "webassembly", "runtime"] +categories = ["wasm"] +edition = "2018" +readme = "README.md" + +[lib] +crate-type = ["cdylib", "staticlib"] + +[dependencies] +lazy_static = "1" +libc = "0.2.60" +# for generating code in the same way thot the wasm-c-api does +# Commented out for now until we can find a solution to the exported function problem +# wasmer-wasm-c-api = { version = "0.16.2", path = "crates/wasm-c-api" } + +[dependencies.wasmer] +default-features = false +features = ["compiler", "engine", "jit", "cranelift"] +path = "../api" +version = "0.16.2" + +[dependencies.wasmer-wasi] +default-features = false +path = "../wasi" +version = "0.16.2" +optional = true + +#[dependencies.wasmer-emscripten] +#path = "../emscripten" +#version = "0.16.2" +#optional = true + +[features] +default = ["cranelift-backend", "wasi"] +singlepass-backend = ["wasmer/singlepass"] +cranelift-backend = ["wasmer/cranelift"] +llvm-backend = ["wasmer/llvm"] +wasi = ["wasmer-wasi"] +#emscripten = ["wasmer-emscripten"] +# used to avoid generating standard Wasm C API types in our header files +ignore-wasm-c-api = [] + +[build-dependencies] +cbindgen = "0.14" \ No newline at end of file diff --git a/lib/c-api/README.md b/lib/c-api/README.md new file mode 100644 index 000000000..d1475b7fd --- /dev/null +++ b/lib/c-api/README.md @@ -0,0 +1,140 @@ +

+ + Wasmer logo + +

+ +

+ + Build Status + + + License + + + Join the Wasmer Community + + + Number of downloads from crates.io + + + Wasmer C API Documentation + +

+ +# Wasmer Runtime C API + +Wasmer is a standalone JIT WebAssembly runtime, aiming to be fully +compatible with WASI, Emscripten, Rust and Go. [Learn +more](https://github.com/wasmerio/wasmer). + +This crate exposes a C and a C++ API for the Wasmer runtime. + +# Usage + +The C and C++ header files can be found in the source tree of this +crate, respectively [`wasmer.h`][wasmer_h] and +[`wasmer.hh`][wasmer_hh]. They are automatically generated, and always +up-to-date in this repository. +The runtime shared library (so, dll, dylib) can also be downloaded in Wasmer [release page](https://github.com/wasmerio/wasmer/releases). + +You can find the full C API documentation here: +https://wasmerio.github.io/wasmer/c/runtime-c-api/ + +Here is a simple example to use the C API: + +```c +#include +#include "../wasmer.h" +#include +#include + +int main() +{ + // Read the Wasm file bytes. + FILE *file = fopen("sum.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); + + // Prepare the imports. + wasmer_import_t imports[] = {}; + + // Instantiate! + wasmer_instance_t *instance = NULL; + wasmer_result_t instantiation_result = wasmer_instantiate(&instance, bytes, len, imports, 0); + + assert(instantiation_result == WASMER_OK); + + // Let's call a function. + // Start by preparing the arguments. + + // Value of argument #1 is `7i32`. + wasmer_value_t argument_one; + argument_one.tag = WASM_I32; + argument_one.value.I32 = 7; + + // Value of argument #2 is `8i32`. + wasmer_value_t argument_two; + argument_two.tag = WASM_I32; + argument_two.value.I32 = 8; + + // Prepare the arguments. + wasmer_value_t arguments[] = {argument_one, argument_two}; + + // Prepare the return value. + wasmer_value_t result_one; + wasmer_value_t results[] = {result_one}; + + // Call the `sum` function with the prepared arguments and the return value. + wasmer_result_t call_result = wasmer_instance_call(instance, "sum", arguments, 2, results, 1); + + // Let's display the result. + printf("Call result: %d\n", call_result); + printf("Result: %d\n", results[0].value.I32); + + // `sum(7, 8) == 15`. + assert(results[0].value.I32 == 15); + assert(call_result == WASMER_OK); + + wasmer_instance_destroy(instance); + + return 0; +} +``` + +# Testing + +Tests are run using the release build of the library. If you make +changes or compile with non-default features, please ensure you +rebuild in release mode for the tests to see the changes. + +The tests can be run via `cargo test`, such as: + +```sh +$ cargo test --release -- --nocapture +``` + +To run tests manually, enter the `lib/runtime-c-api/tests` directory +and run the following commands: + +```sh +$ cmake . +$ make +$ make test +``` + + +# License + +Wasmer is primarily distributed under the terms of the [MIT +license][mit-license] ([LICENSE][license]). + + +[wasmer_h]: ./wasmer.h +[wasmer_hh]: ./wasmer.hh +[mit-license]: http://opensource.org/licenses/MIT +[license]: https://github.com/wasmerio/wasmer/blob/master/LICENSE diff --git a/lib/c-api/build.rs b/lib/c-api/build.rs new file mode 100644 index 000000000..25d2d531f --- /dev/null +++ b/lib/c-api/build.rs @@ -0,0 +1,106 @@ +extern crate cbindgen; + +use cbindgen::{Builder, Language}; +use std::{env, fs, path::PathBuf}; + +fn main() { + let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); + let mut crate_wasmer_header_file = PathBuf::from(&crate_dir); + crate_wasmer_header_file.push("wasmer"); + + let out_dir = env::var("OUT_DIR").unwrap(); + let mut out_wasmer_header_file = PathBuf::from(&out_dir); + out_wasmer_header_file.push("wasmer"); + + let mut pre_header = r#" +#if !defined(WASMER_H_MACROS) + +#define WASMER_H_MACROS + +// Define the `ARCH_X86_X64` constant. +#if defined(MSVC) && defined(_M_AMD64) +# define ARCH_X86_64 +#elif (defined(GCC) || defined(__GNUC__) || defined(__clang__)) && defined(__x86_64__) +# define ARCH_X86_64 +#endif + +// Compatibility with non-Clang compilers. +#if !defined(__has_attribute) +# define __has_attribute(x) 0 +#endif + +// Compatibility with non-Clang compilers. +#if !defined(__has_declspec_attribute) +# define __has_declspec_attribute(x) 0 +#endif + +// Define the `DEPRECATED` macro. +#if defined(GCC) || defined(__GNUC__) || __has_attribute(deprecated) +# define DEPRECATED(message) __attribute__((deprecated(message))) +#elif defined(MSVC) || __has_declspec_attribute(deprecated) +# define DEPRECATED(message) __declspec(deprecated(message)) +#endif + +"# + .to_string(); + + #[cfg(feature = "wasi")] + { + pre_header += "#define WASMER_WASI_ENABLED\n"; + } + + #[cfg(feature = "emscripten")] + { + pre_header += "#define WASMER_EMSCRIPTEN_ENABLED\n"; + } + + // Close pre header. + pre_header += "#endif // WASMER_H_MACROS\n"; + + // Generate the C bindings in the `OUT_DIR`. + out_wasmer_header_file.set_extension("h"); + Builder::new() + .with_crate(crate_dir.clone()) + .with_language(Language::C) + .with_include_guard("WASMER_H") + .with_header(&pre_header) + .with_define("target_family", "windows", "_WIN32") + .with_define("target_arch", "x86_64", "ARCH_X86_64") + .with_define("feature", "wasi", "WASMER_WASI_ENABLED") + .with_define("feature", "emscripten", "WASMER_EMSCRIPTEN_ENABLED") + .generate() + .expect("Unable to generate C bindings") + .write_to_file(out_wasmer_header_file.as_path()); + + // Generate the C++ bindings in the `OUT_DIR`. + out_wasmer_header_file.set_extension("hh"); + Builder::new() + .with_crate(crate_dir) + .with_language(Language::Cxx) + .with_include_guard("WASMER_H") + .with_header(&pre_header) + .with_define("target_family", "windows", "_WIN32") + .with_define("target_arch", "x86_64", "ARCH_X86_64") + .with_define("feature", "wasi", "WASMER_WASI_ENABLED") + .with_define("feature", "emscripten", "WASMER_EMSCRIPTEN_ENABLED") + .generate() + .expect("Unable to generate C++ bindings") + .write_to_file(out_wasmer_header_file.as_path()); + + // Copy the generated C bindings from `OUT_DIR` to + // `CARGO_MANIFEST_DIR`. + crate_wasmer_header_file.set_extension("h"); + out_wasmer_header_file.set_extension("h"); + fs::copy( + out_wasmer_header_file.as_path(), + crate_wasmer_header_file.as_path(), + ) + .expect("Unable to copy the generated C bindings"); + + // Copy the generated C++ bindings from `OUT_DIR` to + // `CARGO_MANIFEST_DIR`. + crate_wasmer_header_file.set_extension("hh"); + out_wasmer_header_file.set_extension("hh"); + fs::copy(out_wasmer_header_file, crate_wasmer_header_file) + .expect("Unable to copy the generated C++ bindings"); +} diff --git a/lib/c-api/crates/wasm-c-api/Cargo.toml b/lib/c-api/crates/wasm-c-api/Cargo.toml new file mode 100644 index 000000000..f825a431f --- /dev/null +++ b/lib/c-api/crates/wasm-c-api/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "wasmer-wasm-c-api" +version = "0.16.2" +description = "The standard Wasm C API for Wasmer" +license = "MIT" +authors = ["The Wasmer Engineering Team "] +edition = "2018" + +[lib] +crate-type = ["cdylib", "rlib", "staticlib"] + +[dependencies] +paste = "0.1" +wasmer = { version = "0.16.2", path = "../../../api", default-features = false } diff --git a/lib/c-api/crates/wasm-c-api/src/lib.rs b/lib/c-api/crates/wasm-c-api/src/lib.rs new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/lib/c-api/crates/wasm-c-api/src/lib.rs @@ -0,0 +1 @@ + diff --git a/lib/c-api/doc/index.md b/lib/c-api/doc/index.md new file mode 100644 index 000000000..ebd5dadb7 --- /dev/null +++ b/lib/c-api/doc/index.md @@ -0,0 +1,107 @@ +# Wasmer Runtime C API + +[Wasmer] is a standalone WebAssembly runtime for running WebAssembly [outside of the browser](https://webassembly.org/docs/non-web/), supporting [WASI](https://github.com/WebAssembly/WASI) and [Emscripten](https://emscripten.org/). + +The Wasmer Runtime C API exposes a C and a C++ API to interact +with the Wasmer Runtime, so you can use WebAssembly anywhere. + +[Wasmer]: https://github.com/wasmerio/wasmer +[WebAssembly]: https://webassembly.org/ + +# Usage + +Since the Wasmer runtime is written in Rust, the C and C++ API are +designed to work hand-in-hand with its shared library. The C and C++ +header files, namely [`wasmer.h`][wasmer_h] and `wasmer.hh` are documented +in the docs. + +Their source code can be found in the source tree of the [wasmer-runtime-c-api](https://github.com/wasmerio/wasmer/tree/master/lib/runtime-c-api) +crate. +The C and C++ header files along with the runtime shared +libraries (`.so`, `.dylib`, `.dll`) can also be downloaded in the +Wasmer [release page]. + +[release page]: https://github.com/wasmerio/wasmer/releases + +Here is a simple example to use the C API: + +```c +#include +#include "wasmer.h" +#include +#include + +int main() +{ + // Read the Wasm file bytes. + FILE *file = fopen("sum.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); + + // Prepare the imports. + wasmer_import_t imports[] = {}; + + // Instantiate! + wasmer_instance_t *instance = NULL; + wasmer_result_t instantiation_result = wasmer_instantiate(&instance, bytes, len, imports, 0); + + assert(instantiation_result == WASMER_OK); + + // Let's call a function. + // Start by preparing the arguments. + + // Value of argument #1 is `7i32`. + wasmer_value_t argument_one; + argument_one.tag = WASM_I32; + argument_one.value.I32 = 7; + + // Value of argument #2 is `8i32`. + wasmer_value_t argument_two; + argument_two.tag = WASM_I32; + argument_two.value.I32 = 8; + + // Prepare the arguments. + wasmer_value_t arguments[] = {argument_one, argument_two}; + + // Prepare the return value. + wasmer_value_t result_one; + wasmer_value_t results[] = {result_one}; + + // Call the `sum` function with the prepared arguments and the return value. + wasmer_result_t call_result = wasmer_instance_call(instance, "sum", arguments, 2, results, 1); + + // Let's display the result. + printf("Call result: %d\n", call_result); + printf("Result: %d\n", results[0].value.I32); + + // `sum(7, 8) == 15`. + assert(results[0].value.I32 == 15); + assert(call_result == WASMER_OK); + + wasmer_instance_destroy(instance); + + return 0; +} +``` + +# Examples + +You can check more examples of how to use the Wasmer C API here: + +https://docs.wasmer.io/integrations/c/examples + + + +# License + +Wasmer is primarily distributed under the terms of the [MIT +license][mit-license] ([LICENSE][license]). + + +[wasmer_h]: https://wasmerio.github.io/wasmer/c/runtime-c-api/wasmer_8h.html +[mit-license]: http://opensource.org/licenses/MIT +[license]: https://github.com/wasmerio/wasmer/blob/master/LICENSE diff --git a/lib/c-api/doc/theme/css/wasmer.css b/lib/c-api/doc/theme/css/wasmer.css new file mode 100644 index 000000000..88322ba03 --- /dev/null +++ b/lib/c-api/doc/theme/css/wasmer.css @@ -0,0 +1,194 @@ +:root { + --primary-color: hsl(261.9, 61.9%, 16.5%); + --primary-negative-color: hsl(0, 0%, 100%); + --primary-dark-color: hsl(261.9, 61.9%, 16.5%); + --primary-light-color: hsl(0, 0%, 96.1%); + --secondary-color: hsl(241.2, 68.9%, 57.1%); + --secondary-light-color: hsl(241.2, 68.9%, 60%); + --white-color: hsl(0, 0%, 100%); +} + +body { + overflow-y: hidden; +} + +[role="header"] { + display: grid; + grid-template-areas: "a b"; + margin-bottom: 1rem; + padding: .5rem 2vw; +} + +[role="header"] > * { + margin: 0; + padding: 0; +} + +[role="header"] > img { + grid-area: a; + max-width: 30vw; + max-height: 15vh; +} + +[role="header"] > div { + grid-area: b; + text-align: right; +} + +[role="footer"] { + position: fixed; + bottom: 0; + text-align: end; + background: var(--white-color); +} + +#MSearchBox { + display: none; +} + +#side-nav { + margin: 0; + padding-bottom: 3rem; +} + +#side-nav .ui-resizable-e { + background: radial-gradient(var(--secondary-light-color), var(--white-color) 60%); +} + +#nav-tree { + background: none; +} + +#nav-tree .selected { + text-shadow: none; + color: var(--primary-negative-color); + background: var(--secondary-color); +} + +#nav-sync { + display: none; +} + +#doc-content > .header { + border: 0; + background: none; +} + +#doc-content > .header > .summary { + font-size: small; +} + +body, div, p, ul, li { + color: var(--primary-color); +} + +#doc-content .contents p, +#doc-content .contents ul, +#doc-content .contents div:not(.line), +#doc-content .contents table.memberdecls { + font-family: serif; + font-size: 1.1rem; +} + +a, .contents a, .contents a:visited { + color: var(--secondary-color); +} + +h1, .headertitle > .title { + color: var(--primary-color); + font-size: 2.5rem; + line-height: 2.7rem; +} + +h2, .memberdecls h2, h2.groupheader { + color: var(--primary-color); + font-size: 2rem; + line-height: 2.5rem; +} + +h2.groupheader { + border-color: var(--secondary-color); + border-bottom-style: dotted; + margin-bottom: 1rem !important; +} + +h3, h2.memtitle { + font-size: 1.5rem; + line-height: 2rem; +} + +.memberdecls [class^="separator"] { + display: none; +} + +.memberdecls [class^="memitem"] > td, +.memberdecls [class^="memdesc"] > td { + background: none; +} + +.memtitle { + margin: 0; + padding: 0; + border: none; + background: none; +} + +.memitem { + margin-left: 1rem; + width: calc(100% - 1rem); +} + +.memtitle { + font-weight: bold; + font-family: monospace; +} + +.memtitle ~ .memtitle { + margin-top: 1.5rem; +} + +.memitem .memproto, +.memitem .memproto .memname, +.memitem .memdoc, +.memitem .memdoc .definition { + margin: 0; + padding: 0; + box-shadow: none; + border: none; + background: none; +} + +.memitem .memproto { + padding-left: 1rem; + margin: .5rem 0; + background: var(--primary-light-color) !important; +} + +.memproto table.memname { + font-family: monospace; + font-size: .85rem; +} + +table.fieldtable { + border-radius: 0; + box-shadow: none; +} + +table.fieldtable th { + color: var(--primary-negative-color); + border-radius: 0; + background: var(--secondary-color); +} + +table.fieldtable, +table.fieldtable td { + border-color: var(--secondary-color) !important; +} + +div.fragment { + padding: 1rem; +} + +div.fragment div.line { + line-height: 1.15rem; +} diff --git a/lib/c-api/doc/theme/footer.html b/lib/c-api/doc/theme/footer.html new file mode 100644 index 000000000..15eec1979 --- /dev/null +++ b/lib/c-api/doc/theme/footer.html @@ -0,0 +1,10 @@ + + + + + diff --git a/lib/c-api/doc/theme/header.html b/lib/c-api/doc/theme/header.html new file mode 100644 index 000000000..c7350004f --- /dev/null +++ b/lib/c-api/doc/theme/header.html @@ -0,0 +1,53 @@ + + + + + $projectname: $title + $title + + + + + + + + + $treeview + $search + $mathjax + + $extrastylesheet + + +
+ + + + Wasmer's logo + + + +
+

+ $projectname + $projectnumber + + $projectbrief + +

+
+ + Visit the repository + License + Join the Wasmer Community + +
+ + + + + + + + + diff --git a/lib/c-api/doxyfile b/lib/c-api/doxyfile new file mode 100644 index 000000000..e38b94b75 --- /dev/null +++ b/lib/c-api/doxyfile @@ -0,0 +1,338 @@ +# Doxyfile 1.8.17 + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- +DOXYFILE_ENCODING = UTF-8 +PROJECT_NAME = "wasmer-runtime-c-api" +PROJECT_NUMBER = +PROJECT_BRIEF = +PROJECT_LOGO = ../../assets/logo.png +OUTPUT_DIRECTORY = doc/ +CREATE_SUBDIRS = NO +ALLOW_UNICODE_NAMES = NO +OUTPUT_LANGUAGE = English +OUTPUT_TEXT_DIRECTION = None +BRIEF_MEMBER_DESC = YES +REPEAT_BRIEF = YES +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the +ALWAYS_DETAILED_SEC = NO +INLINE_INHERITED_MEMB = NO +FULL_PATH_NAMES = YES +STRIP_FROM_PATH = +STRIP_FROM_INC_PATH = +SHORT_NAMES = NO +JAVADOC_AUTOBRIEF = NO +JAVADOC_BANNER = NO +QT_AUTOBRIEF = NO +MULTILINE_CPP_IS_BRIEF = NO +INHERIT_DOCS = YES +SEPARATE_MEMBER_PAGES = NO +TAB_SIZE = 4 +ALIASES = +TCL_SUBST = +OPTIMIZE_OUTPUT_FOR_C = YES +OPTIMIZE_OUTPUT_JAVA = NO +OPTIMIZE_FOR_FORTRAN = NO +OPTIMIZE_OUTPUT_VHDL = NO +OPTIMIZE_OUTPUT_SLICE = NO +EXTENSION_MAPPING = dox=md +MARKDOWN_SUPPORT = YES +TOC_INCLUDE_HEADINGS = 5 +AUTOLINK_SUPPORT = YES +BUILTIN_STL_SUPPORT = NO +CPP_CLI_SUPPORT = NO +SIP_SUPPORT = NO +IDL_PROPERTY_SUPPORT = YES +DISTRIBUTE_GROUP_DOC = NO +GROUP_NESTED_COMPOUNDS = NO +SUBGROUPING = YES +INLINE_GROUPED_CLASSES = NO +INLINE_SIMPLE_STRUCTS = NO +TYPEDEF_HIDES_STRUCT = NO +LOOKUP_CACHE_SIZE = 0 +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- +EXTRACT_ALL = YES +EXTRACT_PRIVATE = NO +EXTRACT_PRIV_VIRTUAL = NO +EXTRACT_PACKAGE = NO +EXTRACT_STATIC = NO +EXTRACT_LOCAL_CLASSES = YES +EXTRACT_LOCAL_METHODS = NO +EXTRACT_ANON_NSPACES = NO +HIDE_UNDOC_MEMBERS = NO +HIDE_UNDOC_CLASSES = NO +HIDE_FRIEND_COMPOUNDS = NO +HIDE_IN_BODY_DOCS = NO +INTERNAL_DOCS = NO +CASE_SENSE_NAMES = NO +HIDE_SCOPE_NAMES = NO +HIDE_COMPOUND_REFERENCE= NO +SHOW_INCLUDE_FILES = YES +SHOW_GROUPED_MEMB_INC = NO +FORCE_LOCAL_INCLUDES = NO +INLINE_INFO = YES +SORT_MEMBER_DOCS = YES +SORT_BRIEF_DOCS = NO +SORT_MEMBERS_CTORS_1ST = NO +SORT_GROUP_NAMES = NO +SORT_BY_SCOPE_NAME = NO +STRICT_PROTO_MATCHING = NO +GENERATE_TODOLIST = NO +GENERATE_TESTLIST = YES +GENERATE_BUGLIST = NO +GENERATE_DEPRECATEDLIST= YES +ENABLED_SECTIONS = +MAX_INITIALIZER_LINES = 30 +SHOW_USED_FILES = YES +SHOW_FILES = YES +SHOW_NAMESPACES = YES +FILE_VERSION_FILTER = +LAYOUT_FILE = +CITE_BIB_FILES = +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- +QUIET = NO +WARNINGS = YES +WARN_IF_UNDOCUMENTED = YES +WARN_IF_DOC_ERROR = YES +WARN_NO_PARAMDOC = NO +WARN_AS_ERROR = NO +WARN_FORMAT = "$file:$line: $text" +WARN_LOGFILE = +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- +INPUT = doc/index.md +INPUT += wasmer.h +INPUT_ENCODING = UTF-8 +FILE_PATTERNS = *.h \ + *.hh +RECURSIVE = NO +EXCLUDE = doc +EXCLUDE_SYMLINKS = NO +EXCLUDE_PATTERNS = +EXCLUDE_SYMBOLS = +EXAMPLE_PATH = +EXAMPLE_PATTERNS = * +EXAMPLE_RECURSIVE = NO +IMAGE_PATH = +INPUT_FILTER = +FILTER_PATTERNS = +FILTER_SOURCE_FILES = NO +FILTER_SOURCE_PATTERNS = +USE_MDFILE_AS_MAINPAGE = doc/index.md +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- +SOURCE_BROWSER = YES +INLINE_SOURCES = NO +STRIP_CODE_COMMENTS = NO +REFERENCED_BY_RELATION = YES +REFERENCES_RELATION = YES +REFERENCES_LINK_SOURCE = YES +SOURCE_TOOLTIPS = YES +USE_HTAGS = NO +VERBATIM_HEADERS = YES +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- +ALPHABETICAL_INDEX = YES +COLS_IN_ALPHA_INDEX = 5 +IGNORE_PREFIX = +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- +GENERATE_HTML = YES +HTML_OUTPUT = html +HTML_FILE_EXTENSION = .html +HTML_HEADER = doc/theme/header.html +HTML_FOOTER = doc/theme/footer.html +HTML_STYLESHEET = +HTML_EXTRA_STYLESHEET = doc/theme/css/wasmer.css +HTML_EXTRA_FILES = +HTML_COLORSTYLE_HUE = 220 +HTML_COLORSTYLE_SAT = 100 +HTML_COLORSTYLE_GAMMA = 80 +HTML_TIMESTAMP = NO +HTML_DYNAMIC_MENUS = YES +HTML_DYNAMIC_SECTIONS = YES +HTML_INDEX_NUM_ENTRIES = 100 +GENERATE_DOCSET = NO +DOCSET_FEEDNAME = "Doxygen generated docs" +DOCSET_BUNDLE_ID = org.doxygen.Project +DOCSET_PUBLISHER_ID = org.doxygen.Publisher +DOCSET_PUBLISHER_NAME = Publisher +GENERATE_HTMLHELP = NO +CHM_FILE = +HHC_LOCATION = +GENERATE_CHI = NO +CHM_INDEX_ENCODING = +BINARY_TOC = NO +TOC_EXPAND = NO +GENERATE_QHP = NO +QCH_FILE = +QHP_NAMESPACE = org.doxygen.Project +QHP_VIRTUAL_FOLDER = doc +QHP_CUST_FILTER_NAME = +QHP_CUST_FILTER_ATTRS = +QHP_SECT_FILTER_ATTRS = +QHG_LOCATION = +GENERATE_ECLIPSEHELP = NO +ECLIPSE_DOC_ID = org.doxygen.Project +DISABLE_INDEX = YES +GENERATE_TREEVIEW = YES +ENUM_VALUES_PER_LINE = 4 +TREEVIEW_WIDTH = 250 +EXT_LINKS_IN_WINDOW = NO +FORMULA_FONTSIZE = 10 +FORMULA_TRANSPARENT = YES +FORMULA_MACROFILE = +USE_MATHJAX = NO +MATHJAX_FORMAT = HTML-CSS +MATHJAX_RELPATH = https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/ +MATHJAX_EXTENSIONS = +MATHJAX_CODEFILE = +SEARCHENGINE = NO +SERVER_BASED_SEARCH = NO +EXTERNAL_SEARCH = NO +SEARCHENGINE_URL = +SEARCHDATA_FILE = searchdata.xml +EXTERNAL_SEARCH_ID = +EXTRA_SEARCH_MAPPINGS = +#--------------------------------------------------------------------------- +# Configuration options related to the LaTeX output +#--------------------------------------------------------------------------- +GENERATE_LATEX = NO +LATEX_OUTPUT = latex +LATEX_CMD_NAME = +MAKEINDEX_CMD_NAME = makeindex +LATEX_MAKEINDEX_CMD = makeindex +COMPACT_LATEX = NO +PAPER_TYPE = a4 +EXTRA_PACKAGES = +LATEX_HEADER = +LATEX_FOOTER = +LATEX_EXTRA_STYLESHEET = +LATEX_EXTRA_FILES = +PDF_HYPERLINKS = YES +USE_PDFLATEX = YES +LATEX_BATCHMODE = NO +LATEX_HIDE_INDICES = NO +LATEX_SOURCE_CODE = NO +LATEX_BIB_STYLE = plain +LATEX_TIMESTAMP = NO +LATEX_EMOJI_DIRECTORY = +#--------------------------------------------------------------------------- +# Configuration options related to the RTF output +#--------------------------------------------------------------------------- +GENERATE_RTF = NO +RTF_OUTPUT = rtf +COMPACT_RTF = NO +RTF_HYPERLINKS = NO +RTF_STYLESHEET_FILE = +RTF_EXTENSIONS_FILE = +RTF_SOURCE_CODE = NO +#--------------------------------------------------------------------------- +# Configuration options related to the man page output +#--------------------------------------------------------------------------- +GENERATE_MAN = NO +MAN_OUTPUT = man +MAN_EXTENSION = .3 +MAN_SUBDIR = +MAN_LINKS = NO +#--------------------------------------------------------------------------- +# Configuration options related to the XML output +#--------------------------------------------------------------------------- +GENERATE_XML = NO +XML_OUTPUT = xml +XML_PROGRAMLISTING = YES +XML_NS_MEMB_FILE_SCOPE = NO +#--------------------------------------------------------------------------- +# Configuration options related to the DOCBOOK output +#--------------------------------------------------------------------------- +GENERATE_DOCBOOK = NO +DOCBOOK_OUTPUT = docbook +DOCBOOK_PROGRAMLISTING = NO +#--------------------------------------------------------------------------- +# Configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- +GENERATE_AUTOGEN_DEF = NO +#--------------------------------------------------------------------------- +# Configuration options related to the Perl module output +#--------------------------------------------------------------------------- +GENERATE_PERLMOD = NO +PERLMOD_LATEX = NO +PERLMOD_PRETTY = YES +PERLMOD_MAKEVAR_PREFIX = +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- +ENABLE_PREPROCESSING = YES +MACRO_EXPANSION = NO +EXPAND_ONLY_PREDEF = NO +SEARCH_INCLUDES = YES +INCLUDE_PATH = +INCLUDE_FILE_PATTERNS = +PREDEFINED = +EXPAND_AS_DEFINED = +SKIP_FUNCTION_MACROS = YES +#--------------------------------------------------------------------------- +# Configuration options related to external references +#--------------------------------------------------------------------------- +TAGFILES = +GENERATE_TAGFILE = +ALLEXTERNALS = NO +EXTERNAL_GROUPS = YES +EXTERNAL_PAGES = YES +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- +CLASS_DIAGRAMS = YES +DIA_PATH = +HIDE_UNDOC_RELATIONS = YES +HAVE_DOT = NO +DOT_NUM_THREADS = 0 +DOT_FONTNAME = Helvetica +DOT_FONTSIZE = 10 +DOT_FONTPATH = +CLASS_GRAPH = YES +COLLABORATION_GRAPH = YES +GROUP_GRAPHS = YES +UML_LOOK = NO +UML_LIMIT_NUM_FIELDS = 10 +TEMPLATE_RELATIONS = NO +INCLUDE_GRAPH = YES +INCLUDED_BY_GRAPH = YES +CALL_GRAPH = NO +CALLER_GRAPH = NO +GRAPHICAL_HIERARCHY = YES +DIRECTORY_GRAPH = YES +DOT_IMAGE_FORMAT = svg +INTERACTIVE_SVG = YES +DOT_PATH = ${DOXYGEN_DOT_PATH} +DOTFILE_DIRS = +MSCFILE_DIRS = +DIAFILE_DIRS = +PLANTUML_JAR_PATH = +PLANTUML_CFG_FILE = +PLANTUML_INCLUDE_PATH = +DOT_GRAPH_MAX_NODES = 50 +MAX_DOT_GRAPH_DEPTH = 0 +DOT_TRANSPARENT = NO +DOT_MULTI_TARGETS = NO +GENERATE_LEGEND = YES +DOT_CLEANUP = YES diff --git a/lib/c-api/src/error.rs b/lib/c-api/src/error.rs new file mode 100644 index 000000000..12e301979 --- /dev/null +++ b/lib/c-api/src/error.rs @@ -0,0 +1,113 @@ +//! Read runtime errors. + +use libc::{c_char, c_int}; +use std::{ + cell::RefCell, + error::Error, + fmt::{self, Display, Formatter}, + ptr, slice, +}; + +thread_local! { + static LAST_ERROR: RefCell>> = RefCell::new(None); +} + +pub fn update_last_error(err: E) { + LAST_ERROR.with(|prev| { + *prev.borrow_mut() = Some(Box::new(err)); + }); +} + +/// Retrieve the most recent error, clearing it in the process. +pub(crate) fn take_last_error() -> Option> { + LAST_ERROR.with(|prev| prev.borrow_mut().take()) +} + +/// Gets the length in bytes of the last error if any. +/// +/// This can be used to dynamically allocate a buffer with the correct number of +/// bytes needed to store a message. +/// +/// See `wasmer_last_error_message()` to get a full example. +#[no_mangle] +pub extern "C" fn wasmer_last_error_length() -> c_int { + LAST_ERROR.with(|prev| match *prev.borrow() { + Some(ref err) => err.to_string().len() as c_int + 1, + None => 0, + }) +} + +/// Gets the last error message if any into the provided buffer +/// `buffer` up to the given `length`. +/// +/// The `length` parameter must be large enough to store the last +/// error message. Ideally, the value should come from +/// `wasmer_last_error_length()`. +/// +/// The function returns the length of the string in bytes, `-1` if an +/// error occurs. Potential errors are: +/// +/// * The buffer is a null pointer, +/// * The buffer is too smal to hold the error message. +/// +/// Note: The error message always has a trailing null character. +/// +/// Example: +/// +/// ```c +/// int error_length = wasmer_last_error_length(); +/// +/// if (error_length > 0) { +/// char *error_message = malloc(error_length); +/// wasmer_last_error_message(error_message, error_length); +/// printf("Error message: `%s`\n", error_message); +/// } else { +/// printf("No error message\n"); +/// } +/// ``` +#[no_mangle] +pub unsafe extern "C" fn wasmer_last_error_message(buffer: *mut c_char, length: c_int) -> c_int { + if buffer.is_null() { + // buffer pointer is null + return -1; + } + + let error_message = match take_last_error() { + Some(err) => err.to_string(), + None => return 0, + }; + + let length = length as usize; + + if error_message.len() >= length { + // buffer is too small to hold the error message + return -1; + } + + let buffer = slice::from_raw_parts_mut(buffer as *mut u8, length); + + ptr::copy_nonoverlapping( + error_message.as_ptr(), + buffer.as_mut_ptr(), + error_message.len(), + ); + + // Add a trailing null so people using the string as a `char *` don't + // accidentally read into garbage. + buffer[error_message.len()] = 0; + + error_message.len() as c_int + 1 +} + +#[derive(Debug)] +pub struct CApiError { + pub msg: String, +} + +impl Display for CApiError { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "{}", &self.msg) + } +} + +impl Error for CApiError {} diff --git a/lib/c-api/src/export.rs b/lib/c-api/src/export.rs new file mode 100644 index 000000000..59190e4a3 --- /dev/null +++ b/lib/c-api/src/export.rs @@ -0,0 +1,573 @@ +//! Create, read, destroy export definitions (function, global, memory +//! and table) on an instance. + +use crate::{ + error::{update_last_error, CApiError}, + global::wasmer_global_t, + import::wasmer_import_func_t, + memory::wasmer_memory_t, + module::wasmer_module_t, + table::wasmer_table_t, + value::{wasmer_value, wasmer_value_t, wasmer_value_tag}, + wasmer_byte_array, wasmer_result_t, +}; +use libc::{c_int, c_uint}; +use std::ptr::{self, NonNull}; +use std::slice; +use wasmer::{ExportType, ExternType, Function, ImportType, Instance, Memory, Module, Val}; + +/// Intermediate representation of an `Export` instance that is +/// exposed to C. +pub(crate) struct NamedExport { + /// The export type and name. + pub(crate) export_type: ExportType, + + /// The instance that holds the export. + pub(crate) instance: *mut Instance, +} + +/// Opaque pointer to `ImportType`. +#[repr(C)] +#[derive(Clone)] +pub struct wasmer_export_t; + +/// Opaque pointer to `wasmer_export_t`. +#[repr(C)] +#[derive(Clone)] +pub struct wasmer_export_func_t; + +/// Intermediate representation of a vector of `NamedExport` that is +/// exposed to C. +pub(crate) struct NamedExports(pub Vec); + +/// Opaque pointer to the opaque structure `crate::NamedExports`, +/// which is a wrapper around a vector of the opaque structure +/// `crate::NamedExport`. +/// +/// Check the `wasmer_instance_exports()` function to learn more. +#[repr(C)] +#[derive(Clone)] +pub struct wasmer_exports_t; + +/// Intermediate representation of an export descriptor that is +/// exposed to C. +pub(crate) struct NamedExportDescriptor { + /// The export name. + name: String, + + /// The export kind. + kind: wasmer_import_export_kind, +} + +/// Opaque pointer to `NamedExportDescriptor`. +#[repr(C)] +#[derive(Clone)] +pub struct wasmer_export_descriptor_t; + +/// Intermediate representation of a vector of `NamedExportDescriptor` +/// that is exposed to C. +pub struct NamedExportDescriptors(Vec); + +/// Opaque pointer to `NamedExportDescriptors`. +#[repr(C)] +#[derive(Clone)] +pub struct wasmer_export_descriptors_t; + +/// Union of import/export value. +#[repr(C)] +#[derive(Clone, Copy)] +pub union wasmer_import_export_value { + pub func: *const wasmer_import_func_t, + pub table: *const wasmer_table_t, + pub memory: *const wasmer_memory_t, + pub global: *const wasmer_global_t, +} + +/// List of export/import kinds. +#[allow(non_camel_case_types)] +#[repr(u32)] +#[derive(Clone, PartialEq, Eq)] +// ================ +// ! DANGER ! +// ================ +// Do not modify these values without updating the `TryFrom` implementation below +pub enum wasmer_import_export_kind { + /// The export/import is a function. + WASM_FUNCTION = 0, + + /// The export/import is a global. + WASM_GLOBAL = 1, + + /// The export/import is a memory. + WASM_MEMORY = 2, + + /// The export/import is a table. + 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 { + 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 +/// +/// The caller owns the object and should call `wasmer_export_descriptors_destroy` to free it. +#[allow(clippy::cast_ptr_alignment)] +#[no_mangle] +pub unsafe extern "C" fn wasmer_export_descriptors( + module: *const wasmer_module_t, + export_descriptors: *mut *mut wasmer_export_descriptors_t, +) { + let module = &*(module as *const Module); + + let named_export_descriptors: Box = Box::new(NamedExportDescriptors( + module.exports().into_iter().map(|e| e.into()).collect(), + )); + *export_descriptors = + Box::into_raw(named_export_descriptors) as *mut wasmer_export_descriptors_t; +} + +/// Frees the memory for the given export descriptors +#[allow(clippy::cast_ptr_alignment)] +#[no_mangle] +pub extern "C" fn wasmer_export_descriptors_destroy( + export_descriptors: *mut wasmer_export_descriptors_t, +) { + if !export_descriptors.is_null() { + unsafe { Box::from_raw(export_descriptors as *mut NamedExportDescriptors) }; + } +} + +/// Gets the length of the export descriptors +#[allow(clippy::cast_ptr_alignment)] +#[no_mangle] +pub unsafe extern "C" fn wasmer_export_descriptors_len( + exports: *mut wasmer_export_descriptors_t, +) -> c_int { + if exports.is_null() { + return 0; + } + (*(exports as *mut NamedExportDescriptors)).0.len() as c_int +} + +/// Gets export descriptor by index +#[allow(clippy::cast_ptr_alignment)] +#[no_mangle] +pub unsafe extern "C" fn wasmer_export_descriptors_get( + export_descriptors: *mut wasmer_export_descriptors_t, + idx: c_int, +) -> *mut wasmer_export_descriptor_t { + if export_descriptors.is_null() { + return ptr::null_mut(); + } + let named_export_descriptors = &mut *(export_descriptors as *mut NamedExportDescriptors); + &mut (*named_export_descriptors).0[idx as usize] as *mut NamedExportDescriptor + as *mut wasmer_export_descriptor_t +} + +/// Gets name for the export descriptor +#[no_mangle] +#[allow(clippy::cast_ptr_alignment)] +pub unsafe extern "C" fn wasmer_export_descriptor_name( + export_descriptor: *mut wasmer_export_descriptor_t, +) -> wasmer_byte_array { + let named_export_descriptor = &*(export_descriptor as *mut NamedExportDescriptor); + wasmer_byte_array { + bytes: named_export_descriptor.name.as_ptr(), + bytes_len: named_export_descriptor.name.len() as u32, + } +} + +/// Gets export descriptor kind +#[no_mangle] +#[allow(clippy::cast_ptr_alignment)] +pub unsafe extern "C" fn wasmer_export_descriptor_kind( + export: *mut wasmer_export_descriptor_t, +) -> wasmer_import_export_kind { + let named_export_descriptor = &*(export as *mut NamedExportDescriptor); + named_export_descriptor.kind.clone() +} + +/// Frees the memory for the given exports. +/// +/// Check the `wasmer_instance_exports()` function to get a complete +/// example. +/// +/// If `exports` is a null pointer, this function does nothing. +/// +/// Example: +/// +/// ```c +/// // Get some exports. +/// wasmer_exports_t *exports = NULL; +/// wasmer_instance_exports(instance, &exports); +/// +/// // Destroy the exports. +/// wasmer_exports_destroy(exports); +/// ``` +#[allow(clippy::cast_ptr_alignment)] +#[no_mangle] +pub extern "C" fn wasmer_exports_destroy(exports: *mut wasmer_exports_t) { + if !exports.is_null() { + unsafe { Box::from_raw(exports as *mut NamedExports) }; + } +} + +/// Gets the length of the exports +#[allow(clippy::cast_ptr_alignment)] +#[no_mangle] +pub unsafe extern "C" fn wasmer_exports_len(exports: Option>) -> c_int { + if let Some(exports_inner) = exports { + exports_inner.cast::().as_ref().0.len() as c_int + } else { + 0 + } +} + +/// Gets wasmer_export by index +#[allow(clippy::cast_ptr_alignment)] +#[no_mangle] +pub unsafe extern "C" fn wasmer_exports_get( + exports: Option>, + idx: c_int, +) -> Option> { + let named_exports = &mut *(exports?.as_ptr() as *mut NamedExports); + Some(NonNull::new_unchecked( + &mut (*named_exports).0[idx as usize] as *mut NamedExport as *mut wasmer_export_t, + )) +} + +/// Gets wasmer_export kind +#[no_mangle] +#[allow(clippy::cast_ptr_alignment)] +pub unsafe extern "C" fn wasmer_export_kind( + export: *mut wasmer_export_t, +) -> wasmer_import_export_kind { + let named_export = &*(export as *mut NamedExport); + (&named_export.export_type).into() +} + +/// Sets the result parameter to the arity of the params of the wasmer_export_func_t +/// +/// Returns `wasmer_result_t::WASMER_OK` upon success. +/// +/// Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` +/// and `wasmer_last_error_message` to get an error message. +#[no_mangle] +#[allow(clippy::cast_ptr_alignment)] +pub unsafe extern "C" fn wasmer_export_func_params_arity( + func: *const wasmer_export_func_t, + result: *mut u32, +) -> wasmer_result_t { + let named_export = &*(func as *const NamedExport); + let export = &named_export.export_type.ty(); + if let ExternType::Function(ref signature) = *export { + *result = signature.params().len() as u32; + wasmer_result_t::WASMER_OK + } else { + update_last_error(CApiError { + msg: "func ptr error in wasmer_export_func_params_arity".to_string(), + }); + wasmer_result_t::WASMER_ERROR + } +} + +/// Sets the params buffer to the parameter types of the given wasmer_export_func_t +/// +/// Returns `wasmer_result_t::WASMER_OK` upon success. +/// +/// Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` +/// and `wasmer_last_error_message` to get an error message. +#[no_mangle] +#[allow(clippy::cast_ptr_alignment)] +pub unsafe extern "C" fn wasmer_export_func_params( + func: *const wasmer_export_func_t, + params: *mut wasmer_value_tag, + params_len: u32, +) -> wasmer_result_t { + let named_export = &*(func as *const NamedExport); + let export = &named_export.export_type.ty(); + if let ExternType::Function(ref signature) = *export { + let params: &mut [wasmer_value_tag] = + slice::from_raw_parts_mut(params, params_len as usize); + for (i, item) in signature.params().iter().enumerate() { + params[i] = item.into(); + } + wasmer_result_t::WASMER_OK + } else { + update_last_error(CApiError { + msg: "func ptr error in wasmer_export_func_params".to_string(), + }); + wasmer_result_t::WASMER_ERROR + } +} + +/// Sets the returns buffer to the parameter types of the given wasmer_export_func_t +/// +/// Returns `wasmer_result_t::WASMER_OK` upon success. +/// +/// Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` +/// and `wasmer_last_error_message` to get an error message. +#[no_mangle] +#[allow(clippy::cast_ptr_alignment)] +pub unsafe extern "C" fn wasmer_export_func_returns( + func: *const wasmer_export_func_t, + returns: *mut wasmer_value_tag, + returns_len: u32, +) -> wasmer_result_t { + let named_export = &*(func as *const NamedExport); + let export = &named_export.export_type.ty(); + if let ExternType::Function(ref signature) = *export { + let returns: &mut [wasmer_value_tag] = + slice::from_raw_parts_mut(returns, returns_len as usize); + for (i, item) in signature.results().iter().enumerate() { + returns[i] = item.into(); + } + wasmer_result_t::WASMER_OK + } else { + update_last_error(CApiError { + msg: "func ptr error in wasmer_export_func_returns".to_string(), + }); + wasmer_result_t::WASMER_ERROR + } +} + +/// Sets the result parameter to the arity of the returns of the wasmer_export_func_t +/// +/// Returns `wasmer_result_t::WASMER_OK` upon success. +/// +/// Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` +/// and `wasmer_last_error_message` to get an error message. +#[no_mangle] +#[allow(clippy::cast_ptr_alignment)] +pub unsafe extern "C" fn wasmer_export_func_returns_arity( + func: *const wasmer_export_func_t, + result: *mut u32, +) -> wasmer_result_t { + let named_export = &*(func as *const NamedExport); + let export = &named_export.export_type.ty(); + if let ExternType::Function(ref signature) = *export { + *result = signature.results().len() as u32; + wasmer_result_t::WASMER_OK + } else { + update_last_error(CApiError { + msg: "func ptr error in wasmer_export_func_results_arity".to_string(), + }); + wasmer_result_t::WASMER_ERROR + } +} + +/// Gets export func from export +#[no_mangle] +#[allow(clippy::cast_ptr_alignment)] +pub unsafe extern "C" fn wasmer_export_to_func( + export: *const wasmer_export_t, +) -> *const wasmer_export_func_t { + export as *const wasmer_export_func_t +} + +/// Gets a memory pointer from an export pointer. +/// +/// Returns `wasmer_result_t::WASMER_OK` upon success. +/// +/// Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` +/// and `wasmer_last_error_message` to get an error message. +#[no_mangle] +#[allow(clippy::cast_ptr_alignment)] +pub unsafe extern "C" fn wasmer_export_to_memory( + export: *const wasmer_export_t, + memory: *mut *mut wasmer_memory_t, +) -> wasmer_result_t { + let named_export = &*(export as *const NamedExport); + let instance = &*named_export.instance; + + if let Ok(exported_memory) = instance + .exports + .get::(&named_export.export_type.name()) + { + let mem = Box::new(exported_memory.clone()); + *memory = Box::into_raw(mem) as *mut wasmer_memory_t; + wasmer_result_t::WASMER_OK + } else { + update_last_error(CApiError { + msg: "cannot cast the `wasmer_export_t` pointer to a `wasmer_memory_t` \ + pointer because it does not represent a memory export." + .to_string(), + }); + wasmer_result_t::WASMER_ERROR + } +} + +/// Gets name from wasmer_export +#[no_mangle] +#[allow(clippy::cast_ptr_alignment)] +pub unsafe extern "C" fn wasmer_export_name(export: *mut wasmer_export_t) -> wasmer_byte_array { + let named_export = &*(export as *mut NamedExport); + wasmer_byte_array { + bytes: named_export.export_type.name().as_ptr(), + bytes_len: named_export.export_type.name().len() as u32, + } +} + +/// Calls a `func` with the provided parameters. +/// Results are set using the provided `results` pointer. +/// +/// Returns `wasmer_result_t::WASMER_OK` upon success. +/// +/// Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` +/// and `wasmer_last_error_message` to get an error message. +#[allow(clippy::cast_ptr_alignment)] +#[no_mangle] +pub unsafe extern "C" fn wasmer_export_func_call( + func: *const wasmer_export_func_t, + params: *const wasmer_value_t, + params_len: c_uint, + results: *mut wasmer_value_t, + results_len: c_uint, +) -> wasmer_result_t { + if func.is_null() { + update_last_error(CApiError { + msg: "func ptr is null".to_string(), + }); + return wasmer_result_t::WASMER_ERROR; + } + + if params_len > 0 && params.is_null() { + update_last_error(CApiError { + msg: "params ptr is null".to_string(), + }); + return wasmer_result_t::WASMER_ERROR; + } + + let params: Vec = { + if params_len == 0 { + vec![] + } else { + slice::from_raw_parts::(params, params_len as usize) + .iter() + .cloned() + .map(|x| x.into()) + .collect() + } + }; + + let named_export = &*(func as *mut NamedExport); + + let results: &mut [wasmer_value_t] = slice::from_raw_parts_mut(results, results_len as usize); + + let instance = &*named_export.instance; + let f: &Function = match instance.exports.get(&named_export.export_type.name()) { + Ok(f) => f, + Err(err) => { + update_last_error(err); + return wasmer_result_t::WASMER_ERROR; + } + }; + + let result = f.call(¶ms[..]); + + match result { + Ok(results_vec) => { + if !results_vec.is_empty() { + let ret = match results_vec[0] { + Val::I32(x) => wasmer_value_t { + tag: wasmer_value_tag::WASM_I32, + value: wasmer_value { I32: x }, + }, + Val::I64(x) => wasmer_value_t { + tag: wasmer_value_tag::WASM_I64, + value: wasmer_value { I64: x }, + }, + Val::F32(x) => wasmer_value_t { + tag: wasmer_value_tag::WASM_F32, + value: wasmer_value { F32: x }, + }, + Val::F64(x) => wasmer_value_t { + tag: wasmer_value_tag::WASM_F64, + value: wasmer_value { F64: x }, + }, + Val::V128(_) => unimplemented!("returning V128 type"), + Val::AnyRef(_) => unimplemented!("returning AnyRef type"), + Val::FuncRef(_) => unimplemented!("returning FuncRef type"), + }; + results[0] = ret; + } + wasmer_result_t::WASMER_OK + } + Err(err) => { + update_last_error(err); + wasmer_result_t::WASMER_ERROR + } + } +} + +impl From for NamedExportDescriptor { + fn from(et: ExportType) -> Self { + NamedExportDescriptor { + name: et.name().to_string(), + kind: et.into(), + } + } +} + +impl From<&ImportType> for wasmer_import_export_kind { + fn from(it: &ImportType) -> Self { + it.ty().into() + } +} + +impl From for wasmer_import_export_kind { + fn from(it: ImportType) -> Self { + (&it).into() + } +} + +impl From<&ExportType> for wasmer_import_export_kind { + fn from(et: &ExportType) -> Self { + et.ty().into() + } +} + +impl From for wasmer_import_export_kind { + fn from(et: ExportType) -> Self { + (&et).into() + } +} + +impl From<&ExternType> for wasmer_import_export_kind { + fn from(et: &ExternType) -> Self { + match et { + ExternType::Memory(_) => wasmer_import_export_kind::WASM_MEMORY, + ExternType::Global(_) => wasmer_import_export_kind::WASM_GLOBAL, + ExternType::Table(_) => wasmer_import_export_kind::WASM_TABLE, + ExternType::Function(_) => wasmer_import_export_kind::WASM_FUNCTION, + } + } +} + +impl From for wasmer_import_export_kind { + fn from(et: ExternType) -> Self { + (&et).into() + } +} diff --git a/lib/c-api/src/global.rs b/lib/c-api/src/global.rs new file mode 100644 index 000000000..ebdcfd195 --- /dev/null +++ b/lib/c-api/src/global.rs @@ -0,0 +1,71 @@ +//! Create, set, get and destroy global variables of an instance. + +use crate::value::{wasmer_value_t, wasmer_value_tag}; +use wasmer::Global; + +#[repr(C)] +#[derive(Clone)] +pub struct wasmer_global_descriptor_t { + mutable: bool, + kind: wasmer_value_tag, +} + +#[repr(C)] +#[derive(Clone)] +pub struct wasmer_global_t; + +/// Creates a new Global and returns a pointer to it. +/// The caller owns the object and should call `wasmer_global_destroy` to free it. +#[no_mangle] +pub unsafe extern "C" fn wasmer_global_new( + value: wasmer_value_t, + mutable: bool, +) -> *mut wasmer_global_t { + let store = crate::get_global_store(); + let global = if mutable { + Global::new_mut(store, value.into()) + } else { + Global::new(store, value.into()) + }; + Box::into_raw(Box::new(global)) as *mut wasmer_global_t +} + +/// Gets the value stored by the given Global +#[allow(clippy::cast_ptr_alignment)] +#[no_mangle] +pub extern "C" fn wasmer_global_get(global: *mut wasmer_global_t) -> wasmer_value_t { + let global = unsafe { &*(global as *mut Global) }; + let value: wasmer_value_t = global.get().into(); + value +} + +/// Sets the value stored by the given Global +#[allow(clippy::cast_ptr_alignment)] +#[no_mangle] +pub extern "C" fn wasmer_global_set(global: *mut wasmer_global_t, value: wasmer_value_t) { + let global = unsafe { &*(global as *mut Global) }; + global.set(value.into()); +} + +/// Returns a descriptor (type, mutability) of the given Global +#[allow(clippy::cast_ptr_alignment)] +#[no_mangle] +pub extern "C" fn wasmer_global_get_descriptor( + global: *mut wasmer_global_t, +) -> wasmer_global_descriptor_t { + let global = unsafe { &*(global as *mut Global) }; + let descriptor = global.ty(); + wasmer_global_descriptor_t { + mutable: descriptor.mutability.into(), + kind: descriptor.ty.into(), + } +} + +/// Frees memory for the given Global +#[allow(clippy::cast_ptr_alignment)] +#[no_mangle] +pub extern "C" fn wasmer_global_destroy(global: *mut wasmer_global_t) { + if !global.is_null() { + unsafe { Box::from_raw(global as *mut Global) }; + } +} diff --git a/lib/c-api/src/import/emscripten.rs b/lib/c-api/src/import/emscripten.rs new file mode 100644 index 000000000..35599bec4 --- /dev/null +++ b/lib/c-api/src/import/emscripten.rs @@ -0,0 +1,145 @@ +//! Functions and types for dealing with Emscripten imports + +use super::*; +use crate::{get_slice_checked, instance::wasmer_instance_t, module::wasmer_module_t}; + +use std::ptr; +use wasmer::wasm::{Instance, Module}; +use wasmer_emscripten::{EmscriptenData, EmscriptenGlobals}; + +/// Type used to construct an import_object_t with Emscripten imports. +#[repr(C)] +pub struct wasmer_emscripten_globals_t; + +/// Create a `wasmer_emscripten_globals_t` from a Wasm module. +#[no_mangle] +pub unsafe extern "C" fn wasmer_emscripten_get_globals( + module: *const wasmer_module_t, +) -> *mut wasmer_emscripten_globals_t { + if module.is_null() { + return ptr::null_mut(); + } + let module = &*(module as *const Module); + match EmscriptenGlobals::new(module) { + Ok(globals) => Box::into_raw(Box::new(globals)) as *mut wasmer_emscripten_globals_t, + Err(msg) => { + update_last_error(CApiError { msg }); + return ptr::null_mut(); + } + } +} + +/// Destroy `wasmer_emscrpten_globals_t` created by +/// `wasmer_emscripten_get_emscripten_globals`. +#[no_mangle] +pub unsafe extern "C" fn wasmer_emscripten_destroy_globals( + globals: *mut wasmer_emscripten_globals_t, +) { + if globals.is_null() { + return; + } + let _ = Box::from_raw(globals); +} + +/// Execute global constructors (required if the module is compiled from C++) +/// and sets up the internal environment. +/// +/// This function sets the data pointer in the same way that +/// [`wasmer_instance_context_data_set`] does. +#[no_mangle] +pub unsafe extern "C" fn wasmer_emscripten_set_up( + instance: *mut wasmer_instance_t, + globals: *mut wasmer_emscripten_globals_t, +) -> wasmer_result_t { + if globals.is_null() || instance.is_null() { + return wasmer_result_t::WASMER_ERROR; + } + let instance = &mut *(instance as *mut Instance); + let globals = &*(globals as *mut EmscriptenGlobals); + let em_data = Box::into_raw(Box::new(EmscriptenData::new( + instance, + &globals.data, + Default::default(), + ))) as *mut c_void; + instance.context_mut().data = em_data; + + match wasmer_emscripten::set_up_emscripten(instance) { + Ok(_) => wasmer_result_t::WASMER_OK, + Err(e) => { + update_last_error(e); + wasmer_result_t::WASMER_ERROR + } + } +} + +/// Convenience function for setting up arguments and calling the Emscripten +/// main function. +/// +/// WARNING: +/// +/// Do not call this function on untrusted code when operating without +/// additional sandboxing in place. +/// Emscripten has access to many host system calls and therefore may do very +/// bad things. +#[no_mangle] +pub unsafe extern "C" fn wasmer_emscripten_call_main( + instance: *mut wasmer_instance_t, + args: *const wasmer_byte_array, + args_len: c_uint, +) -> wasmer_result_t { + if instance.is_null() || args.is_null() { + return wasmer_result_t::WASMER_ERROR; + } + let instance = &mut *(instance as *mut Instance); + + let arg_list = get_slice_checked(args, args_len as usize); + let arg_process_result: Result, _> = + arg_list.iter().map(|arg| arg.as_str()).collect(); + let arg_vec = match arg_process_result.as_ref() { + Ok(arg_vec) => arg_vec, + Err(err) => { + update_last_error(*err); + return wasmer_result_t::WASMER_ERROR; + } + }; + + let prog_name = if let Some(prog_name) = arg_vec.first() { + prog_name + } else { + update_last_error(CApiError { + msg: "First argument (program name) is required to execute Emscripten's main function" + .to_string(), + }); + return wasmer_result_t::WASMER_ERROR; + }; + + match wasmer_emscripten::emscripten_call_main(instance, prog_name, &arg_vec[1..]) { + Ok(_) => wasmer_result_t::WASMER_OK, + Err(e) => { + update_last_error(e); + wasmer_result_t::WASMER_ERROR + } + } +} + +/// Create a `wasmer_import_object_t` with Emscripten imports, use +/// `wasmer_emscripten_get_emscripten_globals` to get a +/// `wasmer_emscripten_globals_t` from a `wasmer_module_t`. +/// +/// WARNING: +/// +/// This `import_object_t` contains thin-wrappers around host system calls. +/// Do not use this to execute untrusted code without additional sandboxing. +#[no_mangle] +pub unsafe extern "C" fn wasmer_emscripten_generate_import_object( + globals: *mut wasmer_emscripten_globals_t, +) -> *mut wasmer_import_object_t { + if globals.is_null() { + return ptr::null_mut(); + } + // TODO: figure out if we should be using UnsafeCell here or something + let g = &mut *(globals as *mut EmscriptenGlobals); + let import_object = Box::new(wasmer_emscripten::generate_emscripten_env(g)); + + Box::into_raw(import_object) as *mut wasmer_import_object_t +} diff --git a/lib/c-api/src/import/mod.rs b/lib/c-api/src/import/mod.rs new file mode 100644 index 000000000..c6aa9229d --- /dev/null +++ b/lib/c-api/src/import/mod.rs @@ -0,0 +1,769 @@ +//! Create, read, destroy import definitions (function, global, memory +//! and table) on an instance. + +use crate::{ + error::{update_last_error, CApiError}, + export::{wasmer_import_export_kind, wasmer_import_export_value}, + instance::wasmer_instance_context_t, + module::wasmer_module_t, + value::wasmer_value_tag, + wasmer_byte_array, wasmer_result_t, +}; +use libc::c_uint; +use std::{ + //convert::TryFrom, + ffi::c_void, + os::raw::c_char, + ptr, + slice, + //sync::Arc, +}; +use wasmer::{ + Function, Global, ImportObject, ImportObjectIterator, ImportType, Memory, Module, Table, +}; +//use wasmer::wasm::{Export, FuncSig, Global, Memory, Module, Table, Type}; +/*use wasmer_runtime_core::{ + export::{Context, FuncPointer}, + module::ImportName, +};*/ + +#[repr(C)] +pub struct wasmer_import_t { + pub module_name: wasmer_byte_array, + pub import_name: wasmer_byte_array, + pub tag: wasmer_import_export_kind, + pub value: wasmer_import_export_value, +} + +#[repr(C)] +pub struct wasmer_import_object_t; + +#[repr(C)] +#[derive(Clone)] +pub struct wasmer_import_func_t; + +#[repr(C)] +#[derive(Clone)] +pub struct wasmer_import_descriptor_t; + +#[repr(C)] +#[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)] +#[no_mangle] +pub unsafe extern "C" fn wasmer_import_object_new() -> *mut wasmer_import_object_t { + let import_object = Box::new(ImportObject::new()); + + Box::into_raw(import_object) as *mut wasmer_import_object_t +} + +#[cfg(feature = "wasi")] +mod wasi; + +#[cfg(feature = "wasi")] +pub use self::wasi::*; + +#[cfg(feature = "emscripten")] +mod emscripten; + +#[cfg(feature = "emscripten")] +pub use self::emscripten::*; + +/// 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 { + todo!("Disabled until ImportObject APIs are updated") + /* + 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.get_export(namespace_str, name_str) { + match export { + Extern::Function(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 Function; + *writer = function.clone(); + } + Extern::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(); + } + Extern::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(); + } + Extern::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!("Extern {} {} 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::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 { + todo!("Disabled until ImportObject APIs are updated") + /* + 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 Extern::Function(_) = e { + true + } else { + false + } + })) as Box::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 { + todo!("Disabled until ImportObject APIs are updated") + /* + 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 { + Extern::Function(function) => { + let func = Box::new(function.clone()); + + out.tag = wasmer_import_export_kind::WASM_FUNCTION; + out.value = wasmer_import_export_value { + func: Box::into_raw(func) as *mut _ as *const _, + }; + } + Extern::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 _, + }; + } + Extern::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 _, + }; + } + Extern::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 = 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] +pub unsafe extern "C" fn wasmer_import_object_extend( + import_object: *mut wasmer_import_object_t, + imports: *const wasmer_import_t, + imports_len: c_uint, +) -> wasmer_result_t { + todo!("Disabled until import object APIs change") + /* + let import_object: &mut ImportObject = &mut *(import_object as *mut ImportObject); + + let mut extensions: Vec<((String, String), Export)> = Vec::new(); + + let imports: &[wasmer_import_t] = slice::from_raw_parts(imports, imports_len as usize); + for import in imports { + let module_name = slice::from_raw_parts( + import.module_name.bytes, + import.module_name.bytes_len as usize, + ); + let module_name = if let Ok(s) = std::str::from_utf8(module_name) { + s + } else { + update_last_error(CApiError { + msg: "error converting module name to string".to_string(), + }); + return wasmer_result_t::WASMER_ERROR; + }; + let import_name = slice::from_raw_parts( + import.import_name.bytes, + import.import_name.bytes_len as usize, + ); + let import_name = if let Ok(s) = std::str::from_utf8(import_name) { + s + } else { + update_last_error(CApiError { + msg: "error converting import_name to string".to_string(), + }); + return wasmer_result_t::WASMER_ERROR; + }; + + let export = match import.tag { + wasmer_import_export_kind::WASM_MEMORY => { + let mem = import.value.memory as *mut Memory; + Extern::Memory((&*mem).clone()) + } + wasmer_import_export_kind::WASM_FUNCTION => { + let func_export = import.value.func as *mut Function; + Extern::Function((&*func_export).clone()) + } + wasmer_import_export_kind::WASM_GLOBAL => { + let global = import.value.global as *mut Global; + Extern::Global((&*global).clone()) + } + wasmer_import_export_kind::WASM_TABLE => { + let table = import.value.table as *mut Table; + Extern::Table((&*table).clone()) + } + }; + + let extension = ((module_name.to_string(), import_name.to_string()), export); + extensions.push(extension) + } + + import_object.extend(extensions); + + return wasmer_result_t::WASMER_OK; + */ +} + +/// Gets import descriptors for the given module +/// +/// The caller owns the object and should call `wasmer_import_descriptors_destroy` to free it. +#[allow(clippy::cast_ptr_alignment)] +#[no_mangle] +pub unsafe extern "C" fn wasmer_import_descriptors( + module: *const wasmer_module_t, + import_descriptors: *mut *mut wasmer_import_descriptors_t, +) { + if module.is_null() { + return; + } + let module = &*(module as *const Module); + let descriptors = module.imports().collect::>(); + + let named_import_descriptors: Box = + Box::new(NamedImportDescriptors(descriptors)); + *import_descriptors = + Box::into_raw(named_import_descriptors) as *mut wasmer_import_descriptors_t; +} + +pub struct NamedImportDescriptors(Vec); + +/// Frees the memory for the given import descriptors +#[allow(clippy::cast_ptr_alignment)] +#[no_mangle] +pub extern "C" fn wasmer_import_descriptors_destroy( + import_descriptors: *mut wasmer_import_descriptors_t, +) { + if !import_descriptors.is_null() { + unsafe { Box::from_raw(import_descriptors as *mut NamedImportDescriptors) }; + } +} + +/// Gets the length of the import descriptors +#[allow(clippy::cast_ptr_alignment)] +#[no_mangle] +pub unsafe extern "C" fn wasmer_import_descriptors_len( + exports: *mut wasmer_import_descriptors_t, +) -> c_uint { + if exports.is_null() { + return 0; + } + (*(exports as *mut NamedImportDescriptors)).0.len() as c_uint +} + +/// Gets import descriptor by index +#[allow(clippy::cast_ptr_alignment)] +#[no_mangle] +pub unsafe extern "C" fn wasmer_import_descriptors_get( + import_descriptors: *mut wasmer_import_descriptors_t, + idx: c_uint, +) -> *mut wasmer_import_descriptor_t { + if import_descriptors.is_null() { + return ptr::null_mut(); + } + let named_import_descriptors = &mut *(import_descriptors as *mut NamedImportDescriptors); + &mut (*named_import_descriptors).0[idx as usize] as *mut ImportType + as *mut wasmer_import_descriptor_t +} + +/// Gets name for the import descriptor +#[no_mangle] +#[allow(clippy::cast_ptr_alignment)] +pub unsafe extern "C" fn wasmer_import_descriptor_name( + import_descriptor: *mut wasmer_import_descriptor_t, +) -> wasmer_byte_array { + let named_import_descriptor = &*(import_descriptor as *mut ImportType); + wasmer_byte_array { + bytes: named_import_descriptor.name().as_ptr(), + bytes_len: named_import_descriptor.name().len() as u32, + } +} + +/// Gets module name for the import descriptor +#[no_mangle] +#[allow(clippy::cast_ptr_alignment)] +pub unsafe extern "C" fn wasmer_import_descriptor_module_name( + import_descriptor: *mut wasmer_import_descriptor_t, +) -> wasmer_byte_array { + let named_import_descriptor = &*(import_descriptor as *mut ImportType); + wasmer_byte_array { + bytes: named_import_descriptor.module().as_ptr(), + bytes_len: named_import_descriptor.module().len() as u32, + } +} + +/// Gets export descriptor kind +#[no_mangle] +#[allow(clippy::cast_ptr_alignment)] +pub unsafe extern "C" fn wasmer_import_descriptor_kind( + export: *mut wasmer_import_descriptor_t, +) -> wasmer_import_export_kind { + let named_import_descriptor = &*(export as *mut ImportType); + named_import_descriptor.ty().into() +} + +/// Sets the result parameter to the arity of the params of the wasmer_import_func_t +/// +/// Returns `wasmer_result_t::WASMER_OK` upon success. +/// +/// Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` +/// and `wasmer_last_error_message` to get an error message. +#[no_mangle] +#[allow(clippy::cast_ptr_alignment)] +pub unsafe extern "C" fn wasmer_import_func_params_arity( + func: *const wasmer_import_func_t, + result: *mut u32, +) -> wasmer_result_t { + todo!("Figure out how to get a usable siganture from an Function") + /*let function = &*(func as *const Function); + if let Extren::Function(function) = *export { + *result = signature.params().len() as u32; + wasmer_result_t::WASMER_OK + } else { + update_last_error(CApiError { + msg: "func ptr error in wasmer_import_func_params_arity".to_string(), + }); + wasmer_result_t::WASMER_ERROR + } + */ +} + +/// Creates new host function, aka imported function. `func` is a +/// function pointer, where the first argument is the famous `vm::Ctx` +/// (in Rust), or `wasmer_instance_context_t` (in C). All arguments +/// must be typed with compatible WebAssembly native types: +/// +/// | WebAssembly type | C/C++ type | +/// | ---------------- | ---------- | +/// | `i32` | `int32_t` | +/// | `i64` | `int64_t` | +/// | `f32` | `float` | +/// | `f64` | `double` | +/// +/// The function pointer must have a lifetime greater than the +/// WebAssembly instance lifetime. +/// +/// The caller owns the object and should call +/// `wasmer_import_func_destroy` to free it. +#[no_mangle] +#[allow(clippy::cast_ptr_alignment)] +pub unsafe extern "C" fn wasmer_import_func_new( + func: extern "C" fn(data: *mut c_void), + params: *const wasmer_value_tag, + params_len: c_uint, + returns: *const wasmer_value_tag, + returns_len: c_uint, +) -> *mut wasmer_import_func_t { + unimplemented!("`wasmer_import_func_new` cannot be implemented yet") + /* + let params: &[wasmer_value_tag] = slice::from_raw_parts(params, params_len as usize); + let params: Vec = params.iter().cloned().map(|x| x.into()).collect(); + let returns: &[wasmer_value_tag] = slice::from_raw_parts(returns, returns_len as usize); + let returns: Vec = returns.iter().cloned().map(|x| x.into()).collect(); + + let export = Box::new(Extern::Function { + func: FuncPointer::new(func as _), + ctx: Context::Internal, + signature: Arc::new(FuncSig::new(params, returns)), + }); + Box::into_raw(export) as *mut wasmer_import_func_t + */ +} + +/// Stop the execution of a host function, aka imported function. The +/// function must be used _only_ inside a host function. +/// +/// The pointer to `wasmer_instance_context_t` is received by the host +/// function as its first argument. Just passing it to `ctx` is fine. +/// +/// The error message must have a greater lifetime than the host +/// function itself since the error is read outside the host function +/// with `wasmer_last_error_message`. +/// +/// This function returns `wasmer_result_t::WASMER_ERROR` if `ctx` or +/// `error_message` are null. +/// +/// This function never returns otherwise. +#[no_mangle] +#[allow(clippy::cast_ptr_alignment)] +pub unsafe extern "C" fn wasmer_trap( + ctx: *const wasmer_instance_context_t, + error_message: *const c_char, +) -> wasmer_result_t { + todo!("wasmer_trap: manually trap without Ctx") + /* + if ctx.is_null() { + update_last_error(CApiError { + msg: "ctx ptr is null in wasmer_trap".to_string(), + }); + + return wasmer_result_t::WASMER_ERROR; + } + + if error_message.is_null() { + update_last_error(CApiError { + msg: "error_message is null in wasmer_trap".to_string(), + }); + + return wasmer_result_t::WASMER_ERROR; + } + + let ctx = &*(ctx as *const Ctx); + let error_message = CStr::from_ptr(error_message).to_str().unwrap(); + + (&*ctx.module) + .runnable_module + .do_early_trap(Box::new(error_message)); // never returns + + // cbindgen does not generate a binding for a function that + // returns `!`. Since we also need to error in some cases, the + // output type of `wasmer_trap` is `wasmer_result_t`. But the OK + // case is not reachable because `do_early_trap` never + // returns. That's a compromise to satisfy the Rust type system, + // cbindgen, and get an acceptable clean code. + #[allow(unreachable_code)] + wasmer_result_t::WASMER_OK + */ +} + +/// Sets the params buffer to the parameter types of the given wasmer_import_func_t +/// +/// Returns `wasmer_result_t::WASMER_OK` upon success. +/// +/// Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` +/// and `wasmer_last_error_message` to get an error message. +#[no_mangle] +#[allow(clippy::cast_ptr_alignment)] +pub unsafe extern "C" fn wasmer_import_func_params( + func: *const wasmer_import_func_t, + params: *mut wasmer_value_tag, + params_len: c_uint, +) -> wasmer_result_t { + todo!("Figure out how to get a usable signature from an `Function`") + /* + let function = &*(func as *const Function); + if let Extern::Function(function) = *export { + let params: &mut [wasmer_value_tag] = + slice::from_raw_parts_mut(params, params_len as usize); + for (i, item) in signature.params().iter().enumerate() { + params[i] = item.into(); + } + wasmer_result_t::WASMER_OK + } else { + update_last_error(CApiError { + msg: "func ptr error in wasmer_import_func_params".to_string(), + }); + wasmer_result_t::WASMER_ERROR + } + */ +} + +/// Sets the returns buffer to the parameter types of the given wasmer_import_func_t +/// +/// Returns `wasmer_result_t::WASMER_OK` upon success. +/// +/// Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` +/// and `wasmer_last_error_message` to get an error message. +#[no_mangle] +#[allow(clippy::cast_ptr_alignment)] +pub unsafe extern "C" fn wasmer_import_func_returns( + func: *const wasmer_import_func_t, + returns: *mut wasmer_value_tag, + returns_len: c_uint, +) -> wasmer_result_t { + todo!("Figure out how to get a usable signature from an `Function`") + /* + let function = &*(func as *const Function); + let returns: &mut [wasmer_value_tag] = + slice::from_raw_parts_mut(returns, returns_len as usize); + for (i, item) in signature.returns().iter().enumerate() { + returns[i] = item.into(); + } + wasmer_result_t::WASMER_OK + */ +} + +/// Sets the result parameter to the arity of the returns of the wasmer_import_func_t +/// +/// Returns `wasmer_result_t::WASMER_OK` upon success. +/// +/// Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` +/// and `wasmer_last_error_message` to get an error message. +#[no_mangle] +#[allow(clippy::cast_ptr_alignment)] +pub unsafe extern "C" fn wasmer_import_func_returns_arity( + func: *const wasmer_import_func_t, + result: *mut u32, +) -> wasmer_result_t { + todo!("Figure out how to get a usable signature from an `Function`") + /* + let function = &*(func as *const Function); + *result = signature.returns().len() as u32; + wasmer_result_t::WASMER_OK + */ +} + +/// Frees memory for the given Func +#[allow(clippy::cast_ptr_alignment)] +#[no_mangle] +pub extern "C" fn wasmer_import_func_destroy(func: *mut wasmer_import_func_t) { + if !func.is_null() { + unsafe { Box::from_raw(func as *mut Function) }; + } +} + +/// Frees memory of the given ImportObject +#[no_mangle] +pub extern "C" fn wasmer_import_object_destroy(import_object: *mut wasmer_import_object_t) { + if !import_object.is_null() { + unsafe { Box::from_raw(import_object as *mut ImportObject) }; + } +} diff --git a/lib/c-api/src/import/wasi.rs b/lib/c-api/src/import/wasi.rs new file mode 100644 index 000000000..5aca465d4 --- /dev/null +++ b/lib/c-api/src/import/wasi.rs @@ -0,0 +1,239 @@ +use super::*; +use crate::get_slice_checked; +use libc::c_uchar; +use std::{path::PathBuf, ptr, str}; +use wasmer::{Memory, MemoryType}; +use wasmer_wasi as wasi; + +#[derive(Debug, PartialEq)] +#[repr(u8)] +pub enum Version { + /// Version cannot be detected or is unknown. + Unknown = 0, + + /// Latest version. See `wasmer_wasi::WasiVersion::Latest` to + /// learn more. + Latest = 1, + + /// `wasi_unstable`. + Snapshot0 = 2, + + /// `wasi_snapshot_preview1`. + Snapshot1 = 3, +} + +impl From for Version { + fn from(value: c_uchar) -> Self { + match value { + 1 => Self::Latest, + 2 => Self::Snapshot0, + 3 => Self::Snapshot1, + _ => Self::Unknown, + } + } +} + +/// Opens a directory that's visible to the WASI module as `alias` but +/// is backed by the host file at `host_file_path` +#[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), str::Utf8Error> { + let alias = self.alias.as_str()?.to_owned(); + let host_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 { + todo!("wasmer_wasi_generate_import_object: blocked on global store") + /* + let arg_list = get_slice_checked(args, args_len as usize); + let env_list = get_slice_checked(envs, envs_len as usize); + let preopened_file_list = get_slice_checked(preopened_files, preopened_files_len as usize); + let mapped_dir_list = get_slice_checked(mapped_dirs, mapped_dirs_len as usize); + + wasmer_wasi_generate_import_object_inner( + Version::Latest, + arg_list, + env_list, + preopened_file_list, + mapped_dir_list, + ) + .unwrap_or(ptr::null_mut()) + */ +} + +/// Creates a WASI import object for a specific version. +/// +/// This function is similar to `wasmer_wasi_generate_import_object` +/// except that the first argument describes the WASI version. +/// +/// The version is expected to be of kind `Version`. +#[no_mangle] +pub unsafe extern "C" fn wasmer_wasi_generate_import_object_for_version( + version: c_uchar, + args: *const wasmer_byte_array, + args_len: c_uint, + envs: *const wasmer_byte_array, + envs_len: c_uint, + preopened_files: *const wasmer_byte_array, + preopened_files_len: c_uint, + mapped_dirs: *const wasmer_wasi_map_dir_entry_t, + mapped_dirs_len: c_uint, +) -> *mut wasmer_import_object_t { + let arg_list = get_slice_checked(args, args_len as usize); + let env_list = get_slice_checked(envs, envs_len as usize); + let preopened_file_list = get_slice_checked(preopened_files, preopened_files_len as usize); + let mapped_dir_list = get_slice_checked(mapped_dirs, mapped_dirs_len as usize); + + wasmer_wasi_generate_import_object_inner( + version.into(), + arg_list, + env_list, + preopened_file_list, + mapped_dir_list, + ) + .unwrap_or(ptr::null_mut()) +} + +/// Find the version of WASI used by the module. +/// +/// In case of error, the returned version is `Version::Unknown`. +#[no_mangle] +pub unsafe extern "C" fn wasmer_wasi_get_version(module: *const wasmer_module_t) -> Version { + if module.is_null() { + return Version::Unknown; + } + + let module = &*(module as *const Module); + + match wasi::get_wasi_version(module, false) { + Some(version) => match version { + wasi::WasiVersion::Snapshot0 => Version::Snapshot0, + wasi::WasiVersion::Snapshot1 => Version::Snapshot1, + wasi::WasiVersion::Latest => Version::Latest, + }, + None => Version::Unknown, + } +} + +/// Inner function that wraps error handling +fn wasmer_wasi_generate_import_object_inner( + version: Version, + arg_list: &[wasmer_byte_array], + env_list: &[wasmer_byte_array], + preopened_file_list: &[wasmer_byte_array], + mapped_dir_list: &[wasmer_wasi_map_dir_entry_t], +) -> Result<*mut wasmer_import_object_t, str::Utf8Error> { + todo!("Arg and env parsing need to be done here; this logic already exists and it's important to get it right, so we should probably reorganize code to make it easier to do the right thing.") + /* + let arg_vec: Vec<_> = arg_list.iter().map(|arg| unsafe { arg.as_vec() }).collect(); + let env_vec: 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 version = match version { + Version::Latest => wasi::WasiVersion::Latest, + Version::Snapshot0 => wasi::WasiVersion::Snapshot0, + Version::Snapshot1 => wasi::WasiVersion::Snapshot1, + _ => panic!("Version {:?} is invalid.", version), + }; + + let store = crate::get_global_store(); + + let mut wasi_state_builder = wasi::WasiState::new( + arg_vec + .first() + .unwrap_or_else("wasmer-wasi-default-program-name"), + ); + wasi_state_builder + .args(&arg_vec[1..]) + .envs(env_vec) + .preopen_dirs(po_file_vec)? + .map_dirs(mapped_dir_vec)?; + let wasi_state = wasi_state_builder.build().unwrap(); + let mut wasi_env = wasi::WasiEnv::new(wasi_state); + let memory_type = MemoryType::new(0, None, false); + let memory = Memory::new(store, memory_type); + wasi_env.set_memory(&memory); + + let import_object = Box::new(wasi::generate_import_object_from_env( + store, + &mut wasi_env, + version, + )); + 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 store = crate::get_global_store(); + let mut wasi_state_builder = wasi::WasiState::new("wasmer-wasi-default-program-name"); + let wasi_state = wasi_state_builder.build().unwrap(); + let mut wasi_env = wasi::WasiEnv::new(wasi_state); + // this API will now leak a `Memory` + let memory_type = MemoryType::new(0, None, false); + let memory = Memory::new(store, memory_type); + wasi_env.set_memory(&memory); + // TODO(mark): review lifetime of `Memory` here + let import_object = Box::new(wasi::generate_import_object_from_env( + store, + &mut wasi_env, + wasi::WasiVersion::Latest, + )); + + Box::into_raw(import_object) as *mut wasmer_import_object_t +} + +#[cfg(test)] +mod tests { + use super::Version; + + #[test] + fn test_versions_from_uint() { + assert_eq!(Version::Unknown, 0.into()); + assert_eq!(Version::Latest, 1.into()); + assert_eq!(Version::Snapshot0, 2.into()); + assert_eq!(Version::Snapshot1, 3.into()); + } +} diff --git a/lib/c-api/src/instance.rs b/lib/c-api/src/instance.rs new file mode 100644 index 000000000..948717004 --- /dev/null +++ b/lib/c-api/src/instance.rs @@ -0,0 +1,558 @@ +//! Instantiate a module, call functions, and read exports. + +use crate::{ + error::{update_last_error, CApiError}, + export::{wasmer_exports_t, wasmer_import_export_kind, NamedExport, NamedExports}, + import::wasmer_import_t, + memory::wasmer_memory_t, + value::{wasmer_value, wasmer_value_t, wasmer_value_tag}, + wasmer_result_t, +}; +use libc::{c_char, c_int, c_void}; +use std::collections::HashMap; +use std::ffi::CStr; +use std::ptr; +use std::slice; +use wasmer::{ + Exports, Extern, Function, Global, ImportObject, Instance, Memory, Module, Table, Val, +}; + +/// Opaque pointer to a `wasmer_runtime::Instance` value in Rust. +/// +/// A `wasmer_runtime::Instance` represents a WebAssembly instance. It +/// is generally generated by the `wasmer_instantiate()` function, or by +/// the `wasmer_module_instantiate()` function for the most common paths. +#[repr(C)] +pub struct wasmer_instance_t; + +/// Opaque pointer to a `wasmer_runtime::Ctx` value in Rust. +/// +/// An instance context is passed to any host function (aka imported +/// function) as the first argument. It is necessary to read the +/// instance data or the memory, respectively with the +/// `wasmer_instance_context_data_get()` function, and the +/// `wasmer_instance_context_memory()` function. +/// +/// It is also possible to get the instance context outside a host +/// function by using the `wasmer_instance_context_get()` +/// function. See also `wasmer_instance_context_data_set()` to set the +/// instance context data. +/// +/// Example: +/// +/// ```c +/// // A host function that prints data from the WebAssembly memory to +/// // the standard output. +/// void print(wasmer_instance_context_t *context, int32_t pointer, int32_t length) { +/// // Use `wasmer_instance_context` to get back the first instance memory. +/// const wasmer_memory_t *memory = wasmer_instance_context_memory(context, 0); +/// +/// // Continue… +/// } +/// ``` +#[repr(C)] +pub struct wasmer_instance_context_t; + +/// Creates a new WebAssembly instance from the given bytes and imports. +/// +/// The result is stored in the first argument `instance` if +/// successful, i.e. when the function returns +/// `wasmer_result_t::WASMER_OK`. Otherwise +/// `wasmer_result_t::WASMER_ERROR` is returned, and +/// `wasmer_last_error_length()` with `wasmer_last_error_message()` must +/// be used to read the error message. +/// +/// The caller is responsible to free the instance with +/// `wasmer_instance_destroy()`. +/// +/// Example: +/// +/// ```c +/// // 1. Read a WebAssembly module from a file. +/// FILE *file = fopen("sum.wasm", "r"); +/// fseek(file, 0, SEEK_END); +/// long bytes_length = ftell(file); +/// uint8_t *bytes = malloc(bytes_length); +/// fseek(file, 0, SEEK_SET); +/// fread(bytes, 1, bytes_length, file); +/// fclose(file); +/// +/// // 2. Declare the imports (here, none). +/// wasmer_import_t imports[] = {}; +/// +/// // 3. Instantiate the WebAssembly module. +/// wasmer_instance_t *instance = NULL; +/// wasmer_result_t result = wasmer_instantiate(&instance, bytes, bytes_length, imports, 0); +/// +/// // 4. Check for errors. +/// if (result != WASMER_OK) { +/// int error_length = wasmer_last_error_length(); +/// char *error = malloc(error_length); +/// wasmer_last_error_message(error, error_length); +/// // Do something with `error`… +/// } +/// +/// // 5. Free the memory! +/// wasmer_instance_destroy(instance); +/// ``` +#[allow(clippy::cast_ptr_alignment)] +#[no_mangle] +pub unsafe extern "C" fn wasmer_instantiate( + instance: *mut *mut wasmer_instance_t, + wasm_bytes: *mut u8, + wasm_bytes_len: u32, + imports: *mut wasmer_import_t, + imports_len: c_int, +) -> wasmer_result_t { + if wasm_bytes.is_null() { + update_last_error(CApiError { + msg: "wasm bytes ptr is null".to_string(), + }); + return wasmer_result_t::WASMER_ERROR; + } + let imports: &[wasmer_import_t] = slice::from_raw_parts(imports, imports_len as usize); + let mut import_object = ImportObject::new(); + let mut namespaces = HashMap::new(); + for import in imports { + let module_name = slice::from_raw_parts( + import.module_name.bytes, + import.module_name.bytes_len as usize, + ); + let module_name = if let Ok(s) = std::str::from_utf8(module_name) { + s + } else { + update_last_error(CApiError { + msg: "error converting module name to string".to_string(), + }); + return wasmer_result_t::WASMER_ERROR; + }; + let import_name = slice::from_raw_parts( + import.import_name.bytes, + import.import_name.bytes_len as usize, + ); + let import_name = if let Ok(s) = std::str::from_utf8(import_name) { + s + } else { + update_last_error(CApiError { + msg: "error converting import_name to string".to_string(), + }); + return wasmer_result_t::WASMER_ERROR; + }; + + let namespace = namespaces.entry(module_name).or_insert_with(Exports::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; + Extern::Memory((&*mem).clone()) + } + wasmer_import_export_kind::WASM_FUNCTION => { + let func_export = import.value.func as *mut Function; + Extern::Function((&*func_export).clone()) + } + wasmer_import_export_kind::WASM_GLOBAL => { + let global = import.value.global as *mut Global; + Extern::Global((&*global).clone()) + } + wasmer_import_export_kind::WASM_TABLE => { + let table = import.value.table as *mut Table; + Extern::Table((&*table).clone()) + } + }; + namespace.insert(import_name, export); + } + for (module_name, namespace) in namespaces.into_iter() { + import_object.register(module_name, namespace); + } + + let bytes: &[u8] = slice::from_raw_parts_mut(wasm_bytes, wasm_bytes_len as usize); + let store = crate::get_global_store(); + + let module_result = Module::from_binary(store, bytes); + let module = match module_result { + Ok(module) => module, + Err(error) => { + update_last_error(error); + return wasmer_result_t::WASMER_ERROR; + } + }; + let result = Instance::new(&module, &import_object); + let new_instance = match result { + Ok(instance) => instance, + Err(error) => { + update_last_error(error); + return wasmer_result_t::WASMER_ERROR; + } + }; + *instance = Box::into_raw(Box::new(new_instance)) as *mut wasmer_instance_t; + wasmer_result_t::WASMER_OK +} + +/// Returns the instance context. Learn more by looking at the +/// `wasmer_instance_context_t` struct. +/// +/// This function returns `null` if `instance` is a null pointer. +/// +/// Example: +/// +/// ```c +/// const wasmer_instance_context_get *context = wasmer_instance_context_get(instance); +/// my_data *data = (my_data *) wasmer_instance_context_data_get(context); +/// // Do something with `my_data`. +/// ``` +/// +/// It is often useful with `wasmer_instance_context_data_set()`. +#[allow(clippy::cast_ptr_alignment)] +#[no_mangle] +pub extern "C" fn wasmer_instance_context_get( + instance: *mut wasmer_instance_t, +) -> *const wasmer_instance_context_t { + if instance.is_null() { + return ptr::null() as _; + } + + unimplemented!("wasmer_instance_context_get: API changed") + /* + let instance = unsafe { &*(instance as *const Instance) }; + let context: *const Ctx = instance.context() as *const _; + + context as *const wasmer_instance_context_t + */ +} + +/// Calls an exported function of a WebAssembly instance by `name` +/// with the provided parameters. The exported function results are +/// stored on the provided `results` pointer. +/// +/// This function returns `wasmer_result_t::WASMER_OK` upon success, +/// `wasmer_result_t::WASMER_ERROR` otherwise. You can use +/// `wasmer_last_error_message()` to get the generated error message. +/// +/// Potential errors are the following: +/// +/// * `instance` is a null pointer, +/// * `name` is a null pointer, +/// * `params` is a null pointer. +/// +/// Example of calling an exported function that needs two parameters, and returns one value: +/// +/// ```c +/// // First argument. +/// wasmer_value_t argument_one = { +/// .tag = WASM_I32, +/// .value.I32 = 3, +/// }; +/// +/// // Second argument. +/// wasmer_value_t argument_two = { +/// .tag = WASM_I32, +/// .value.I32 = 4, +/// }; +/// +/// // First result. +/// wasmer_value_t result_one; +/// +/// // All arguments and results. +/// wasmer_value_t arguments[] = {argument_one, argument_two}; +/// wasmer_value_t results[] = {result_one}; +/// +/// wasmer_result_t call_result = wasmer_instance_call( +/// instance, // instance pointer +/// "sum", // the exported function name +/// arguments, // the arguments +/// 2, // the number of arguments +/// results, // the results +/// 1 // the number of results +/// ); +/// +/// if (call_result == WASMER_OK) { +/// printf("Result is: %d\n", results[0].value.I32); +/// } +/// ``` +#[allow(clippy::cast_ptr_alignment)] +#[no_mangle] +pub unsafe extern "C" fn wasmer_instance_call( + instance: *mut wasmer_instance_t, + name: *const c_char, + params: *const wasmer_value_t, + params_len: u32, + results: *mut wasmer_value_t, + results_len: u32, +) -> wasmer_result_t { + if instance.is_null() { + update_last_error(CApiError { + msg: "instance ptr is null".to_string(), + }); + + return wasmer_result_t::WASMER_ERROR; + } + + if name.is_null() { + update_last_error(CApiError { + msg: "name ptr is null".to_string(), + }); + + return wasmer_result_t::WASMER_ERROR; + } + + if params.is_null() { + update_last_error(CApiError { + msg: "params ptr is null".to_string(), + }); + + return wasmer_result_t::WASMER_ERROR; + } + + let params: &[wasmer_value_t] = slice::from_raw_parts(params, params_len as usize); + let params: Vec = params.iter().cloned().map(|x| x.into()).collect(); + + let func_name_c = CStr::from_ptr(name); + let func_name_r = func_name_c.to_str().unwrap(); + + let results: &mut [wasmer_value_t] = slice::from_raw_parts_mut(results, results_len as usize); + + let instance = &*(instance as *mut Instance); + let f: &Function = match instance.exports.get(func_name_r) { + Ok(f) => f, + Err(err) => { + update_last_error(err); + return wasmer_result_t::WASMER_ERROR; + } + }; + + let result = f.call(¶ms[..]); + + match result { + Ok(results_vec) => { + if !results_vec.is_empty() { + let ret = match results_vec[0] { + Val::I32(x) => wasmer_value_t { + tag: wasmer_value_tag::WASM_I32, + value: wasmer_value { I32: x }, + }, + Val::I64(x) => wasmer_value_t { + tag: wasmer_value_tag::WASM_I64, + value: wasmer_value { I64: x }, + }, + Val::F32(x) => wasmer_value_t { + tag: wasmer_value_tag::WASM_F32, + value: wasmer_value { F32: x }, + }, + Val::F64(x) => wasmer_value_t { + tag: wasmer_value_tag::WASM_F64, + value: wasmer_value { F64: x }, + }, + Val::V128(_) => unimplemented!("calling function with V128 parameter"), + Val::AnyRef(_) => unimplemented!("returning AnyRef type"), + Val::FuncRef(_) => unimplemented!("returning FuncRef type"), + }; + results[0] = ret; + } + wasmer_result_t::WASMER_OK + } + Err(err) => { + update_last_error(err); + wasmer_result_t::WASMER_ERROR + } + } +} + +/// Gets all the exports of the given WebAssembly instance. +/// + +/// This function stores a Rust vector of exports into `exports` as an +/// opaque pointer of kind `wasmer_exports_t`. +/// +/// As is, you can do anything with `exports` except using the +/// companion functions, like `wasmer_exports_len()`, +/// `wasmer_exports_get()` or `wasmer_export_kind()`. See the example below. +/// +/// **Warning**: The caller owns the object and should call +/// `wasmer_exports_destroy()` to free it. +/// +/// Example: +/// +/// ```c +/// // Get the exports. +/// wasmer_exports_t *exports = NULL; +/// wasmer_instance_exports(instance, &exports); +/// +/// // Get the number of exports. +/// int exports_length = wasmer_exports_len(exports); +/// printf("Number of exports: %d\n", exports_length); +/// +/// // Read the first export. +/// wasmer_export_t *export = wasmer_exports_get(exports, 0); +/// +/// // Get the kind of the export. +/// wasmer_import_export_kind export_kind = wasmer_export_kind(export); +/// +/// // Assert it is a function (why not). +/// assert(export_kind == WASM_FUNCTION); +/// +/// // Read the export name. +/// wasmer_byte_array name_bytes = wasmer_export_name(export); +/// +/// assert(name_bytes.bytes_len == sizeof("sum") - 1); +/// assert(memcmp(name_bytes.bytes, "sum", sizeof("sum") - 1) == 0); +/// +/// // Destroy the exports. +/// wasmer_exports_destroy(exports); +/// ``` +#[allow(clippy::cast_ptr_alignment)] +#[no_mangle] +pub unsafe extern "C" fn wasmer_instance_exports( + instance: *mut wasmer_instance_t, + exports: *mut *mut wasmer_exports_t, +) { + if instance.is_null() { + return; + } + + let instance_ref = &mut *(instance as *mut Instance); + let mut exports_vec: Vec = instance_ref + .module() + .exports() + .map(|export_type| NamedExport { + export_type, + instance: instance as *mut Instance, + }) + .collect(); + + let named_exports: Box = Box::new(NamedExports(exports_vec)); + + *exports = Box::into_raw(named_exports) as *mut wasmer_exports_t; +} + +/// Sets the data that can be hold by an instance context. +/// +/// An instance context (represented by the opaque +/// `wasmer_instance_context_t` structure) can hold user-defined +/// data. This function sets the data. This function is complementary +/// of `wasmer_instance_context_data_get()`. +/// +/// This function does nothing if `instance` is a null pointer. +/// +/// Example: +/// +/// ```c +/// // Define your own data. +/// typedef struct { +/// // … +/// } my_data; +/// +/// // Allocate them and set them on the given instance. +/// my_data *data = malloc(sizeof(my_data)); +/// data->… = …; +/// wasmer_instance_context_data_set(instance, (void*) data); +/// +/// // You can read your data. +/// { +/// my_data *data = (my_data*) wasmer_instance_context_data_get(wasmer_instance_context_get(instance)); +/// // … +/// } +/// ``` +#[allow(clippy::cast_ptr_alignment)] +#[no_mangle] +pub extern "C" fn wasmer_instance_context_data_set( + instance: *mut wasmer_instance_t, + data_ptr: *mut c_void, +) { + unimplemented!( + "wasmer_instance_context_data_set: API changed in a way that this is non-obvious" + ) + /* + if instance.is_null() { + return; + } + + let instance = unsafe { &mut *(instance as *mut Instance) }; + + instance.context_mut().data = data_ptr; + */ +} + +/// Gets the `memory_idx`th memory of the instance. +/// +/// Note that the index is always `0` until multiple memories are supported. +/// +/// This function is mostly used inside host functions (aka imported +/// functions) to read the instance memory. +/// +/// Example of a _host function_ that reads and prints a string based on a pointer and a length: +/// +/// ```c +/// void print_string(const wasmer_instance_context_t *context, int32_t pointer, int32_t length) { +/// // Get the 0th memory. +/// const wasmer_memory_t *memory = wasmer_instance_context_memory(context, 0); +/// +/// // Get the memory data as a pointer. +/// uint8_t *memory_bytes = wasmer_memory_data(memory); +/// +/// // Print what we assumed to be a string! +/// printf("%.*s", length, memory_bytes + pointer); +/// } +/// ``` +#[allow(clippy::cast_ptr_alignment)] +#[no_mangle] +pub extern "C" fn wasmer_instance_context_memory( + ctx: *const wasmer_instance_context_t, + _memory_idx: u32, +) -> *const wasmer_memory_t { + unimplemented!("wasmer_instance_context_memory: API changed") + /*let ctx = unsafe { &*(ctx as *const Ctx) }; + let memory = ctx.memory(0); + memory as *const Memory as *const wasmer_memory_t + */ +} + +/// Gets the data that can be hold by an instance. +/// +/// This function is complementary of +/// `wasmer_instance_context_data_set()`. Please read its +/// documentation. You can also read the documentation of +/// `wasmer_instance_context_t` to get other examples. +/// +/// This function returns nothing if `ctx` is a null pointer. +#[allow(clippy::cast_ptr_alignment)] +#[no_mangle] +pub extern "C" fn wasmer_instance_context_data_get( + ctx: *const wasmer_instance_context_t, +) -> *mut c_void { + unimplemented!("wasmer_instance_context_data_get: API changed") + /* + if ctx.is_null() { + return ptr::null_mut() as _; + } + + let ctx = unsafe { &*(ctx as *const Ctx) }; + + ctx.data + */ +} + +/// Frees memory for the given `wasmer_instance_t`. +/// +/// Check the `wasmer_instantiate()` function to get a complete +/// example. +/// +/// If `instance` is a null pointer, this function does nothing. +/// +/// Example: +/// +/// ```c +/// // Get an instance. +/// wasmer_instance_t *instance = NULL; +/// wasmer_instantiate(&instance, bytes, bytes_length, imports, 0); +/// +/// // Destroy the instance. +/// wasmer_instance_destroy(instance); +/// ``` +#[allow(clippy::cast_ptr_alignment)] +#[no_mangle] +pub extern "C" fn wasmer_instance_destroy(instance: *mut wasmer_instance_t) { + if !instance.is_null() { + unsafe { Box::from_raw(instance as *mut Instance) }; + } +} diff --git a/lib/c-api/src/lib.rs b/lib/c-api/src/lib.rs new file mode 100644 index 000000000..9b172f602 --- /dev/null +++ b/lib/c-api/src/lib.rs @@ -0,0 +1,192 @@ +#![doc(html_favicon_url = "https://wasmer.io/static/icons/favicon.ico")] +#![doc(html_logo_url = "https://avatars3.githubusercontent.com/u/44205449?s=200&v=4")] + +//! # Wasmer Runtime C API +//! +//! Wasmer is a standalone JIT WebAssembly runtime, aiming to be fully +//! compatible with Emscripten, Rust and Go. [Learn +//! more](https://github.com/wasmerio/wasmer). +//! +//! This crate exposes a C and C++ API for the Wasmer runtime. +//! +//! # Usage +//! +//! The C and C++ header files can be found in the source tree of this +//! crate, respectively [`wasmer.h`][wasmer_h] and +//! [`wasmer.hh`][wasmer_hh]. They are automatically generated, and always +//! up-to-date in this repository. +//! +//! Here is a simple example to use the C API: +//! +//! ```c +//! #include +//! #include "wasmer.h" +//! #include +//! #include +//! +//! int main() +//! { +//! // Read the Wasm file bytes. +//! FILE *file = fopen("sum.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); +//! +//! // Prepare the imports. +//! wasmer_import_t imports[] = {}; +//! +//! // Instantiate! +//! wasmer_instance_t *instance = NULL; +//! wasmer_result_t instantiation_result = wasmer_instantiate(&instance, bytes, len, imports, 0); +//! +//! assert(instantiation_result == WASMER_OK); +//! +//! // Let's call a function. +//! // Start by preparing the arguments. +//! +//! // Value of argument #1 is `7i32`. +//! wasmer_value_t argument_one; +//! argument_one.tag = WASM_I32; +//! argument_one.value.I32 = 7; +//! +//! // Value of argument #2 is `8i32`. +//! wasmer_value_t argument_two; +//! argument_two.tag = WASM_I32; +//! argument_two.value.I32 = 8; +//! +//! // Prepare the arguments. +//! wasmer_value_t arguments[] = {argument_one, argument_two}; +//! +//! // Prepare the return value. +//! wasmer_value_t result_one; +//! wasmer_value_t results[] = {result_one}; +//! +//! // Call the `sum` function with the prepared arguments and the return value. +//! wasmer_result_t call_result = wasmer_instance_call(instance, "sum", arguments, 2, results, 1); +//! +//! // Let's display the result. +//! printf("Call result: %d\n", call_result); +//! printf("Result: %d\n", results[0].value.I32); +//! +//! // `sum(7, 8) == 15`. +//! assert(results[0].value.I32 == 15); +//! assert(call_result == WASMER_OK); +//! +//! wasmer_instance_destroy(instance); +//! +//! return 0; +//! } +//! ``` +//! +//! [wasmer_h]: ./wasmer.h +//! [wasmer_hh]: ./wasmer.hh +#![deny( + dead_code, + unused_imports, + // temporarily disabled + //unused_variables, + unused_unsafe, + unreachable_patterns +)] + +#[macro_use] +extern crate lazy_static; + +pub mod error; +pub mod export; +pub mod global; +pub mod import; +pub mod instance; +pub mod memory; +pub mod module; +pub mod table; +// `not(target_family = "windows")` is simpler than `unix`. See build.rs +// if you want to change the meaning of these `cfg`s in the header file. +/* +TODO: reenable `trampoline` module when the refactor gains feature parity with Wasmer master +#[cfg(all(not(target_family = "windows"), target_arch = "x86_64"))] +pub mod trampoline;*/ +pub mod value; + +/// The `wasmer_result_t` enum is a type that represents either a +/// success, or a failure. +#[allow(non_camel_case_types)] +#[repr(C)] +pub enum wasmer_result_t { + /// Represents a success. + WASMER_OK = 1, + + /// Represents a failure. + WASMER_ERROR = 2, +} + +/// The `wasmer_limits_t` struct is a type that describes the limits of something +/// such as a memory or a table. See the `wasmer_memory_new()` function to get +/// more information. +#[repr(C)] +pub struct wasmer_limits_t { + /// The minimum number of allowed pages. + pub min: u32, + + /// The maximum number of allowed pages. + pub max: wasmer_limit_option_t, +} + +/// The `wasmer_limit_option_t` struct represents an optional limit +/// for `wasmer_limits_t`. +#[repr(C)] +pub struct wasmer_limit_option_t { + /// Whether the limit is set. + pub has_some: bool, + + /// The limit value. + pub some: u32, +} + +#[repr(C)] +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 { + 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) + } +} + +lazy_static! { + pub(crate) static ref GLOBAL_STORE: wasmer::Store = wasmer::Store::default(); +} + +pub(crate) fn get_global_store() -> &'static wasmer::Store { + &*GLOBAL_STORE +} diff --git a/lib/c-api/src/memory.rs b/lib/c-api/src/memory.rs new file mode 100644 index 000000000..f73e7f9ee --- /dev/null +++ b/lib/c-api/src/memory.rs @@ -0,0 +1,212 @@ +//! Create, read, write, grow, destroy memory of an instance. + +use crate::{ + error::{update_last_error, CApiError}, + wasmer_limits_t, wasmer_result_t, +}; +use std::{cell::Cell, ptr}; +use wasmer::{Bytes, Memory, MemoryType, Pages}; + +/// Opaque pointer to a `wasmer_runtime::Memory` value in Rust. +/// +/// A `wasmer_runtime::Memory` represents a WebAssembly memory. It is +/// possible to create one with `wasmer_memory_new()` and pass it as +/// imports of an instance, or to read it from exports of an instance +/// with `wasmer_export_to_memory()`. +#[repr(C)] +#[derive(Clone)] +pub struct wasmer_memory_t; + +/// Creates a new empty WebAssembly memory for the given descriptor. +/// +/// The result is stored in the first argument `memory` if successful, +/// i.e. when the function returns +/// `wasmer_result_t::WASMER_OK`. Otherwise, +/// `wasmer_result_t::WASMER_ERROR` is returned, and +/// `wasmer_last_error_length()` with `wasmer_last_error_message()` +/// must be used to read the error message. +/// +/// The caller owns the memory and is responsible to free it with +/// `wasmer_memory_destroy()`. +/// +/// Example: +/// +/// ```c +/// // 1. The memory object. +/// wasmer_memory_t *memory = NULL; +/// +/// // 2. The memory descriptor. +/// wasmer_limits_t memory_descriptor = { +/// .min = 10, +/// .max = { +/// .has_some = true, +/// .some = 15, +/// }, +/// }; +/// +/// // 3. Initialize the memory. +/// wasmer_result_t result = wasmer_memory_new(&memory, memory_descriptor); +/// +/// if (result != WASMER_OK) { +/// int error_length = wasmer_last_error_length(); +/// char *error = malloc(error_length); +/// wasmer_last_error_message(error, error_length); +/// // Do something with `error`… +/// } +/// +/// // 4. Free the memory! +/// wasmer_memory_destroy(memory); +/// ``` +#[no_mangle] +pub unsafe extern "C" fn wasmer_memory_new( + memory: *mut *mut wasmer_memory_t, + limits: wasmer_limits_t, +) -> wasmer_result_t { + let max = if limits.max.has_some { + Some(Pages(limits.max.some)) + } else { + None + }; + let store = crate::get_global_store(); + let desc = MemoryType::new(Pages(limits.min), max, false); + let new_memory = Memory::new(store, desc); + *memory = Box::into_raw(Box::new(new_memory)) as *mut wasmer_memory_t; + wasmer_result_t::WASMER_OK +} + +/// Grows a memory by the given number of pages (of 65Kb each). +/// +/// The functions return `wasmer_result_t::WASMER_OK` upon success, +/// `wasmer_result_t::WASMER_ERROR` otherwise. Use +/// `wasmer_last_error_length()` with `wasmer_last_error_message()` to +/// read the error message. +/// +/// Example: +/// +/// ```c +/// wasmer_result_t result = wasmer_memory_grow(memory, 10); +/// +/// if (result != WASMER_OK) { +/// // … +/// } +/// ``` +#[allow(clippy::cast_ptr_alignment)] +#[no_mangle] +pub extern "C" fn wasmer_memory_grow(memory: *mut wasmer_memory_t, delta: u32) -> wasmer_result_t { + if memory.is_null() { + update_last_error(CApiError { + msg: "`memory` is NULL.".to_string(), + }); + + return wasmer_result_t::WASMER_ERROR; + } + + let memory = unsafe { &*(memory as *mut Memory) }; + let grow_result = memory.grow(Pages(delta)); + + match grow_result { + Some(_) => wasmer_result_t::WASMER_OK, + _ => wasmer_result_t::WASMER_ERROR, + } +} + +/// Reads the current length (in pages) of the given memory. +/// +/// The function returns zero if `memory` is a null pointer. +/// +/// Example: +/// +/// ```c +/// uint32_t memory_length = wasmer_memory_length(memory); +/// +/// printf("Memory pages length: %d\n", memory_length); +/// ``` +#[allow(clippy::cast_ptr_alignment)] +#[no_mangle] +pub extern "C" fn wasmer_memory_length(memory: *const wasmer_memory_t) -> u32 { + if memory.is_null() { + return 0; + } + + let memory = unsafe { &*(memory as *const Memory) }; + let Pages(length) = memory.size(); + + length +} + +/// Gets a pointer to the beginning of the contiguous memory data +/// bytes. +/// +/// The function returns `NULL` if `memory` is a null pointer. +/// +/// Note that when the memory grows, it can be reallocated, and thus +/// the returned pointer can be invalidated. +/// +/// Example: +/// +/// ```c +/// uint8_t *memory_data = wasmer_memory_data(memory); +/// char *str = (char*) malloc(sizeof(char) * 7); +/// +/// for (uint32_t nth = 0; nth < 7; ++nth) { +/// str[nth] = (char) memory_data[nth]; +/// } +/// ``` +#[allow(clippy::cast_ptr_alignment)] +#[no_mangle] +pub extern "C" fn wasmer_memory_data(memory: *const wasmer_memory_t) -> *mut u8 { + if memory.is_null() { + return ptr::null_mut(); + } + + let memory = unsafe { &*(memory as *const Memory) }; + + memory.view::()[..].as_ptr() as *mut Cell as *mut u8 +} + +/// Gets the size in bytes of the memory data. +/// +/// This function returns 0 if `memory` is a null pointer. +/// +/// Example: +/// +/// ```c +/// uint32_t memory_data_length = wasmer_memory_data_length(memory); +/// ``` +#[allow(clippy::cast_ptr_alignment)] +#[no_mangle] +pub extern "C" fn wasmer_memory_data_length(memory: *const wasmer_memory_t) -> u32 { + if memory.is_null() { + return 0; + } + + let memory = unsafe { &*(memory as *const Memory) }; + let Bytes(length) = memory.size().bytes(); + + length as u32 +} + +/// Frees memory for the given `wasmer_memory_t`. +/// +/// Check the `wasmer_memory_new()` function to get a complete +/// example. +/// +/// If `memory` is a null pointer, this function does nothing. +/// +/// Example: +/// +/// ```c +/// // Get a memory. +/// wasmer_memory_t *memory = NULL; +/// wasmer_result_t result = wasmer_memory_new(&memory, memory_descriptor); +/// +/// // Destroy the memory. +/// wasmer_memory_destroy(memory); +/// ``` +#[allow(clippy::cast_ptr_alignment)] +#[no_mangle] +pub extern "C" fn wasmer_memory_destroy(memory: *mut wasmer_memory_t) { + if !memory.is_null() { + unsafe { Box::from_raw(memory as *mut Memory) }; + } +} diff --git a/lib/c-api/src/module.rs b/lib/c-api/src/module.rs new file mode 100644 index 000000000..8bab7ee16 --- /dev/null +++ b/lib/c-api/src/module.rs @@ -0,0 +1,313 @@ +//! Compile, validate, instantiate, serialize, and destroy modules. + +use crate::{ + error::{update_last_error, CApiError}, + export::wasmer_import_export_kind, + import::{wasmer_import_object_t, wasmer_import_t}, + instance::wasmer_instance_t, + wasmer_byte_array, wasmer_result_t, +}; +use libc::c_int; +use std::collections::HashMap; +use std::slice; +use wasmer::{Exports, Extern, Function, Global, ImportObject, Instance, Memory, Module, Table}; + +#[repr(C)] +pub struct wasmer_module_t; + +#[repr(C)] +pub struct wasmer_serialized_module_t; + +/// Creates a new Module from the given wasm bytes. +/// +/// Returns `wasmer_result_t::WASMER_OK` upon success. +/// +/// Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` +/// and `wasmer_last_error_message` to get an error message. +#[allow(clippy::cast_ptr_alignment)] +#[no_mangle] +pub unsafe extern "C" fn wasmer_compile( + module: *mut *mut wasmer_module_t, + wasm_bytes: *mut u8, + wasm_bytes_len: u32, +) -> wasmer_result_t { + let bytes: &[u8] = slice::from_raw_parts_mut(wasm_bytes, wasm_bytes_len as usize); + let store = crate::get_global_store(); + let result = Module::from_binary(store, bytes); + let new_module = match result { + Ok(instance) => instance, + Err(error) => { + update_last_error(error); + return wasmer_result_t::WASMER_ERROR; + } + }; + *module = Box::into_raw(Box::new(new_module)) as *mut wasmer_module_t; + wasmer_result_t::WASMER_OK +} + +/// Validates a sequence of bytes hoping it represents a valid WebAssembly module. +/// +/// The function returns true if the bytes are valid, false otherwise. +/// +/// Example: +/// +/// ```c +/// bool result = wasmer_validate(bytes, bytes_length); +/// +/// if (false == result) { +/// // Do something… +/// } +/// ``` +#[allow(clippy::cast_ptr_alignment)] +#[no_mangle] +pub unsafe extern "C" fn wasmer_validate(wasm_bytes: *const u8, wasm_bytes_len: u32) -> bool { + if wasm_bytes.is_null() { + return false; + } + + let bytes: &[u8] = slice::from_raw_parts(wasm_bytes, wasm_bytes_len as usize); + + let store = crate::get_global_store(); + Module::validate(store, bytes).is_ok() +} + +/// Creates a new Instance from the given module and imports. +/// +/// Returns `wasmer_result_t::WASMER_OK` upon success. +/// +/// Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` +/// and `wasmer_last_error_message` to get an error message. +#[allow(clippy::cast_ptr_alignment)] +#[no_mangle] +pub unsafe extern "C" fn wasmer_module_instantiate( + module: *const wasmer_module_t, + instance: *mut *mut wasmer_instance_t, + imports: *mut wasmer_import_t, + imports_len: c_int, +) -> wasmer_result_t { + let imports: &[wasmer_import_t] = slice::from_raw_parts(imports, imports_len as usize); + let mut import_object = ImportObject::new(); + let mut namespaces = HashMap::new(); + for import in imports { + let module_name = slice::from_raw_parts( + import.module_name.bytes, + import.module_name.bytes_len as usize, + ); + let module_name = if let Ok(s) = std::str::from_utf8(module_name) { + s + } else { + update_last_error(CApiError { + msg: "error converting module name to string".to_string(), + }); + return wasmer_result_t::WASMER_ERROR; + }; + let import_name = slice::from_raw_parts( + import.import_name.bytes, + import.import_name.bytes_len as usize, + ); + let import_name = if let Ok(s) = std::str::from_utf8(import_name) { + s + } else { + update_last_error(CApiError { + msg: "error converting import_name to string".to_string(), + }); + return wasmer_result_t::WASMER_ERROR; + }; + + let namespace = namespaces.entry(module_name).or_insert_with(Exports::new); + + let export = match import.tag { + wasmer_import_export_kind::WASM_MEMORY => { + let mem = import.value.memory as *mut Memory; + Extern::Memory((&*mem).clone()) + } + wasmer_import_export_kind::WASM_FUNCTION => { + let func_export = import.value.func as *mut Function; + Extern::Function((&*func_export).clone()) + } + wasmer_import_export_kind::WASM_GLOBAL => { + let global = import.value.global as *mut Global; + Extern::Global((&*global).clone()) + } + wasmer_import_export_kind::WASM_TABLE => { + let table = import.value.table as *mut Table; + Extern::Table((&*table).clone()) + } + }; + namespace.insert(import_name, export); + } + for (module_name, namespace) in namespaces.into_iter() { + import_object.register(module_name, namespace); + } + + let module = &*(module as *const Module); + let new_instance = match Instance::new(module, &import_object) { + Ok(instance) => instance, + Err(error) => { + update_last_error(error); + return wasmer_result_t::WASMER_ERROR; + } + }; + + *instance = Box::into_raw(Box::new(new_instance)) as *mut wasmer_instance_t; + wasmer_result_t::WASMER_OK +} + +/// Given: +/// * A prepared `wasmer` import-object +/// * A compiled wasmer module +/// +/// Instantiates a wasmer instance +#[no_mangle] +pub unsafe extern "C" fn wasmer_module_import_instantiate( + instance: *mut *mut wasmer_instance_t, + module: *const wasmer_module_t, + import_object: *const wasmer_import_object_t, +) -> wasmer_result_t { + let import_object: &ImportObject = &*(import_object as *const ImportObject); + let module: &Module = &*(module as *const Module); + + let new_instance: Instance = match Instance::new(module, import_object) { + Ok(instance) => instance, + Err(error) => { + update_last_error(error); + return wasmer_result_t::WASMER_ERROR; + } + }; + *instance = Box::into_raw(Box::new(new_instance)) as *mut wasmer_instance_t; + + return wasmer_result_t::WASMER_OK; +} + +/// Serialize the given Module. +/// +/// The caller owns the object and should call `wasmer_serialized_module_destroy` to free it. +/// +/// Returns `wasmer_result_t::WASMER_OK` upon success. +/// +/// Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` +/// and `wasmer_last_error_message` to get an error message. +#[allow(clippy::cast_ptr_alignment)] +#[no_mangle] +pub unsafe extern "C" fn wasmer_module_serialize( + serialized_module_out: *mut *mut wasmer_serialized_module_t, + module: *const wasmer_module_t, +) -> wasmer_result_t { + let module = &*(module as *const Module); + + match module.serialize() { + Ok(mut serialized_module) => { + let boxed_slice = serialized_module.into_boxed_slice(); + *serialized_module_out = Box::into_raw(Box::new(boxed_slice)) as _; + + wasmer_result_t::WASMER_OK + } + Err(_) => { + update_last_error(CApiError { + msg: "Failed to serialize the module".to_string(), + }); + wasmer_result_t::WASMER_ERROR + } + } +} + +/// Get bytes of the serialized module. +#[allow(clippy::cast_ptr_alignment)] +#[no_mangle] +pub unsafe extern "C" fn wasmer_serialized_module_bytes( + serialized_module: *const wasmer_serialized_module_t, +) -> wasmer_byte_array { + let serialized_module = &*(serialized_module as *const &[u8]); + + wasmer_byte_array { + bytes: serialized_module.as_ptr(), + bytes_len: serialized_module.len() as u32, + } +} + +/// Transform a sequence of bytes into a serialized module. +/// +/// The caller owns the object and should call `wasmer_serialized_module_destroy` to free it. +/// +/// Returns `wasmer_result_t::WASMER_OK` upon success. +/// +/// Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` +/// and `wasmer_last_error_message` to get an error message. +#[allow(clippy::cast_ptr_alignment)] +#[no_mangle] +pub unsafe extern "C" fn wasmer_serialized_module_from_bytes( + serialized_module: *mut *mut wasmer_serialized_module_t, + serialized_module_bytes: *const u8, + serialized_module_bytes_length: u32, +) -> wasmer_result_t { + if serialized_module.is_null() { + update_last_error(CApiError { + msg: "`serialized_module_bytes` pointer is null".to_string(), + }); + return wasmer_result_t::WASMER_ERROR; + } + + let serialized_module_bytes: &[u8] = slice::from_raw_parts( + serialized_module_bytes, + serialized_module_bytes_length as usize, + ); + + *serialized_module = Box::into_raw(Box::new(serialized_module_bytes)) as _; + wasmer_result_t::WASMER_OK +} + +/// Deserialize the given serialized module. +/// +/// Returns `wasmer_result_t::WASMER_OK` upon success. +/// +/// Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` +/// and `wasmer_last_error_message` to get an error message. +#[allow(dead_code, unused_variables)] +#[allow(clippy::cast_ptr_alignment)] +#[no_mangle] +pub unsafe extern "C" fn wasmer_module_deserialize( + module: *mut *mut wasmer_module_t, + serialized_module: *const wasmer_serialized_module_t, +) -> wasmer_result_t { + if serialized_module.is_null() { + update_last_error(CApiError { + msg: "`serialized_module` pointer is null".to_string(), + }); + return wasmer_result_t::WASMER_ERROR; + } + + let serialized_module: &[u8] = &*(serialized_module as *const &[u8]); + let store = crate::get_global_store(); + + match Module::deserialize(store, serialized_module) { + Ok(deserialized_module) => { + *module = Box::into_raw(Box::new(deserialized_module)) as _; + wasmer_result_t::WASMER_OK + } + Err(e) => { + update_last_error(CApiError { msg: e.to_string() }); + wasmer_result_t::WASMER_ERROR + } + } +} + +/// Frees memory for the given serialized Module. +#[allow(clippy::cast_ptr_alignment)] +#[no_mangle] +pub extern "C" fn wasmer_serialized_module_destroy( + serialized_module: *mut wasmer_serialized_module_t, +) { + // TODO(mark): review all serialized logic memory logic + if !serialized_module.is_null() { + unsafe { Box::from_raw(serialized_module as *mut &[u8]) }; + } +} + +/// Frees memory for the given Module +#[allow(clippy::cast_ptr_alignment)] +#[no_mangle] +pub extern "C" fn wasmer_module_destroy(module: *mut wasmer_module_t) { + if !module.is_null() { + unsafe { Box::from_raw(module as *mut Module) }; + } +} diff --git a/lib/c-api/src/table.rs b/lib/c-api/src/table.rs new file mode 100644 index 000000000..909fdf041 --- /dev/null +++ b/lib/c-api/src/table.rs @@ -0,0 +1,97 @@ +//! Create, grow, destroy tables of an instance. + +use crate::{error::update_last_error, wasmer_limits_t, wasmer_result_t}; +use wasmer::{AnyRef, Table, TableType, Val, ValType}; + +#[repr(C)] +#[derive(Clone)] +pub struct wasmer_table_t; + +// TODO: this logic should be in wasmer itself +fn get_default_table_value(table_type: ValType) -> Val { + match table_type { + ValType::I32 => Val::I32(0), + ValType::I64 => Val::I64(0), + ValType::F32 => Val::F32(0.), + ValType::F64 => Val::F64(0.), + ValType::V128 => Val::V128(0), + ValType::AnyRef => Val::AnyRef(AnyRef::null()), // todo!("Figure out what the default AnyRef value is"), + ValType::FuncRef => Val::AnyRef(AnyRef::null()), //todo!("Figure out what the default FuncRef value is"), + } +} + +/// Creates a new Table for the given descriptor and initializes the given +/// pointer to pointer to a pointer to the new Table. +/// +/// The caller owns the object and should call `wasmer_table_destroy` to free it. +/// +/// Returns `wasmer_result_t::WASMER_OK` upon success. +/// +/// Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` +/// and `wasmer_last_error_message` to get an error message. +#[no_mangle] +pub unsafe extern "C" fn wasmer_table_new( + table: *mut *mut wasmer_table_t, + limits: wasmer_limits_t, +) -> wasmer_result_t { + let max = if limits.max.has_some { + Some(limits.max.some) + } else { + None + }; + let desc = TableType { + ty: ValType::FuncRef, + minimum: limits.min, + maximum: max, + }; + let store = crate::get_global_store(); + let result = Table::new(store, desc, get_default_table_value(ValType::FuncRef)); + let new_table = match result { + Ok(table) => table, + Err(error) => { + update_last_error(error); + return wasmer_result_t::WASMER_ERROR; + } + }; + *table = Box::into_raw(Box::new(new_table)) as *mut wasmer_table_t; + wasmer_result_t::WASMER_OK +} + +/// Grows a Table by the given number of elements. +/// +/// Returns `wasmer_result_t::WASMER_OK` upon success. +/// +/// Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` +/// and `wasmer_last_error_message` to get an error message. +#[allow(clippy::cast_ptr_alignment)] +#[no_mangle] +pub extern "C" fn wasmer_table_grow(table: *mut wasmer_table_t, delta: u32) -> wasmer_result_t { + let table = unsafe { &*(table as *mut Table) }; + let table_type = table.ty().ty; + let table_default_value = get_default_table_value(table_type); + let delta_result = table.grow(delta, table_default_value); + match delta_result { + Ok(_) => wasmer_result_t::WASMER_OK, + Err(grow_error) => { + update_last_error(grow_error); + wasmer_result_t::WASMER_ERROR + } + } +} + +/// Returns the current length of the given Table +#[allow(clippy::cast_ptr_alignment)] +#[no_mangle] +pub extern "C" fn wasmer_table_length(table: *mut wasmer_table_t) -> u32 { + let table = unsafe { &*(table as *mut Table) }; + table.size() +} + +/// Frees memory for the given Table +#[allow(clippy::cast_ptr_alignment)] +#[no_mangle] +pub extern "C" fn wasmer_table_destroy(table: *mut wasmer_table_t) { + if !table.is_null() { + unsafe { Box::from_raw(table as *mut Table) }; + } +} diff --git a/lib/c-api/src/trampoline.rs b/lib/c-api/src/trampoline.rs new file mode 100644 index 000000000..4bcdbe080 --- /dev/null +++ b/lib/c-api/src/trampoline.rs @@ -0,0 +1,92 @@ +//! Trampoline emitter for transforming function calls. + +use std::ffi::c_void; +use std::mem; +use wasmer_runtime_core::trampoline::*; + +#[repr(C)] +pub struct wasmer_trampoline_buffer_builder_t; + +#[repr(C)] +pub struct wasmer_trampoline_buffer_t; + +#[repr(C)] +pub struct wasmer_trampoline_callable_t; + +/// Creates a new trampoline builder. +#[no_mangle] +#[allow(clippy::cast_ptr_alignment)] +pub extern "C" fn wasmer_trampoline_buffer_builder_new() -> *mut wasmer_trampoline_buffer_builder_t +{ + Box::into_raw(Box::new(TrampolineBufferBuilder::new())) as *mut _ +} + +/// Adds a context trampoline to the builder. +#[no_mangle] +#[allow(clippy::cast_ptr_alignment)] +pub unsafe extern "C" fn wasmer_trampoline_buffer_builder_add_context_trampoline( + builder: *mut wasmer_trampoline_buffer_builder_t, + func: *const wasmer_trampoline_callable_t, + ctx: *const c_void, +) -> usize { + let builder = &mut *(builder as *mut TrampolineBufferBuilder); + builder.add_context_trampoline(func as *const CallTarget, ctx as *const CallContext) +} + +/// Adds a callinfo trampoline to the builder. +/// +/// Deprecated. In a future version `DynamicFunc::new` will be exposed to the C API and should be used instead of this function. +#[no_mangle] +#[allow(clippy::cast_ptr_alignment)] +pub unsafe extern "C" fn wasmer_trampoline_buffer_builder_add_callinfo_trampoline( + builder: *mut wasmer_trampoline_buffer_builder_t, + func: *const wasmer_trampoline_callable_t, + ctx: *const c_void, + num_params: u32, +) -> usize { + use wasmer::ValType; + let builder = &mut *(builder as *mut TrampolineBufferBuilder); + builder.add_callinfo_trampoline( + mem::transmute(func), + ctx as *const CallContext, + &vec![ValType::I64; num_params as usize], + &[ValType::I64], + ) +} + +/// Finalizes the trampoline builder into an executable buffer. +#[no_mangle] +#[allow(clippy::cast_ptr_alignment)] +pub unsafe extern "C" fn wasmer_trampoline_buffer_builder_build( + builder: *mut wasmer_trampoline_buffer_builder_t, +) -> *mut wasmer_trampoline_buffer_t { + let builder = Box::from_raw(builder as *mut TrampolineBufferBuilder); + Box::into_raw(Box::new(builder.build())) as *mut _ +} + +/// Destroys the trampoline buffer if not null. +#[no_mangle] +#[allow(clippy::cast_ptr_alignment)] +pub unsafe extern "C" fn wasmer_trampoline_buffer_destroy(buffer: *mut wasmer_trampoline_buffer_t) { + if !buffer.is_null() { + Box::from_raw(buffer as *mut TrampolineBuffer); + } +} + +/// Returns the callable pointer for the trampoline with index `idx`. +#[no_mangle] +#[allow(clippy::cast_ptr_alignment)] +pub unsafe extern "C" fn wasmer_trampoline_buffer_get_trampoline( + buffer: *const wasmer_trampoline_buffer_t, + idx: usize, +) -> *const wasmer_trampoline_callable_t { + let buffer = &*(buffer as *const TrampolineBuffer); + buffer.get_trampoline(idx) as _ +} + +/// Returns the context added by `add_context_trampoline`, from within the callee function. +#[no_mangle] +#[allow(clippy::cast_ptr_alignment)] +pub unsafe extern "C" fn wasmer_trampoline_get_context() -> *mut c_void { + get_context() as *const c_void as *mut c_void +} diff --git a/lib/c-api/src/value.rs b/lib/c-api/src/value.rs new file mode 100644 index 000000000..6a71f281c --- /dev/null +++ b/lib/c-api/src/value.rs @@ -0,0 +1,165 @@ +//! Create and map Rust to WebAssembly values. + +use wasmer::Val; +use wasmer::ValType; + +/// Represents all possibles WebAssembly value types. +/// +/// See `wasmer_value_t` to get a complete example. +#[allow(non_camel_case_types)] +#[repr(u32)] +#[derive(Clone)] +pub enum wasmer_value_tag { + /// Represents the `i32` WebAssembly type. + WASM_I32, + + /// Represents the `i64` WebAssembly type. + WASM_I64, + + /// Represents the `f32` WebAssembly type. + WASM_F32, + + /// Represents the `f64` WebAssembly type. + WASM_F64, +} + +/// Represents a WebAssembly value. +/// +/// This is a [Rust union][rust-union], which is equivalent to the C +/// union. See `wasmer_value_t` to get a complete example. +/// +/// [rust-union]: https://doc.rust-lang.org/reference/items/unions.html +#[repr(C)] +#[derive(Clone, Copy)] +#[allow(non_snake_case)] +pub union wasmer_value { + pub I32: i32, + pub I64: i64, + pub F32: f32, + pub F64: f64, +} + +/// Represents a WebAssembly type and value pair, +/// i.e. `wasmer_value_tag` and `wasmer_value`. Since the latter is an +/// union, it's the safe way to read or write a WebAssembly value in +/// C. +/// +/// Example: +/// +/// ```c +/// // Create a WebAssembly value. +/// wasmer_value_t wasm_value = { +/// .tag = WASM_I32, +/// .value.I32 = 42, +/// }; +/// +/// // Read a WebAssembly value. +/// if (wasm_value.tag == WASM_I32) { +/// int32_t x = wasm_value.value.I32; +/// // … +/// } +/// ``` +#[repr(C)] +#[derive(Clone)] +pub struct wasmer_value_t { + /// The value type. + pub tag: wasmer_value_tag, + + /// The value. + pub value: wasmer_value, +} + +impl From for Val { + fn from(v: wasmer_value_t) -> Self { + unsafe { + #[allow(unreachable_patterns, non_snake_case)] + match v { + wasmer_value_t { + tag: wasmer_value_tag::WASM_I32, + value: wasmer_value { I32 }, + } => Val::I32(I32), + wasmer_value_t { + tag: wasmer_value_tag::WASM_I64, + value: wasmer_value { I64 }, + } => Val::I64(I64), + wasmer_value_t { + tag: wasmer_value_tag::WASM_F32, + value: wasmer_value { F32 }, + } => Val::F32(F32), + wasmer_value_t { + tag: wasmer_value_tag::WASM_F64, + value: wasmer_value { F64 }, + } => Val::F64(F64), + _ => unreachable!("unknown WASM type"), + } + } + } +} + +impl From for wasmer_value_t { + fn from(val: Val) -> Self { + match val { + Val::I32(x) => wasmer_value_t { + tag: wasmer_value_tag::WASM_I32, + value: wasmer_value { I32: x }, + }, + Val::I64(x) => wasmer_value_t { + tag: wasmer_value_tag::WASM_I64, + value: wasmer_value { I64: x }, + }, + Val::F32(x) => wasmer_value_t { + tag: wasmer_value_tag::WASM_F32, + value: wasmer_value { F32: x }, + }, + Val::F64(x) => wasmer_value_t { + tag: wasmer_value_tag::WASM_F64, + value: wasmer_value { F64: x }, + }, + Val::V128(_) => unimplemented!("V128 not supported in C API"), + Val::AnyRef(_) => unimplemented!("AnyRef not supported in C API"), + Val::FuncRef(_) => unimplemented!("AnyFunc not supported in C API"), + } + } +} + +impl From for wasmer_value_tag { + fn from(ty: ValType) -> Self { + #[allow(unreachable_patterns)] + match ty { + ValType::I32 => wasmer_value_tag::WASM_I32, + ValType::I64 => wasmer_value_tag::WASM_I64, + ValType::F32 => wasmer_value_tag::WASM_F32, + ValType::F64 => wasmer_value_tag::WASM_F64, + ValType::V128 => unreachable!("V128 not supported in C API"), + ValType::AnyRef => unimplemented!("AnyRef not supported in C API"), + ValType::FuncRef => unimplemented!("FuncRef not supported in C API"), + } + } +} + +impl From for ValType { + fn from(v: wasmer_value_tag) -> Self { + #[allow(unreachable_patterns)] + match v { + wasmer_value_tag::WASM_I32 => ValType::I32, + wasmer_value_tag::WASM_I64 => ValType::I64, + wasmer_value_tag::WASM_F32 => ValType::F32, + wasmer_value_tag::WASM_F64 => ValType::F64, + _ => unreachable!("unknown WASM type"), + } + } +} + +impl From<&ValType> for wasmer_value_tag { + fn from(ty: &ValType) -> Self { + match *ty { + ValType::I32 => wasmer_value_tag::WASM_I32, + ValType::I64 => wasmer_value_tag::WASM_I64, + ValType::F32 => wasmer_value_tag::WASM_F32, + ValType::F64 => wasmer_value_tag::WASM_F64, + ValType::V128 => unimplemented!("V128 not supported in C API"), + ValType::AnyRef => unimplemented!("AnyRef not supported in C API"), + ValType::FuncRef => unimplemented!("FuncRef not supported in C API"), + } + } +} diff --git a/lib/c-api/tests/.gitignore b/lib/c-api/tests/.gitignore new file mode 100644 index 000000000..2b5e09c0f --- /dev/null +++ b/lib/c-api/tests/.gitignore @@ -0,0 +1,34 @@ +CMakeLists.txt.user +CMakeCache.txt +CMakeFiles +CMakeScripts +Testing +Makefile +cmake_install.cmake +install_manifest.txt +compile_commands.json +CTestTestfile.cmake +_deps +rust-build +test-context +test-exported-memory +test-exports +test-globals +test-import-function +test-import-trap +test-import-object +test-imports +test-instantiate +test-memory +test-module +test-module-exports +test-module-import-instantiate +test-module-imports +test-module-serialize +test-tables +test-validate +test-wasi-import-object +test-emscripten-import-object + +# ignore wasm-c-api binaries +wasm-c-api-* \ No newline at end of file diff --git a/lib/c-api/tests/CMakeLists.txt b/lib/c-api/tests/CMakeLists.txt new file mode 100644 index 000000000..6393cbe1f --- /dev/null +++ b/lib/c-api/tests/CMakeLists.txt @@ -0,0 +1,179 @@ +cmake_minimum_required (VERSION 2.6) +project (WasmerRuntimeCApiTests) + +add_executable(test-exported-memory test-exported-memory.c) +add_executable(test-exports test-exports.c) +add_executable(test-globals test-globals.c) +# functionality not yet implemented in wasmer reborn +#add_executable(test-import-function test-import-function.c) +add_executable(test-import-trap test-import-trap.c) +add_executable(test-imports test-imports.c) +add_executable(test-import-object test-import-object.c) +add_executable(test-instantiate test-instantiate.c) +add_executable(test-memory test-memory.c) +add_executable(test-module test-module.c) +add_executable(test-module-exports test-module-exports.c) +add_executable(test-module-imports test-module-imports.c) +add_executable(test-module-serialize test-module-serialize.c) +add_executable(test-tables test-tables.c) +add_executable(test-validate test-validate.c) +add_executable(test-context test-context.c) +add_executable(test-module-import-instantiate test-module-import-instantiate.c) + +# Wasm C API tests +# Commented out until we can find a solution to the exported function problem +#add_executable(wasm-c-api-hello wasm-c-api/example/hello.c) +#add_executable(wasm-c-api-memory wasm-c-api/example/memory.c) +#add_executable(wasm-c-api-global wasm-c-api/example/global.c) +#add_executable(wasm-c-api-serialize wasm-c-api/example/serialize.c) + +if (DEFINED WASI_TESTS) + add_executable(test-wasi-import-object test-wasi-import-object.c) +endif() + +if (DEFINED EMSCRIPTEN_TESTS) + add_executable(test-emscripten-import-object test-emscripten-import-object.c) +endif() + +include_directories(wasm-c-api/include) + + +find_library( + WASMER_LIB NAMES libwasmer_runtime_c_api.dylib libwasmer_runtime_c_api.so wasmer_runtime_c_api.dll + PATHS ${CMAKE_SOURCE_DIR}/../../../target/release/ +) + +if(NOT WASMER_LIB) + message(FATAL_ERROR "wasmer library not found") +endif() + +enable_testing() + +set( + COMPILER_OPTIONS + # Clang or gcc + $<$,$>: + "-Werror" > + # MSVC + $<$: + "/WX" > +) + +target_link_libraries(test-exported-memory general ${WASMER_LIB}) +target_compile_options(test-exported-memory PRIVATE ${COMPILER_OPTIONS}) +add_test(test-exported-memory test-exported-memory) + +target_link_libraries(test-exports general ${WASMER_LIB}) +target_compile_options(test-exports PRIVATE ${COMPILER_OPTIONS}) +add_test(test-exports test-exports) + +target_link_libraries(test-globals general ${WASMER_LIB}) +target_compile_options(test-globals PRIVATE ${COMPILER_OPTIONS}) +add_test(test-globals test-globals) + +# functionality not yet implemented in wasmer reborn +#target_link_libraries(test-import-function general ${WASMER_LIB}) +#target_compile_options(test-import-function PRIVATE ${COMPILER_OPTIONS}) +#add_test(test-import-function test-import-function) + +target_link_libraries(test-import-trap general ${WASMER_LIB}) +target_compile_options(test-import-trap PRIVATE ${COMPILER_OPTIONS}) +# TODO: reenable this test +#add_test(test-import-trap test-import-trap) + +target_link_libraries(test-imports general ${WASMER_LIB}) +target_compile_options(test-imports PRIVATE ${COMPILER_OPTIONS}) +# TODO: reenable this test +#add_test(test-imports test-imports) + +target_link_libraries(test-import-object general ${WASMER_LIB}) +target_compile_options(test-import-object PRIVATE ${COMPILER_OPTIONS}) +# TODO: reenable this test +#add_test(test-import-object test-import-object) + + +if (DEFINED WASI_TESTS) + target_link_libraries(test-wasi-import-object general ${WASMER_LIB}) + target_compile_options(test-wasi-import-object PRIVATE ${COMPILER_OPTIONS}) + # TODO: reenable this test + #add_test(test-wasi-import-object test-wasi-import-object) +endif() + +if (DEFINED EMSCRIPTEN_TESTS) + target_link_libraries(test-emscripten-import-object general ${WASMER_LIB}) + target_compile_options(test-emscripten-import-object PRIVATE ${COMPILER_OPTIONS}) + add_test(test-emscripten-import-object test-emscripten-import-object) +endif() + +target_link_libraries(test-instantiate general ${WASMER_LIB}) +target_compile_options(test-instantiate PRIVATE ${COMPILER_OPTIONS}) +add_test(test-instantiate test-instantiate) + +target_link_libraries(test-memory general ${WASMER_LIB}) +target_compile_options(test-memory PRIVATE ${COMPILER_OPTIONS}) +# TODO: reenable this test +#add_test(test-memory test-memory) + +target_link_libraries(test-module general ${WASMER_LIB}) +target_compile_options(test-module PRIVATE ${COMPILER_OPTIONS}) +add_test(test-module test-module) + +target_link_libraries(test-module-exports general ${WASMER_LIB}) +target_compile_options(test-module-exports PRIVATE ${COMPILER_OPTIONS}) +add_test(test-module-exports test-module-exports) + +target_link_libraries(test-module-imports general ${WASMER_LIB}) +target_compile_options(test-module-imports PRIVATE ${COMPILER_OPTIONS}) +add_test(test-module-imports test-module-imports) + +target_link_libraries(test-module-serialize general ${WASMER_LIB}) +target_compile_options(test-module-serialize PRIVATE ${COMPILER_OPTIONS}) +add_test(test-module-serialize test-module-serialize) + +target_link_libraries(test-tables general ${WASMER_LIB}) +target_compile_options(test-tables PRIVATE ${COMPILER_OPTIONS}) +add_test(test-tables test-tables) + +target_link_libraries(test-validate general ${WASMER_LIB}) +target_compile_options(test-validate PRIVATE ${COMPILER_OPTIONS}) +add_test(test-validate test-validate) + +target_link_libraries(test-context general ${WASMER_LIB}) +target_compile_options(test-context PRIVATE ${COMPILER_OPTIONS}) +# TODO: reenable this test +#add_test(test-context test-context) + +target_link_libraries(test-module-import-instantiate general ${WASMER_LIB}) +target_compile_options(test-module-import-instantiate PRIVATE ${COMPILER_OPTIONS}) +# TODO: reenable this test +#add_test(test-module-import-instantiate test-module-import-instantiate) + +# Commented out until we can find a solution to the exported function problem +# +#target_link_libraries(wasm-c-api-hello general ${WASMER_LIB}) +#target_compile_options(wasm-c-api-hello PRIVATE ${COMPILER_OPTIONS}) +#add_test(NAME wasm-c-api-hello +# COMMAND wasm-c-api-hello +# WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/wasm-c-api/example +#) +# +#target_link_libraries(wasm-c-api-memory general ${WASMER_LIB}) +#target_compile_options(wasm-c-api-memory PRIVATE ${COMPILER_OPTIONS}) +#add_test(NAME wasm-c-api-memory +# COMMAND wasm-c-api-memory +# WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/wasm-c-api/example +#) +# +#target_link_libraries(wasm-c-api-global general ${WASMER_LIB}) +#target_compile_options(wasm-c-api-global PRIVATE ${COMPILER_OPTIONS}) +#add_test(NAME wasm-c-api-global +# COMMAND wasm-c-api-global +# WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/wasm-c-api/example +#) +# +#target_link_libraries(wasm-c-api-serialize general ${WASMER_LIB}) +#target_compile_options(wasm-c-api-serialize PRIVATE ${COMPILER_OPTIONS}) +#add_test(NAME wasm-c-api-serialize +# COMMAND wasm-c-api-serialize +# WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/wasm-c-api/example +#) diff --git a/lib/c-api/tests/assets/README.md b/lib/c-api/tests/assets/README.md new file mode 100644 index 000000000..52e92c052 --- /dev/null +++ b/lib/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/c-api/tests/assets/emscripten_hello_world.c b/lib/c-api/tests/assets/emscripten_hello_world.c new file mode 100644 index 000000000..998892f20 --- /dev/null +++ b/lib/c-api/tests/assets/emscripten_hello_world.c @@ -0,0 +1,10 @@ +#include + +int main(int argc, char *argv[]) { + printf("Hello, world\n"); + for ( int i = 0; i < argc; ++i ) { + printf("Arg %d: '%s'\n", i, argv[i]); + } + return 0; +} + diff --git a/lib/c-api/tests/assets/emscripten_hello_world.wasm b/lib/c-api/tests/assets/emscripten_hello_world.wasm new file mode 100644 index 000000000..96100d4e4 Binary files /dev/null and b/lib/c-api/tests/assets/emscripten_hello_world.wasm differ diff --git a/lib/c-api/tests/assets/exports.rs b/lib/c-api/tests/assets/exports.rs new file mode 100644 index 000000000..9e55985cf --- /dev/null +++ b/lib/c-api/tests/assets/exports.rs @@ -0,0 +1,37 @@ +#[no_mangle] +pub extern "C" fn sum(x: i32, y: i32) -> i32 { + x + y +} + +#[no_mangle] +pub extern "C" fn arity_0() -> i32 { + 42 +} + +#[no_mangle] +pub extern "C" fn i32_i32(x: i32) -> i32 { + x +} + +#[no_mangle] +pub extern "C" fn i64_i64(x: i64) -> i64 { + x +} + +#[no_mangle] +pub extern "C" fn f32_f32(x: f32) -> f32 { + x +} + +#[no_mangle] +pub extern "C" fn f64_f64(x: f64) -> f64 { + x +} + +#[no_mangle] +pub extern "C" fn string() -> *const u8 { + b"Hello, World!\0".as_ptr() +} + +#[no_mangle] +pub extern "C" fn void() {} diff --git a/lib/c-api/tests/assets/exports.wasm b/lib/c-api/tests/assets/exports.wasm new file mode 100644 index 000000000..6bea6de33 Binary files /dev/null and b/lib/c-api/tests/assets/exports.wasm differ diff --git a/lib/c-api/tests/assets/extended_wasi.rs b/lib/c-api/tests/assets/extended_wasi.rs new file mode 100644 index 000000000..31c659f8e --- /dev/null +++ b/lib/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/c-api/tests/assets/extended_wasi.wasm b/lib/c-api/tests/assets/extended_wasi.wasm new file mode 100755 index 000000000..b8eedd722 Binary files /dev/null and b/lib/c-api/tests/assets/extended_wasi.wasm differ diff --git a/lib/c-api/tests/assets/hello_wasm.wasm b/lib/c-api/tests/assets/hello_wasm.wasm new file mode 100644 index 000000000..b2287be03 Binary files /dev/null and b/lib/c-api/tests/assets/hello_wasm.wasm differ diff --git a/lib/c-api/tests/assets/inc.wasm b/lib/c-api/tests/assets/inc.wasm new file mode 100644 index 000000000..9ad23b5e7 Binary files /dev/null and b/lib/c-api/tests/assets/inc.wasm differ diff --git a/lib/c-api/tests/assets/inc.wast b/lib/c-api/tests/assets/inc.wast new file mode 100644 index 000000000..36a3eecb1 --- /dev/null +++ b/lib/c-api/tests/assets/inc.wast @@ -0,0 +1,12 @@ +(module + (func $inc (import "env" "inc")) + (func $mul (import "env" "mul")) + (func $get (import "env" "get") (result i32)) + + (func (export "inc_and_get") (result i32) + call $inc + call $get) + + (func (export "mul_and_get") (result i32) + call $mul + call $get)) diff --git a/lib/c-api/tests/assets/return_hello.rs b/lib/c-api/tests/assets/return_hello.rs new file mode 100644 index 000000000..39e770193 --- /dev/null +++ b/lib/c-api/tests/assets/return_hello.rs @@ -0,0 +1,4 @@ +#[no_mangle] +pub extern "C" fn return_hello() -> *const u8 { + b"Hello, World!\0"[..].as_ptr() +} diff --git a/lib/c-api/tests/assets/return_hello.wasm b/lib/c-api/tests/assets/return_hello.wasm new file mode 100644 index 000000000..a7f79c376 Binary files /dev/null and b/lib/c-api/tests/assets/return_hello.wasm differ diff --git a/lib/c-api/tests/assets/sum.wasm b/lib/c-api/tests/assets/sum.wasm new file mode 100644 index 000000000..135da2604 Binary files /dev/null and b/lib/c-api/tests/assets/sum.wasm differ diff --git a/lib/c-api/tests/assets/wasm_sample_app.wasm b/lib/c-api/tests/assets/wasm_sample_app.wasm new file mode 100755 index 000000000..7c8c4b72a Binary files /dev/null and b/lib/c-api/tests/assets/wasm_sample_app.wasm differ diff --git a/lib/c-api/tests/runtime_c_api_tests.rs b/lib/c-api/tests/runtime_c_api_tests.rs new file mode 100644 index 000000000..e6e29863b --- /dev/null +++ b/lib/c-api/tests/runtime_c_api_tests.rs @@ -0,0 +1,60 @@ +use std::process::Command; + +#[test] +fn test_c_api() { + let project_tests_dir = concat!(env!("CARGO_MANIFEST_DIR"), "/tests"); + + let cmake_args = vec![ + ".", + #[cfg(feature = "wasi")] + "-DWASI_TESTS=ON", + #[cfg(feature = "emscripten")] + "-DEMSCRIPTEN_TESTS=ON", + // We need something like this to get this working on Windows, but this doesn't seem + // quite right -- perhaps it's double escaping the quotes? + #[cfg(target_os = "windows")] + r#"-G "MinGW Makefiles""#, + ]; + // we use -f so it doesn't fail if the file doesn't exist + run_command("rm", project_tests_dir, vec!["-f", "CMakeCache.txt"]); + run_command("cmake", project_tests_dir, cmake_args); + run_command("make", project_tests_dir, vec!["-Wdev", "-Werror=dev"]); + run_command("make", project_tests_dir, vec!["test", "ARGS=\"-V\""]); +} + +fn run_command(command_str: &str, dir: &str, args: Vec<&str>) { + println!("Running command: `{}` args: {:?}", command_str, args); + + let mut command = Command::new(command_str); + + command.args(&args); + + command.current_dir(dir); + + let result = command.output(); + + match result { + Ok(r) => { + println!("output:"); + + if let Some(code) = r.status.code() { + println!("status: {}", code); + } else { + println!("status: None"); + } + + println!("stdout:"); + println!("{}", String::from_utf8_lossy(&r.stdout[..])); + println!("stderr:"); + println!("{}", String::from_utf8_lossy(&r.stderr[..])); + + if r.status.success() { + assert!(true) + } else { + panic!("Command failed with exit status: {:?}", r.status); + } + } + + Err(e) => panic!("Command failed: {}", e), + } +} diff --git a/lib/c-api/tests/test-context.c b/lib/c-api/tests/test-context.c new file mode 100644 index 000000000..a8d0f7099 --- /dev/null +++ b/lib/c-api/tests/test-context.c @@ -0,0 +1,137 @@ +#include +#include "../wasmer.h" +#include +#include +#include + +typedef struct { + int32_t amount; + int32_t value; +} counter_data; + +typedef struct { + uint8_t* bytes; + long bytes_len; +} wasm_file_t; + +wasm_file_t read_wasm_file(const char* file_name) { + wasm_file_t wasm_file; + + FILE *file = fopen(file_name, "r"); + fseek(file, 0, SEEK_END); + wasm_file.bytes_len = ftell(file); + + wasm_file.bytes = malloc(wasm_file.bytes_len); + fseek(file, 0, SEEK_SET); + fread(wasm_file.bytes, 1, wasm_file.bytes_len, file); + fclose(file); + + return wasm_file; +} + +void inc_counter(wasmer_instance_context_t *ctx) { + counter_data* data = (counter_data*)wasmer_instance_context_data_get(ctx); + data->value = data->value + data->amount; +} + +void mul_counter(wasmer_instance_context_t *ctx) { + counter_data* data = (counter_data*)wasmer_instance_context_data_get(ctx); + data->value = data->value * data->amount; +} + +int32_t get_counter(wasmer_instance_context_t *ctx) { + counter_data* data = (counter_data*)wasmer_instance_context_data_get(ctx); + return data->value; +} + +counter_data *init_counter(int32_t value, int32_t amount) { + counter_data* counter = malloc(sizeof(counter_data)); + counter->value = value; + counter->amount = amount; + return counter; +} + +void assert_counter(wasmer_instance_t *instance, int32_t expected) { + wasmer_value_t result_one; + wasmer_value_t params[] = {}; + wasmer_value_t results[] = {result_one}; + + wasmer_result_t call1_result = wasmer_instance_call(instance, "inc_and_get", params, 0, results, 1); + printf("Call result: %d\n", call1_result); + printf("Result: %d\n", results[0].value.I32); + assert(results[0].value.I32 == expected); + assert(call1_result == WASMER_OK); + + const wasmer_instance_context_t *ctx = wasmer_instance_context_get(instance); + counter_data *cd = (counter_data*)wasmer_instance_context_data_get(ctx); + assert(cd->value == expected); +} + +wasmer_import_t create_import(char* module_name, char* import_name, wasmer_import_func_t *func) { + wasmer_import_t import; + wasmer_byte_array module_name_bytes; + wasmer_byte_array import_name_bytes; + + module_name_bytes.bytes = (const uint8_t *) module_name; + module_name_bytes.bytes_len = strlen(module_name); + + import_name_bytes.bytes = (const uint8_t *) import_name; + import_name_bytes.bytes_len = strlen(import_name); + + import.module_name = module_name_bytes; + import.import_name = import_name_bytes; + + import.tag = WASM_FUNCTION; + import.value.func = func; + + return import; +} + +int main() +{ + // Prepare Imports + wasmer_value_tag inc_params_sig[] = {}; + wasmer_value_tag inc_returns_sig[] = {}; + wasmer_import_func_t *inc_func = wasmer_import_func_new((void (*)(void *)) inc_counter, inc_params_sig, 0, inc_returns_sig, 0); + wasmer_import_t inc_import = create_import("env", "inc", inc_func); + + wasmer_value_tag mul_params_sig[] = {}; + wasmer_value_tag mul_returns_sig[] = {}; + wasmer_import_func_t *mul_func = wasmer_import_func_new((void (*)(void *)) mul_counter, mul_params_sig, 0, mul_returns_sig, 0); + wasmer_import_t mul_import = create_import("env", "mul", mul_func); + + wasmer_value_tag get_params_sig[] = {}; + wasmer_value_tag get_returns_sig[] = {WASM_I32}; + wasmer_import_func_t *get_func = wasmer_import_func_new((void (*)(void *)) get_counter, get_params_sig, 0, get_returns_sig, 1); + wasmer_import_t get_import = create_import("env", "get", get_func); + + wasmer_import_t imports[] = {inc_import, mul_import, get_import}; + + // Read the wasm file + wasm_file_t wasm_file = read_wasm_file("assets/inc.wasm"); + + // Instantiate instance + printf("Instantiating\n"); + wasmer_instance_t *instance = NULL; + wasmer_result_t instantiate_res = wasmer_instantiate(&instance, wasm_file.bytes, wasm_file.bytes_len, imports, 3); + printf("Compile result: %d\n", instantiate_res); + assert(instantiate_res == WASMER_OK); + + // Init counter + counter_data *counter = init_counter(2, 5); + wasmer_instance_context_data_set(instance, counter); + + // Run `instance.inc_and_get` and assert + assert_counter(instance, 7); + assert_counter(instance, 12); + assert_counter(instance, 17); + + // Clear resources + wasmer_import_func_destroy(inc_func); + wasmer_import_func_destroy(get_func); + wasmer_instance_destroy(instance); + free(counter); + free(wasm_file.bytes); + + return 0; +} diff --git a/lib/c-api/tests/test-emscripten-import-object.c b/lib/c-api/tests/test-emscripten-import-object.c new file mode 100644 index 000000000..bbc581db3 --- /dev/null +++ b/lib/c-api/tests/test-emscripten-import-object.c @@ -0,0 +1,138 @@ +#include +#include "../wasmer.h" +#include +#include +#include + +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() +{ + // Read the Wasm file bytes. + FILE *file = fopen("assets/emscripten_hello_world.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); + + // Set up data for Emscripten + wasmer_emscripten_globals_t *emscripten_globals = wasmer_emscripten_get_globals(module); + + if (!emscripten_globals) + { + print_wasmer_error(); + } + assert(emscripten_globals); + + // Create the Emscripten import object + wasmer_import_object_t *import_object = + wasmer_emscripten_generate_import_object(emscripten_globals); + + + // 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); + + // Set up emscripten to be called + wasmer_result_t setup_result = wasmer_emscripten_set_up(instance, emscripten_globals); + printf("Set up result: %d\n", setup_result); + + if (setup_result != WASMER_OK) + { + print_wasmer_error(); + } + assert(setup_result == WASMER_OK); + + + const char *emscripten_prog_name = "emscripten_test_program"; + const char *emscripten_first_arg = "--help"; + wasmer_byte_array args[] = { + { .bytes = (const uint8_t *) emscripten_prog_name, + .bytes_len = strlen(emscripten_prog_name) }, + { .bytes = (const uint8_t *) emscripten_first_arg, + .bytes_len = strlen(emscripten_first_arg) } + }; + int emscripten_argc = sizeof(args) / sizeof(args[0]); + + wasmer_result_t main_result = wasmer_emscripten_call_main(instance, args, emscripten_argc); + + printf("Main result: %d\n", main_result); + assert(main_result == WASMER_OK); + + 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_emscripten_destroy_globals(emscripten_globals); + wasmer_instance_destroy(instance); + wasmer_import_object_destroy(import_object); + wasmer_module_destroy(module); + + return 0; +} + diff --git a/lib/c-api/tests/test-exported-memory.c b/lib/c-api/tests/test-exported-memory.c new file mode 100644 index 000000000..895b5c27c --- /dev/null +++ b/lib/c-api/tests/test-exported-memory.c @@ -0,0 +1,74 @@ +#include +#include "../wasmer.h" +#include +#include +#include + +int main() +{ + // Read the wasm file bytes + FILE *file = fopen("assets/return_hello.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); + + // Instantiate the module. + wasmer_import_t imports[] = {}; + wasmer_instance_t *instance = NULL; + wasmer_result_t compile_result = wasmer_instantiate(&instance, bytes, len, imports, 0); + printf("Compile result: %d\n", compile_result); + assert(compile_result == WASMER_OK); + + // Call the `return_hello` function. + wasmer_value_t params[] = {}; + wasmer_value_t result; + wasmer_value_t results[] = {result}; + + wasmer_result_t call_result = wasmer_instance_call(instance, "return_hello", params, 0, results, 1); + printf("Call result: %d\n", call_result); + printf("Result: %d\n", results[0].value.I32); + assert(call_result == WASMER_OK); + assert(results[0].value.I32 == 1048576); + + // Get all exports. + wasmer_exports_t *exports = NULL; + wasmer_instance_exports(instance, &exports); + + int export_length = wasmer_exports_len(exports); + printf("exports_length: %d\n", export_length); + assert(export_length == 5); + + // Get the `memory` export. + wasmer_export_t *export = wasmer_exports_get(exports, 0); + wasmer_import_export_kind kind = wasmer_export_kind(export); + printf("Wasmer import export kind: %d (Memory is %d)\n", kind, WASM_MEMORY); + assert(kind == WASM_MEMORY); + + wasmer_byte_array export_name = wasmer_export_name(export); + printf("export_name: `%.*s`\n", export_name.bytes_len, export_name.bytes); + + // Cast the export into a memory. + wasmer_memory_t *memory; + wasmer_result_t export_to_memory_result = wasmer_export_to_memory(export, &memory); + printf("Export to memory result: %d\n", export_to_memory_result); + printf("Memory pointer: %p\n", memory); + assert(export_to_memory_result == WASMER_OK); + + uint32_t memory_length = wasmer_memory_length(memory); + assert(memory_length == 17); + + // Read the data from the memory. + uint8_t *memory_data = wasmer_memory_data(memory); + uint8_t *returned_string = memory_data + results[0].value.I32; + + printf("Returned string from Wasm: %s\n", returned_string); + assert(strcmp("Hello, World!", (const char *) returned_string) == 0); + + printf("Destroy instance\n"); + wasmer_instance_destroy(instance); + + return 0; +} diff --git a/lib/c-api/tests/test-exports.c b/lib/c-api/tests/test-exports.c new file mode 100644 index 000000000..e245414be --- /dev/null +++ b/lib/c-api/tests/test-exports.c @@ -0,0 +1,445 @@ +#include +#include +#include "../wasmer.h" +#include +#include +#include + +int main() +{ + // Read the WebAssembly bytes. + uint8_t *wasm_bytes = NULL; + long wasm_bytes_length = 0; + + { + FILE *file = fopen("assets/exports.wasm", "r"); + fseek(file, 0, SEEK_END); + wasm_bytes_length = ftell(file); + wasm_bytes = (uint8_t *) malloc(wasm_bytes_length); + fseek(file, 0, SEEK_SET); + fread(wasm_bytes, 1, wasm_bytes_length, file); + fclose(file); + } + + wasmer_import_t imports[] = {}; + wasmer_instance_t *instance = NULL; + wasmer_result_t compile_result = wasmer_instantiate(&instance, wasm_bytes, wasm_bytes_length, imports, 0); + + assert(compile_result == WASMER_OK); + + wasmer_exports_t *exports = NULL; + wasmer_instance_exports(instance, &exports); + + int exports_length = wasmer_exports_len(exports); + printf("Number of exports: %d\n", exports_length); + + { + printf("\nCheck the `sum` exported function\n"); + + wasmer_export_t *export = wasmer_exports_get(exports, 3); + wasmer_import_export_kind export_kind = wasmer_export_kind(export); + + assert(export_kind == WASM_FUNCTION); + + const wasmer_export_func_t *exported_function = wasmer_export_to_func(export); + + wasmer_byte_array name_bytes = wasmer_export_name(export); + + assert(name_bytes.bytes_len == sizeof("sum") - 1); + assert(memcmp(name_bytes.bytes, "sum", sizeof("sum") - 1) == 0); + + printf("Check arity\n"); + + uint32_t inputs_arity; + wasmer_export_func_params_arity(exported_function, &inputs_arity); + + uint32_t outputs_arity; + wasmer_export_func_returns_arity(exported_function, &outputs_arity); + + assert(inputs_arity == 2); + assert(outputs_arity == 1); + + printf("Check signature\n"); + + wasmer_value_tag *input_types = (wasmer_value_tag *) calloc(inputs_arity, sizeof(wasmer_value_tag)); + wasmer_export_func_params(exported_function, input_types, inputs_arity); + + assert(input_types[0] == WASM_I32); + assert(input_types[1] == WASM_I32); + + free(input_types); + + wasmer_value_tag *output_types = (wasmer_value_tag *) calloc(outputs_arity, sizeof(wasmer_value_tag)); + wasmer_export_func_returns(exported_function, output_types, outputs_arity); + + assert(output_types[0] == WASM_I32); + + free(output_types); + + printf("Call the exported function\n"); + + wasmer_value_t input_0; + input_0.tag = WASM_I32; + input_0.value.I32 = 7; + + wasmer_value_t input_1; + input_1.tag = WASM_I32; + input_1.value.I32 = 8; + + wasmer_value_t inputs[] = {input_0, input_1}; + + wasmer_value_t output_0; + wasmer_value_t outputs[] = {output_0}; + + wasmer_result_t call_result = wasmer_export_func_call(exported_function, inputs, inputs_arity, outputs, outputs_arity); + + printf("Call result: %d\n", call_result); + printf("Result: %d\n", outputs[0].value.I32); + + assert(outputs[0].value.I32 == 15); + assert(call_result == WASMER_OK); + } + + { + printf("\nCheck the `arity_0` exported function\n"); + + wasmer_export_t *export = wasmer_exports_get(exports, 4); + wasmer_import_export_kind export_kind = wasmer_export_kind(export); + + assert(export_kind == WASM_FUNCTION); + + const wasmer_export_func_t *exported_function = wasmer_export_to_func(export); + wasmer_byte_array name_bytes = wasmer_export_name(export); + + assert(name_bytes.bytes_len == sizeof("arity_0") - 1); + assert(memcmp(name_bytes.bytes, "arity_0", sizeof("arity_0") - 1) == 0); + + uint32_t inputs_arity; + wasmer_export_func_params_arity(exported_function, &inputs_arity); + + uint32_t outputs_arity; + wasmer_export_func_returns_arity(exported_function, &outputs_arity); + + assert(inputs_arity == 0); + assert(outputs_arity == 1); + + wasmer_value_tag *output_types = (wasmer_value_tag *) calloc(outputs_arity, sizeof(wasmer_value_tag)); + wasmer_export_func_returns(exported_function, output_types, outputs_arity); + + assert(output_types[0] == WASM_I32); + + free(output_types); + + wasmer_value_t inputs[] = {}; + + wasmer_value_t output_0; + wasmer_value_t outputs[] = {output_0}; + + wasmer_result_t call_result = wasmer_export_func_call(exported_function, inputs, inputs_arity, outputs, outputs_arity); + + printf("Result: %d\n", outputs[0].value.I32); + + assert(outputs[0].value.I32 == 42); + assert(call_result == WASMER_OK); + } + + { + printf("\nCheck the `i32_i32` exported function\n"); + + wasmer_export_t *export = wasmer_exports_get(exports, 5); + wasmer_import_export_kind export_kind = wasmer_export_kind(export); + + assert(export_kind == WASM_FUNCTION); + + const wasmer_export_func_t *exported_function = wasmer_export_to_func(export); + wasmer_byte_array name_bytes = wasmer_export_name(export); + + assert(name_bytes.bytes_len == sizeof("i32_i32") - 1); + assert(memcmp(name_bytes.bytes, "i32_i32", sizeof("i32_i32") - 1) == 0); + + uint32_t inputs_arity; + wasmer_export_func_params_arity(exported_function, &inputs_arity); + + uint32_t outputs_arity; + wasmer_export_func_returns_arity(exported_function, &outputs_arity); + + assert(inputs_arity == 1); + assert(outputs_arity == 1); + + wasmer_value_tag *input_types = (wasmer_value_tag *) calloc(inputs_arity, sizeof(wasmer_value_tag)); + wasmer_export_func_params(exported_function, input_types, inputs_arity); + + assert(input_types[0] == WASM_I32); + + free(input_types); + + wasmer_value_tag *output_types = (wasmer_value_tag *) calloc(outputs_arity, sizeof(wasmer_value_tag)); + wasmer_export_func_returns(exported_function, output_types, outputs_arity); + + assert(output_types[0] == WASM_I32); + + free(output_types); + + wasmer_value_t input_0; + input_0.tag = WASM_I32; + input_0.value.I32 = 7; + wasmer_value_t inputs[] = {input_0}; + + wasmer_value_t output_0; + wasmer_value_t outputs[] = {output_0}; + + wasmer_result_t call_result = wasmer_export_func_call(exported_function, inputs, inputs_arity, outputs, outputs_arity); + + printf("Result: %d\n", outputs[0].value.I32); + + assert(outputs[0].value.I32 == 7); + assert(call_result == WASMER_OK); + } + + { + printf("\nCheck the `i64_i64` exported function\n"); + + wasmer_export_t *export = wasmer_exports_get(exports, 6); + wasmer_import_export_kind export_kind = wasmer_export_kind(export); + + assert(export_kind == WASM_FUNCTION); + + const wasmer_export_func_t *exported_function = wasmer_export_to_func(export); + wasmer_byte_array name_bytes = wasmer_export_name(export); + + assert(name_bytes.bytes_len == sizeof("i64_i64") - 1); + assert(memcmp(name_bytes.bytes, "i64_i64", sizeof("i64_i64") - 1) == 0); + + uint32_t inputs_arity; + wasmer_export_func_params_arity(exported_function, &inputs_arity); + + uint32_t outputs_arity; + wasmer_export_func_returns_arity(exported_function, &outputs_arity); + + assert(inputs_arity == 1); + assert(outputs_arity == 1); + + wasmer_value_tag *input_types = (wasmer_value_tag *) calloc(inputs_arity, sizeof(wasmer_value_tag)); + wasmer_export_func_params(exported_function, input_types, inputs_arity); + + assert(input_types[0] == WASM_I64); + + free(input_types); + + wasmer_value_tag *output_types = (wasmer_value_tag *) calloc(outputs_arity, sizeof(wasmer_value_tag)); + wasmer_export_func_returns(exported_function, output_types, outputs_arity); + + assert(output_types[0] == WASM_I64); + + free(output_types); + + wasmer_value_t input_0; + input_0.tag = WASM_I64; + input_0.value.I64 = 7; + wasmer_value_t inputs[] = {input_0}; + + wasmer_value_t output_0; + wasmer_value_t outputs[] = {output_0}; + + wasmer_result_t call_result = wasmer_export_func_call(exported_function, inputs, inputs_arity, outputs, outputs_arity); + + printf("Result: %" PRId64 "\n", outputs[0].value.I64); + + assert(outputs[0].value.I64 == 7); + assert(call_result == WASMER_OK); + } + + { + printf("\nCheck the `f32_f32` exported function\n"); + + wasmer_export_t *export = wasmer_exports_get(exports, 7); + wasmer_import_export_kind export_kind = wasmer_export_kind(export); + + assert(export_kind == WASM_FUNCTION); + + const wasmer_export_func_t *exported_function = wasmer_export_to_func(export); + wasmer_byte_array name_bytes = wasmer_export_name(export); + + assert(name_bytes.bytes_len == sizeof("f32_f32") - 1); + assert(memcmp(name_bytes.bytes, "f32_f32", sizeof("f32_f32") - 1) == 0); + + uint32_t inputs_arity; + wasmer_export_func_params_arity(exported_function, &inputs_arity); + + uint32_t outputs_arity; + wasmer_export_func_returns_arity(exported_function, &outputs_arity); + + assert(inputs_arity == 1); + assert(outputs_arity == 1); + + wasmer_value_tag *input_types = (wasmer_value_tag *) calloc(inputs_arity, sizeof(wasmer_value_tag)); + wasmer_export_func_params(exported_function, input_types, inputs_arity); + + assert(input_types[0] == WASM_F32); + + free(input_types); + + wasmer_value_tag *output_types = (wasmer_value_tag *) calloc(outputs_arity, sizeof(wasmer_value_tag)); + wasmer_export_func_returns(exported_function, output_types, outputs_arity); + + assert(output_types[0] == WASM_F32); + + free(output_types); + + wasmer_value_t input_0; + input_0.tag = WASM_F32; + input_0.value.F32 = 7.42; + wasmer_value_t inputs[] = {input_0}; + + wasmer_value_t output_0; + wasmer_value_t outputs[] = {output_0}; + + wasmer_result_t call_result = wasmer_export_func_call(exported_function, inputs, inputs_arity, outputs, outputs_arity); + + printf("Result: %f\n", outputs[0].value.F32); + + assert(call_result == WASMER_OK); + } + + { + printf("\nCheck the `f64_f64` exported function\n"); + + wasmer_export_t *export = wasmer_exports_get(exports, 8); + wasmer_import_export_kind export_kind = wasmer_export_kind(export); + + assert(export_kind == WASM_FUNCTION); + + const wasmer_export_func_t *exported_function = wasmer_export_to_func(export); + wasmer_byte_array name_bytes = wasmer_export_name(export); + + assert(name_bytes.bytes_len == sizeof("f64_f64") - 1); + assert(memcmp(name_bytes.bytes, "f64_f64", sizeof("f64_f64") - 1) == 0); + + uint32_t inputs_arity; + wasmer_export_func_params_arity(exported_function, &inputs_arity); + + uint32_t outputs_arity; + wasmer_export_func_returns_arity(exported_function, &outputs_arity); + + assert(inputs_arity == 1); + assert(outputs_arity == 1); + + wasmer_value_tag *input_types = (wasmer_value_tag *) calloc(inputs_arity, sizeof(wasmer_value_tag)); + wasmer_export_func_params(exported_function, input_types, inputs_arity); + + assert(input_types[0] == WASM_F64); + + free(input_types); + + wasmer_value_tag *output_types = (wasmer_value_tag *) calloc(outputs_arity, sizeof(wasmer_value_tag)); + wasmer_export_func_returns(exported_function, output_types, outputs_arity); + + assert(output_types[0] == WASM_F64); + + free(output_types); + + wasmer_value_t input_0; + input_0.tag = WASM_F64; + input_0.value.F64 = 7.42; + wasmer_value_t inputs[] = {input_0}; + + wasmer_value_t output_0; + wasmer_value_t outputs[] = {output_0}; + + wasmer_result_t call_result = wasmer_export_func_call(exported_function, inputs, inputs_arity, outputs, outputs_arity); + + printf("Result: %f\n", outputs[0].value.F64); + + assert(call_result == WASMER_OK); + } + + { + printf("\nCheck the `string` exported function\n"); + + wasmer_export_t *export = wasmer_exports_get(exports, 9); + wasmer_import_export_kind export_kind = wasmer_export_kind(export); + + assert(export_kind == WASM_FUNCTION); + + const wasmer_export_func_t *exported_function = wasmer_export_to_func(export); + wasmer_byte_array name_bytes = wasmer_export_name(export); + + assert(name_bytes.bytes_len == sizeof("string") - 1); + assert(memcmp(name_bytes.bytes, "string", sizeof("string") - 1) == 0); + + uint32_t inputs_arity; + wasmer_export_func_params_arity(exported_function, &inputs_arity); + + uint32_t outputs_arity; + wasmer_export_func_returns_arity(exported_function, &outputs_arity); + + assert(inputs_arity == 0); + assert(outputs_arity == 1); + + wasmer_value_tag *output_types = (wasmer_value_tag *) calloc(outputs_arity, sizeof(wasmer_value_tag)); + wasmer_export_func_returns(exported_function, output_types, outputs_arity); + + assert(output_types[0] == WASM_I32); + + free(output_types); + + wasmer_value_t inputs[] = {}; + + wasmer_value_t output_0; + wasmer_value_t outputs[] = {output_0}; + + wasmer_result_t call_result = wasmer_export_func_call(exported_function, inputs, inputs_arity, outputs, outputs_arity); + + printf("Result: %d\n", outputs[0].value.I32); + + assert(outputs[0].value.I32 == 1048576); + assert(call_result == WASMER_OK); + } + + { + printf("\nCheck the `void` exported function\n"); + + wasmer_export_t *export = wasmer_exports_get(exports, 10); + wasmer_import_export_kind export_kind = wasmer_export_kind(export); + + assert(export_kind == WASM_FUNCTION); + + const wasmer_export_func_t *exported_function = wasmer_export_to_func(export); + wasmer_byte_array name_bytes = wasmer_export_name(export); + + assert(name_bytes.bytes_len == sizeof("void") - 1); + assert(memcmp(name_bytes.bytes, "void", sizeof("void") - 1) == 0); + + uint32_t inputs_arity; + wasmer_export_func_params_arity(exported_function, &inputs_arity); + + uint32_t outputs_arity; + wasmer_export_func_returns_arity(exported_function, &outputs_arity); + + assert(inputs_arity == 0); + assert(outputs_arity == 0); + + wasmer_value_t inputs[] = {}; + wasmer_value_t outputs[] = {}; + + { + wasmer_result_t call_result = wasmer_export_func_call(exported_function, inputs, inputs_arity, outputs, outputs_arity); + assert(call_result == WASMER_OK); + } + + { + wasmer_result_t call_result = wasmer_export_func_call(exported_function, NULL, inputs_arity, NULL, outputs_arity); + assert(call_result == WASMER_OK); + } + } + + printf("\nDestroy instance\n"); + + wasmer_instance_destroy(instance); + + printf("Destroy exports\n"); + + wasmer_exports_destroy(exports); + + return 0; +} diff --git a/lib/c-api/tests/test-globals.c b/lib/c-api/tests/test-globals.c new file mode 100644 index 000000000..a694d2e75 --- /dev/null +++ b/lib/c-api/tests/test-globals.c @@ -0,0 +1,30 @@ +#include +#include "../wasmer.h" +#include +#include + +int main() +{ + wasmer_value_t val; + val.tag = WASM_I32; + val.value.I32 = 7; + wasmer_global_t *global = wasmer_global_new(val, true); + + wasmer_value_t get_val = wasmer_global_get(global); + assert( get_val.value.I32 == 7); + + wasmer_value_t val2; + val2.tag = WASM_I32; + val2.value.I32 = 14; + wasmer_global_set(global, val2); + + wasmer_value_t new_get_val = wasmer_global_get(global); + assert( new_get_val.value.I32 == 14); + + wasmer_global_descriptor_t desc = wasmer_global_get_descriptor(global); + assert(desc.mutable_); + assert(desc.kind == WASM_I32); + + wasmer_global_destroy(global); + return 0; +} diff --git a/lib/c-api/tests/test-import-function-callinfo.c b/lib/c-api/tests/test-import-function-callinfo.c new file mode 100644 index 000000000..371f9eeba --- /dev/null +++ b/lib/c-api/tests/test-import-function-callinfo.c @@ -0,0 +1,132 @@ +#include +#include "../wasmer.h" +#include +#include +#include + +static bool print_str_called = false; +static int memory_len = 0; +static int ptr_len = 0; +static char actual_str[14] = {}; +static int actual_context_data_value = 0; + +typedef struct { + int value; +} context_data; + +struct print_str_context { + int call_count; +}; + +void print_str(struct print_str_context *local_context, uint64_t *args) +{ + local_context->call_count++; + + wasmer_instance_context_t *ctx = (void *) args[0]; + int32_t ptr = args[1]; + int32_t len = args[2]; + + 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); + for (int32_t idx = 0; idx < len; idx++) + { + actual_str[idx] = mem_bytes[ptr + idx]; + } + actual_str[13] = '\0'; + printf("In print_str, memory len: %d, ptr_len: %d\n, str %s", mem_len, len, actual_str); + print_str_called = true; + memory_len = mem_len; + ptr_len = len; + + actual_context_data_value = ((context_data *) wasmer_instance_context_data_get(ctx))->value; +} + +int main() +{ + wasmer_value_tag params_sig[] = {WASM_I32, WASM_I32}; + wasmer_value_tag returns_sig[] = {}; + struct print_str_context local_context = { + .call_count = 0 + }; + + printf("Creating trampoline buffer\n"); + wasmer_trampoline_buffer_builder_t *tbb = wasmer_trampoline_buffer_builder_new(); + unsigned long print_str_idx = wasmer_trampoline_buffer_builder_add_callinfo_trampoline( + tbb, + (wasmer_trampoline_callable_t *) print_str, + (void *) &local_context, + 3 + ); + wasmer_trampoline_buffer_t *tb = wasmer_trampoline_buffer_builder_build(tbb); + const wasmer_trampoline_callable_t *print_str_callable = wasmer_trampoline_buffer_get_trampoline(tb, print_str_idx); + + printf("Creating new func\n"); + wasmer_import_func_t *func = wasmer_import_func_new((void (*)(void *)) print_str_callable, params_sig, 2, returns_sig, 0); + wasmer_import_t import; + + 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); + 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); + + import.module_name = module_name_bytes; + import.import_name = import_name_bytes; + import.tag = WASM_FUNCTION; + import.value.func = func; + wasmer_import_t imports[] = {import}; + + // Read the wasm file bytes + FILE *file = fopen("assets/wasm_sample_app.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); + + printf("Instantiating\n"); + wasmer_instance_t *instance = NULL; + wasmer_result_t compile_result = wasmer_instantiate(&instance, bytes, len, imports, 1); + printf("Compile result: %d\n", compile_result); + + assert(compile_result == WASMER_OK); + + context_data* context_data = malloc(sizeof(context_data)); + int context_data_value = 42; + context_data->value = context_data_value; + wasmer_instance_context_data_set(instance, context_data); + + wasmer_value_t params[] = {}; + wasmer_value_t results[] = {}; + wasmer_result_t call_result = wasmer_instance_call(instance, "hello_wasm", params, 0, results, 0); + printf("Call result: %d\n", call_result); + + 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); + + assert(call_result == WASMER_OK); + + assert(print_str_called); + assert(memory_len == 17); + assert(ptr_len == 13); + assert(0 == strcmp(actual_str, "Hello, World!")); + assert(context_data_value == actual_context_data_value); + assert(local_context.call_count == 1); + + printf("Destroying trampoline buffer\n"); + wasmer_trampoline_buffer_destroy(tb); + printf("Destroying func\n"); + wasmer_import_func_destroy(func); + printf("Destroy instance\n"); + wasmer_instance_destroy(instance); + free(context_data); + return 0; +} diff --git a/lib/c-api/tests/test-import-function.c b/lib/c-api/tests/test-import-function.c new file mode 100644 index 000000000..a53dec75f --- /dev/null +++ b/lib/c-api/tests/test-import-function.c @@ -0,0 +1,128 @@ +#include +#include "../wasmer.h" +#include +#include +#include + +static bool print_str_called = false; +static int memory_len = 0; +static int ptr_len = 0; +static char actual_str[14] = {}; +static int actual_context_data_value = 0; + +typedef struct { + int value; +} context_data; + +struct print_str_context { + int call_count; +}; + +void print_str(wasmer_instance_context_t *ctx, int32_t ptr, int32_t len) +{ + struct print_str_context *local_context = wasmer_trampoline_get_context(); + local_context->call_count++; + + 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); + for (int32_t idx = 0; idx < len; idx++) + { + actual_str[idx] = mem_bytes[ptr + idx]; + } + actual_str[13] = '\0'; + printf("In print_str, memory len: %d, ptr_len: %d, str %s\n", mem_len, len, actual_str); + print_str_called = true; + memory_len = mem_len; + ptr_len = len; + + actual_context_data_value = ((context_data *) wasmer_instance_context_data_get(ctx))->value; +} + +int main() +{ + wasmer_value_tag params_sig[] = {WASM_I32, WASM_I32}; + wasmer_value_tag returns_sig[] = {}; + struct print_str_context local_context = { + .call_count = 0 + }; + + printf("Creating trampoline buffer\n"); + wasmer_trampoline_buffer_builder_t *tbb = wasmer_trampoline_buffer_builder_new(); + unsigned long print_str_idx = wasmer_trampoline_buffer_builder_add_context_trampoline( + tbb, + (wasmer_trampoline_callable_t *) print_str, + (void *) &local_context + ); + wasmer_trampoline_buffer_t *tb = wasmer_trampoline_buffer_builder_build(tbb); + const wasmer_trampoline_callable_t *print_str_callable = wasmer_trampoline_buffer_get_trampoline(tb, print_str_idx); + + printf("Creating new func\n"); + wasmer_import_func_t *func = wasmer_import_func_new((void (*)(void *)) print_str_callable, params_sig, 2, returns_sig, 0); + wasmer_import_t import; + + 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); + 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); + + import.module_name = module_name_bytes; + import.import_name = import_name_bytes; + import.tag = WASM_FUNCTION; + import.value.func = func; + wasmer_import_t imports[] = {import}; + + // Read the wasm file bytes + FILE *file = fopen("assets/wasm_sample_app.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); + + printf("Instantiating\n"); + wasmer_instance_t *instance = NULL; + wasmer_result_t compile_result = wasmer_instantiate(&instance, bytes, len, imports, 1); + printf("Compile result: %d\n", compile_result); + + assert(compile_result == WASMER_OK); + + context_data* context_data = malloc(sizeof(context_data)); + int context_data_value = 42; + context_data->value = context_data_value; + wasmer_instance_context_data_set(instance, context_data); + + wasmer_value_t params[] = {}; + wasmer_value_t results[] = {}; + wasmer_result_t call_result = wasmer_instance_call(instance, "hello_wasm", params, 0, results, 0); + printf("Call result: %d\n", call_result); + + 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); + + assert(call_result == WASMER_OK); + + assert(print_str_called); + assert(memory_len == 17); + assert(ptr_len == 13); + assert(0 == strcmp(actual_str, "Hello, World!")); + assert(context_data_value == actual_context_data_value); + assert(local_context.call_count == 1); + + printf("Destroying trampoline buffer\n"); + wasmer_trampoline_buffer_destroy(tb); + printf("Destroying func\n"); + wasmer_import_func_destroy(func); + printf("Destroy instance\n"); + wasmer_instance_destroy(instance); + free(context_data); + return 0; +} diff --git a/lib/c-api/tests/test-import-object.c b/lib/c-api/tests/test-import-object.c new file mode 100644 index 000000000..640f16640 --- /dev/null +++ b/lib/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/c-api/tests/test-import-trap.c b/lib/c-api/tests/test-import-trap.c new file mode 100644 index 000000000..0e28208ec --- /dev/null +++ b/lib/c-api/tests/test-import-trap.c @@ -0,0 +1,80 @@ +#include +#include "../wasmer.h" +#include +#include +#include + +static const char *trap_error_message = "Hello"; + +void print_str(wasmer_instance_context_t *ctx, int32_t _ptr, int32_t _len) +{ + wasmer_trap(ctx, trap_error_message); +} + +int main() +{ + wasmer_value_tag params_sig[] = {WASM_I32, WASM_I32}; + wasmer_value_tag returns_sig[] = {}; + + printf("Creating new func\n"); + wasmer_import_func_t *func = wasmer_import_func_new((void (*)(void *)) print_str, params_sig, 2, returns_sig, 0); + + char *module_name = "env"; + wasmer_byte_array module_name_bytes = { + .bytes = (const uint8_t *) module_name, + .bytes_len = strlen(module_name), + }; + + char *import_name = "print_str"; + wasmer_byte_array import_name_bytes = { + .bytes = (const uint8_t *) import_name, + .bytes_len = strlen(import_name), + }; + + wasmer_import_t import = { + .module_name = module_name_bytes, + .import_name = import_name_bytes, + .tag = WASM_FUNCTION, + .value.func = func, + }; + + wasmer_import_t imports[] = {import}; + + // Read the wasm file bytes + FILE *file = fopen("assets/wasm_sample_app.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); + + printf("Instantiating\n"); + wasmer_instance_t *instance = NULL; + wasmer_result_t compile_result = wasmer_instantiate(&instance, bytes, len, imports, 1); + printf("Compile result: %d\n", compile_result); + + assert(compile_result == WASMER_OK); + + wasmer_value_t params[] = {}; + wasmer_value_t results[] = {}; + wasmer_result_t call_result = wasmer_instance_call(instance, "hello_wasm", params, 0, results, 0); + printf("Call result: %d\n", call_result); + + assert(call_result == 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); + + assert(0 == strcmp(error_str, "Call error: \"Hello\"")); + + printf("Destroying func\n"); + wasmer_import_func_destroy(func); + printf("Destroy instance\n"); + wasmer_instance_destroy(instance); + + return 0; +} diff --git a/lib/c-api/tests/test-imports.c b/lib/c-api/tests/test-imports.c new file mode 100644 index 000000000..4713719ed --- /dev/null +++ b/lib/c-api/tests/test-imports.c @@ -0,0 +1,155 @@ +#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 array containing our imports + wasmer_import_t imports[] = {func_import, global_import, memory_import, table_import}; + + // 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); + + // Creates a WebAssembly Instance from wasm bytes and imports + wasmer_instance_t *instance = NULL; + wasmer_result_t compile_result = wasmer_instantiate(&instance, bytes, len, imports, 4); + printf("Compile result: %d\n", compile_result); + if (compile_result != WASMER_OK) + { + print_wasmer_error(); + } + assert(compile_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); + + return 0; +} diff --git a/lib/c-api/tests/test-instantiate.c b/lib/c-api/tests/test-instantiate.c new file mode 100644 index 000000000..e6fe9ce5e --- /dev/null +++ b/lib/c-api/tests/test-instantiate.c @@ -0,0 +1,59 @@ +#include +#include "../wasmer.h" +#include +#include +#include + +int main() +{ + // Read the wasm file bytes + FILE *file = fopen("assets/sum.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_import_t imports[] = {}; + wasmer_instance_t *instance = NULL; + wasmer_result_t compile_result = wasmer_instantiate(&instance, bytes, len, imports, 0); + printf("Compile result: %d\n", compile_result); + assert(compile_result == WASMER_OK); + + wasmer_value_t param_one = { + .tag = WASM_I32, + .value.I32 = 7, + }; + wasmer_value_t param_two = { + .tag = WASM_I32, + .value.I32 = 8, + }; + wasmer_value_t params[] = {param_one, param_two}; + + wasmer_value_t result_one; + wasmer_value_t results[] = {result_one}; + + wasmer_result_t call_result = wasmer_instance_call(instance, "sum", params, 2, results, 1); + printf("Call result: %d\n", call_result); + printf("Result: %d\n", results[0].value.I32); + assert(results[0].value.I32 == 15); + assert(call_result == WASMER_OK); + + wasmer_result_t call_result2 = wasmer_instance_call(instance, "sum", params, 1, results, 1); + printf("Call result bad: %d\n", call_result2); + assert(call_result2 == WASMER_ERROR); + + int error_len = wasmer_last_error_length(); + printf("Error len: `%d`\n", error_len); + char *error_str = malloc(error_len); + int error_result = wasmer_last_error_message(error_str, error_len); + assert(error_len == error_result); + printf("Error str: `%s`\n", error_str); + assert(0 == strcmp(error_str, "RuntimeError: expected 2 arguments, got 1: Parameters of type [I32] did not match signature [I32, I32] -> [I32]")); + free(error_str); + + printf("Destroy instance\n"); + wasmer_instance_destroy(instance); + return 0; +} diff --git a/lib/c-api/tests/test-memory.c b/lib/c-api/tests/test-memory.c new file mode 100644 index 000000000..a2aca65a8 --- /dev/null +++ b/lib/c-api/tests/test-memory.c @@ -0,0 +1,67 @@ +#include +#include "../wasmer.h" +#include +#include +#include + +int main() +{ + wasmer_memory_t *memory = NULL; + wasmer_limits_t descriptor = { + .min = 10, + .max = { + .has_some = true, + .some = 15, + }, + }; + wasmer_result_t memory_result = wasmer_memory_new(&memory, descriptor); + printf("Memory result: %d\n", memory_result); + assert(memory_result == WASMER_OK); + + uint32_t len = wasmer_memory_length(memory); + printf("Memory pages length: %d\n", len); + assert(len == 10); + + wasmer_result_t grow_result = wasmer_memory_grow(memory, 2); + assert(grow_result == WASMER_OK); + + uint32_t new_len = wasmer_memory_length(memory); + printf("Memory pages length: %d\n", new_len); + assert(new_len == 12); + + uint32_t bytes_len = wasmer_memory_data_length(memory); + printf("Memory bytes length: %d\n", bytes_len); + assert(bytes_len == 12 * 65536); + + // Err, grow beyond max + wasmer_result_t grow_result2 = wasmer_memory_grow(memory, 10); + assert(grow_result2 == WASMER_ERROR); + int error_len = wasmer_last_error_length(); + char *error_str = malloc(error_len); + wasmer_last_error_message(error_str, error_len); + printf("Error str: `%s`\n", error_str); + assert(0 == strcmp(error_str, "Failed to add pages because would exceed maximum number of pages for the memory. Left: 22, Added: 15")); + free(error_str); + + wasmer_memory_t *bad_memory = NULL; + wasmer_limits_t bad_descriptor; + bad_descriptor.min = 15; + wasmer_limit_option_t max2; + max2.has_some = true; + max2.some = 10; + bad_descriptor.max = max2; + wasmer_result_t bad_memory_result = wasmer_memory_new(&bad_memory, bad_descriptor); + printf("Bad memory result: %d\n", bad_memory_result); + assert(bad_memory_result == WASMER_ERROR); + + int error_len2 = wasmer_last_error_length(); + char *error_str2 = malloc(error_len2); + wasmer_last_error_message(error_str2, error_len2); + printf("Error str 2: `%s`\n", error_str2); + assert(0 == strcmp(error_str2, "Unable to create because the supplied descriptor is invalid: \"Max number of memory pages is less than the minimum number of pages\"")); + free(error_str2); + + printf("Destroy memory\n"); + wasmer_memory_destroy(memory); + return 0; +} diff --git a/lib/c-api/tests/test-module-exports.c b/lib/c-api/tests/test-module-exports.c new file mode 100644 index 000000000..f290ef45b --- /dev/null +++ b/lib/c-api/tests/test-module-exports.c @@ -0,0 +1,53 @@ +#include +#include "../wasmer.h" +#include +#include + +int main() +{ + // Read the wasm file bytes + FILE *file = fopen("assets/sum.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; + wasmer_result_t compile_result = wasmer_compile(&module, bytes, len); + printf("Compile result: %d\n", compile_result); + assert(compile_result == WASMER_OK); + + wasmer_import_t imports[] = {}; + wasmer_instance_t *instance = NULL; + wasmer_result_t instantiate_result = wasmer_module_instantiate(module, &instance, imports, 0); + printf("Instantiate result: %d\n", compile_result); + assert(instantiate_result == WASMER_OK); + + wasmer_export_descriptors_t *exports = NULL; + wasmer_export_descriptors(module, &exports); + + int exports_len = wasmer_export_descriptors_len(exports); + printf("exports_len: %d\n", exports_len); + assert(exports_len == 1); + + wasmer_export_descriptor_t *export = wasmer_export_descriptors_get(exports, 0); + + wasmer_import_export_kind kind = wasmer_export_descriptor_kind(export); + assert(kind == WASM_FUNCTION); + + wasmer_byte_array name_bytes = wasmer_export_descriptor_name(export); + assert(name_bytes.bytes_len == 3); + char expected[] = {'s', 'u', 'm'}; + for(int idx = 0; idx < 3; idx++){ + printf("%c\n", name_bytes.bytes[idx]); + assert(name_bytes.bytes[idx] == expected[idx]); + } + + printf("Destroy module\n"); + wasmer_module_destroy(module); + printf("Destroy exports\n"); + wasmer_export_descriptors_destroy(exports); + return 0; +} diff --git a/lib/c-api/tests/test-module-import-instantiate.c b/lib/c-api/tests/test-module-import-instantiate.c new file mode 100644 index 000000000..9649335e8 --- /dev/null +++ b/lib/c-api/tests/test-module-import-instantiate.c @@ -0,0 +1,149 @@ +#include +#include "../wasmer.h" +#include +#include +#include + +typedef struct { + int32_t amount; + int32_t value; +} counter_data; + +typedef struct { + uint8_t* bytes; + long bytes_len; +} wasm_file_t; + +wasm_file_t read_wasm_file(const char* file_name) { + wasm_file_t wasm_file; + + FILE *file = fopen(file_name, "r"); + fseek(file, 0, SEEK_END); + wasm_file.bytes_len = ftell(file); + + wasm_file.bytes = malloc(wasm_file.bytes_len); + fseek(file, 0, SEEK_SET); + fread(wasm_file.bytes, 1, wasm_file.bytes_len, file); + fclose(file); + + return wasm_file; +} + +void inc_counter(wasmer_instance_context_t *ctx) { + counter_data* data = (counter_data*)wasmer_instance_context_data_get(ctx); + data->value = data->value + data->amount; +} + +void mul_counter(wasmer_instance_context_t *ctx) { + counter_data* data = (counter_data*)wasmer_instance_context_data_get(ctx); + data->value = data->value * data->amount; +} + +int32_t get_counter(wasmer_instance_context_t *ctx) { + counter_data* data = (counter_data*)wasmer_instance_context_data_get(ctx); + return data->value; +} + +counter_data *init_counter(int32_t value, int32_t amount) { + counter_data* counter = malloc(sizeof(counter_data)); + counter->value = value; + counter->amount = amount; + return counter; +} + +wasmer_import_t create_import(char* module_name, char* import_name, wasmer_import_func_t *func) { + wasmer_import_t import; + wasmer_byte_array module_name_bytes; + wasmer_byte_array import_name_bytes; + + module_name_bytes.bytes = (const uint8_t *) module_name; + module_name_bytes.bytes_len = strlen(module_name); + + import_name_bytes.bytes = (const uint8_t *) import_name; + import_name_bytes.bytes_len = strlen(import_name); + + import.module_name = module_name_bytes; + import.import_name = import_name_bytes; + + import.tag = WASM_FUNCTION; + import.value.func = func; + + return import; +} + +int main() +{ + // Prepare Imports + wasmer_value_tag inc_params_sig[] = {}; + wasmer_value_tag inc_returns_sig[] = {}; + wasmer_import_func_t *inc_func = wasmer_import_func_new((void (*)(void *)) inc_counter, inc_params_sig, 0, inc_returns_sig, 0); + wasmer_import_t inc_import = create_import("env", "inc", inc_func); + + wasmer_value_tag mul_params_sig[] = {}; + wasmer_value_tag mul_returns_sig[] = {}; + wasmer_import_func_t *mul_func = wasmer_import_func_new((void (*)(void *)) mul_counter, mul_params_sig, 0, mul_returns_sig, 0); + wasmer_import_t mul_import = create_import("env", "mul", mul_func); + + wasmer_value_tag get_params_sig[] = {}; + wasmer_value_tag get_returns_sig[] = {WASM_I32}; + wasmer_import_func_t *get_func = wasmer_import_func_new((void (*)(void *)) get_counter, get_params_sig, 0, get_returns_sig, 1); + wasmer_import_t get_import = create_import("env", "get", get_func); + + // Read the wasm file + wasm_file_t wasm_file = read_wasm_file("assets/inc.wasm"); + + // Compile module + wasmer_module_t *module = NULL; + wasmer_result_t compile_res = wasmer_compile(&module, wasm_file.bytes, wasm_file.bytes_len); + assert(compile_res == WASMER_OK); + + // Prepare Import Object + wasmer_import_object_t *import_object = wasmer_import_object_new(); + + // First, we import `inc_counter` and `mul_counter` + wasmer_import_t imports[] = {inc_import, mul_import}; + wasmer_result_t extend_res = wasmer_import_object_extend(import_object, imports, 2); + assert(extend_res == WASMER_OK); + + // Now, we'll import `inc_counter` and `mul_counter` + wasmer_import_t more_imports[] = {get_import}; + wasmer_result_t extend_res2 = wasmer_import_object_extend(import_object, more_imports, 1); + assert(extend_res2 == WASMER_OK); + + // Same `wasmer_import_object_extend` as the first, doesn't affect anything + wasmer_result_t extend_res3 = wasmer_import_object_extend(import_object, imports, 2); + assert(extend_res3 == WASMER_OK); + + // Instantiate instance + printf("Instantiating\n"); + wasmer_instance_t *instance = NULL; + wasmer_result_t instantiate_res = wasmer_module_import_instantiate(&instance, module, import_object); + printf("Compile result: %d\n", instantiate_res); + assert(instantiate_res == WASMER_OK); + + // Init counter + counter_data *counter = init_counter(2, 5); + wasmer_instance_context_data_set(instance, counter); + + wasmer_value_t result_one; + wasmer_value_t params[] = {}; + wasmer_value_t results[] = {result_one}; + + wasmer_result_t call1_result = wasmer_instance_call(instance, "inc_and_get", params, 0, results, 1); + printf("Call result: %d\n", call1_result); + printf("Result: %d\n", results[0].value.I32); + + wasmer_result_t call2_result = wasmer_instance_call(instance, "mul_and_get", params, 0, results, 1); + printf("Call result: %d\n", call2_result); + printf("Result: %d\n", results[0].value.I32); + + // Clear resources + wasmer_import_func_destroy(inc_func); + wasmer_import_func_destroy(mul_func); + wasmer_import_func_destroy(get_func); + wasmer_instance_destroy(instance); + free(counter); + free(wasm_file.bytes); + + return 0; +} diff --git a/lib/c-api/tests/test-module-imports.c b/lib/c-api/tests/test-module-imports.c new file mode 100644 index 000000000..532005928 --- /dev/null +++ b/lib/c-api/tests/test-module-imports.c @@ -0,0 +1,56 @@ +#include +#include "../wasmer.h" +#include +#include + +int main() +{ + // Read the wasm file bytes + FILE *file = fopen("assets/wasm_sample_app.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; + wasmer_result_t compile_result = wasmer_compile(&module, bytes, len); + printf("Compile result: %d\n", compile_result); + assert(compile_result == WASMER_OK); + + wasmer_import_descriptors_t *imports = NULL; + wasmer_import_descriptors(module, &imports); + + int imports_len = wasmer_import_descriptors_len(imports); + printf("imports_len: %d\n", imports_len); + assert(imports_len == 1); + + wasmer_import_descriptor_t *import = wasmer_import_descriptors_get(imports, 0); + + wasmer_import_export_kind kind = wasmer_import_descriptor_kind(import); + assert(kind == WASM_FUNCTION); + + wasmer_byte_array name_bytes = wasmer_import_descriptor_name(import); + assert(name_bytes.bytes_len == 9); + char expected[] = {'p', 'r', 'i', 'n', 't', '_', 's', 't', 'r'}; + + for(int idx = 0; idx < 9; idx++){ + printf("%c\n", name_bytes.bytes[idx]); + assert(name_bytes.bytes[idx] == expected[idx]); + } + + wasmer_byte_array module_name_bytes = wasmer_import_descriptor_module_name(import); + assert(module_name_bytes.bytes_len == 3); + char module_expected[] = {'e', 'n', 'v'}; + for(int idx = 0; idx < 3; idx++){ + printf("%c\n", module_name_bytes.bytes[idx]); + assert(module_name_bytes.bytes[idx] == module_expected[idx]); + } + + printf("Destroy module\n"); + wasmer_module_destroy(module); + printf("Destroy imports\n"); + wasmer_import_descriptors_destroy(imports); + return 0; +} diff --git a/lib/c-api/tests/test-module-serialize.c b/lib/c-api/tests/test-module-serialize.c new file mode 100644 index 000000000..56b5834af --- /dev/null +++ b/lib/c-api/tests/test-module-serialize.c @@ -0,0 +1,98 @@ +#include +#include "../wasmer.h" +#include +#include + +int main() +{ + // Read the wasm file bytes + FILE *file = fopen("assets/sum.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_one = NULL; + wasmer_result_t compile_result = wasmer_compile(&module_one, bytes, len); + printf("Compile result: %d\n", compile_result); + assert(compile_result == WASMER_OK); + + wasmer_serialized_module_t *serialized_module = NULL; + wasmer_result_t serialize_result = wasmer_module_serialize(&serialized_module, module_one); + printf("Serialize result: %d\n", serialize_result); + assert(serialize_result == WASMER_OK); + + wasmer_byte_array serialized_module_bytes = wasmer_serialized_module_bytes(serialized_module); + printf("Serialized module pointer: %p\n", serialized_module_bytes.bytes); + printf("Serialized module length: %d\n", serialized_module_bytes.bytes_len); + assert(serialized_module_bytes.bytes != NULL); + assert(serialized_module_bytes.bytes_len > 11); + assert(serialized_module_bytes.bytes[0] == '\0'); + assert(serialized_module_bytes.bytes[1] == 'w'); + assert(serialized_module_bytes.bytes[2] == 'a'); + assert(serialized_module_bytes.bytes[3] == 's'); + assert(serialized_module_bytes.bytes[4] == 'm'); + assert(serialized_module_bytes.bytes[5] == 'e'); + assert(serialized_module_bytes.bytes[6] == 'r'); + assert(serialized_module_bytes.bytes[7] == '-'); + assert(serialized_module_bytes.bytes[8] == 'j'); + assert(serialized_module_bytes.bytes[9] == 'i'); + assert(serialized_module_bytes.bytes[10] == 't'); + + wasmer_module_t *module_two = NULL; + wasmer_result_t unserialize_result = wasmer_module_deserialize(&module_two, serialized_module); + assert(unserialize_result == WASMER_OK); + + wasmer_import_t imports[] = {}; + wasmer_instance_t *instance = NULL; + wasmer_result_t instantiate_result = wasmer_module_instantiate(module_two, &instance, imports, 0); + printf("Instantiate result: %d\n", compile_result); + assert(instantiate_result == WASMER_OK); + + wasmer_value_t param_one; + param_one.tag = WASM_I32; + param_one.value.I32 = 7; + wasmer_value_t param_two; + param_two.tag = WASM_I32; + param_two.value.I32 = 8; + wasmer_value_t params[] = {param_one, param_two}; + + wasmer_value_t result_one; + wasmer_value_t results[] = {result_one}; + + wasmer_result_t call_result = wasmer_instance_call(instance, "sum", params, 2, results, 1); + printf("Call result: %d\n", call_result); + printf("Result: %d\n", results[0].value.I32); + assert(results[0].value.I32 == 15); + assert(call_result == WASMER_OK); + + wasmer_serialized_module_t *serialized_module_two = NULL; + wasmer_result_t serialized_module_from_bytes_result = wasmer_serialized_module_from_bytes( + &serialized_module_two, + serialized_module_bytes.bytes, + serialized_module_bytes.bytes_len + ); + assert(serialized_module_from_bytes_result == WASMER_OK); + + wasmer_module_t *module_three = NULL; + wasmer_result_t unserialized_result_two = wasmer_module_deserialize(&module_three, serialized_module_two); + assert(unserialized_result_two == WASMER_OK); + + wasmer_instance_t *instance_two = NULL; + wasmer_result_t instantiate_result_two = wasmer_module_instantiate(module_three, &instance, imports, 0); + assert(instantiate_result_two == WASMER_OK); + + printf("Destroy the serialized module\n"); + wasmer_serialized_module_destroy(serialized_module); + wasmer_serialized_module_destroy(serialized_module_two); + + printf("Destroy instance\n"); + wasmer_instance_destroy(instance); + + printf("Destroy modules\n"); + wasmer_module_destroy(module_one); + wasmer_module_destroy(module_two); + return 0; +} diff --git a/lib/c-api/tests/test-module.c b/lib/c-api/tests/test-module.c new file mode 100644 index 000000000..a3f21f6a1 --- /dev/null +++ b/lib/c-api/tests/test-module.c @@ -0,0 +1,51 @@ +#include +#include "../wasmer.h" +#include +#include + +int main() +{ + // Read the wasm file bytes + FILE *file = fopen("assets/sum.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; + wasmer_result_t compile_result = wasmer_compile(&module, bytes, len); + printf("Compile result: %d\n", compile_result); + assert(compile_result == WASMER_OK); + + wasmer_import_t imports[] = {}; + wasmer_instance_t *instance = NULL; + wasmer_result_t instantiate_result = wasmer_module_instantiate(module, &instance, imports, 0); + printf("Instantiate result: %d\n", compile_result); + assert(instantiate_result == WASMER_OK); + + wasmer_value_t param_one; + param_one.tag = WASM_I32; + param_one.value.I32 = 7; + wasmer_value_t param_two; + param_two.tag = WASM_I32; + param_two.value.I32 = 8; + wasmer_value_t params[] = {param_one, param_two}; + + wasmer_value_t result_one; + wasmer_value_t results[] = {result_one}; + + wasmer_result_t call_result = wasmer_instance_call(instance, "sum", params, 2, results, 1); + printf("Call result: %d\n", call_result); + printf("Result: %d\n", results[0].value.I32); + assert(results[0].value.I32 == 15); + assert(call_result == WASMER_OK); + + printf("Destroy instance\n"); + wasmer_instance_destroy(instance); + + printf("Destroy module\n"); + wasmer_module_destroy(module); + return 0; +} diff --git a/lib/c-api/tests/test-tables.c b/lib/c-api/tests/test-tables.c new file mode 100644 index 000000000..ac093d182 --- /dev/null +++ b/lib/c-api/tests/test-tables.c @@ -0,0 +1,51 @@ +#include +#include "../wasmer.h" +#include +#include + +int main() +{ + wasmer_table_t *table = NULL; + wasmer_limits_t descriptor; + descriptor.min = 10; + wasmer_limit_option_t max; + // max.has_some = false; + max.has_some = true; + max.some = 15; + descriptor.max = max; + wasmer_result_t table_result = wasmer_table_new(&table, descriptor); + printf("Table result: %d\n", table_result); + assert(table_result == WASMER_OK); + + uint32_t len = wasmer_table_length(table); + printf("Table length: %d\n", len); + assert(len == 10); + + wasmer_result_t grow_result1 = wasmer_table_grow(table, 5); + assert(grow_result1 == WASMER_OK); + uint32_t len_grow1 = wasmer_table_length(table); + printf("Table length: %d\n", len_grow1); + assert(len_grow1 == 15); + + // Try to grow beyond max + wasmer_result_t grow_result2 = wasmer_table_grow(table, 1); + assert(grow_result2 == WASMER_ERROR); + uint32_t len_grow2 = wasmer_table_length(table); + printf("Table length: %d\n", len_grow2); + assert(len_grow2 == 15); + + wasmer_table_t *table_bad = NULL; + wasmer_limits_t bad_descriptor; + bad_descriptor.min = 15; + wasmer_limit_option_t max2; + max2.has_some = true; + max2.some = 10; + bad_descriptor.max = max2; + wasmer_result_t table_bad_result = wasmer_table_new(&table_bad, bad_descriptor); + printf("Table result: %d\n", table_bad_result); + assert(table_bad_result == WASMER_ERROR); + + printf("Destroy table\n"); + wasmer_table_destroy(table); + return 0; +} diff --git a/lib/c-api/tests/test-validate.c b/lib/c-api/tests/test-validate.c new file mode 100644 index 000000000..8ce135d82 --- /dev/null +++ b/lib/c-api/tests/test-validate.c @@ -0,0 +1,22 @@ +#include +#include "../wasmer.h" +#include +#include + +int main() +{ + // Read the wasm file bytes + FILE *file = fopen("assets/sum.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); + + bool result = wasmer_validate(bytes, len); + printf("Result: %d", result); + assert(result); + + return 0; +} diff --git a/lib/c-api/tests/test-wasi-import-object.c b/lib/c-api/tests/test-wasi-import-object.c new file mode 100644 index 000000000..da9484996 --- /dev/null +++ b/lib/c-api/tests/test-wasi-import-object.c @@ -0,0 +1,260 @@ +#include +#include "../wasmer.h" +#include +#include +#include + +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]); + + // 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); + + // Detect the WASI version if any. This step is not mandatory, we + // use it to test the WASI version API. + Version wasi_version = wasmer_wasi_get_version(module); + + printf("WASI version: %d\n", wasi_version); + + // Create the WASI import object + wasmer_import_object_t *import_object = + wasmer_wasi_generate_import_object_for_version(wasi_version, + args, wasi_argc, + envs, wasi_env_len, + NULL, 0, + mapped_dirs, mapped_dir_len); + + // Create our imports + wasmer_import_t imports[] = {func_import, global_import, memory_import, table_import}; + int imports_len = sizeof(imports) / sizeof(imports[0]); + // Add our imports to the import object + wasmer_import_object_extend(import_object, imports, imports_len); + + // Instantiatoe the module with our import_object + wasmer_instance_t *instance = NULL; + wasmer_result_t instantiate_result = wasmer_module_import_instantiate(&instance, module, import_object); + printf("Instantiate result: %d\n", instantiate_result); + + if (instantiate_result != WASMER_OK) + { + print_wasmer_error(); + } + 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; +} + diff --git a/lib/c-api/tests/wasm-c-api/.gitignore b/lib/c-api/tests/wasm-c-api/.gitignore new file mode 100644 index 000000000..ad0095aaf --- /dev/null +++ b/lib/c-api/tests/wasm-c-api/.gitignore @@ -0,0 +1,2 @@ +out +v8 diff --git a/lib/c-api/tests/wasm-c-api/Dockerfile b/lib/c-api/tests/wasm-c-api/Dockerfile new file mode 100644 index 000000000..d9f8de77d --- /dev/null +++ b/lib/c-api/tests/wasm-c-api/Dockerfile @@ -0,0 +1,18 @@ +FROM ubuntu:bionic +RUN apt-get update && apt-get install -y \ + apt-utils \ + clang \ + cmake \ + curl \ + git \ + libc++-dev \ + libc++abi-dev \ + libglib2.0-dev \ + libgmp-dev \ + ninja-build \ + python +ADD . /code/wasm-c-api +WORKDIR /code/wasm-c-api +RUN make v8-checkout +RUN make -j v8 +RUN make diff --git a/lib/c-api/tests/wasm-c-api/LICENSE b/lib/c-api/tests/wasm-c-api/LICENSE new file mode 100644 index 000000000..8f71f43fe --- /dev/null +++ b/lib/c-api/tests/wasm-c-api/LICENSE @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/lib/c-api/tests/wasm-c-api/README.md b/lib/c-api/tests/wasm-c-api/README.md new file mode 100644 index 000000000..6ba4799e8 --- /dev/null +++ b/lib/c-api/tests/wasm-c-api/README.md @@ -0,0 +1,111 @@ +# WebAssembly C and C++ API + +Work in progress! No docs yet. + + +### Design Goals + +* Provide a "black box" API for embedding a Wasm engine in other C/C++ applications. + + * Be completely agnostic to VM specifics. + + * Non-goal: "white box" interoperability with embedder (such as combined GC instead of mere finalisation) -- *much* more difficult to achieve. + +* Allow creation of bindings for other languages through typical C foreign function interfaces. + + * Support a plain C API. + + * Stick to mostly manual memory management of interface objects. + +* Avoid language features that raise barrier to use. + + * E.g., no exceptions or post-C++11 features in C++ API. + + * E.g., no passing of structs by-value or post-C99 features in C API. + +* Achieve link-time compatibility between different implementations. + + * All implementation-dependent API classes are abstract and can be instantiated through factory methods only. + + +### Interfaces + +* C++ API: + + * See `include/wasm.hh` for interface. + + * See `example/*.cc` for example usages. + +* C API: + + * See `include/wasm.h` for interface. + + * See `example/*.c` for example usages. + +Some random explanations: + +* The VM must be initialised by creating an instance of an *engine* (`wasm::Engine`/`wasm_engine_t`) and is shut down by deleting it. Such an instance may only be created once per process. + +* All runtime objects are tied to a specific *store* (`wasm::Store`/`wasm_store_t`). Multiple stores can be created, but their objects cannot interact. Every store and its objects must only be accessed in a single thread. + +* To exchange module objects between threads, create a *shared* module (`wasm::Shared`/`wasm_shared_module_t`). Other objects cannot be shared in current Wasm. + +* *Vector* structures (`wasm::vec`/`wasm_x_vec_t`) are lightweight abstractions of a pair of a plain array and its length. The C++ API does not use `std::vector` because that does not support adopting pre-existing arrays. + +* *References* point to runtime objects, but may involve internal indirections, which may or may not be cached. Thus, pointer equality on `Ref*` or subclasses cannot be used to compare identity of the underlying objects (`Ref::eq` may be added later). However, `nullptr`/`NULL` uniquely represents null references. + +* The API already encompasses current proposals like [multiple return values](https://github.com/WebAssembly/multi-value/blob/master/proposals/multi-value/Overview.md) and [reference types](https://github.com/WebAssembly/reference-types/blob/master/proposals/reference-types/Overview.md), but not yet [threads](https://github.com/WebAssembly/threads/blob/master/proposals/threads/Overview.md). + + +### Prototype Implementation + +* This repo contains a prototype implementation based on V8 is in `src`. + + * Note that this requires adding a module to V8, so it patches V8's build file. + +* The C API is implemented on top of the C++ API. + +* See `Makefile` for build recipe. Canonical steps to run examples: + + 1. `make v8-checkout` + 2. `make v8` + 3. `make all` + + +#### Limitations + +V8 implementation: + +* Currently requires patching V8 by adding a module. + +* Host functions (`Func::make`) create a JavaScript function internally, since V8 cannot handle raw C imports yet. + +* As a consequence, does not support multiple results in external calls or host functions. + +* Host functions and host globals are created through auxiliary modules constructed on the fly, to work around limitations in JS API. + +* `Shared` is currently implemented via serialisation, since V8 does not currently have direct support for cross-isolate sharing. + + +### Other Implementations + +Currently, known implementations of this API are included in + +* V8 natively (both C and C++) +* Wabt (only C?) +* Wasmtime (only C?) + + +### TODO + +Possible API tweaks: + + * Add `Ref::eq` (or better, a subclass `EqRef::eq`) for reference equality? + + * Add a way to return error messages from `Module::make` and `Module::validate`. + + * Use `restrict` in C API? + + * Find a way to perform C callbacks through C++ without extra wrapper? + + * Add iterators to `vec` class? diff --git a/lib/c-api/tests/wasm-c-api/example/callback.c b/lib/c-api/tests/wasm-c-api/example/callback.c new file mode 100644 index 000000000..e17429bdd --- /dev/null +++ b/lib/c-api/tests/wasm-c-api/example/callback.c @@ -0,0 +1,168 @@ +#include +#include +#include +#include + +#include "wasm.h" + +#define own + +// Print a Wasm value +void wasm_val_print(wasm_val_t val) { + switch (val.kind) { + case WASM_I32: { + printf("%" PRIu32, val.of.i32); + } break; + case WASM_I64: { + printf("%" PRIu64, val.of.i64); + } break; + case WASM_F32: { + printf("%f", val.of.f32); + } break; + case WASM_F64: { + printf("%g", val.of.f64); + } break; + case WASM_ANYREF: + case WASM_FUNCREF: { + if (val.of.ref == NULL) { + printf("null"); + } else { + printf("ref(%p)", val.of.ref); + } + } break; + } +} + +// A function to be called from Wasm code. +own wasm_trap_t* print_callback( + const wasm_val_t args[], wasm_val_t results[] +) { + printf("Calling back...\n> "); + wasm_val_print(args[0]); + printf("\n"); + + wasm_val_copy(&results[0], &args[0]); + return NULL; +} + + +// A function closure. +own wasm_trap_t* closure_callback( + void* env, const wasm_val_t args[], wasm_val_t results[] +) { + int i = *(int*)env; + printf("Calling back closure...\n"); + printf("> %d\n", i); + + results[0].kind = WASM_I32; + results[0].of.i32 = (int32_t)i; + return NULL; +} + + +int main(int argc, const char* argv[]) { + // Initialize. + printf("Initializing...\n"); + wasm_engine_t* engine = wasm_engine_new(); + wasm_store_t* store = wasm_store_new(engine); + + // Load binary. + printf("Loading binary...\n"); + FILE* file = fopen("callback.wasm", "r"); + if (!file) { + printf("> Error loading module!\n"); + return 1; + } + fseek(file, 0L, SEEK_END); + size_t file_size = ftell(file); + fseek(file, 0L, SEEK_SET); + wasm_byte_vec_t binary; + wasm_byte_vec_new_uninitialized(&binary, file_size); + if (fread(binary.data, file_size, 1, file) != 1) { + printf("> Error loading module!\n"); + return 1; + } + fclose(file); + + // Compile. + printf("Compiling module...\n"); + own wasm_module_t* module = wasm_module_new(store, &binary); + if (!module) { + printf("> Error compiling module!\n"); + return 1; + } + + wasm_byte_vec_delete(&binary); + + // Create external print functions. + printf("Creating callback...\n"); + own wasm_functype_t* print_type = wasm_functype_new_1_1(wasm_valtype_new_i32(), wasm_valtype_new_i32()); + own wasm_func_t* print_func = wasm_func_new(store, print_type, print_callback); + + int i = 42; + own wasm_functype_t* closure_type = wasm_functype_new_0_1(wasm_valtype_new_i32()); + own wasm_func_t* closure_func = wasm_func_new_with_env(store, closure_type, closure_callback, &i, NULL); + + wasm_functype_delete(print_type); + wasm_functype_delete(closure_type); + + // Instantiate. + printf("Instantiating module...\n"); + const wasm_extern_t* imports[] = { + wasm_func_as_extern(print_func), wasm_func_as_extern(closure_func) + }; + own wasm_instance_t* instance = + wasm_instance_new(store, module, imports, NULL); + if (!instance) { + printf("> Error instantiating module!\n"); + return 1; + } + + wasm_func_delete(print_func); + wasm_func_delete(closure_func); + + // Extract export. + printf("Extracting export...\n"); + own wasm_extern_vec_t exports; + wasm_instance_exports(instance, &exports); + if (exports.size == 0) { + printf("> Error accessing exports!\n"); + return 1; + } + const wasm_func_t* run_func = wasm_extern_as_func(exports.data[0]); + if (run_func == NULL) { + printf("> Error accessing export!\n"); + return 1; + } + + wasm_module_delete(module); + wasm_instance_delete(instance); + + // Call. + printf("Calling export...\n"); + wasm_val_t args[2]; + args[0].kind = WASM_I32; + args[0].of.i32 = 3; + args[1].kind = WASM_I32; + args[1].of.i32 = 4; + wasm_val_t results[1]; + if (wasm_func_call(run_func, args, results)) { + printf("> Error calling function!\n"); + return 1; + } + + wasm_extern_vec_delete(&exports); + + // Print result. + printf("Printing result...\n"); + printf("> %u\n", results[0].of.i32); + + // Shut down. + printf("Shutting down...\n"); + wasm_store_delete(store); + wasm_engine_delete(engine); + + // All done. + printf("Done.\n"); + return 0; +} diff --git a/lib/c-api/tests/wasm-c-api/example/callback.cc b/lib/c-api/tests/wasm-c-api/example/callback.cc new file mode 100644 index 000000000..41a388ca9 --- /dev/null +++ b/lib/c-api/tests/wasm-c-api/example/callback.cc @@ -0,0 +1,145 @@ +#include +#include +#include +#include +#include + +#include "wasm.hh" + +// Print a Wasm value +auto operator<<(std::ostream& out, const wasm::Val& val) -> std::ostream& { + switch (val.kind()) { + case wasm::ValKind::I32: { + out << val.i32(); + } break; + case wasm::ValKind::I64: { + out << val.i64(); + } break; + case wasm::ValKind::F32: { + out << val.f32(); + } break; + case wasm::ValKind::F64: { + out << val.f64(); + } break; + case wasm::ValKind::ANYREF: + case wasm::ValKind::FUNCREF: { + if (val.ref() == nullptr) { + out << "null"; + } else { + out << "ref(" << val.ref() << ")"; + } + } break; + } + return out; +} + +// A function to be called from Wasm code. +auto print_callback( + const wasm::Val args[], wasm::Val results[] +) -> wasm::own { + std::cout << "Calling back..." << std::endl << "> " << args[0] << std::endl; + results[0] = args[0].copy(); + return nullptr; +} + + +// A function closure. +auto closure_callback( + void* env, const wasm::Val args[], wasm::Val results[] +) -> wasm::own { + auto i = *reinterpret_cast(env); + std::cout << "Calling back closure..." << std::endl; + std::cout << "> " << i << std::endl; + results[0] = wasm::Val::i32(static_cast(i)); + return nullptr; +} + + +void run() { + // Initialize. + std::cout << "Initializing..." << std::endl; + auto engine = wasm::Engine::make(); + auto store_ = wasm::Store::make(engine.get()); + auto store = store_.get(); + + // Load binary. + std::cout << "Loading binary..." << std::endl; + std::ifstream file("callback.wasm"); + file.seekg(0, std::ios_base::end); + auto file_size = file.tellg(); + file.seekg(0); + auto binary = wasm::vec::make_uninitialized(file_size); + file.read(binary.get(), file_size); + file.close(); + if (file.fail()) { + std::cout << "> Error loading module!" << std::endl; + exit(1); + } + + // Compile. + std::cout << "Compiling module..." << std::endl; + auto module = wasm::Module::make(store, binary); + if (!module) { + std::cout << "> Error compiling module!" << std::endl; + exit(1); + } + + // Create external print functions. + std::cout << "Creating callback..." << std::endl; + auto print_type = wasm::FuncType::make( + wasm::ownvec::make(wasm::ValType::make(wasm::ValKind::I32)), + wasm::ownvec::make(wasm::ValType::make(wasm::ValKind::I32)) + ); + auto print_func = wasm::Func::make(store, print_type.get(), print_callback); + + // Creating closure. + std::cout << "Creating closure..." << std::endl; + int i = 42; + auto closure_type = wasm::FuncType::make( + wasm::ownvec::make(), + wasm::ownvec::make(wasm::ValType::make(wasm::ValKind::I32)) + ); + auto closure_func = wasm::Func::make(store, closure_type.get(), closure_callback, &i); + + // Instantiate. + std::cout << "Instantiating module..." << std::endl; + wasm::Extern* imports[] = {print_func.get(), closure_func.get()}; + auto instance = wasm::Instance::make(store, module.get(), imports); + if (!instance) { + std::cout << "> Error instantiating module!" << std::endl; + exit(1); + } + + // Extract export. + std::cout << "Extracting export..." << std::endl; + auto exports = instance->exports(); + if (exports.size() == 0 || exports[0]->kind() != wasm::ExternKind::FUNC || !exports[0]->func()) { + std::cout << "> Error accessing export!" << std::endl; + exit(1); + } + auto run_func = exports[0]->func(); + + // Call. + std::cout << "Calling export..." << std::endl; + wasm::Val args[] = {wasm::Val::i32(3), wasm::Val::i32(4)}; + wasm::Val results[1]; + if (run_func->call(args, results)) { + std::cout << "> Error calling function!" << std::endl; + exit(1); + } + + // Print result. + std::cout << "Printing result..." << std::endl; + std::cout << "> " << results[0].i32() << std::endl; + + // Shut down. + std::cout << "Shutting down..." << std::endl; +} + + +int main(int argc, const char* argv[]) { + run(); + std::cout << "Done." << std::endl; + return 0; +} + diff --git a/lib/c-api/tests/wasm-c-api/example/callback.wasm b/lib/c-api/tests/wasm-c-api/example/callback.wasm new file mode 100644 index 000000000..7e00b5801 Binary files /dev/null and b/lib/c-api/tests/wasm-c-api/example/callback.wasm differ diff --git a/lib/c-api/tests/wasm-c-api/example/callback.wat b/lib/c-api/tests/wasm-c-api/example/callback.wat new file mode 100644 index 000000000..d86195f51 --- /dev/null +++ b/lib/c-api/tests/wasm-c-api/example/callback.wat @@ -0,0 +1,10 @@ +(module + (func $print (import "" "print") (param i32) (result i32)) + (func $closure (import "" "closure") (result i32)) + (func (export "run") (param $x i32) (param $y i32) (result i32) + (i32.add + (call $print (i32.add (local.get $x) (local.get $y))) + (call $closure) + ) + ) +) diff --git a/lib/c-api/tests/wasm-c-api/example/finalize.c b/lib/c-api/tests/wasm-c-api/example/finalize.c new file mode 100644 index 000000000..247368f28 --- /dev/null +++ b/lib/c-api/tests/wasm-c-api/example/finalize.c @@ -0,0 +1,109 @@ +#include +#include +#include +#include + +#include "wasm.h" + +#define own + +const int iterations = 100000; + +int live_count = 0; + +void finalize(void* data) { + int i = (int)data; + if (i % (iterations / 10) == 0) printf("Finalizing #%d...\n", i); + --live_count; +} + +void run_in_store(wasm_store_t* store) { + // Load binary. + printf("Loading binary...\n"); + FILE* file = fopen("finalize.wasm", "r"); + if (!file) { + printf("> Error loading module!\n"); + exit(1); + } + fseek(file, 0L, SEEK_END); + size_t file_size = ftell(file); + fseek(file, 0L, SEEK_SET); + wasm_byte_vec_t binary; + wasm_byte_vec_new_uninitialized(&binary, file_size); + if (fread(binary.data, file_size, 1, file) != 1) { + printf("> Error loading module!\n"); + exit(1); + } + fclose(file); + + // Compile. + printf("Compiling module...\n"); + own wasm_module_t* module = wasm_module_new(store, &binary); + if (!module) { + printf("> Error compiling module!\n"); + exit(1); + } + + wasm_byte_vec_delete(&binary); + + // Instantiate. + printf("Instantiating modules...\n"); + for (int i = 0; i <= iterations; ++i) { + if (i % (iterations / 10) == 0) printf("%d\n", i); + own wasm_instance_t* instance = + wasm_instance_new(store, module, NULL, NULL); + if (!instance) { + printf("> Error instantiating module %d!\n", i); + exit(1); + } + void* data = (void*)(intptr_t)i; + wasm_instance_set_host_info_with_finalizer(instance, data, &finalize); + wasm_instance_delete(instance); + ++live_count; + } + + wasm_module_delete(module); +} + +int main(int argc, const char* argv[]) { + // Initialize. + printf("Initializing...\n"); + wasm_engine_t* engine = wasm_engine_new(); + + printf("Live count %d\n", live_count); + printf("Creating store 1...\n"); + wasm_store_t* store1 = wasm_store_new(engine); + + printf("Running in store 1...\n"); + run_in_store(store1); + printf("Live count %d\n", live_count); + + printf("Creating store 2...\n"); + wasm_store_t* store2 = wasm_store_new(engine); + + printf("Running in store 2...\n"); + run_in_store(store2); + printf("Live count %d\n", live_count); + + printf("Deleting store 2...\n"); + wasm_store_delete(store2); + printf("Live count %d\n", live_count); + + printf("Running in store 1...\n"); + run_in_store(store1); + printf("Live count %d\n", live_count); + + printf("Deleting store 1...\n"); + wasm_store_delete(store1); + printf("Live count %d\n", live_count); + + assert(live_count == 0); + + // Shut down. + printf("Shutting down...\n"); + wasm_engine_delete(engine); + + // All done. + printf("Done.\n"); + return 0; +} diff --git a/lib/c-api/tests/wasm-c-api/example/finalize.cc b/lib/c-api/tests/wasm-c-api/example/finalize.cc new file mode 100644 index 000000000..64e134b8d --- /dev/null +++ b/lib/c-api/tests/wasm-c-api/example/finalize.cc @@ -0,0 +1,103 @@ +#include +#include +#include +#include +#include + +#include "wasm.hh" + + +const int iterations = 100000; + +int live_count = 0; + +void finalize(void* data) { + intptr_t i = reinterpret_cast(data); + if (i % (iterations / 10) == 0) { + std::cout << "Finalizing #" << i << "..." << std::endl; + } + --live_count; +} + +void run_in_store(wasm::Store* store) { + // Load binary. + std::cout << "Loading binary..." << std::endl; + std::ifstream file("finalize.wasm"); + file.seekg(0, std::ios_base::end); + auto file_size = file.tellg(); + file.seekg(0); + auto binary = wasm::vec::make_uninitialized(file_size); + file.read(binary.get(), file_size); + file.close(); + if (file.fail()) { + std::cout << "> Error loading module!" << std::endl; + exit(1); + } + + // Compile. + std::cout << "Compiling module..." << std::endl; + auto module = wasm::Module::make(store, binary); + if (!module) { + std::cout << "> Error compiling module!" << std::endl; + exit(1); + } + + // Instantiate. + std::cout << "Instantiating modules..." << std::endl; + for (int i = 0; i <= iterations; ++i) { + if (i % (iterations / 10) == 0) std::cout << i << std::endl; + auto instance = wasm::Instance::make(store, module.get(), nullptr); + if (!instance) { + std::cout << "> Error instantiating module " << i << "!" << std::endl; + exit(1); + } + instance->set_host_info(reinterpret_cast(i), &finalize); + ++live_count; + } + + // Shut down. + std::cout << "Shutting down..." << std::endl; +} + + +void run() { + // Initialize. + std::cout << "Initializing..." << std::endl; + auto engine = wasm::Engine::make(); + + std::cout << "Live count " << live_count << std::endl; + std::cout << "Creating store 1..." << std::endl; + auto store1 = wasm::Store::make(engine.get()); + + std::cout << "Running in store 1..." << std::endl; + run_in_store(store1.get()); + std::cout << "Live count " << live_count << std::endl; + + { + std::cout << "Creating store 2..." << std::endl; + auto store2 = wasm::Store::make(engine.get()); + + std::cout << "Running in store 2..." << std::endl; + run_in_store(store2.get()); + std::cout << "Live count " << live_count << std::endl; + + std::cout << "Deleting store 2..." << std::endl; + std::cout << "Live count " << live_count << std::endl; + } + + std::cout << "Running in store 1..." << std::endl; + run_in_store(store1.get()); + std::cout << "Live count " << live_count << std::endl; + + std::cout << "Deleting store 1..." << std::endl; +} + + +int main(int argc, const char* argv[]) { + run(); + std::cout << "Live count " << live_count << std::endl; + assert(live_count == 0); + std::cout << "Done." << std::endl; + return 0; +} + diff --git a/lib/c-api/tests/wasm-c-api/example/finalize.wasm b/lib/c-api/tests/wasm-c-api/example/finalize.wasm new file mode 100644 index 000000000..74f9c5662 Binary files /dev/null and b/lib/c-api/tests/wasm-c-api/example/finalize.wasm differ diff --git a/lib/c-api/tests/wasm-c-api/example/finalize.wat b/lib/c-api/tests/wasm-c-api/example/finalize.wat new file mode 100644 index 000000000..6237e734a --- /dev/null +++ b/lib/c-api/tests/wasm-c-api/example/finalize.wat @@ -0,0 +1,5 @@ +(module + (func (export "f")) + (func (export "g")) + (func (export "h")) +) diff --git a/lib/c-api/tests/wasm-c-api/example/global.c b/lib/c-api/tests/wasm-c-api/example/global.c new file mode 100644 index 000000000..5fe357cd4 --- /dev/null +++ b/lib/c-api/tests/wasm-c-api/example/global.c @@ -0,0 +1,232 @@ +#include +#include +#include +#include + +#include "wasm.h" + +#define own + +wasm_global_t* get_export_global(const wasm_extern_vec_t* exports, size_t i) { + if (exports->size <= i || !wasm_extern_as_global(exports->data[i])) { + printf("> Error accessing global export %zu!\n", i); + exit(1); + } + return wasm_extern_as_global(exports->data[i]); +} + +wasm_func_t* get_export_func(const wasm_extern_vec_t* exports, size_t i) { + if (exports->size <= i || !wasm_extern_as_func(exports->data[i])) { + printf("> Error accessing function export %zu!\n", i); + exit(1); + } + return wasm_extern_as_func(exports->data[i]); +} + + +#define check(val, type, expected) \ + if (val.of.type != expected) { \ + printf("> Error reading value\n"); \ + exit(1); \ + } + +#define check_global(global, type, expected) \ + { \ + wasm_val_t val; \ + wasm_global_get(global, &val); \ + check(val, type, expected); \ + } + +#define check_call(func, type, expected) \ + { \ + wasm_val_t results[1]; \ + wasm_func_call(func, NULL, results); \ + check(results[0], type, expected); \ + } + + +int main(int argc, const char* argv[]) { + // Initialize. + printf("Initializing...\n"); + wasm_engine_t* engine = wasm_engine_new(); + wasm_store_t* store = wasm_store_new(engine); + + // Load binary. + printf("Loading binary...\n"); + FILE* file = fopen("global.wasm", "r"); + if (!file) { + printf("> Error loading module!\n"); + return 1; + } + fseek(file, 0L, SEEK_END); + size_t file_size = ftell(file); + fseek(file, 0L, SEEK_SET); + wasm_byte_vec_t binary; + wasm_byte_vec_new_uninitialized(&binary, file_size); + if (fread(binary.data, file_size, 1, file) != 1) { + printf("> Error loading module!\n"); + return 1; + } + fclose(file); + + // Compile. + printf("Compiling module...\n"); + own wasm_module_t* module = wasm_module_new(store, &binary); + if (!module) { + printf("> Error compiling module!\n"); + return 1; + } + + wasm_byte_vec_delete(&binary); + + // Create external globals. + printf("Creating globals...\n"); + own wasm_globaltype_t* const_f32_type = wasm_globaltype_new( + wasm_valtype_new(WASM_F32), WASM_CONST); + own wasm_globaltype_t* const_i64_type = wasm_globaltype_new( + wasm_valtype_new(WASM_I64), WASM_CONST); + own wasm_globaltype_t* var_f32_type = wasm_globaltype_new( + wasm_valtype_new(WASM_F32), WASM_VAR); + own wasm_globaltype_t* var_i64_type = wasm_globaltype_new( + wasm_valtype_new(WASM_I64), WASM_VAR); + + wasm_val_t val_f32_1 = {.kind = WASM_F32, .of = {.f32 = 1}}; + own wasm_global_t* const_f32_import = + wasm_global_new(store, const_f32_type, &val_f32_1); + wasm_val_t val_i64_2 = {.kind = WASM_I64, .of = {.i64 = 2}}; + own wasm_global_t* const_i64_import = + wasm_global_new(store, const_i64_type, &val_i64_2); + wasm_val_t val_f32_3 = {.kind = WASM_F32, .of = {.f32 = 3}}; + own wasm_global_t* var_f32_import = + wasm_global_new(store, var_f32_type, &val_f32_3); + wasm_val_t val_i64_4 = {.kind = WASM_I64, .of = {.i64 = 4}}; + own wasm_global_t* var_i64_import = + wasm_global_new(store, var_i64_type, &val_i64_4); + + wasm_globaltype_delete(const_f32_type); + wasm_globaltype_delete(const_i64_type); + wasm_globaltype_delete(var_f32_type); + wasm_globaltype_delete(var_i64_type); + + // Instantiate. + printf("Instantiating module...\n"); + const wasm_extern_t* imports[] = { + wasm_global_as_extern(const_f32_import), + wasm_global_as_extern(const_i64_import), + wasm_global_as_extern(var_f32_import), + wasm_global_as_extern(var_i64_import) + }; + own wasm_instance_t* instance = + wasm_instance_new(store, module, imports, NULL); + if (!instance) { + printf("> Error instantiating module!\n"); + return 1; + } + + wasm_module_delete(module); + + // Extract export. + printf("Extracting exports...\n"); + own wasm_extern_vec_t exports; + wasm_instance_exports(instance, &exports); + size_t i = 0; + wasm_global_t* const_f32_export = get_export_global(&exports, i++); + wasm_global_t* const_i64_export = get_export_global(&exports, i++); + wasm_global_t* var_f32_export = get_export_global(&exports, i++); + wasm_global_t* var_i64_export = get_export_global(&exports, i++); + wasm_func_t* get_const_f32_import = get_export_func(&exports, i++); + wasm_func_t* get_const_i64_import = get_export_func(&exports, i++); + wasm_func_t* get_var_f32_import = get_export_func(&exports, i++); + wasm_func_t* get_var_i64_import = get_export_func(&exports, i++); + wasm_func_t* get_const_f32_export = get_export_func(&exports, i++); + wasm_func_t* get_const_i64_export = get_export_func(&exports, i++); + wasm_func_t* get_var_f32_export = get_export_func(&exports, i++); + wasm_func_t* get_var_i64_export = get_export_func(&exports, i++); + wasm_func_t* set_var_f32_import = get_export_func(&exports, i++); + wasm_func_t* set_var_i64_import = get_export_func(&exports, i++); + wasm_func_t* set_var_f32_export = get_export_func(&exports, i++); + wasm_func_t* set_var_i64_export = get_export_func(&exports, i++); + + // Try cloning. + own wasm_global_t* copy = wasm_global_copy(var_f32_import); + assert(wasm_global_same(var_f32_import, copy)); + wasm_global_delete(copy); + + // Interact. + printf("Accessing globals...\n"); + + // Check initial values. + check_global(const_f32_import, f32, 1); + check_global(const_i64_import, i64, 2); + check_global(var_f32_import, f32, 3); + check_global(var_i64_import, i64, 4); + check_global(const_f32_export, f32, 5); + check_global(const_i64_export, i64, 6); + check_global(var_f32_export, f32, 7); + check_global(var_i64_export, i64, 8); + + check_call(get_const_f32_import, f32, 1); + check_call(get_const_i64_import, i64, 2); + check_call(get_var_f32_import, f32, 3); + check_call(get_var_i64_import, i64, 4); + check_call(get_const_f32_export, f32, 5); + check_call(get_const_i64_export, i64, 6); + check_call(get_var_f32_export, f32, 7); + check_call(get_var_i64_export, i64, 8); + + // Modify variables through API and check again. + wasm_val_t val33 = {.kind = WASM_F32, .of = {.f32 = 33}}; + wasm_global_set(var_f32_import, &val33); + wasm_val_t val34 = {.kind = WASM_I64, .of = {.i64 = 34}}; + wasm_global_set(var_i64_import, &val34); + wasm_val_t val37 = {.kind = WASM_F32, .of = {.f32 = 37}}; + wasm_global_set(var_f32_export, &val37); + wasm_val_t val38 = {.kind = WASM_I64, .of = {.i64 = 38}}; + wasm_global_set(var_i64_export, &val38); + + check_global(var_f32_import, f32, 33); + check_global(var_i64_import, i64, 34); + check_global(var_f32_export, f32, 37); + check_global(var_i64_export, i64, 38); + + check_call(get_var_f32_import, f32, 33); + check_call(get_var_i64_import, i64, 34); + check_call(get_var_f32_export, f32, 37); + check_call(get_var_i64_export, i64, 38); + + // Modify variables through calls and check again. + wasm_val_t args73[] = { {.kind = WASM_F32, .of = {.f32 = 73}} }; + wasm_func_call(set_var_f32_import, args73, NULL); + wasm_val_t args74[] = { {.kind = WASM_I64, .of = {.i64 = 74}} }; + wasm_func_call(set_var_i64_import, args74, NULL); + wasm_val_t args77[] = { {.kind = WASM_F32, .of = {.f32 = 77}} }; + wasm_func_call(set_var_f32_export, args77, NULL); + wasm_val_t args78[] = { {.kind = WASM_I64, .of = {.i64 = 78}} }; + wasm_func_call(set_var_i64_export, args78, NULL); + + check_global(var_f32_import, f32, 73); + check_global(var_i64_import, i64, 74); + check_global(var_f32_export, f32, 77); + check_global(var_i64_export, i64, 78); + + check_call(get_var_f32_import, f32, 73); + check_call(get_var_i64_import, i64, 74); + check_call(get_var_f32_export, f32, 77); + check_call(get_var_i64_export, i64, 78); + + wasm_global_delete(const_f32_import); + wasm_global_delete(const_i64_import); + wasm_global_delete(var_f32_import); + wasm_global_delete(var_i64_import); + wasm_extern_vec_delete(&exports); + wasm_instance_delete(instance); + + // Shut down. + printf("Shutting down...\n"); + wasm_store_delete(store); + wasm_engine_delete(engine); + + // All done. + printf("Done.\n"); + return 0; +} diff --git a/lib/c-api/tests/wasm-c-api/example/global.cc b/lib/c-api/tests/wasm-c-api/example/global.cc new file mode 100644 index 000000000..efe90f05c --- /dev/null +++ b/lib/c-api/tests/wasm-c-api/example/global.cc @@ -0,0 +1,196 @@ +#include +#include +#include +#include +#include + +#include "wasm.hh" + + +auto get_export_global(wasm::ownvec& exports, size_t i) -> wasm::Global* { + if (exports.size() <= i || !exports[i]->global()) { + std::cout << "> Error accessing global export " << i << "!" << std::endl; + exit(1); + } + return exports[i]->global(); +} + +auto get_export_func(const wasm::ownvec& exports, size_t i) -> const wasm::Func* { + if (exports.size() <= i || !exports[i]->func()) { + std::cout << "> Error accessing function export " << i << "!" << std::endl; + exit(1); + } + return exports[i]->func(); +} + +template +void check(T actual, U expected) { + if (actual != expected) { + std::cout << "> Error reading value, expected " << expected << ", got " << actual << std::endl; + exit(1); + } +} + +auto call(const wasm::Func* func) -> wasm::Val { + wasm::Val results[1]; + if (func->call(nullptr, results)) { + std::cout << "> Error calling function!" << std::endl; + exit(1); + } + return results[0].copy(); +} + +void call(const wasm::Func* func, wasm::Val&& arg) { + wasm::Val args[1] = {std::move(arg)}; + if (func->call(args)) { + std::cout << "> Error calling function!" << std::endl; + exit(1); + } +} + + +void run() { + // Initialize. + std::cout << "Initializing..." << std::endl; + auto engine = wasm::Engine::make(); + auto store_ = wasm::Store::make(engine.get()); + auto store = store_.get(); + + // Load binary. + std::cout << "Loading binary..." << std::endl; + std::ifstream file("global.wasm"); + file.seekg(0, std::ios_base::end); + auto file_size = file.tellg(); + file.seekg(0); + auto binary = wasm::vec::make_uninitialized(file_size); + file.read(binary.get(), file_size); + file.close(); + if (file.fail()) { + std::cout << "> Error loading module!" << std::endl; + exit(1); + } + + // Compile. + std::cout << "Compiling module..." << std::endl; + auto module = wasm::Module::make(store, binary); + if (!module) { + std::cout << "> Error compiling module!" << std::endl; + exit(1); + } + + // Create external globals. + std::cout << "Creating globals..." << std::endl; + auto const_f32_type = wasm::GlobalType::make( + wasm::ValType::make(wasm::ValKind::F32), wasm::Mutability::CONST); + auto const_i64_type = wasm::GlobalType::make( + wasm::ValType::make(wasm::ValKind::I64), wasm::Mutability::CONST); + auto var_f32_type = wasm::GlobalType::make( + wasm::ValType::make(wasm::ValKind::F32), wasm::Mutability::VAR); + auto var_i64_type = wasm::GlobalType::make( + wasm::ValType::make(wasm::ValKind::I64), wasm::Mutability::VAR); + auto const_f32_import = wasm::Global::make(store, const_f32_type.get(), wasm::Val::f32(1)); + auto const_i64_import = wasm::Global::make(store, const_i64_type.get(), wasm::Val::i64(2)); + auto var_f32_import = wasm::Global::make(store, var_f32_type.get(), wasm::Val::f32(3)); + auto var_i64_import = wasm::Global::make(store, var_i64_type.get(), wasm::Val::i64(4)); + + // Instantiate. + std::cout << "Instantiating module..." << std::endl; + wasm::Extern* imports[] = { + const_f32_import.get(), const_i64_import.get(), + var_f32_import.get(), var_i64_import.get() + }; + auto instance = wasm::Instance::make(store, module.get(), imports); + if (!instance) { + std::cout << "> Error instantiating module!" << std::endl; + exit(1); + } + + // Extract export. + std::cout << "Extracting exports..." << std::endl; + auto exports = instance->exports(); + size_t i = 0; + auto const_f32_export = get_export_global(exports, i++); + auto const_i64_export = get_export_global(exports, i++); + auto var_f32_export = get_export_global(exports, i++); + auto var_i64_export = get_export_global(exports, i++); + auto get_const_f32_import = get_export_func(exports, i++); + auto get_const_i64_import = get_export_func(exports, i++); + auto get_var_f32_import = get_export_func(exports, i++); + auto get_var_i64_import = get_export_func(exports, i++); + auto get_const_f32_export = get_export_func(exports, i++); + auto get_const_i64_export = get_export_func(exports, i++); + auto get_var_f32_export = get_export_func(exports, i++); + auto get_var_i64_export = get_export_func(exports, i++); + auto set_var_f32_import = get_export_func(exports, i++); + auto set_var_i64_import = get_export_func(exports, i++); + auto set_var_f32_export = get_export_func(exports, i++); + auto set_var_i64_export = get_export_func(exports, i++); + + // Try cloning. + assert(var_f32_import->copy()->same(var_f32_import.get())); + + // Interact. + std::cout << "Accessing globals..." << std::endl; + + // Check initial values. + check(const_f32_import->get().f32(), 1); + check(const_i64_import->get().i64(), 2); + check(var_f32_import->get().f32(), 3); + check(var_i64_import->get().i64(), 4); + check(const_f32_export->get().f32(), 5); + check(const_i64_export->get().i64(), 6); + check(var_f32_export->get().f32(), 7); + check(var_i64_export->get().i64(), 8); + + check(call(get_const_f32_import).f32(), 1); + check(call(get_const_i64_import).i64(), 2); + check(call(get_var_f32_import).f32(), 3); + check(call(get_var_i64_import).i64(), 4); + check(call(get_const_f32_export).f32(), 5); + check(call(get_const_i64_export).i64(), 6); + check(call(get_var_f32_export).f32(), 7); + check(call(get_var_i64_export).i64(), 8); + + // Modify variables through API and check again. + var_f32_import->set(wasm::Val::f32(33)); + var_i64_import->set(wasm::Val::i64(34)); + var_f32_export->set(wasm::Val::f32(37)); + var_i64_export->set(wasm::Val::i64(38)); + + check(var_f32_import->get().f32(), 33); + check(var_i64_import->get().i64(), 34); + check(var_f32_export->get().f32(), 37); + check(var_i64_export->get().i64(), 38); + + check(call(get_var_f32_import).f32(), 33); + check(call(get_var_i64_import).i64(), 34); + check(call(get_var_f32_export).f32(), 37); + check(call(get_var_i64_export).i64(), 38); + + // Modify variables through calls and check again. + call(set_var_f32_import, wasm::Val::f32(73)); + call(set_var_i64_import, wasm::Val::i64(74)); + call(set_var_f32_export, wasm::Val::f32(77)); + call(set_var_i64_export, wasm::Val::i64(78)); + + check(var_f32_import->get().f32(), 73); + check(var_i64_import->get().i64(), 74); + check(var_f32_export->get().f32(), 77); + check(var_i64_export->get().i64(), 78); + + check(call(get_var_f32_import).f32(), 73); + check(call(get_var_i64_import).i64(), 74); + check(call(get_var_f32_export).f32(), 77); + check(call(get_var_i64_export).i64(), 78); + + // Shut down. + std::cout << "Shutting down..." << std::endl; +} + + +int main(int argc, const char* argv[]) { + run(); + std::cout << "Done." << std::endl; + return 0; +} + diff --git a/lib/c-api/tests/wasm-c-api/example/global.wasm b/lib/c-api/tests/wasm-c-api/example/global.wasm new file mode 100644 index 000000000..0e7686327 Binary files /dev/null and b/lib/c-api/tests/wasm-c-api/example/global.wasm differ diff --git a/lib/c-api/tests/wasm-c-api/example/global.wat b/lib/c-api/tests/wasm-c-api/example/global.wat new file mode 100644 index 000000000..dea085772 --- /dev/null +++ b/lib/c-api/tests/wasm-c-api/example/global.wat @@ -0,0 +1,27 @@ +(module + (global $f32_import (import "" "const f32") f32) + (global $i64_import (import "" "const i64") i64) + (global $mut_f32_import (import "" "var f32") (mut f32)) + (global $mut_i64_import (import "" "var i64") (mut i64)) + + (global $f32_export (export "const f32") f32 (f32.const 5)) + (global $i64_export (export "const i64") i64 (i64.const 6)) + (global $mut_f32_export (export "var f32") (mut f32) (f32.const 7)) + (global $mut_i64_export (export "var i64") (mut i64) (i64.const 8)) + + (func (export "get const f32 import") (result f32) (global.get $f32_import)) + (func (export "get const i64 import") (result i64) (global.get $i64_import)) + (func (export "get var f32 import") (result f32) (global.get $mut_f32_import)) + (func (export "get var i64 import") (result i64) (global.get $mut_i64_import)) + + (func (export "get const f32 export") (result f32) (global.get $f32_export)) + (func (export "get const i64 export") (result i64) (global.get $i64_export)) + (func (export "get var f32 export") (result f32) (global.get $mut_f32_export)) + (func (export "get var i64 export") (result i64) (global.get $mut_i64_export)) + + (func (export "set var f32 import") (param f32) (global.set $mut_f32_import (local.get 0))) + (func (export "set var i64 import") (param i64) (global.set $mut_i64_import (local.get 0))) + + (func (export "set var f32 export") (param f32) (global.set $mut_f32_export (local.get 0))) + (func (export "set var f64 export") (param i64) (global.set $mut_i64_export (local.get 0))) +) diff --git a/lib/c-api/tests/wasm-c-api/example/hello.c b/lib/c-api/tests/wasm-c-api/example/hello.c new file mode 100644 index 000000000..e4ef9837f --- /dev/null +++ b/lib/c-api/tests/wasm-c-api/example/hello.c @@ -0,0 +1,108 @@ +#include +#include +#include +#include + +#include "wasm.h" + +#define own + +// A function to be called from Wasm code. +own wasm_trap_t* hello_callback( + const wasm_val_t args[], wasm_val_t results[] +) { + printf("Calling back...\n"); + printf("> Hello World!\n"); + return NULL; +} + + +int main(int argc, const char* argv[]) { + // Initialize. + printf("Initializing...\n"); + wasm_engine_t* engine = wasm_engine_new(); + wasm_store_t* store = wasm_store_new(engine); + + // Load binary. + printf("Loading binary...\n"); + FILE* file = fopen("hello.wasm", "r"); + if (!file) { + printf("> Error loading module!\n"); + return 1; + } + fseek(file, 0L, SEEK_END); + size_t file_size = ftell(file); + fseek(file, 0L, SEEK_SET); + wasm_byte_vec_t binary; + wasm_byte_vec_new_uninitialized(&binary, file_size); + if (fread(binary.data, file_size, 1, file) != 1) { + printf("> Error loading module!\n"); + return 1; + } + fclose(file); + + // Compile. + printf("Compiling module...\n"); + own wasm_module_t* module = wasm_module_new(store, &binary); + if (!module) { + printf("> Error compiling module!\n"); + return 1; + } + + wasm_byte_vec_delete(&binary); + + // Create external print functions. + printf("Creating callback...\n"); + own wasm_functype_t* hello_type = wasm_functype_new_0_0(); + own wasm_func_t* hello_func = + wasm_func_new(store, hello_type, hello_callback); + + wasm_functype_delete(hello_type); + + // Instantiate. + printf("Instantiating module...\n"); + const wasm_extern_t* imports[] = { wasm_func_as_extern(hello_func) }; + own wasm_instance_t* instance = + wasm_instance_new(store, module, imports, NULL); + if (!instance) { + printf("> Error instantiating module!\n"); + return 1; + } + + wasm_func_delete(hello_func); + + // Extract export. + printf("Extracting export...\n"); + own wasm_extern_vec_t exports; + wasm_instance_exports(instance, &exports); + if (exports.size == 0) { + printf("> Error accessing exports!\n"); + return 1; + } + const wasm_func_t* run_func = wasm_extern_as_func(exports.data[0]); + if (run_func == NULL) { + printf("> Error accessing export!\n"); + return 1; + } + + wasm_module_delete(module); + wasm_instance_delete(instance); + + // Call. + printf("Calling export...\n"); + if (wasm_func_call(run_func, NULL, NULL)) { + printf("> Error calling function!\n"); + return 1; + } + + wasm_extern_vec_delete(&exports); + + // Shut down. + printf("Shutting down...\n"); + wasm_store_delete(store); + wasm_engine_delete(engine); + + // All done. + printf("Done.\n"); + return 0; +} diff --git a/lib/c-api/tests/wasm-c-api/example/hello.cc b/lib/c-api/tests/wasm-c-api/example/hello.cc new file mode 100644 index 000000000..a6bbe28ac --- /dev/null +++ b/lib/c-api/tests/wasm-c-api/example/hello.cc @@ -0,0 +1,91 @@ +#include +#include +#include +#include +#include + +#include "wasm.hh" + + +// A function to be called from Wasm code. +auto hello_callback( + const wasm::Val args[], wasm::Val results[] +) -> wasm::own { + std::cout << "Calling back..." << std::endl; + std::cout << "> Hello world!" << std::endl; + return nullptr; +} + + +void run() { + // Initialize. + std::cout << "Initializing..." << std::endl; + auto engine = wasm::Engine::make(); + auto store_ = wasm::Store::make(engine.get()); + auto store = store_.get(); + + // Load binary. + std::cout << "Loading binary..." << std::endl; + std::ifstream file("hello.wasm"); + file.seekg(0, std::ios_base::end); + auto file_size = file.tellg(); + file.seekg(0); + auto binary = wasm::vec::make_uninitialized(file_size); + file.read(binary.get(), file_size); + file.close(); + if (file.fail()) { + std::cout << "> Error loading module!" << std::endl; + exit(1); + } + + // Compile. + std::cout << "Compiling module..." << std::endl; + auto module = wasm::Module::make(store, binary); + if (!module) { + std::cout << "> Error compiling module!" << std::endl; + exit(1); + } + + // Create external print functions. + std::cout << "Creating callback..." << std::endl; + auto hello_type = wasm::FuncType::make( + wasm::ownvec::make(), wasm::ownvec::make() + ); + auto hello_func = wasm::Func::make(store, hello_type.get(), hello_callback); + + // Instantiate. + std::cout << "Instantiating module..." << std::endl; + wasm::Extern* imports[] = {hello_func.get()}; + auto instance = wasm::Instance::make(store, module.get(), imports); + if (!instance) { + std::cout << "> Error instantiating module!" << std::endl; + exit(1); + } + + // Extract export. + std::cout << "Extracting export..." << std::endl; + auto exports = instance->exports(); + if (exports.size() == 0 || exports[0]->kind() != wasm::ExternKind::FUNC || !exports[0]->func()) { + std::cout << "> Error accessing export!" << std::endl; + exit(1); + } + auto run_func = exports[0]->func(); + + // Call. + std::cout << "Calling export..." << std::endl; + if (run_func->call()) { + std::cout << "> Error calling function!" << std::endl; + exit(1); + } + + // Shut down. + std::cout << "Shutting down..." << std::endl; +} + + +int main(int argc, const char* argv[]) { + run(); + std::cout << "Done." << std::endl; + return 0; +} + diff --git a/lib/c-api/tests/wasm-c-api/example/hello.wasm b/lib/c-api/tests/wasm-c-api/example/hello.wasm new file mode 100644 index 000000000..2207c03ee Binary files /dev/null and b/lib/c-api/tests/wasm-c-api/example/hello.wasm differ diff --git a/lib/c-api/tests/wasm-c-api/example/hello.wat b/lib/c-api/tests/wasm-c-api/example/hello.wat new file mode 100644 index 000000000..1c56c5582 --- /dev/null +++ b/lib/c-api/tests/wasm-c-api/example/hello.wat @@ -0,0 +1,4 @@ +(module + (func $hello (import "" "hello")) + (func (export "run") (call $hello)) +) diff --git a/lib/c-api/tests/wasm-c-api/example/hostref.c b/lib/c-api/tests/wasm-c-api/example/hostref.c new file mode 100644 index 000000000..b70218e61 --- /dev/null +++ b/lib/c-api/tests/wasm-c-api/example/hostref.c @@ -0,0 +1,269 @@ +#include +#include +#include +#include + +#include "wasm.h" + +#define own + + +// A function to be called from Wasm code. +own wasm_trap_t* callback( + const wasm_val_t args[], wasm_val_t results[] +) { + printf("Calling back...\n> "); + printf("> %p\n", + args[0].of.ref ? wasm_ref_get_host_info(args[0].of.ref) : NULL); + wasm_val_copy(&results[0], &args[0]); + return NULL; +} + + +wasm_func_t* get_export_func(const wasm_extern_vec_t* exports, size_t i) { + if (exports->size <= i || !wasm_extern_as_func(exports->data[i])) { + printf("> Error accessing function export %zu!\n", i); + exit(1); + } + return wasm_extern_as_func(exports->data[i]); +} + +wasm_global_t* get_export_global(const wasm_extern_vec_t* exports, size_t i) { + if (exports->size <= i || !wasm_extern_as_global(exports->data[i])) { + printf("> Error accessing global export %zu!\n", i); + exit(1); + } + return wasm_extern_as_global(exports->data[i]); +} + +wasm_table_t* get_export_table(const wasm_extern_vec_t* exports, size_t i) { + if (exports->size <= i || !wasm_extern_as_table(exports->data[i])) { + printf("> Error accessing table export %zu!\n", i); + exit(1); + } + return wasm_extern_as_table(exports->data[i]); +} + + +own wasm_ref_t* call_v_r(const wasm_func_t* func) { + printf("call_v_r... "); fflush(stdout); + wasm_val_t results[1]; + if (wasm_func_call(func, NULL, results)) { + printf("> Error calling function!\n"); + exit(1); + } + printf("okay\n"); + return results[0].of.ref; +} + +void call_r_v(const wasm_func_t* func, wasm_ref_t* ref) { + printf("call_r_v... "); fflush(stdout); + wasm_val_t args[1]; + args[0].kind = WASM_ANYREF; + args[0].of.ref = ref; + if (wasm_func_call(func, args, NULL)) { + printf("> Error calling function!\n"); + exit(1); + } + printf("okay\n"); +} + +own wasm_ref_t* call_r_r(const wasm_func_t* func, wasm_ref_t* ref) { + printf("call_r_r... "); fflush(stdout); + wasm_val_t args[1]; + args[0].kind = WASM_ANYREF; + args[0].of.ref = ref; + wasm_val_t results[1]; + if (wasm_func_call(func, args, results)) { + printf("> Error calling function!\n"); + exit(1); + } + printf("okay\n"); + return results[0].of.ref; +} + +void call_ir_v(const wasm_func_t* func, int32_t i, wasm_ref_t* ref) { + printf("call_ir_v... "); fflush(stdout); + wasm_val_t args[2]; + args[0].kind = WASM_I32; + args[0].of.i32 = i; + args[1].kind = WASM_ANYREF; + args[1].of.ref = ref; + if (wasm_func_call(func, args, NULL)) { + printf("> Error calling function!\n"); + exit(1); + } + printf("okay\n"); +} + +own wasm_ref_t* call_i_r(const wasm_func_t* func, int32_t i) { + printf("call_i_r... "); fflush(stdout); + wasm_val_t args[1]; + args[0].kind = WASM_I32; + args[0].of.i32 = i; + wasm_val_t results[1]; + if (wasm_func_call(func, args, results)) { + printf("> Error calling function!\n"); + exit(1); + } + printf("okay\n"); + return results[0].of.ref; +} + +void check(own wasm_ref_t* actual, const wasm_ref_t* expected) { + if (actual != expected && + !(actual && expected && wasm_ref_same(actual, expected))) { + printf("> Error reading reference, expected %p, got %p\n", + expected ? wasm_ref_get_host_info(expected) : NULL, + actual ? wasm_ref_get_host_info(actual) : NULL); + exit(1); + } + if (actual) wasm_ref_delete(actual); +} + + +int main(int argc, const char* argv[]) { + // Initialize. + printf("Initializing...\n"); + wasm_engine_t* engine = wasm_engine_new(); + wasm_store_t* store = wasm_store_new(engine); + + // Load binary. + printf("Loading binary...\n"); + FILE* file = fopen("hostref.wasm", "r"); + if (!file) { + printf("> Error loading module!\n"); + return 1; + } + fseek(file, 0L, SEEK_END); + size_t file_size = ftell(file); + fseek(file, 0L, SEEK_SET); + wasm_byte_vec_t binary; + wasm_byte_vec_new_uninitialized(&binary, file_size); + if (fread(binary.data, file_size, 1, file) != 1) { + printf("> Error loading module!\n"); + return 1; + } + fclose(file); + + // Compile. + printf("Compiling module...\n"); + own wasm_module_t* module = wasm_module_new(store, &binary); + if (!module) { + printf("> Error compiling module!\n"); + return 1; + } + + wasm_byte_vec_delete(&binary); + + // Create external callback function. + printf("Creating callback...\n"); + own wasm_functype_t* callback_type = wasm_functype_new_1_1( + wasm_valtype_new(WASM_ANYREF), wasm_valtype_new(WASM_ANYREF)); + own wasm_func_t* callback_func = + wasm_func_new(store, callback_type, callback); + + wasm_functype_delete(callback_type); + + // Instantiate. + printf("Instantiating module...\n"); + const wasm_extern_t* imports[] = { wasm_func_as_extern(callback_func) }; + own wasm_instance_t* instance = + wasm_instance_new(store, module, imports, NULL); + if (!instance) { + printf("> Error instantiating module!\n"); + return 1; + } + + wasm_func_delete(callback_func); + wasm_module_delete(module); + + // Extract export. + printf("Extracting exports...\n"); + own wasm_extern_vec_t exports; + wasm_instance_exports(instance, &exports); + size_t i = 0; + wasm_global_t* global = get_export_global(&exports, i++); + wasm_table_t* table = get_export_table(&exports, i++); + wasm_func_t* global_set = get_export_func(&exports, i++); + wasm_func_t* global_get = get_export_func(&exports, i++); + wasm_func_t* table_set = get_export_func(&exports, i++); + wasm_func_t* table_get = get_export_func(&exports, i++); + wasm_func_t* func_call = get_export_func(&exports, i++); + + wasm_instance_delete(instance); + + // Create host references. + printf("Creating host references...\n"); + own wasm_ref_t* host1 = wasm_foreign_as_ref(wasm_foreign_new(store)); + own wasm_ref_t* host2 = wasm_foreign_as_ref(wasm_foreign_new(store)); + wasm_ref_set_host_info(host1, (void*)1); + wasm_ref_set_host_info(host2, (void*)2); + + // Some sanity checks. + check(NULL, NULL); + check(wasm_ref_copy(host1), host1); + check(wasm_ref_copy(host2), host2); + + own wasm_val_t val; + val.kind = WASM_ANYREF; + val.of.ref = wasm_ref_copy(host1); + check(wasm_ref_copy(val.of.ref), host1); + own wasm_ref_t* ref = val.of.ref; + check(wasm_ref_copy(ref), host1); + wasm_val_delete(&val); + + // Interact. + printf("Accessing global...\n"); + check(call_v_r(global_get), NULL); + call_r_v(global_set, host1); + check(call_v_r(global_get), host1); + call_r_v(global_set, host2); + check(call_v_r(global_get), host2); + call_r_v(global_set, NULL); + check(call_v_r(global_get), NULL); + + wasm_global_get(global, &val); + assert(val.kind == WASM_ANYREF); + check(val.of.ref, NULL); + val.of.ref = host2; + wasm_global_set(global, &val); + check(call_v_r(global_get), host2); + wasm_global_get(global, &val); + assert(val.kind == WASM_ANYREF); + check(val.of.ref, host2); + + printf("Accessing table...\n"); + check(call_i_r(table_get, 0), NULL); + check(call_i_r(table_get, 1), NULL); + call_ir_v(table_set, 0, host1); + call_ir_v(table_set, 1, host2); + check(call_i_r(table_get, 0), host1); + check(call_i_r(table_get, 1), host2); + call_ir_v(table_set, 0, NULL); + check(call_i_r(table_get, 0), NULL); + + check(wasm_table_get(table, 2), NULL); + wasm_table_set(table, 2, host1); + check(call_i_r(table_get, 2), host1); + check(wasm_table_get(table, 2), host1); + + printf("Accessing function...\n"); + check(call_r_r(func_call, NULL), NULL); + check(call_r_r(func_call, host1), host1); + check(call_r_r(func_call, host2), host2); + + wasm_ref_delete(host1); + wasm_ref_delete(host2); + + wasm_extern_vec_delete(&exports); + + // Shut down. + printf("Shutting down...\n"); + wasm_store_delete(store); + wasm_engine_delete(engine); + + // All done. + printf("Done.\n"); + return 0; +} diff --git a/lib/c-api/tests/wasm-c-api/example/hostref.cc b/lib/c-api/tests/wasm-c-api/example/hostref.cc new file mode 100644 index 000000000..c94c8f596 --- /dev/null +++ b/lib/c-api/tests/wasm-c-api/example/hostref.cc @@ -0,0 +1,232 @@ +#include +#include +#include +#include +#include + +#include "wasm.hh" + + +// A function to be called from Wasm code. +auto callback( + const wasm::Val args[], wasm::Val results[] +) -> wasm::own { + std::cout << "Calling back..." << std::endl; + std::cout << "> " << (args[0].ref() ? args[0].ref()->get_host_info() : nullptr) << std::endl; + results[0] = args[0].copy(); + return nullptr; +} + + +auto get_export_func(const wasm::ownvec& exports, size_t i) -> const wasm::Func* { + if (exports.size() <= i || !exports[i]->func()) { + std::cout << "> Error accessing function export " << i << "/" << exports.size() << "!" << std::endl; + exit(1); + } + return exports[i]->func(); +} + +auto get_export_global(wasm::ownvec& exports, size_t i) -> wasm::Global* { + if (exports.size() <= i || !exports[i]->global()) { + std::cout << "> Error accessing global export " << i << "!" << std::endl; + exit(1); + } + return exports[i]->global(); +} + +auto get_export_table(wasm::ownvec& exports, size_t i) -> wasm::Table* { + if (exports.size() <= i || !exports[i]->table()) { + std::cout << "> Error accessing table export " << i << "!" << std::endl; + exit(1); + } + return exports[i]->table(); +} + + +void call_r_v(const wasm::Func* func, const wasm::Ref* ref) { + std::cout << "call_r_v... " << std::flush; + wasm::Val args[1] = {wasm::Val::ref(ref ? ref->copy() : wasm::own())}; + if (func->call(args, nullptr)) { + std::cout << "> Error calling function!" << std::endl; + exit(1); + } + std::cout << "okay" << std::endl; +} + +auto call_v_r(const wasm::Func* func) -> wasm::own { + std::cout << "call_v_r... " << std::flush; + wasm::Val results[1]; + if (func->call(nullptr, results)) { + std::cout << "> Error calling function!" << std::endl; + exit(1); + } + std::cout << "okay" << std::endl; + return results[0].release_ref(); +} + +auto call_r_r(const wasm::Func* func, const wasm::Ref* ref) -> wasm::own { + std::cout << "call_r_r... " << std::flush; + wasm::Val args[1] = {wasm::Val::ref(ref ? ref->copy() : wasm::own())}; + wasm::Val results[1]; + if (func->call(args, results)) { + std::cout << "> Error calling function!" << std::endl; + exit(1); + } + std::cout << "okay" << std::endl; + return results[0].release_ref(); +} + +void call_ir_v(const wasm::Func* func, int32_t i, const wasm::Ref* ref) { + std::cout << "call_ir_v... " << std::flush; + wasm::Val args[2] = {wasm::Val::i32(i), wasm::Val::ref(ref ? ref->copy() : wasm::own())}; + if (func->call(args, nullptr)) { + std::cout << "> Error calling function!" << std::endl; + exit(1); + } + std::cout << "okay" << std::endl; +} + +auto call_i_r(const wasm::Func* func, int32_t i) -> wasm::own { + std::cout << "call_i_r... " << std::flush; + wasm::Val args[1] = {wasm::Val::i32(i)}; + wasm::Val results[1]; + if (func->call(args, results)) { + std::cout << "> Error calling function!" << std::endl; + exit(1); + } + std::cout << "okay" << std::endl; + return results[0].release_ref(); +} + +void check(wasm::own actual, const wasm::Ref* expected) { + if (actual.get() != expected && + !(actual && expected && actual->same(expected))) { + std::cout << "> Error reading reference, expected " + << (expected ? expected->get_host_info() : nullptr) << ", got " + << (actual ? actual->get_host_info() : nullptr) << std::endl; + exit(1); + } +} + +void run() { + // Initialize. + std::cout << "Initializing..." << std::endl; + auto engine = wasm::Engine::make(); + auto store_ = wasm::Store::make(engine.get()); + auto store = store_.get(); + + // Load binary. + std::cout << "Loading binary..." << std::endl; + std::ifstream file("hostref.wasm"); + file.seekg(0, std::ios_base::end); + auto file_size = file.tellg(); + file.seekg(0); + auto binary = wasm::vec::make_uninitialized(file_size); + file.read(binary.get(), file_size); + file.close(); + if (file.fail()) { + std::cout << "> Error loading module!" << std::endl; + return; + } + + // Compile. + std::cout << "Compiling module..." << std::endl; + auto module = wasm::Module::make(store, binary); + if (!module) { + std::cout << "> Error compiling module!" << std::endl; + return; + } + + // Create external callback function. + std::cout << "Creating callback..." << std::endl; + auto callback_type = wasm::FuncType::make( + wasm::ownvec::make(wasm::ValType::make(wasm::ValKind::ANYREF)), + wasm::ownvec::make(wasm::ValType::make(wasm::ValKind::ANYREF)) + ); + auto callback_func = wasm::Func::make(store, callback_type.get(), callback); + + // Instantiate. + std::cout << "Instantiating module..." << std::endl; + wasm::Extern* imports[] = {callback_func.get()}; + auto instance = wasm::Instance::make(store, module.get(), imports); + if (!instance) { + std::cout << "> Error instantiating module!" << std::endl; + return; + } + + // Extract export. + std::cout << "Extracting exports..." << std::endl; + auto exports = instance->exports(); + size_t i = 0; + auto global = get_export_global(exports, i++); + auto table = get_export_table(exports, i++); + auto global_set = get_export_func(exports, i++); + auto global_get = get_export_func(exports, i++); + auto table_set = get_export_func(exports, i++); + auto table_get = get_export_func(exports, i++); + auto func_call = get_export_func(exports, i++); + + // Create host references. + std::cout << "Creating host references..." << std::endl; + auto host1 = wasm::Foreign::make(store); + auto host2 = wasm::Foreign::make(store); + host1->set_host_info(reinterpret_cast(1)); + host2->set_host_info(reinterpret_cast(2)); + + // Some sanity checks. + check(nullptr, nullptr); + check(host1->copy(), host1.get()); + check(host2->copy(), host2.get()); + + wasm::Val val = wasm::Val::ref(host1->copy()); + check(val.ref()->copy(), host1.get()); + auto ref = val.release_ref(); + assert(val.ref() == nullptr); + check(ref->copy(), host1.get()); + + // Interact. + std::cout << "Accessing global..." << std::endl; + check(call_v_r(global_get), nullptr); + call_r_v(global_set, host1.get()); + check(call_v_r(global_get), host1.get()); + call_r_v(global_set, host2.get()); + check(call_v_r(global_get), host2.get()); + call_r_v(global_set, nullptr); + check(call_v_r(global_get), nullptr); + + check(global->get().release_ref(), nullptr); + global->set(wasm::Val(host2->copy())); + check(call_v_r(global_get), host2.get()); + check(global->get().release_ref(), host2.get()); + + std::cout << "Accessing table..." << std::endl; + check(call_i_r(table_get, 0), nullptr); + check(call_i_r(table_get, 1), nullptr); + call_ir_v(table_set, 0, host1.get()); + call_ir_v(table_set, 1, host2.get()); + check(call_i_r(table_get, 0), host1.get()); + check(call_i_r(table_get, 1), host2.get()); + call_ir_v(table_set, 0, nullptr); + check(call_i_r(table_get, 0), nullptr); + + check(table->get(2), nullptr); + table->set(2, host1.get()); + check(call_i_r(table_get, 2), host1.get()); + check(table->get(2), host1.get()); + + std::cout << "Accessing function..." << std::endl; + check(call_r_r(func_call, nullptr), nullptr); + check(call_r_r(func_call, host1.get()), host1.get()); + check(call_r_r(func_call, host2.get()), host2.get()); + + // Shut down. + std::cout << "Shutting down..." << std::endl; +} + + +int main(int argc, const char* argv[]) { + run(); + std::cout << "Done." << std::endl; + return 0; +} + diff --git a/lib/c-api/tests/wasm-c-api/example/hostref.wasm b/lib/c-api/tests/wasm-c-api/example/hostref.wasm new file mode 100644 index 000000000..7bfc7288e Binary files /dev/null and b/lib/c-api/tests/wasm-c-api/example/hostref.wasm differ diff --git a/lib/c-api/tests/wasm-c-api/example/hostref.wat b/lib/c-api/tests/wasm-c-api/example/hostref.wat new file mode 100644 index 000000000..4d14ba6ae --- /dev/null +++ b/lib/c-api/tests/wasm-c-api/example/hostref.wat @@ -0,0 +1,24 @@ +(module + (import "" "f" (func $fun (param anyref) (result anyref))) + + (global $glob (export "global") (mut anyref) (ref.null)) + (table $tab (export "table") 10 anyref) + + (func (export "global.set") (param $r anyref) + (global.set $glob (local.get $r)) + ) + (func (export "global.get") (result anyref) + (global.get $glob) + ) + + (func (export "table.set") (param $i i32) (param $r anyref) + (table.set $tab (local.get $i) (local.get $r)) + ) + (func (export "table.get") (param $i i32) (result anyref) + (table.get $tab (local.get $i)) + ) + + (func (export "func.call") (param $r anyref) (result anyref) + (call $fun (local.get $r)) + ) +) diff --git a/lib/c-api/tests/wasm-c-api/example/memory.c b/lib/c-api/tests/wasm-c-api/example/memory.c new file mode 100644 index 000000000..af2b9414d --- /dev/null +++ b/lib/c-api/tests/wasm-c-api/example/memory.c @@ -0,0 +1,222 @@ +#include +#include +#include +#include + +#include "wasm.h" + +#define own + + +wasm_memory_t* get_export_memory(const wasm_extern_vec_t* exports, size_t i) { + if (exports->size <= i || !wasm_extern_as_memory(exports->data[i])) { + printf("> Error accessing memory export %zu!\n", i); + exit(1); + } + return wasm_extern_as_memory(exports->data[i]); +} + +wasm_func_t* get_export_func(const wasm_extern_vec_t* exports, size_t i) { + if (exports->size <= i || !wasm_extern_as_func(exports->data[i])) { + printf("> Error accessing function export %zu!\n", i); + exit(1); + } + return wasm_extern_as_func(exports->data[i]); +} + + +void check(bool success) { + if (!success) { + printf("> Error, expected success\n"); + exit(1); + } +} + +void check_call(wasm_func_t* func, wasm_val_t args[], int32_t expected) { + wasm_val_t results[1]; + if (wasm_func_call(func, args, results) || results[0].of.i32 != expected) { + printf("> Error on result\n"); + exit(1); + } +} + +void check_call0(wasm_func_t* func, int32_t expected) { + check_call(func, NULL, expected); +} + +void check_call1(wasm_func_t* func, int32_t arg, int32_t expected) { + wasm_val_t args[] = { {.kind = WASM_I32, .of = {.i32 = arg}} }; + check_call(func, args, expected); +} + +void check_call2(wasm_func_t* func, int32_t arg1, int32_t arg2, int32_t expected) { + wasm_val_t args[2] = { + {.kind = WASM_I32, .of = {.i32 = arg1}}, + {.kind = WASM_I32, .of = {.i32 = arg2}} + }; + check_call(func, args, expected); +} + +void check_ok(wasm_func_t* func, wasm_val_t args[]) { + if (wasm_func_call(func, args, NULL)) { + printf("> Error on result, expected empty\n"); + exit(1); + } +} + +void check_ok2(wasm_func_t* func, int32_t arg1, int32_t arg2) { + wasm_val_t args[2] = { + {.kind = WASM_I32, .of = {.i32 = arg1}}, + {.kind = WASM_I32, .of = {.i32 = arg2}} + }; + check_ok(func, args); +} + +void check_trap(wasm_func_t* func, wasm_val_t args[]) { + wasm_val_t results[1]; + own wasm_trap_t* trap = wasm_func_call(func, args, results); + if (! trap) { + printf("> Error on result, expected trap\n"); + exit(1); + } + wasm_trap_delete(trap); +} + +void check_trap1(wasm_func_t* func, int32_t arg) { + wasm_val_t args[1] = { {.kind = WASM_I32, .of = {.i32 = arg}} }; + check_trap(func, args); +} + +void check_trap2(wasm_func_t* func, int32_t arg1, int32_t arg2) { + wasm_val_t args[2] = { + {.kind = WASM_I32, .of = {.i32 = arg1}}, + {.kind = WASM_I32, .of = {.i32 = arg2}} + }; + check_trap(func, args); +} + + +int main(int argc, const char* argv[]) { + // Initialize. + printf("Initializing...\n"); + wasm_engine_t* engine = wasm_engine_new(); + wasm_store_t* store = wasm_store_new(engine); + + // Load binary. + printf("Loading binary...\n"); + FILE* file = fopen("memory.wasm", "r"); + if (!file) { + printf("> Error loading module!\n"); + return 1; + } + fseek(file, 0L, SEEK_END); + size_t file_size = ftell(file); + fseek(file, 0L, SEEK_SET); + wasm_byte_vec_t binary; + wasm_byte_vec_new_uninitialized(&binary, file_size); + if (fread(binary.data, file_size, 1, file) != 1) { + printf("> Error loading module!\n"); + return 1; + } + fclose(file); + + // Compile. + printf("Compiling module...\n"); + own wasm_module_t* module = wasm_module_new(store, &binary); + if (!module) { + printf("> Error compiling module!\n"); + return 1; + } + + wasm_byte_vec_delete(&binary); + + // Instantiate. + printf("Instantiating module...\n"); + own wasm_instance_t* instance = wasm_instance_new(store, module, NULL, NULL); + if (!instance) { + printf("> Error instantiating module!\n"); + return 1; + } + + // Extract export. + printf("Extracting exports...\n"); + own wasm_extern_vec_t exports; + wasm_instance_exports(instance, &exports); + size_t i = 0; + wasm_memory_t* memory = get_export_memory(&exports, i++); + wasm_func_t* size_func = get_export_func(&exports, i++); + wasm_func_t* load_func = get_export_func(&exports, i++); + wasm_func_t* store_func = get_export_func(&exports, i++); + + wasm_module_delete(module); + + // Try cloning. + own wasm_memory_t* copy = wasm_memory_copy(memory); + assert(wasm_memory_same(memory, copy)); + wasm_memory_delete(copy); + + // Check initial memory. + printf("Checking memory...\n"); + check(wasm_memory_size(memory) == 2); + check(wasm_memory_data_size(memory) == 0x20000); + check(wasm_memory_data(memory)[0] == 0); + check(wasm_memory_data(memory)[0x1000] == 1); + check(wasm_memory_data(memory)[0x1003] == 4); + + check_call0(size_func, 2); + check_call1(load_func, 0, 0); + check_call1(load_func, 0x1000, 1); + check_call1(load_func, 0x1003, 4); + check_call1(load_func, 0x1ffff, 0); + check_trap1(load_func, 0x20000); + + // Mutate memory. + printf("Mutating memory...\n"); + wasm_memory_data(memory)[0x1003] = 5; + check_ok2(store_func, 0x1002, 6); + check_trap2(store_func, 0x20000, 0); + + check(wasm_memory_data(memory)[0x1002] == 6); + check(wasm_memory_data(memory)[0x1003] == 5); + check_call1(load_func, 0x1002, 6); + check_call1(load_func, 0x1003, 5); + + // Grow memory. + printf("Growing memory...\n"); + check(wasm_memory_grow(memory, 1)); + check(wasm_memory_size(memory) == 3); + check(wasm_memory_data_size(memory) == 0x30000); + + check_call1(load_func, 0x20000, 0); + check_ok2(store_func, 0x20000, 0); + check_trap1(load_func, 0x30000); + check_trap2(store_func, 0x30000, 0); + + check(! wasm_memory_grow(memory, 1)); + check(wasm_memory_grow(memory, 0)); + + wasm_extern_vec_delete(&exports); + wasm_instance_delete(instance); + + // Create stand-alone memory. + // TODO(wasm+): Once Wasm allows multiple memories, turn this into import. + printf("Creating stand-alone memory...\n"); + wasm_limits_t limits = {5, 5}; + own wasm_memorytype_t* memorytype = wasm_memorytype_new(&limits); + own wasm_memory_t* memory2 = wasm_memory_new(store, memorytype); + check(wasm_memory_size(memory2) == 5); + check(! wasm_memory_grow(memory2, 1)); + check(wasm_memory_grow(memory2, 0)); + + wasm_memorytype_delete(memorytype); + wasm_memory_delete(memory2); + + // Shut down. + printf("Shutting down...\n"); + wasm_store_delete(store); + wasm_engine_delete(engine); + + // All done. + printf("Done.\n"); + return 0; +} diff --git a/lib/c-api/tests/wasm-c-api/example/memory.cc b/lib/c-api/tests/wasm-c-api/example/memory.cc new file mode 100644 index 000000000..4094accd8 --- /dev/null +++ b/lib/c-api/tests/wasm-c-api/example/memory.cc @@ -0,0 +1,172 @@ +#include +#include +#include +#include +#include + +#include "wasm.hh" + + +auto get_export_memory(wasm::ownvec& exports, size_t i) -> wasm::Memory* { + if (exports.size() <= i || !exports[i]->memory()) { + std::cout << "> Error accessing memory export " << i << "!" << std::endl; + exit(1); + } + return exports[i]->memory(); +} + +auto get_export_func(const wasm::ownvec& exports, size_t i) -> const wasm::Func* { + if (exports.size() <= i || !exports[i]->func()) { + std::cout << "> Error accessing function export " << i << "!" << std::endl; + exit(1); + } + return exports[i]->func(); +} + +template +void check(T actual, U expected) { + if (actual != expected) { + std::cout << "> Error on result, expected " << expected << ", got " << actual << std::endl; + exit(1); + } +} + +template +void check_ok(const wasm::Func* func, Args... xs) { + wasm::Val args[] = {wasm::Val::i32(xs)...}; + if (func->call(args)) { + std::cout << "> Error on result, expected return" << std::endl; + exit(1); + } +} + +template +void check_trap(const wasm::Func* func, Args... xs) { + wasm::Val args[] = {wasm::Val::i32(xs)...}; + if (! func->call(args)) { + std::cout << "> Error on result, expected trap" << std::endl; + exit(1); + } +} + +template +auto call(const wasm::Func* func, Args... xs) -> int32_t { + wasm::Val args[] = {wasm::Val::i32(xs)...}; + wasm::Val results[1]; + if (func->call(args, results)) { + std::cout << "> Error on result, expected return" << std::endl; + exit(1); + } + return results[0].i32(); +} + + +void run() { + // Initialize. + std::cout << "Initializing..." << std::endl; + auto engine = wasm::Engine::make(); + auto store_ = wasm::Store::make(engine.get()); + auto store = store_.get(); + + // Load binary. + std::cout << "Loading binary..." << std::endl; + std::ifstream file("memory.wasm"); + file.seekg(0, std::ios_base::end); + auto file_size = file.tellg(); + file.seekg(0); + auto binary = wasm::vec::make_uninitialized(file_size); + file.read(binary.get(), file_size); + file.close(); + if (file.fail()) { + std::cout << "> Error loading module!" << std::endl; + exit(1); + } + + // Compile. + std::cout << "Compiling module..." << std::endl; + auto module = wasm::Module::make(store, binary); + if (!module) { + std::cout << "> Error compiling module!" << std::endl; + exit(1); + } + + // Instantiate. + std::cout << "Instantiating module..." << std::endl; + auto instance = wasm::Instance::make(store, module.get(), nullptr); + if (!instance) { + std::cout << "> Error instantiating module!" << std::endl; + exit(1); + } + + // Extract export. + std::cout << "Extracting exports..." << std::endl; + auto exports = instance->exports(); + size_t i = 0; + auto memory = get_export_memory(exports, i++); + auto size_func = get_export_func(exports, i++); + auto load_func = get_export_func(exports, i++); + auto store_func = get_export_func(exports, i++); + + // Try cloning. + assert(memory->copy()->same(memory)); + + // Check initial memory. + std::cout << "Checking memory..." << std::endl; + check(memory->size(), 2u); + check(memory->data_size(), 0x20000u); + check(memory->data()[0], 0); + check(memory->data()[0x1000], 1); + check(memory->data()[0x1003], 4); + + check(call(size_func), 2); + check(call(load_func, 0), 0); + check(call(load_func, 0x1000), 1); + check(call(load_func, 0x1003), 4); + check(call(load_func, 0x1ffff), 0); + check_trap(load_func, 0x20000); + + // Mutate memory. + std::cout << "Mutating memory..." << std::endl; + memory->data()[0x1003] = 5; + check_ok(store_func, 0x1002, 6); + check_trap(store_func, 0x20000, 0); + + check(memory->data()[0x1002], 6); + check(memory->data()[0x1003], 5); + check(call(load_func, 0x1002), 6); + check(call(load_func, 0x1003), 5); + + // Grow memory. + std::cout << "Growing memory..." << std::endl; + check(memory->grow(1), true); + check(memory->size(), 3u); + check(memory->data_size(), 0x30000u); + + check(call(load_func, 0x20000), 0); + check_ok(store_func, 0x20000, 0); + check_trap(load_func, 0x30000); + check_trap(store_func, 0x30000, 0); + + check(memory->grow(1), false); + check(memory->grow(0), true); + + // Create stand-alone memory. + // TODO(wasm+): Once Wasm allows multiple memories, turn this into import. + std::cout << "Creating stand-alone memory..." << std::endl; + auto memorytype = wasm::MemoryType::make(wasm::Limits(5, 5)); + auto memory2 = wasm::Memory::make(store, memorytype.get()); + check(memory2->size(), 5u); + check(memory2->grow(1), false); + check(memory2->grow(0), true); + + // Shut down. + std::cout << "Shutting down..." << std::endl; +} + + +int main(int argc, const char* argv[]) { + run(); + std::cout << "Done." << std::endl; + return 0; +} + diff --git a/lib/c-api/tests/wasm-c-api/example/memory.wasm b/lib/c-api/tests/wasm-c-api/example/memory.wasm new file mode 100644 index 000000000..6f6518b18 Binary files /dev/null and b/lib/c-api/tests/wasm-c-api/example/memory.wasm differ diff --git a/lib/c-api/tests/wasm-c-api/example/memory.wat b/lib/c-api/tests/wasm-c-api/example/memory.wat new file mode 100644 index 000000000..4cf43e2c7 --- /dev/null +++ b/lib/c-api/tests/wasm-c-api/example/memory.wat @@ -0,0 +1,11 @@ +(module + (memory (export "memory") 2 3) + + (func (export "size") (result i32) (memory.size)) + (func (export "load") (param i32) (result i32) (i32.load8_s (local.get 0))) + (func (export "store") (param i32 i32) + (i32.store8 (local.get 0) (local.get 1)) + ) + + (data (i32.const 0x1000) "\01\02\03\04") +) diff --git a/lib/c-api/tests/wasm-c-api/example/multi.c b/lib/c-api/tests/wasm-c-api/example/multi.c new file mode 100644 index 000000000..3859401c0 --- /dev/null +++ b/lib/c-api/tests/wasm-c-api/example/multi.c @@ -0,0 +1,157 @@ +#include +#include +#include +#include + +#include "wasm.h" + +#define own + +// A function to be called from Wasm code. +own wasm_trap_t* callback( + const wasm_val_t args[], wasm_val_t results[] +) { + printf("Calling back...\n> "); + printf("> %"PRIu32" %"PRIu64" %"PRIu64" %"PRIu32"\n", + args[0].of.i32, args[1].of.i64, args[2].of.i64, args[3].of.i32); + printf("\n"); + + wasm_val_copy(&results[0], &args[3]); + wasm_val_copy(&results[1], &args[1]); + wasm_val_copy(&results[2], &args[2]); + wasm_val_copy(&results[3], &args[0]); + return NULL; +} + + +// A function closure. +own wasm_trap_t* closure_callback( + void* env, const wasm_val_t args[], wasm_val_t results[] +) { + int i = *(int*)env; + printf("Calling back closure...\n"); + printf("> %d\n", i); + + results[0].kind = WASM_I32; + results[0].of.i32 = (int32_t)i; + return NULL; +} + + +int main(int argc, const char* argv[]) { + // Initialize. + printf("Initializing...\n"); + wasm_engine_t* engine = wasm_engine_new(); + wasm_store_t* store = wasm_store_new(engine); + + // Load binary. + printf("Loading binary...\n"); + FILE* file = fopen("multi.wasm", "r"); + if (!file) { + printf("> Error loading module!\n"); + return 1; + } + fseek(file, 0L, SEEK_END); + size_t file_size = ftell(file); + fseek(file, 0L, SEEK_SET); + wasm_byte_vec_t binary; + wasm_byte_vec_new_uninitialized(&binary, file_size); + if (fread(binary.data, file_size, 1, file) != 1) { + printf("> Error loading module!\n"); + return 1; + } + fclose(file); + + // Compile. + printf("Compiling module...\n"); + own wasm_module_t* module = wasm_module_new(store, &binary); + if (!module) { + printf("> Error compiling module!\n"); + return 1; + } + + wasm_byte_vec_delete(&binary); + + // Create external print functions. + printf("Creating callback...\n"); + wasm_valtype_t* types[4] = { + wasm_valtype_new_i32(), wasm_valtype_new_i64(), + wasm_valtype_new_i64(), wasm_valtype_new_i32() + }; + own wasm_valtype_vec_t tuple1, tuple2; + wasm_valtype_vec_new(&tuple1, 4, types); + wasm_valtype_vec_copy(&tuple2, &tuple1); + own wasm_functype_t* callback_type = wasm_functype_new(&tuple1, &tuple2); + own wasm_func_t* callback_func = + wasm_func_new(store, callback_type, callback); + + wasm_functype_delete(callback_type); + + // Instantiate. + printf("Instantiating module...\n"); + const wasm_extern_t* imports[] = {wasm_func_as_extern(callback_func)}; + own wasm_instance_t* instance = + wasm_instance_new(store, module, imports, NULL); + if (!instance) { + printf("> Error instantiating module!\n"); + return 1; + } + + wasm_func_delete(callback_func); + + // Extract export. + printf("Extracting export...\n"); + own wasm_extern_vec_t exports; + wasm_instance_exports(instance, &exports); + if (exports.size == 0) { + printf("> Error accessing exports!\n"); + return 1; + } + const wasm_func_t* run_func = wasm_extern_as_func(exports.data[0]); + if (run_func == NULL) { + printf("> Error accessing export!\n"); + return 1; + } + + wasm_module_delete(module); + wasm_instance_delete(instance); + + // Call. + printf("Calling export...\n"); + wasm_val_t args[4]; + args[0].kind = WASM_I32; + args[0].of.i32 = 1; + args[1].kind = WASM_I64; + args[1].of.i64 = 2; + args[2].kind = WASM_I64; + args[2].of.i64 = 3; + args[3].kind = WASM_I32; + args[3].of.i32 = 4; + wasm_val_t results[4]; + if (wasm_func_call(run_func, args, results)) { + printf("> Error calling function!\n"); + return 1; + } + + wasm_extern_vec_delete(&exports); + + // Print result. + printf("Printing result...\n"); + printf("> %"PRIu32" %"PRIu64" %"PRIu64" %"PRIu32"\n", + results[0].of.i32, results[1].of.i64, + results[2].of.i64, results[3].of.i32); + + assert(results[0].of.i32 == 4); + assert(results[1].of.i64 == 3); + assert(results[2].of.i64 == 2); + assert(results[3].of.i32 == 1); + + // Shut down. + printf("Shutting down...\n"); + wasm_store_delete(store); + wasm_engine_delete(engine); + + // All done. + printf("Done.\n"); + return 0; +} diff --git a/lib/c-api/tests/wasm-c-api/example/multi.cc b/lib/c-api/tests/wasm-c-api/example/multi.cc new file mode 100644 index 000000000..8f7a555cd --- /dev/null +++ b/lib/c-api/tests/wasm-c-api/example/multi.cc @@ -0,0 +1,118 @@ +#include +#include +#include +#include +#include + +#include "wasm.hh" + +// A function to be called from Wasm code. +auto callback( + const wasm::Val args[], wasm::Val results[] +) -> wasm::own { + std::cout << "Calling back..." << std::endl; + std::cout << "> " << args[0].i32(); + std::cout << " " << args[1].i64(); + std::cout << " " << args[2].i64(); + std::cout << " " << args[3].i32() << std::endl; + results[0] = args[3].copy(); + results[1] = args[1].copy(); + results[2] = args[2].copy(); + results[3] = args[0].copy(); + return nullptr; +} + + +void run() { + // Initialize. + std::cout << "Initializing..." << std::endl; + auto engine = wasm::Engine::make(); + auto store_ = wasm::Store::make(engine.get()); + auto store = store_.get(); + + // Load binary. + std::cout << "Loading binary..." << std::endl; + std::ifstream file("multi.wasm"); + file.seekg(0, std::ios_base::end); + auto file_size = file.tellg(); + file.seekg(0); + auto binary = wasm::vec::make_uninitialized(file_size); + file.read(binary.get(), file_size); + file.close(); + if (file.fail()) { + std::cout << "> Error loading module!" << std::endl; + exit(1); + } + + // Compile. + std::cout << "Compiling module..." << std::endl; + auto module = wasm::Module::make(store, binary); + if (!module) { + std::cout << "> Error compiling module!" << std::endl; + exit(1); + } + + // Create external print functions. + std::cout << "Creating callback..." << std::endl; + auto tuple = wasm::ownvec::make( + wasm::ValType::make(wasm::ValKind::I32), + wasm::ValType::make(wasm::ValKind::I64), + wasm::ValType::make(wasm::ValKind::I64), + wasm::ValType::make(wasm::ValKind::I32) + ); + auto callback_type = + wasm::FuncType::make(tuple.deep_copy(), tuple.deep_copy()); + auto callback_func = wasm::Func::make(store, callback_type.get(), callback); + + // Instantiate. + std::cout << "Instantiating module..." << std::endl; + wasm::Extern* imports[] = {callback_func.get()}; + auto instance = wasm::Instance::make(store, module.get(), imports); + if (!instance) { + std::cout << "> Error instantiating module!" << std::endl; + exit(1); + } + + // Extract export. + std::cout << "Extracting export..." << std::endl; + auto exports = instance->exports(); + if (exports.size() == 0 || exports[0]->kind() != wasm::ExternKind::FUNC || !exports[0]->func()) { + std::cout << "> Error accessing export!" << std::endl; + exit(1); + } + auto run_func = exports[0]->func(); + + // Call. + std::cout << "Calling export..." << std::endl; + wasm::Val args[] = { + wasm::Val::i32(1), wasm::Val::i64(2), wasm::Val::i64(3), wasm::Val::i32(4) + }; + wasm::Val results[4]; + if (wasm::own trap = run_func->call(args, results)) { + std::cout << "> Error calling function! " << trap->message().get() << std::endl; + exit(1); + } + + // Print result. + std::cout << "Printing result..." << std::endl; + std::cout << "> " << results[0].i32(); + std::cout << " " << results[1].i64(); + std::cout << " " << results[2].i64(); + std::cout << " " << results[3].i32() << std::endl; + + assert(results[0].i32() == 4); + assert(results[1].i64() == 3); + assert(results[2].i64() == 2); + assert(results[3].i32() == 1); + + // Shut down. + std::cout << "Shutting down..." << std::endl; +} + + +int main(int argc, const char* argv[]) { + run(); + std::cout << "Done." << std::endl; + return 0; +} + diff --git a/lib/c-api/tests/wasm-c-api/example/multi.wasm b/lib/c-api/tests/wasm-c-api/example/multi.wasm new file mode 100644 index 000000000..bff0143f3 Binary files /dev/null and b/lib/c-api/tests/wasm-c-api/example/multi.wasm differ diff --git a/lib/c-api/tests/wasm-c-api/example/multi.wat b/lib/c-api/tests/wasm-c-api/example/multi.wat new file mode 100644 index 000000000..e7fb33112 --- /dev/null +++ b/lib/c-api/tests/wasm-c-api/example/multi.wat @@ -0,0 +1,7 @@ +(module + (func $f (import "" "f") (param i32 i64 i64 i32) (result i32 i64 i64 i32)) + + (func $g (export "g") (param i32 i64 i64 i32) (result i32 i64 i64 i32) + (call $f (local.get 0) (local.get 2) (local.get 1) (local.get 3)) + ) +) diff --git a/lib/c-api/tests/wasm-c-api/example/reflect.c b/lib/c-api/tests/wasm-c-api/example/reflect.c new file mode 100644 index 000000000..15e0165d1 --- /dev/null +++ b/lib/c-api/tests/wasm-c-api/example/reflect.c @@ -0,0 +1,169 @@ +#include +#include +#include +#include + +#include "wasm.h" + +#define own + +void print_mutability(wasm_mutability_t mut) { + switch (mut) { + case WASM_VAR: printf("var"); break; + case WASM_CONST: printf("const"); break; + } +} + +void print_limits(const wasm_limits_t* limits) { + printf("%ud", limits->min); + if (limits->max < wasm_limits_max_default) printf(" %ud", limits->max); +} + +void print_valtype(const wasm_valtype_t* type) { + switch (wasm_valtype_kind(type)) { + case WASM_I32: printf("i32"); break; + case WASM_I64: printf("i64"); break; + case WASM_F32: printf("f32"); break; + case WASM_F64: printf("f64"); break; + case WASM_ANYREF: printf("anyref"); break; + case WASM_FUNCREF: printf("funcref"); break; + } +} + +void print_valtypes(const wasm_valtype_vec_t* types) { + bool first = true; + for (size_t i = 0; i < types->size; ++i) { + if (first) { + first = false; + } else { + printf(" "); + } + print_valtype(types->data[i]); + } +} + +void print_externtype(const wasm_externtype_t* type) { + switch (wasm_externtype_kind(type)) { + case WASM_EXTERN_FUNC: { + const wasm_functype_t* functype = + wasm_externtype_as_functype_const(type); + printf("func "); + print_valtypes(wasm_functype_params(functype)); + printf(" -> "); + print_valtypes(wasm_functype_results(functype)); + } break; + case WASM_EXTERN_GLOBAL: { + const wasm_globaltype_t* globaltype = + wasm_externtype_as_globaltype_const(type); + printf("global "); + print_mutability(wasm_globaltype_mutability(globaltype)); + printf(" "); + print_valtype(wasm_globaltype_content(globaltype)); + } break; + case WASM_EXTERN_TABLE: { + const wasm_tabletype_t* tabletype = + wasm_externtype_as_tabletype_const(type); + printf("table "); + print_limits(wasm_tabletype_limits(tabletype)); + printf(" "); + print_valtype(wasm_tabletype_element(tabletype)); + } break; + case WASM_EXTERN_MEMORY: { + const wasm_memorytype_t* memorytype = + wasm_externtype_as_memorytype_const(type); + printf("memory "); + print_limits(wasm_memorytype_limits(memorytype)); + } break; + } +} + +void print_name(const wasm_name_t* name) { + printf("\"%.*s\"", (int)name->size, name->data); +} + + +int main(int argc, const char* argv[]) { + // Initialize. + printf("Initializing...\n"); + wasm_engine_t* engine = wasm_engine_new(); + wasm_store_t* store = wasm_store_new(engine); + + // Load binary. + printf("Loading binary...\n"); + FILE* file = fopen("reflect.wasm", "r"); + if (!file) { + printf("> Error loading module!\n"); + return 1; + } + fseek(file, 0L, SEEK_END); + size_t file_size = ftell(file); + fseek(file, 0L, SEEK_SET); + wasm_byte_vec_t binary; + wasm_byte_vec_new_uninitialized(&binary, file_size); + if (fread(binary.data, file_size, 1, file) != 1) { + printf("> Error loading module!\n"); + return 1; + } + fclose(file); + + // Compile. + printf("Compiling module...\n"); + own wasm_module_t* module = wasm_module_new(store, &binary); + if (!module) { + printf("> Error compiling module!\n"); + return 1; + } + + wasm_byte_vec_delete(&binary); + + // Instantiate. + printf("Instantiating module...\n"); + own wasm_instance_t* instance = wasm_instance_new(store, module, NULL, NULL); + if (!instance) { + printf("> Error instantiating module!\n"); + return 1; + } + + // Extract export. + printf("Extracting export...\n"); + own wasm_exporttype_vec_t export_types; + own wasm_extern_vec_t exports; + wasm_module_exports(module, &export_types); + wasm_instance_exports(instance, &exports); + assert(exports.size == export_types.size); + + for (size_t i = 0; i < exports.size; ++i) { + assert(wasm_extern_kind(exports.data[i]) == + wasm_externtype_kind(wasm_exporttype_type(export_types.data[i]))); + printf("> export %zu ", i); + print_name(wasm_exporttype_name(export_types.data[i])); + printf("\n"); + printf(">> initial: "); + print_externtype(wasm_exporttype_type(export_types.data[i])); + printf("\n"); + printf(">> current: "); + own wasm_externtype_t* current = wasm_extern_type(exports.data[i]); + print_externtype(current); + wasm_externtype_delete(current); + printf("\n"); + if (wasm_extern_kind(exports.data[i]) == WASM_EXTERN_FUNC) { + wasm_func_t* func = wasm_extern_as_func(exports.data[i]); + printf(">> in-arity: %zu", wasm_func_param_arity(func)); + printf(", out-arity: %zu\n", wasm_func_result_arity(func)); + } + } + + wasm_module_delete(module); + wasm_instance_delete(instance); + wasm_extern_vec_delete(&exports); + wasm_exporttype_vec_delete(&export_types); + + // Shut down. + printf("Shutting down...\n"); + wasm_store_delete(store); + wasm_engine_delete(engine); + + // All done. + printf("Done.\n"); + return 0; +} diff --git a/lib/c-api/tests/wasm-c-api/example/reflect.cc b/lib/c-api/tests/wasm-c-api/example/reflect.cc new file mode 100644 index 000000000..704ae478f --- /dev/null +++ b/lib/c-api/tests/wasm-c-api/example/reflect.cc @@ -0,0 +1,138 @@ +#include +#include +#include +#include +#include + +#include "wasm.hh" + + +auto operator<<(std::ostream& out, wasm::Mutability mut) -> std::ostream& { + switch (mut) { + case wasm::Mutability::VAR: return out << "var"; + case wasm::Mutability::CONST: return out << "const"; + } + return out; +} + +auto operator<<(std::ostream& out, wasm::Limits limits) -> std::ostream& { + out << limits.min; + if (limits.max < wasm::Limits(0).max) out << " " << limits.max; + return out; +} + +auto operator<<(std::ostream& out, const wasm::ValType& type) -> std::ostream& { + switch (type.kind()) { + case wasm::ValKind::I32: return out << "i32"; + case wasm::ValKind::I64: return out << "i64"; + case wasm::ValKind::F32: return out << "f32"; + case wasm::ValKind::F64: return out << "f64"; + case wasm::ValKind::ANYREF: return out << "anyref"; + case wasm::ValKind::FUNCREF: return out << "funcref"; + } + return out; +} + +auto operator<<(std::ostream& out, const wasm::ownvec& types) -> std::ostream& { + bool first = true; + for (size_t i = 0; i < types.size(); ++i) { + if (first) { + first = false; + } else { + out << " "; + } + out << *types[i].get(); + } + return out; +} + +auto operator<<(std::ostream& out, const wasm::ExternType& type) -> std::ostream& { + switch (type.kind()) { + case wasm::ExternKind::FUNC: { + out << "func " << type.func()->params() << " -> " << type.func()->results(); + } break; + case wasm::ExternKind::GLOBAL: { + out << "global " << type.global()->mutability() << " " << *type.global()->content(); + } break; + case wasm::ExternKind::TABLE: { + out << "table " << type.table()->limits() << " " << *type.table()->element(); + } break; + case wasm::ExternKind::MEMORY: { + out << "memory " << type.memory()->limits(); + } break; + } + return out; +} + +auto operator<<(std::ostream& out, const wasm::Name& name) -> std::ostream& { + out << "\"" << std::string(name.get(), name.size()) << "\""; + return out; +} + + +void run() { + // Initialize. + std::cout << "Initializing..." << std::endl; + auto engine = wasm::Engine::make(); + auto store_ = wasm::Store::make(engine.get()); + auto store = store_.get(); + + // Load binary. + std::cout << "Loading binary..." << std::endl; + std::ifstream file("reflect.wasm"); + file.seekg(0, std::ios_base::end); + auto file_size = file.tellg(); + file.seekg(0); + auto binary = wasm::vec::make_uninitialized(file_size); + file.read(binary.get(), file_size); + file.close(); + if (file.fail()) { + std::cout << "> Error loading module!" << std::endl; + exit(1); + } + + // Compile. + std::cout << "Compiling module..." << std::endl; + auto module = wasm::Module::make(store, binary); + if (!module) { + std::cout << "> Error compiling module!" << std::endl; + exit(1); + } + + // Instantiate. + std::cout << "Instantiating module..." << std::endl; + auto instance = wasm::Instance::make(store, module.get(), nullptr); + if (!instance) { + std::cout << "> Error instantiating module!" << std::endl; + exit(1); + } + + // Extract exports. + std::cout << "Extracting export..." << std::endl; + auto export_types = module->exports(); + auto exports = instance->exports(); + assert(exports.size() == export_types.size()); + + for (size_t i = 0; i < exports.size(); ++i) { + assert(exports[i]->kind() == export_types[i]->type()->kind()); + std::cout << "> export " << i << " " << export_types[i]->name() << std::endl; + std::cout << ">> initial: " << *export_types[i]->type() << std::endl; + std::cout << ">> current: " << *exports[i]->type() << std::endl; + if (exports[i]->kind() == wasm::ExternKind::FUNC) { + auto func = exports[i]->func(); + std::cout << ">> in-arity: " << func->param_arity(); + std::cout << ", out-arity: " << func->result_arity() << std::endl; + } + } + + // Shut down. + std::cout << "Shutting down..." << std::endl; +} + + +int main(int argc, const char* argv[]) { + run(); + std::cout << "Done." << std::endl; + return 0; +} + diff --git a/lib/c-api/tests/wasm-c-api/example/reflect.wasm b/lib/c-api/tests/wasm-c-api/example/reflect.wasm new file mode 100644 index 000000000..15a68fe8f Binary files /dev/null and b/lib/c-api/tests/wasm-c-api/example/reflect.wasm differ diff --git a/lib/c-api/tests/wasm-c-api/example/reflect.wat b/lib/c-api/tests/wasm-c-api/example/reflect.wat new file mode 100644 index 000000000..261dfd3c3 --- /dev/null +++ b/lib/c-api/tests/wasm-c-api/example/reflect.wat @@ -0,0 +1,6 @@ +(module + (func (export "func") (param i32 f64 f32) (result i32) (unreachable)) + (global (export "global") f64 (f64.const 0)) + (table (export "table") 0 50 anyfunc) + (memory (export "memory") 1) +) diff --git a/lib/c-api/tests/wasm-c-api/example/serialize.c b/lib/c-api/tests/wasm-c-api/example/serialize.c new file mode 100644 index 000000000..4522c00df --- /dev/null +++ b/lib/c-api/tests/wasm-c-api/example/serialize.c @@ -0,0 +1,123 @@ +#include +#include +#include +#include + +#include "wasm.h" + +#define own + +// A function to be called from Wasm code. +own wasm_trap_t* hello_callback(const wasm_val_t args[], wasm_val_t results[]) { + printf("Calling back...\n"); + printf("> Hello World!\n"); + return NULL; +} + + +int main(int argc, const char* argv[]) { + // Initialize. + printf("Initializing...\n"); + wasm_engine_t* engine = wasm_engine_new(); + wasm_store_t* store = wasm_store_new(engine); + + // Load binary. + printf("Loading binary...\n"); + FILE* file = fopen("serialize.wasm", "r"); + if (!file) { + printf("> Error loading module!\n"); + return 1; + } + fseek(file, 0L, SEEK_END); + size_t file_size = ftell(file); + fseek(file, 0L, SEEK_SET); + wasm_byte_vec_t binary; + wasm_byte_vec_new_uninitialized(&binary, file_size); + if (fread(binary.data, file_size, 1, file) != 1) { + printf("> Error loading module!\n"); + return 1; + } + fclose(file); + + // Compile. + printf("Compiling module...\n"); + own wasm_module_t* module = wasm_module_new(store, &binary); + if (!module) { + printf("> Error compiling module!\n"); + return 1; + } + + wasm_byte_vec_delete(&binary); + + // Serialize module. + printf("Serializing module...\n"); + own wasm_byte_vec_t serialized; + wasm_module_serialize(module, &serialized); + + wasm_module_delete(module); + + // Deserialize module. + printf("Deserializing module...\n"); + own wasm_module_t* deserialized = wasm_module_deserialize(store, &serialized); + if (!deserialized) { + printf("> Error deserializing module!\n"); + return 1; + } + + wasm_byte_vec_delete(&serialized); + + // Create external print functions. + printf("Creating callback...\n"); + own wasm_functype_t* hello_type = wasm_functype_new_0_0(); + own wasm_func_t* hello_func = + wasm_func_new(store, hello_type, hello_callback); + + wasm_functype_delete(hello_type); + + // Instantiate. + printf("Instantiating deserialized module...\n"); + const wasm_extern_t* imports[] = { wasm_func_as_extern(hello_func) }; + own wasm_instance_t* instance = + wasm_instance_new(store, deserialized, imports, NULL); + if (!instance) { + printf("> Error instantiating module!\n"); + return 1; + } + + wasm_func_delete(hello_func); + + // Extract export. + printf("Extracting export...\n"); + own wasm_extern_vec_t exports; + wasm_instance_exports(instance, &exports); + if (exports.size == 0) { + printf("> Error accessing exports!\n"); + return 1; + } + const wasm_func_t* run_func = wasm_extern_as_func(exports.data[0]); + if (run_func == NULL) { + printf("> Error accessing export!\n"); + return 1; + } + + wasm_module_delete(deserialized); + wasm_instance_delete(instance); + + // Call. + printf("Calling export...\n"); + if (wasm_func_call(run_func, NULL, NULL)) { + printf("> Error calling function!\n"); + return 1; + } + + wasm_extern_vec_delete(&exports); + + // Shut down. + printf("Shutting down...\n"); + wasm_store_delete(store); + wasm_engine_delete(engine); + + // All done. + printf("Done.\n"); + return 0; +} diff --git a/lib/c-api/tests/wasm-c-api/example/serialize.cc b/lib/c-api/tests/wasm-c-api/example/serialize.cc new file mode 100644 index 000000000..3a059b5a1 --- /dev/null +++ b/lib/c-api/tests/wasm-c-api/example/serialize.cc @@ -0,0 +1,103 @@ +#include +#include +#include +#include +#include + +#include "wasm.hh" + + +// A function to be called from Wasm code. +auto hello_callback( + const wasm::Val args[], wasm::Val results[] +) -> wasm::own { + std::cout << "Calling back..." << std::endl; + std::cout << "> Hello world!" << std::endl; + return nullptr; +} + + +void run() { + // Initialize. + std::cout << "Initializing..." << std::endl; + auto engine = wasm::Engine::make(); + auto store_ = wasm::Store::make(engine.get()); + auto store = store_.get(); + + // Load binary. + std::cout << "Loading binary..." << std::endl; + std::ifstream file("serialize.wasm"); + file.seekg(0, std::ios_base::end); + auto file_size = file.tellg(); + file.seekg(0); + auto binary = wasm::vec::make_uninitialized(file_size); + file.read(binary.get(), file_size); + file.close(); + if (file.fail()) { + std::cout << "> Error loading module!" << std::endl; + exit(1); + } + + // Compile. + std::cout << "Compiling module..." << std::endl; + auto module = wasm::Module::make(store, binary); + if (!module) { + std::cout << "> Error compiling module!" << std::endl; + exit(1); + } + + // Serialize module. + std::cout << "Serializing module..." << std::endl; + auto serialized = module->serialize(); + + // Deserialize module. + std::cout << "Deserializing module..." << std::endl; + auto deserialized = wasm::Module::deserialize(store, serialized); + if (!deserialized) { + std::cout << "> Error deserializing module!" << std::endl; + exit(1); + } + + // Create external print functions. + std::cout << "Creating callback..." << std::endl; + auto hello_type = wasm::FuncType::make( + wasm::ownvec::make(), wasm::ownvec::make() + ); + auto hello_func = wasm::Func::make(store, hello_type.get(), hello_callback); + + // Instantiate. + std::cout << "Instantiating deserialized module..." << std::endl; + wasm::Extern* imports[] = {hello_func.get()}; + auto instance = wasm::Instance::make(store, deserialized.get(), imports); + if (!instance) { + std::cout << "> Error instantiating module!" << std::endl; + exit(1); + } + + // Extract export. + std::cout << "Extracting export..." << std::endl; + auto exports = instance->exports(); + if (exports.size() == 0 || exports[0]->kind() != wasm::ExternKind::FUNC || !exports[0]->func()) { + std::cout << "> Error accessing export!" << std::endl; + exit(1); + } + auto run_func = exports[0]->func(); + + // Call. + std::cout << "Calling export..." << std::endl; + if (run_func->call()) { + std::cout << "> Error calling function!" << std::endl; + exit(1); + } + + // Shut down. + std::cout << "Shutting down..." << std::endl; +} + + +int main(int argc, const char* argv[]) { + run(); + std::cout << "Done." << std::endl; + return 0; +} + diff --git a/lib/c-api/tests/wasm-c-api/example/serialize.wasm b/lib/c-api/tests/wasm-c-api/example/serialize.wasm new file mode 100644 index 000000000..2207c03ee Binary files /dev/null and b/lib/c-api/tests/wasm-c-api/example/serialize.wasm differ diff --git a/lib/c-api/tests/wasm-c-api/example/serialize.wat b/lib/c-api/tests/wasm-c-api/example/serialize.wat new file mode 100644 index 000000000..1c56c5582 --- /dev/null +++ b/lib/c-api/tests/wasm-c-api/example/serialize.wat @@ -0,0 +1,4 @@ +(module + (func $hello (import "" "hello")) + (func (export "run") (call $hello)) +) diff --git a/lib/c-api/tests/wasm-c-api/example/start.c b/lib/c-api/tests/wasm-c-api/example/start.c new file mode 100644 index 000000000..42fa31749 --- /dev/null +++ b/lib/c-api/tests/wasm-c-api/example/start.c @@ -0,0 +1,105 @@ +#include +#include +#include +#include + +#include "wasm.h" + +#define own + + +void print_frame(wasm_frame_t* frame) { + printf("> %p @ 0x%zx = %"PRIu32".0x%zx\n", + wasm_frame_instance(frame), + wasm_frame_module_offset(frame), + wasm_frame_func_index(frame), + wasm_frame_func_offset(frame) + ); +} + + +int main(int argc, const char* argv[]) { + // Initialize. + printf("Initializing...\n"); + wasm_engine_t* engine = wasm_engine_new(); + wasm_store_t* store = wasm_store_new(engine); + + // Load binary. + printf("Loading binary...\n"); + FILE* file = fopen("start.wasm", "r"); + if (!file) { + printf("> Error loading module!\n"); + return 1; + } + fseek(file, 0L, SEEK_END); + size_t file_size = ftell(file); + fseek(file, 0L, SEEK_SET); + wasm_byte_vec_t binary; + wasm_byte_vec_new_uninitialized(&binary, file_size); + if (fread(binary.data, file_size, 1, file) != 1) { + printf("> Error loading module!\n"); + return 1; + } + fclose(file); + + // Compile. + printf("Compiling module...\n"); + own wasm_module_t* module = wasm_module_new(store, &binary); + if (!module) { + printf("> Error compiling module!\n"); + return 1; + } + + wasm_byte_vec_delete(&binary); + + // Instantiate. + printf("Instantiating module...\n"); + own wasm_trap_t* trap = NULL; + own wasm_instance_t* instance = + wasm_instance_new(store, module, NULL, &trap); + if (instance || !trap) { + printf("> Error instantiating module, expected trap!\n"); + return 1; + } + + wasm_module_delete(module); + + // Print result. + printf("Printing message...\n"); + own wasm_name_t message; + wasm_trap_message(trap, &message); + printf("> %s\n", message.data); + + printf("Printing origin...\n"); + own wasm_frame_t* frame = wasm_trap_origin(trap); + if (frame) { + print_frame(frame); + wasm_frame_delete(frame); + } else { + printf("> Empty origin.\n"); + } + + printf("Printing trace...\n"); + own wasm_frame_vec_t trace; + wasm_trap_trace(trap, &trace); + if (trace.size > 0) { + for (size_t i = 0; i < trace.size; ++i) { + print_frame(trace.data[i]); + } + } else { + printf("> Empty trace.\n"); + } + + wasm_frame_vec_delete(&trace); + wasm_trap_delete(trap); + wasm_name_delete(&message); + + // Shut down. + printf("Shutting down...\n"); + wasm_store_delete(store); + wasm_engine_delete(engine); + + // All done. + printf("Done.\n"); + return 0; +} diff --git a/lib/c-api/tests/wasm-c-api/example/start.cc b/lib/c-api/tests/wasm-c-api/example/start.cc new file mode 100644 index 000000000..71d6fd25a --- /dev/null +++ b/lib/c-api/tests/wasm-c-api/example/start.cc @@ -0,0 +1,88 @@ +#include +#include +#include +#include +#include + +#include "wasm.hh" + + +void print_frame(const wasm::Frame* frame) { + std::cout << "> " << frame->instance(); + std::cout << " @ 0x" << std::hex << frame->module_offset(); + std::cout << " = " << frame->func_index(); + std::cout << ".0x" << std::hex << frame->func_offset() << std::endl; +} + + +void run() { + // Initialize. + std::cout << "Initializing..." << std::endl; + auto engine = wasm::Engine::make(); + auto store_ = wasm::Store::make(engine.get()); + auto store = store_.get(); + + // Load binary. + std::cout << "Loading binary..." << std::endl; + std::ifstream file("start.wasm"); + file.seekg(0, std::ios_base::end); + auto file_size = file.tellg(); + file.seekg(0); + auto binary = wasm::vec::make_uninitialized(file_size); + file.read(binary.get(), file_size); + file.close(); + if (file.fail()) { + std::cout << "> Error loading module!" << std::endl; + exit(1); + } + + // Compile. + std::cout << "Compiling module..." << std::endl; + auto module = wasm::Module::make(store, binary); + if (!module) { + std::cout << "> Error compiling module!" << std::endl; + exit(1); + } + + // Instantiate. + std::cout << "Instantiating module..." << std::endl; + wasm::own trap; + auto instance = wasm::Instance::make(store, module.get(), nullptr, &trap); + if (instance || !trap) { + std::cout << "> Error instantiating module, expected trap!" << std::endl; + exit(1); + } + + // Print result. + std::cout << "Printing message..." << std::endl; + std::cout << "> " << trap->message().get() << std::endl; + + std::cout << "Printing origin..." << std::endl; + auto frame = trap->origin(); + if (frame) { + print_frame(frame.get()); + } else { + std::cout << "> Empty origin." << std::endl; + } + + std::cout << "Printing trace..." << std::endl; + auto trace = trap->trace(); + if (trace.size() > 0) { + for (size_t i = 0; i < trace.size(); ++i) { + print_frame(trace[i].get()); + } + } else { + std::cout << "> Empty trace." << std::endl; + } + + // Shut down. + std::cout << "Shutting down..." << std::endl; +} + + +int main(int argc, const char* argv[]) { + run(); + std::cout << "Done." << std::endl; + return 0; +} + diff --git a/lib/c-api/tests/wasm-c-api/example/start.wasm b/lib/c-api/tests/wasm-c-api/example/start.wasm new file mode 100644 index 000000000..90cba2107 Binary files /dev/null and b/lib/c-api/tests/wasm-c-api/example/start.wasm differ diff --git a/lib/c-api/tests/wasm-c-api/example/start.wat b/lib/c-api/tests/wasm-c-api/example/start.wat new file mode 100644 index 000000000..eb95116a4 --- /dev/null +++ b/lib/c-api/tests/wasm-c-api/example/start.wat @@ -0,0 +1,4 @@ +(module + (func $start (unreachable)) + (start $start) +) diff --git a/lib/c-api/tests/wasm-c-api/example/table.c b/lib/c-api/tests/wasm-c-api/example/table.c new file mode 100644 index 000000000..9b3beeb63 --- /dev/null +++ b/lib/c-api/tests/wasm-c-api/example/table.c @@ -0,0 +1,213 @@ +#include +#include +#include +#include + +#include "wasm.h" + +#define own + +// A function to be called from Wasm code. +own wasm_trap_t* neg_callback( + const wasm_val_t args[], wasm_val_t results[] +) { + printf("Calling back...\n"); + results[0].kind = WASM_I32; + results[0].of.i32 = -args[0].of.i32; + return NULL; +} + + +wasm_table_t* get_export_table(const wasm_extern_vec_t* exports, size_t i) { + if (exports->size <= i || !wasm_extern_as_table(exports->data[i])) { + printf("> Error accessing table export %zu!\n", i); + exit(1); + } + return wasm_extern_as_table(exports->data[i]); +} + +wasm_func_t* get_export_func(const wasm_extern_vec_t* exports, size_t i) { + if (exports->size <= i || !wasm_extern_as_func(exports->data[i])) { + printf("> Error accessing function export %zu!\n", i); + exit(1); + } + return wasm_extern_as_func(exports->data[i]); +} + + +void check(bool success) { + if (!success) { + printf("> Error, expected success\n"); + exit(1); + } +} + +void check_table(wasm_table_t* table, int32_t i, bool expect_set) { + own wasm_ref_t* ref = wasm_table_get(table, i); + check((ref != NULL) == expect_set); + if (ref) wasm_ref_delete(ref); +} + +void check_call(wasm_func_t* func, int32_t arg1, int32_t arg2, int32_t expected) { + wasm_val_t args[2] = { + {.kind = WASM_I32, .of = {.i32 = arg1}}, + {.kind = WASM_I32, .of = {.i32 = arg2}} + }; + wasm_val_t results[1]; + if (wasm_func_call(func, args, results) || results[0].of.i32 != expected) { + printf("> Error on result\n"); + exit(1); + } +} + +void check_trap(wasm_func_t* func, int32_t arg1, int32_t arg2) { + wasm_val_t args[2] = { + {.kind = WASM_I32, .of = {.i32 = arg1}}, + {.kind = WASM_I32, .of = {.i32 = arg2}} + }; + own wasm_trap_t* trap = wasm_func_call(func, args, NULL); + if (! trap) { + printf("> Error on result, expected trap\n"); + exit(1); + } + wasm_trap_delete(trap); +} + + +int main(int argc, const char* argv[]) { + // Initialize. + printf("Initializing...\n"); + wasm_engine_t* engine = wasm_engine_new(); + wasm_store_t* store = wasm_store_new(engine); + + // Load binary. + printf("Loading binary...\n"); + FILE* file = fopen("table.wasm", "r"); + if (!file) { + printf("> Error loading module!\n"); + return 1; + } + fseek(file, 0L, SEEK_END); + size_t file_size = ftell(file); + fseek(file, 0L, SEEK_SET); + wasm_byte_vec_t binary; + wasm_byte_vec_new_uninitialized(&binary, file_size); + if (fread(binary.data, file_size, 1, file) != 1) { + printf("> Error loading module!\n"); + return 1; + } + fclose(file); + + // Compile. + printf("Compiling module...\n"); + own wasm_module_t* module = wasm_module_new(store, &binary); + if (!module) { + printf("> Error compiling module!\n"); + return 1; + } + + wasm_byte_vec_delete(&binary); + + // Instantiate. + printf("Instantiating module...\n"); + own wasm_instance_t* instance = wasm_instance_new(store, module, NULL, NULL); + if (!instance) { + printf("> Error instantiating module!\n"); + return 1; + } + + // Extract export. + printf("Extracting exports...\n"); + own wasm_extern_vec_t exports; + wasm_instance_exports(instance, &exports); + size_t i = 0; + wasm_table_t* table = get_export_table(&exports, i++); + wasm_func_t* call_indirect = get_export_func(&exports, i++); + wasm_func_t* f = get_export_func(&exports, i++); + wasm_func_t* g = get_export_func(&exports, i++); + + wasm_module_delete(module); + + // Create external function. + printf("Creating callback...\n"); + own wasm_functype_t* neg_type = wasm_functype_new_1_1(wasm_valtype_new_i32(), wasm_valtype_new_i32()); + own wasm_func_t* h = wasm_func_new(store, neg_type, neg_callback); + + wasm_functype_delete(neg_type); + + // Try cloning. + own wasm_table_t* copy = wasm_table_copy(table); + assert(wasm_table_same(table, copy)); + wasm_table_delete(copy); + + // Check initial table. + printf("Checking table...\n"); + check(wasm_table_size(table) == 2); + check_table(table, 0, false); + check_table(table, 1, true); + check_trap(call_indirect, 0, 0); + check_call(call_indirect, 7, 1, 7); + check_trap(call_indirect, 0, 2); + + // Mutate table. + printf("Mutating table...\n"); + check(wasm_table_set(table, 0, wasm_func_as_ref(g))); + check(wasm_table_set(table, 1, NULL)); + check(! wasm_table_set(table, 2, wasm_func_as_ref(f))); + check_table(table, 0, true); + check_table(table, 1, false); + check_call(call_indirect, 7, 0, 666); + check_trap(call_indirect, 0, 1); + check_trap(call_indirect, 0, 2); + + // Grow table. + printf("Growing table...\n"); + check(wasm_table_grow(table, 3, NULL)); + check(wasm_table_size(table) == 5); + check(wasm_table_set(table, 2, wasm_func_as_ref(f))); + check(wasm_table_set(table, 3, wasm_func_as_ref(h))); + check(! wasm_table_set(table, 5, NULL)); + check_table(table, 2, true); + check_table(table, 3, true); + check_table(table, 4, false); + check_call(call_indirect, 5, 2, 5); + check_call(call_indirect, 6, 3, -6); + check_trap(call_indirect, 0, 4); + check_trap(call_indirect, 0, 5); + + check(wasm_table_grow(table, 2, wasm_func_as_ref(f))); + check(wasm_table_size(table) == 7); + check_table(table, 5, true); + check_table(table, 6, true); + + check(! wasm_table_grow(table, 5, NULL)); + check(wasm_table_grow(table, 3, NULL)); + check(wasm_table_grow(table, 0, NULL)); + + wasm_func_delete(h); + wasm_extern_vec_delete(&exports); + wasm_instance_delete(instance); + + // Create stand-alone table. + // TODO(wasm+): Once Wasm allows multiple tables, turn this into import. + printf("Creating stand-alone table...\n"); + wasm_limits_t limits = {5, 5}; + own wasm_tabletype_t* tabletype = + wasm_tabletype_new(wasm_valtype_new(WASM_FUNCREF), &limits); + own wasm_table_t* table2 = wasm_table_new(store, tabletype, NULL); + check(wasm_table_size(table2) == 5); + check(! wasm_table_grow(table2, 1, NULL)); + check(wasm_table_grow(table2, 0, NULL)); + + wasm_tabletype_delete(tabletype); + wasm_table_delete(table2); + + // Shut down. + printf("Shutting down...\n"); + wasm_store_delete(store); + wasm_engine_delete(engine); + + // All done. + printf("Done.\n"); + return 0; +} diff --git a/lib/c-api/tests/wasm-c-api/example/table.cc b/lib/c-api/tests/wasm-c-api/example/table.cc new file mode 100644 index 000000000..6890ae7e2 --- /dev/null +++ b/lib/c-api/tests/wasm-c-api/example/table.cc @@ -0,0 +1,192 @@ +#include +#include +#include +#include +#include + +#include "wasm.hh" + + +// A function to be called from Wasm code. +auto neg_callback( + const wasm::Val args[], wasm::Val results[] +) -> wasm::own { + std::cout << "Calling back..." << std::endl; + results[0] = wasm::Val(-args[0].i32()); + return nullptr; +} + + +auto get_export_table(wasm::ownvec& exports, size_t i) -> wasm::Table* { + if (exports.size() <= i || !exports[i]->table()) { + std::cout << "> Error accessing table export " << i << "!" << std::endl; + exit(1); + } + return exports[i]->table(); +} + +auto get_export_func(const wasm::ownvec& exports, size_t i) -> const wasm::Func* { + if (exports.size() <= i || !exports[i]->func()) { + std::cout << "> Error accessing function export " << i << "!" << std::endl; + exit(1); + } + return exports[i]->func(); +} + +template +void check(T actual, U expected) { + if (actual != expected) { + std::cout << "> Error on result, expected " << expected << ", got " << actual << std::endl; + exit(1); + } +} + +void check(bool success) { + if (! success) { + std::cout << "> Error, expected success" << std::endl; + exit(1); + } +} + +auto call( + const wasm::Func* func, wasm::Val&& arg1, wasm::Val&& arg2 +) -> wasm::Val { + wasm::Val args[2] = {std::move(arg1), std::move(arg2)}; + wasm::Val results[1]; + if (func->call(args, results)) { + std::cout << "> Error on result, expected return" << std::endl; + exit(1); + } + return results[0].copy(); +} + +void check_trap(const wasm::Func* func, wasm::Val&& arg1, wasm::Val&& arg2) { + wasm::Val args[2] = {std::move(arg1), std::move(arg2)}; + wasm::Val results[1]; + if (! func->call(args, results)) { + std::cout << "> Error on result, expected trap" << std::endl; + exit(1); + } +} + +void run() { + // Initialize. + std::cout << "Initializing..." << std::endl; + auto engine = wasm::Engine::make(); + auto store_ = wasm::Store::make(engine.get()); + auto store = store_.get(); + + // Load binary. + std::cout << "Loading binary..." << std::endl; + std::ifstream file("table.wasm"); + file.seekg(0, std::ios_base::end); + auto file_size = file.tellg(); + file.seekg(0); + auto binary = wasm::vec::make_uninitialized(file_size); + file.read(binary.get(), file_size); + file.close(); + if (file.fail()) { + std::cout << "> Error loading module!" << std::endl; + exit(1); + } + + // Compile. + std::cout << "Compiling module..." << std::endl; + auto module = wasm::Module::make(store, binary); + if (!module) { + std::cout << "> Error compiling module!" << std::endl; + exit(1); + } + + // Instantiate. + std::cout << "Instantiating module..." << std::endl; + auto instance = wasm::Instance::make(store, module.get(), nullptr); + if (!instance) { + std::cout << "> Error instantiating module!" << std::endl; + exit(1); + } + + // Extract export. + std::cout << "Extracting exports..." << std::endl; + auto exports = instance->exports(); + size_t i = 0; + auto table = get_export_table(exports, i++); + auto call_indirect = get_export_func(exports, i++); + auto f = get_export_func(exports, i++); + auto g = get_export_func(exports, i++); + + // Create external function. + std::cout << "Creating callback..." << std::endl; + auto neg_type = wasm::FuncType::make( + wasm::ownvec::make(wasm::ValType::make(wasm::ValKind::I32)), + wasm::ownvec::make(wasm::ValType::make(wasm::ValKind::I32)) + ); + auto h = wasm::Func::make(store, neg_type.get(), neg_callback); + + // Try cloning. + assert(table->copy()->same(table)); + + // Check initial table. + std::cout << "Checking table..." << std::endl; + check(table->size(), 2u); + check(table->get(0) == nullptr); + check(table->get(1) != nullptr); + check_trap(call_indirect, wasm::Val::i32(0), wasm::Val::i32(0)); + check(call(call_indirect, wasm::Val::i32(7), wasm::Val::i32(1)).i32(), 7); + check_trap(call_indirect, wasm::Val::i32(0), wasm::Val::i32(2)); + + // Mutate table. + std::cout << "Mutating table..." << std::endl; + check(table->set(0, g)); + check(table->set(1, nullptr)); + check(! table->set(2, f)); + check(table->get(0) != nullptr); + check(table->get(1) == nullptr); + check(call(call_indirect, wasm::Val::i32(7), wasm::Val::i32(0)).i32(), 666); + check_trap(call_indirect, wasm::Val::i32(0), wasm::Val::i32(1)); + check_trap(call_indirect, wasm::Val::i32(0), wasm::Val::i32(2)); + + // Grow table. + std::cout << "Growing table..." << std::endl; + check(table->grow(3)); + check(table->size(), 5u); + check(table->set(2, f)); + check(table->set(3, h.get())); + check(! table->set(5, nullptr)); + check(table->get(2) != nullptr); + check(table->get(3) != nullptr); + check(table->get(4) == nullptr); + check(call(call_indirect, wasm::Val::i32(5), wasm::Val::i32(2)).i32(), 5); + check(call(call_indirect, wasm::Val::i32(6), wasm::Val::i32(3)).i32(), -6); + check_trap(call_indirect, wasm::Val::i32(0), wasm::Val::i32(4)); + check_trap(call_indirect, wasm::Val::i32(0), wasm::Val::i32(5)); + + check(table->grow(2, f)); + check(table->size(), 7u); + check(table->get(5) != nullptr); + check(table->get(6) != nullptr); + + check(! table->grow(5)); + check(table->grow(3)); + check(table->grow(0)); + + // Create stand-alone table. + // TODO(wasm+): Once Wasm allows multiple tables, turn this into import. + std::cout << "Creating stand-alone table..." << std::endl; + auto tabletype = wasm::TableType::make( + wasm::ValType::make(wasm::ValKind::FUNCREF), wasm::Limits(5, 5)); + auto table2 = wasm::Table::make(store, tabletype.get()); + check(table2->size() == 5); + check(! table2->grow(1)); + check(table2->grow(0)); + + // Shut down. + std::cout << "Shutting down..." << std::endl; +} + + +int main(int argc, const char* argv[]) { + run(); + std::cout << "Done." << std::endl; + return 0; +} diff --git a/lib/c-api/tests/wasm-c-api/example/table.wasm b/lib/c-api/tests/wasm-c-api/example/table.wasm new file mode 100644 index 000000000..cdc0d8c35 Binary files /dev/null and b/lib/c-api/tests/wasm-c-api/example/table.wasm differ diff --git a/lib/c-api/tests/wasm-c-api/example/table.wat b/lib/c-api/tests/wasm-c-api/example/table.wat new file mode 100644 index 000000000..d3e3a945a --- /dev/null +++ b/lib/c-api/tests/wasm-c-api/example/table.wat @@ -0,0 +1,12 @@ +(module + (table (export "table") 2 10 funcref) + + (func (export "call_indirect") (param i32 i32) (result i32) + (call_indirect (param i32) (result i32) (local.get 0) (local.get 1)) + ) + + (func $f (export "f") (param i32) (result i32) (local.get 0)) + (func (export "g") (param i32) (result i32) (i32.const 666)) + + (elem (i32.const 1) $f) +) diff --git a/lib/c-api/tests/wasm-c-api/example/threads.c b/lib/c-api/tests/wasm-c-api/example/threads.c new file mode 100644 index 000000000..9f9d5894a --- /dev/null +++ b/lib/c-api/tests/wasm-c-api/example/threads.c @@ -0,0 +1,153 @@ +#include +#include +#include +#include +#include +#include + +#include "wasm.h" + +#define own + +const int N_THREADS = 10; +const int N_REPS = 3; + +// A function to be called from Wasm code. +own wasm_trap_t* callback(const wasm_val_t args[], wasm_val_t results[]) { + assert(args[0].kind == WASM_I32); + printf("> Thread %d running\n", args[0].of.i32); + return NULL; +} + + +typedef struct { + wasm_engine_t* engine; + wasm_shared_module_t* module; + int id; +} thread_args; + +void* run(void* args_abs) { + thread_args* args = (thread_args*)args_abs; + + // Rereate store and module. + own wasm_store_t* store = wasm_store_new(args->engine); + own wasm_module_t* module = wasm_module_obtain(store, args->module); + + // Run the example N times. + for (int i = 0; i < N_REPS; ++i) { + usleep(100000); + + // Create imports. + own wasm_functype_t* func_type = wasm_functype_new_1_0(wasm_valtype_new_i32()); + own wasm_func_t* func = wasm_func_new(store, func_type, callback); + wasm_functype_delete(func_type); + + wasm_val_t val = {.kind = WASM_I32, .of = {.i32 = (int32_t)args->id}}; + own wasm_globaltype_t* global_type = + wasm_globaltype_new(wasm_valtype_new_i32(), WASM_CONST); + own wasm_global_t* global = wasm_global_new(store, global_type, &val); + wasm_globaltype_delete(global_type); + + // Instantiate. + const wasm_extern_t* imports[] = { + wasm_func_as_extern(func), wasm_global_as_extern(global), + }; + own wasm_instance_t* instance = + wasm_instance_new(store, module, imports, NULL); + if (!instance) { + printf("> Error instantiating module!\n"); + return NULL; + } + + wasm_func_delete(func); + wasm_global_delete(global); + + // Extract export. + own wasm_extern_vec_t exports; + wasm_instance_exports(instance, &exports); + if (exports.size == 0) { + printf("> Error accessing exports!\n"); + return NULL; + } + const wasm_func_t *run_func = wasm_extern_as_func(exports.data[0]); + if (run_func == NULL) { + printf("> Error accessing export!\n"); + return NULL; + } + + wasm_instance_delete(instance); + + // Call. + if (wasm_func_call(run_func, NULL, NULL)) { + printf("> Error calling function!\n"); + return NULL; + } + + wasm_extern_vec_delete(&exports); + } + + wasm_module_delete(module); + wasm_store_delete(store); + + free(args_abs); + + return NULL; +} + +int main(int argc, const char *argv[]) { + // Initialize. + wasm_engine_t* engine = wasm_engine_new(); + + // Load binary. + FILE* file = fopen("threads.wasm", "r"); + if (!file) { + printf("> Error loading module!\n"); + return 1; + } + fseek(file, 0L, SEEK_END); + size_t file_size = ftell(file); + fseek(file, 0L, SEEK_SET); + wasm_byte_vec_t binary; + wasm_byte_vec_new_uninitialized(&binary, file_size); + if (fread(binary.data, file_size, 1, file) != 1) { + printf("> Error loading module!\n"); + return 1; + } + fclose(file); + + // Compile and share. + own wasm_store_t* store = wasm_store_new(engine); + own wasm_module_t* module = wasm_module_new(store, &binary); + if (!module) { + printf("> Error compiling module!\n"); + return 1; + } + + wasm_byte_vec_delete(&binary); + + own wasm_shared_module_t* shared = wasm_module_share(module); + + wasm_module_delete(module); + wasm_store_delete(store); + + // Spawn threads. + pthread_t threads[N_THREADS]; + for (int i = 0; i < N_THREADS; i++) { + thread_args* args = malloc(sizeof(thread_args)); + args->id = i; + args->engine = engine; + args->module = shared; + printf("Initializing thread %d...\n", i); + pthread_create(&threads[i], NULL, &run, args); + } + + for (int i = 0; i < N_THREADS; i++) { + printf("Waiting for thread: %d\n", i); + pthread_join(threads[i], NULL); + } + + wasm_shared_module_delete(shared); + wasm_engine_delete(engine); + + return 0; +} diff --git a/lib/c-api/tests/wasm-c-api/example/threads.cc b/lib/c-api/tests/wasm-c-api/example/threads.cc new file mode 100644 index 000000000..6148be655 --- /dev/null +++ b/lib/c-api/tests/wasm-c-api/example/threads.cc @@ -0,0 +1,124 @@ +#include +#include +#include +#include + +#include "wasm.hh" + +const int N_THREADS = 10; +const int N_REPS = 3; + +// A function to be called from Wasm code. +auto callback( + void* env, const wasm::Val args[], wasm::Val results[] +) -> wasm::own { + assert(args[0].kind() == wasm::ValKind::I32); + std::lock_guard lock(*reinterpret_cast(env)); + std::cout << "Thread " << args[0].i32() << " running..." << std::endl; + std::cout.flush(); + return nullptr; +} + + +void run( + wasm::Engine* engine, const wasm::Shared* shared, + std::mutex* mutex, int id +) { + // Create store. + auto store_ = wasm::Store::make(engine); + auto store = store_.get(); + + // Obtain. + auto module = wasm::Module::obtain(store, shared); + if (!module) { + std::lock_guard lock(*mutex); + std::cout << "> Error compiling module!" << std::endl; + exit(1); + } + + // Run the example N times. + for (int i = 0; i < N_REPS; ++i) { + std::this_thread::sleep_for(std::chrono::nanoseconds(100000)); + + // Create imports. + auto func_type = wasm::FuncType::make( + wasm::ownvec::make(wasm::ValType::make(wasm::ValKind::I32)), + wasm::ownvec::make() + ); + auto func = wasm::Func::make(store, func_type.get(), callback, mutex); + + auto global_type = wasm::GlobalType::make( + wasm::ValType::make(wasm::ValKind::I32), wasm::Mutability::CONST); + auto global = wasm::Global::make( + store, global_type.get(), wasm::Val::i32(i)); + + // Instantiate. + wasm::Extern* imports[] = {func.get(), global.get()}; + auto instance = wasm::Instance::make(store, module.get(), imports); + if (!instance) { + std::lock_guard lock(*mutex); + std::cout << "> Error instantiating module!" << std::endl; + exit(1); + } + + // Extract export. + auto exports = instance->exports(); + if (exports.size() == 0 || exports[0]->kind() != wasm::ExternKind::FUNC || !exports[0]->func()) { + std::lock_guard lock(*mutex); + std::cout << "> Error accessing export!" << std::endl; + exit(1); + } + auto run_func = exports[0]->func(); + + // Call. + run_func->call(); + } +} + +int main(int argc, const char *argv[]) { + // Initialize. + std::cout << "Initializing..." << std::endl; + auto engine = wasm::Engine::make(); + + // Load binary. + std::cout << "Loading binary..." << std::endl; + std::ifstream file("threads.wasm"); + file.seekg(0, std::ios_base::end); + auto file_size = file.tellg(); + file.seekg(0); + auto binary = wasm::vec::make_uninitialized(file_size); + file.read(binary.get(), file_size); + file.close(); + if (file.fail()) { + std::cout << "> Error loading module!" << std::endl; + return 1; + } + + // Compile and share. + std::cout << "Compiling and sharing module..." << std::endl; + auto store = wasm::Store::make(engine.get()); + auto module = wasm::Module::make(store.get(), binary); + auto shared = module->share(); + + // Spawn threads. + std::cout << "Spawning threads..." << std::endl; + std::mutex mutex; + std::thread threads[N_THREADS]; + for (int i = 0; i < N_THREADS; ++i) { + { + std::lock_guard lock(mutex); + std::cout << "Initializing thread " << i << "..." << std::endl; + } + threads[i] = std::thread(run, engine.get(), shared.get(), &mutex, i); + } + + for (int i = 0; i < N_THREADS; ++i) { + { + std::lock_guard lock(mutex); + std::cout << "Waiting for thread " << i << "..." << std::endl; + } + threads[i].join(); + } + + return 0; +} diff --git a/lib/c-api/tests/wasm-c-api/example/threads.wasm b/lib/c-api/tests/wasm-c-api/example/threads.wasm new file mode 100644 index 000000000..9a5c19d0a Binary files /dev/null and b/lib/c-api/tests/wasm-c-api/example/threads.wasm differ diff --git a/lib/c-api/tests/wasm-c-api/example/threads.wat b/lib/c-api/tests/wasm-c-api/example/threads.wat new file mode 100644 index 000000000..29a3bbcc1 --- /dev/null +++ b/lib/c-api/tests/wasm-c-api/example/threads.wat @@ -0,0 +1,5 @@ +(module + (func $message (import "" "hello") (param i32)) + (global $id (import "" "id") i32) + (func (export "run") (call $message (global.get $id))) +) diff --git a/lib/c-api/tests/wasm-c-api/example/trap.c b/lib/c-api/tests/wasm-c-api/example/trap.c new file mode 100644 index 000000000..975d6f859 --- /dev/null +++ b/lib/c-api/tests/wasm-c-api/example/trap.c @@ -0,0 +1,155 @@ +#include +#include +#include +#include + +#include "wasm.h" + +#define own + +// A function to be called from Wasm code. +own wasm_trap_t* fail_callback( + void* env, const wasm_val_t args[], wasm_val_t results[] +) { + printf("Calling back...\n"); + own wasm_name_t message; + wasm_name_new_from_string(&message, "callback abort"); + own wasm_trap_t* trap = wasm_trap_new((wasm_store_t*)env, &message); + wasm_name_delete(&message); + return trap; +} + + +void print_frame(wasm_frame_t* frame) { + printf("> %p @ 0x%zx = %"PRIu32".0x%zx\n", + wasm_frame_instance(frame), + wasm_frame_module_offset(frame), + wasm_frame_func_index(frame), + wasm_frame_func_offset(frame) + ); +} + + +int main(int argc, const char* argv[]) { + // Initialize. + printf("Initializing...\n"); + wasm_engine_t* engine = wasm_engine_new(); + wasm_store_t* store = wasm_store_new(engine); + + // Load binary. + printf("Loading binary...\n"); + FILE* file = fopen("trap.wasm", "r"); + if (!file) { + printf("> Error loading module!\n"); + return 1; + } + fseek(file, 0L, SEEK_END); + size_t file_size = ftell(file); + fseek(file, 0L, SEEK_SET); + wasm_byte_vec_t binary; + wasm_byte_vec_new_uninitialized(&binary, file_size); + if (fread(binary.data, file_size, 1, file) != 1) { + printf("> Error loading module!\n"); + return 1; + } + fclose(file); + + // Compile. + printf("Compiling module...\n"); + own wasm_module_t* module = wasm_module_new(store, &binary); + if (!module) { + printf("> Error compiling module!\n"); + return 1; + } + + wasm_byte_vec_delete(&binary); + + // Create external print functions. + printf("Creating callback...\n"); + own wasm_functype_t* fail_type = + wasm_functype_new_0_1(wasm_valtype_new_i32()); + own wasm_func_t* fail_func = + wasm_func_new_with_env(store, fail_type, fail_callback, store, NULL); + + wasm_functype_delete(fail_type); + + // Instantiate. + printf("Instantiating module...\n"); + const wasm_extern_t* imports[] = { wasm_func_as_extern(fail_func) }; + own wasm_instance_t* instance = + wasm_instance_new(store, module, imports, NULL); + if (!instance) { + printf("> Error instantiating module!\n"); + return 1; + } + + wasm_func_delete(fail_func); + + // Extract export. + printf("Extracting exports...\n"); + own wasm_extern_vec_t exports; + wasm_instance_exports(instance, &exports); + if (exports.size < 2) { + printf("> Error accessing exports!\n"); + return 1; + } + + wasm_module_delete(module); + wasm_instance_delete(instance); + + // Call. + for (int i = 0; i < 2; ++i) { + const wasm_func_t* func = wasm_extern_as_func(exports.data[i]); + if (func == NULL) { + printf("> Error accessing export!\n"); + return 1; + } + + printf("Calling export %d...\n", i); + own wasm_trap_t* trap = wasm_func_call(func, NULL, NULL); + if (!trap) { + printf("> Error calling function, expected trap!\n"); + return 1; + } + + printf("Printing message...\n"); + own wasm_name_t message; + wasm_trap_message(trap, &message); + printf("> %s\n", message.data); + + printf("Printing origin...\n"); + own wasm_frame_t* frame = wasm_trap_origin(trap); + if (frame) { + print_frame(frame); + wasm_frame_delete(frame); + } else { + printf("> Empty origin.\n"); + } + + printf("Printing trace...\n"); + own wasm_frame_vec_t trace; + wasm_trap_trace(trap, &trace); + if (trace.size > 0) { + for (size_t i = 0; i < trace.size; ++i) { + print_frame(trace.data[i]); + } + } else { + printf("> Empty trace.\n"); + } + + wasm_frame_vec_delete(&trace); + wasm_trap_delete(trap); + wasm_name_delete(&message); + } + + wasm_extern_vec_delete(&exports); + + // Shut down. + printf("Shutting down...\n"); + wasm_store_delete(store); + wasm_engine_delete(engine); + + // All done. + printf("Done.\n"); + return 0; +} diff --git a/lib/c-api/tests/wasm-c-api/example/trap.cc b/lib/c-api/tests/wasm-c-api/example/trap.cc new file mode 100644 index 000000000..9cfcf5016 --- /dev/null +++ b/lib/c-api/tests/wasm-c-api/example/trap.cc @@ -0,0 +1,126 @@ +#include +#include +#include +#include +#include + +#include "wasm.hh" + +// A function to be called from Wasm code. +auto fail_callback( + void* env, const wasm::Val args[], wasm::Val results[] +) -> wasm::own { + std::cout << "Calling back..." << std::endl; + auto store = reinterpret_cast(env); + auto message = wasm::Name::make(std::string("callback abort")); + return wasm::Trap::make(store, message); +} + + +void print_frame(const wasm::Frame* frame) { + std::cout << "> " << frame->instance(); + std::cout << " @ 0x" << std::hex << frame->module_offset(); + std::cout << " = " << frame->func_index(); + std::cout << ".0x" << std::hex << frame->func_offset() << std::endl; +} + + +void run() { + // Initialize. + std::cout << "Initializing..." << std::endl; + auto engine = wasm::Engine::make(); + auto store_ = wasm::Store::make(engine.get()); + auto store = store_.get(); + + // Load binary. + std::cout << "Loading binary..." << std::endl; + std::ifstream file("trap.wasm"); + file.seekg(0, std::ios_base::end); + auto file_size = file.tellg(); + file.seekg(0); + auto binary = wasm::vec::make_uninitialized(file_size); + file.read(binary.get(), file_size); + file.close(); + if (file.fail()) { + std::cout << "> Error loading module!" << std::endl; + exit(1); + } + + // Compile. + std::cout << "Compiling module..." << std::endl; + auto module = wasm::Module::make(store, binary); + if (!module) { + std::cout << "> Error compiling module!" << std::endl; + exit(1); + } + + // Create external print functions. + std::cout << "Creating callback..." << std::endl; + auto fail_type = wasm::FuncType::make( + wasm::ownvec::make(), + wasm::ownvec::make(wasm::ValType::make(wasm::ValKind::I32)) + ); + auto fail_func = + wasm::Func::make(store, fail_type.get(), fail_callback, store); + + // Instantiate. + std::cout << "Instantiating module..." << std::endl; + wasm::Extern* imports[] = {fail_func.get()}; + auto instance = wasm::Instance::make(store, module.get(), imports); + if (!instance) { + std::cout << "> Error instantiating module!" << std::endl; + exit(1); + } + + // Extract export. + std::cout << "Extracting exports..." << std::endl; + auto exports = instance->exports(); + if (exports.size() < 2 || + exports[0]->kind() != wasm::ExternKind::FUNC || !exports[0]->func() || + exports[1]->kind() != wasm::ExternKind::FUNC || !exports[1]->func()) { + std::cout << "> Error accessing exports!" << std::endl; + exit(1); + } + + // Call. + for (size_t i = 0; i < 2; ++i) { + std::cout << "Calling export " << i << "..." << std::endl; + auto trap = exports[i]->func()->call(); + if (!trap) { + std::cout << "> Error calling function, expected trap!" << std::endl; + exit(1); + } + + std::cout << "Printing message..." << std::endl; + std::cout << "> " << trap->message().get() << std::endl; + + std::cout << "Printing origin..." << std::endl; + auto frame = trap->origin(); + if (frame) { + print_frame(frame.get()); + } else { + std::cout << "> Empty origin." << std::endl; + } + + std::cout << "Printing trace..." << std::endl; + auto trace = trap->trace(); + if (trace.size() > 0) { + for (size_t i = 0; i < trace.size(); ++i) { + print_frame(trace[i].get()); + } + } else { + std::cout << "> Empty trace." << std::endl; + } + } + + // Shut down. + std::cout << "Shutting down..." << std::endl; +} + + +int main(int argc, const char* argv[]) { + run(); + std::cout << "Done." << std::endl; + return 0; +} + diff --git a/lib/c-api/tests/wasm-c-api/example/trap.wasm b/lib/c-api/tests/wasm-c-api/example/trap.wasm new file mode 100644 index 000000000..eeed14c89 Binary files /dev/null and b/lib/c-api/tests/wasm-c-api/example/trap.wasm differ diff --git a/lib/c-api/tests/wasm-c-api/example/trap.wat b/lib/c-api/tests/wasm-c-api/example/trap.wat new file mode 100644 index 000000000..dfd20fb12 --- /dev/null +++ b/lib/c-api/tests/wasm-c-api/example/trap.wat @@ -0,0 +1,5 @@ +(module + (func $callback (import "" "callback") (result i32)) + (func (export "callback") (result i32) (call $callback)) + (func (export "unreachable") (result i32) (unreachable) (i32.const 1)) +) diff --git a/lib/c-api/tests/wasm-c-api/include/wasm.h b/lib/c-api/tests/wasm-c-api/include/wasm.h new file mode 100644 index 000000000..46e75cacb --- /dev/null +++ b/lib/c-api/tests/wasm-c-api/include/wasm.h @@ -0,0 +1,704 @@ +// WebAssembly C API + +#ifndef WASM_H +#define WASM_H + +#include +#include +#include +#include +#include + +#ifndef WASM_API_EXTERN +#ifdef _WIN32 +#define WASM_API_EXTERN __declspec(dllimport) +#else +#define WASM_API_EXTERN +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/////////////////////////////////////////////////////////////////////////////// +// Auxiliaries + +// Machine types + +inline void assertions() { + static_assert(sizeof(float) == sizeof(uint32_t), "incompatible float type"); + static_assert(sizeof(double) == sizeof(uint64_t), "incompatible double type"); + static_assert(sizeof(intptr_t) == sizeof(uint32_t) || + sizeof(intptr_t) == sizeof(uint64_t), + "incompatible pointer type"); +} + +typedef char byte_t; +typedef float float32_t; +typedef double float64_t; + + +// Ownership + +#define own + +// The qualifier `own` is used to indicate ownership of data in this API. +// It is intended to be interpreted similar to a `const` qualifier: +// +// - `own wasm_xxx_t*` owns the pointed-to data +// - `own wasm_xxx_t` distributes to all fields of a struct or union `xxx` +// - `own wasm_xxx_vec_t` owns the vector as well as its elements(!) +// - an `own` function parameter passes ownership from caller to callee +// - an `own` function result passes ownership from callee to caller +// - an exception are `own` pointer parameters named `out`, which are copy-back +// output parameters passing back ownership from callee to caller +// +// Own data is created by `wasm_xxx_new` functions and some others. +// It must be released with the corresponding `wasm_xxx_delete` function. +// +// Deleting a reference does not necessarily delete the underlying object, +// it merely indicates that this owner no longer uses it. +// +// For vectors, `const wasm_xxx_vec_t` is used informally to indicate that +// neither the vector nor its elements should be modified. +// TODO: introduce proper `wasm_xxx_const_vec_t`? + + +#define WASM_DECLARE_OWN(name) \ + typedef struct wasm_##name##_t wasm_##name##_t; \ + \ + WASM_API_EXTERN void wasm_##name##_delete(own wasm_##name##_t*); + + +// Vectors + +#define WASM_DECLARE_VEC(name, ptr_or_none) \ + typedef struct wasm_##name##_vec_t { \ + size_t size; \ + wasm_##name##_t ptr_or_none* data; \ + } wasm_##name##_vec_t; \ + \ + WASM_API_EXTERN void wasm_##name##_vec_new_empty(own wasm_##name##_vec_t* out); \ + WASM_API_EXTERN void wasm_##name##_vec_new_uninitialized( \ + own wasm_##name##_vec_t* out, size_t); \ + WASM_API_EXTERN void wasm_##name##_vec_new( \ + own wasm_##name##_vec_t* out, \ + size_t, own wasm_##name##_t ptr_or_none const[]); \ + WASM_API_EXTERN void wasm_##name##_vec_copy( \ + own wasm_##name##_vec_t* out, const wasm_##name##_vec_t*); \ + WASM_API_EXTERN void wasm_##name##_vec_delete(own wasm_##name##_vec_t*); + + +// Byte vectors + +typedef byte_t wasm_byte_t; +WASM_DECLARE_VEC(byte, ) + +typedef wasm_byte_vec_t wasm_name_t; + +#define wasm_name wasm_byte_vec +#define wasm_name_new wasm_byte_vec_new +#define wasm_name_new_empty wasm_byte_vec_new_empty +#define wasm_name_new_new_uninitialized wasm_byte_vec_new_uninitialized +#define wasm_name_copy wasm_byte_vec_copy +#define wasm_name_delete wasm_byte_vec_delete + +static inline void wasm_name_new_from_string( + own wasm_name_t* out, const char* s +) { + wasm_name_new(out, strlen(s) + 1, s); +} + + +/////////////////////////////////////////////////////////////////////////////// +// Runtime Environment + +// Configuration + +WASM_DECLARE_OWN(config) + +WASM_API_EXTERN own wasm_config_t* wasm_config_new(); + +// Embedders may provide custom functions for manipulating configs. + + +// Engine + +WASM_DECLARE_OWN(engine) + +WASM_API_EXTERN own wasm_engine_t* wasm_engine_new(); +WASM_API_EXTERN own wasm_engine_t* wasm_engine_new_with_config(own wasm_config_t*); + + +// Store + +WASM_DECLARE_OWN(store) + +WASM_API_EXTERN own wasm_store_t* wasm_store_new(wasm_engine_t*); + + +/////////////////////////////////////////////////////////////////////////////// +// Type Representations + +// Type attributes + +typedef uint8_t wasm_mutability_t; +enum wasm_mutability_enum { + WASM_CONST, + WASM_VAR, +}; + +typedef struct wasm_limits_t { + uint32_t min; + uint32_t max; +} wasm_limits_t; + +static const uint32_t wasm_limits_max_default = 0xffffffff; + + +// Generic + +#define WASM_DECLARE_TYPE(name) \ + WASM_DECLARE_OWN(name) \ + WASM_DECLARE_VEC(name, *) \ + \ + WASM_API_EXTERN own wasm_##name##_t* wasm_##name##_copy(wasm_##name##_t*); + + +// Value Types + +WASM_DECLARE_TYPE(valtype) + +typedef uint8_t wasm_valkind_t; +enum wasm_valkind_enum { + WASM_I32, + WASM_I64, + WASM_F32, + WASM_F64, + WASM_ANYREF = 128, + WASM_FUNCREF, +}; + +WASM_API_EXTERN own wasm_valtype_t* wasm_valtype_new(wasm_valkind_t); + +WASM_API_EXTERN wasm_valkind_t wasm_valtype_kind(const wasm_valtype_t*); + +static inline bool wasm_valkind_is_num(wasm_valkind_t k) { + return k < WASM_ANYREF; +} +static inline bool wasm_valkind_is_ref(wasm_valkind_t k) { + return k >= WASM_ANYREF; +} + +static inline bool wasm_valtype_is_num(const wasm_valtype_t* t) { + return wasm_valkind_is_num(wasm_valtype_kind(t)); +} +static inline bool wasm_valtype_is_ref(const wasm_valtype_t* t) { + return wasm_valkind_is_ref(wasm_valtype_kind(t)); +} + + +// Function Types + +WASM_DECLARE_TYPE(functype) + +WASM_API_EXTERN own wasm_functype_t* wasm_functype_new( + own wasm_valtype_vec_t* params, own wasm_valtype_vec_t* results); + +WASM_API_EXTERN const wasm_valtype_vec_t* wasm_functype_params(const wasm_functype_t*); +WASM_API_EXTERN const wasm_valtype_vec_t* wasm_functype_results(const wasm_functype_t*); + + +// Global Types + +WASM_DECLARE_TYPE(globaltype) + +WASM_API_EXTERN own wasm_globaltype_t* wasm_globaltype_new( + own wasm_valtype_t*, wasm_mutability_t); + +WASM_API_EXTERN const wasm_valtype_t* wasm_globaltype_content(const wasm_globaltype_t*); +WASM_API_EXTERN wasm_mutability_t wasm_globaltype_mutability(const wasm_globaltype_t*); + + +// Table Types + +WASM_DECLARE_TYPE(tabletype) + +WASM_API_EXTERN own wasm_tabletype_t* wasm_tabletype_new( + own wasm_valtype_t*, const wasm_limits_t*); + +WASM_API_EXTERN const wasm_valtype_t* wasm_tabletype_element(const wasm_tabletype_t*); +WASM_API_EXTERN const wasm_limits_t* wasm_tabletype_limits(const wasm_tabletype_t*); + + +// Memory Types + +WASM_DECLARE_TYPE(memorytype) + +WASM_API_EXTERN own wasm_memorytype_t* wasm_memorytype_new(const wasm_limits_t*); + +WASM_API_EXTERN const wasm_limits_t* wasm_memorytype_limits(const wasm_memorytype_t*); + + +// Extern Types + +WASM_DECLARE_TYPE(externtype) + +typedef uint8_t wasm_externkind_t; +enum wasm_externkind_enum { + WASM_EXTERN_FUNC, + WASM_EXTERN_GLOBAL, + WASM_EXTERN_TABLE, + WASM_EXTERN_MEMORY, +}; + +WASM_API_EXTERN wasm_externkind_t wasm_externtype_kind(const wasm_externtype_t*); + +WASM_API_EXTERN wasm_externtype_t* wasm_functype_as_externtype(wasm_functype_t*); +WASM_API_EXTERN wasm_externtype_t* wasm_globaltype_as_externtype(wasm_globaltype_t*); +WASM_API_EXTERN wasm_externtype_t* wasm_tabletype_as_externtype(wasm_tabletype_t*); +WASM_API_EXTERN wasm_externtype_t* wasm_memorytype_as_externtype(wasm_memorytype_t*); + +WASM_API_EXTERN wasm_functype_t* wasm_externtype_as_functype(wasm_externtype_t*); +WASM_API_EXTERN wasm_globaltype_t* wasm_externtype_as_globaltype(wasm_externtype_t*); +WASM_API_EXTERN wasm_tabletype_t* wasm_externtype_as_tabletype(wasm_externtype_t*); +WASM_API_EXTERN wasm_memorytype_t* wasm_externtype_as_memorytype(wasm_externtype_t*); + +WASM_API_EXTERN const wasm_externtype_t* wasm_functype_as_externtype_const(const wasm_functype_t*); +WASM_API_EXTERN const wasm_externtype_t* wasm_globaltype_as_externtype_const(const wasm_globaltype_t*); +WASM_API_EXTERN const wasm_externtype_t* wasm_tabletype_as_externtype_const(const wasm_tabletype_t*); +WASM_API_EXTERN const wasm_externtype_t* wasm_memorytype_as_externtype_const(const wasm_memorytype_t*); + +WASM_API_EXTERN const wasm_functype_t* wasm_externtype_as_functype_const(const wasm_externtype_t*); +WASM_API_EXTERN const wasm_globaltype_t* wasm_externtype_as_globaltype_const(const wasm_externtype_t*); +WASM_API_EXTERN const wasm_tabletype_t* wasm_externtype_as_tabletype_const(const wasm_externtype_t*); +WASM_API_EXTERN const wasm_memorytype_t* wasm_externtype_as_memorytype_const(const wasm_externtype_t*); + + +// Import Types + +WASM_DECLARE_TYPE(importtype) + +WASM_API_EXTERN own wasm_importtype_t* wasm_importtype_new( + own wasm_name_t* module, own wasm_name_t* name, own wasm_externtype_t*); + +WASM_API_EXTERN const wasm_name_t* wasm_importtype_module(const wasm_importtype_t*); +WASM_API_EXTERN const wasm_name_t* wasm_importtype_name(const wasm_importtype_t*); +WASM_API_EXTERN const wasm_externtype_t* wasm_importtype_type(const wasm_importtype_t*); + + +// Export Types + +WASM_DECLARE_TYPE(exporttype) + +WASM_API_EXTERN own wasm_exporttype_t* wasm_exporttype_new( + own wasm_name_t*, own wasm_externtype_t*); + +WASM_API_EXTERN const wasm_name_t* wasm_exporttype_name(const wasm_exporttype_t*); +WASM_API_EXTERN const wasm_externtype_t* wasm_exporttype_type(const wasm_exporttype_t*); + + +/////////////////////////////////////////////////////////////////////////////// +// Runtime Objects + +// Values + +struct wasm_ref_t; + +typedef struct wasm_val_t { + wasm_valkind_t kind; + union { + int32_t i32; + int64_t i64; + float32_t f32; + float64_t f64; + struct wasm_ref_t* ref; + } of; +} wasm_val_t; + +WASM_API_EXTERN void wasm_val_delete(own wasm_val_t* v); +WASM_API_EXTERN void wasm_val_copy(own wasm_val_t* out, const wasm_val_t*); + +WASM_DECLARE_VEC(val, ) + + +// References + +#define WASM_DECLARE_REF_BASE(name) \ + WASM_DECLARE_OWN(name) \ + \ + WASM_API_EXTERN own wasm_##name##_t* wasm_##name##_copy(const wasm_##name##_t*); \ + WASM_API_EXTERN bool wasm_##name##_same(const wasm_##name##_t*, const wasm_##name##_t*); \ + \ + WASM_API_EXTERN void* wasm_##name##_get_host_info(const wasm_##name##_t*); \ + WASM_API_EXTERN void wasm_##name##_set_host_info(wasm_##name##_t*, void*); \ + WASM_API_EXTERN void wasm_##name##_set_host_info_with_finalizer( \ + wasm_##name##_t*, void*, void (*)(void*)); + +#define WASM_DECLARE_REF(name) \ + WASM_DECLARE_REF_BASE(name) \ + \ + WASM_API_EXTERN wasm_ref_t* wasm_##name##_as_ref(wasm_##name##_t*); \ + WASM_API_EXTERN wasm_##name##_t* wasm_ref_as_##name(wasm_ref_t*); \ + WASM_API_EXTERN const wasm_ref_t* wasm_##name##_as_ref_const(const wasm_##name##_t*); \ + WASM_API_EXTERN const wasm_##name##_t* wasm_ref_as_##name##_const(const wasm_ref_t*); + +#define WASM_DECLARE_SHARABLE_REF(name) \ + WASM_DECLARE_REF(name) \ + WASM_DECLARE_OWN(shared_##name) \ + \ + WASM_API_EXTERN own wasm_shared_##name##_t* wasm_##name##_share(const wasm_##name##_t*); \ + WASM_API_EXTERN own wasm_##name##_t* wasm_##name##_obtain(wasm_store_t*, const wasm_shared_##name##_t*); + + +WASM_DECLARE_REF_BASE(ref) + + +// Frames + +WASM_DECLARE_OWN(frame) +WASM_DECLARE_VEC(frame, *) +WASM_API_EXTERN own wasm_frame_t* wasm_frame_copy(const wasm_frame_t*); + +WASM_API_EXTERN struct wasm_instance_t* wasm_frame_instance(const wasm_frame_t*); +WASM_API_EXTERN uint32_t wasm_frame_func_index(const wasm_frame_t*); +WASM_API_EXTERN size_t wasm_frame_func_offset(const wasm_frame_t*); +WASM_API_EXTERN size_t wasm_frame_module_offset(const wasm_frame_t*); + + +// Traps + +typedef wasm_name_t wasm_message_t; // null terminated + +WASM_DECLARE_REF(trap) + +WASM_API_EXTERN own wasm_trap_t* wasm_trap_new(wasm_store_t* store, const wasm_message_t*); + +WASM_API_EXTERN void wasm_trap_message(const wasm_trap_t*, own wasm_message_t* out); +WASM_API_EXTERN own wasm_frame_t* wasm_trap_origin(const wasm_trap_t*); +WASM_API_EXTERN void wasm_trap_trace(const wasm_trap_t*, own wasm_frame_vec_t* out); + + +// Foreign Objects + +WASM_DECLARE_REF(foreign) + +WASM_API_EXTERN own wasm_foreign_t* wasm_foreign_new(wasm_store_t*); + + +// Modules + +WASM_DECLARE_SHARABLE_REF(module) + +WASM_API_EXTERN own wasm_module_t* wasm_module_new( + wasm_store_t*, const wasm_byte_vec_t* binary); + +WASM_API_EXTERN bool wasm_module_validate(wasm_store_t*, const wasm_byte_vec_t* binary); + +WASM_API_EXTERN void wasm_module_imports(const wasm_module_t*, own wasm_importtype_vec_t* out); +WASM_API_EXTERN void wasm_module_exports(const wasm_module_t*, own wasm_exporttype_vec_t* out); + +WASM_API_EXTERN void wasm_module_serialize(const wasm_module_t*, own wasm_byte_vec_t* out); +WASM_API_EXTERN own wasm_module_t* wasm_module_deserialize(wasm_store_t*, const wasm_byte_vec_t*); + + +// Function Instances + +WASM_DECLARE_REF(func) + +typedef own wasm_trap_t* (*wasm_func_callback_t)( + const wasm_val_t args[], wasm_val_t results[]); +typedef own wasm_trap_t* (*wasm_func_callback_with_env_t)( + void* env, const wasm_val_t args[], wasm_val_t results[]); + +WASM_API_EXTERN own wasm_func_t* wasm_func_new( + wasm_store_t*, const wasm_functype_t*, wasm_func_callback_t); +WASM_API_EXTERN own wasm_func_t* wasm_func_new_with_env( + wasm_store_t*, const wasm_functype_t* type, wasm_func_callback_with_env_t, + void* env, void (*finalizer)(void*)); + +WASM_API_EXTERN own wasm_functype_t* wasm_func_type(const wasm_func_t*); +WASM_API_EXTERN size_t wasm_func_param_arity(const wasm_func_t*); +WASM_API_EXTERN size_t wasm_func_result_arity(const wasm_func_t*); + +WASM_API_EXTERN own wasm_trap_t* wasm_func_call( + const wasm_func_t*, const wasm_val_t args[], wasm_val_t results[]); + + +// Global Instances + +WASM_DECLARE_REF(global) + +WASM_API_EXTERN own wasm_global_t* wasm_global_new( + wasm_store_t*, const wasm_globaltype_t*, const wasm_val_t*); + +WASM_API_EXTERN own wasm_globaltype_t* wasm_global_type(const wasm_global_t*); + +WASM_API_EXTERN void wasm_global_get(const wasm_global_t*, own wasm_val_t* out); +WASM_API_EXTERN void wasm_global_set(wasm_global_t*, const wasm_val_t*); + + +// Table Instances + +WASM_DECLARE_REF(table) + +typedef uint32_t wasm_table_size_t; + +WASM_API_EXTERN own wasm_table_t* wasm_table_new( + wasm_store_t*, const wasm_tabletype_t*, wasm_ref_t* init); + +WASM_API_EXTERN own wasm_tabletype_t* wasm_table_type(const wasm_table_t*); + +WASM_API_EXTERN own wasm_ref_t* wasm_table_get(const wasm_table_t*, wasm_table_size_t index); +WASM_API_EXTERN bool wasm_table_set(wasm_table_t*, wasm_table_size_t index, wasm_ref_t*); + +WASM_API_EXTERN wasm_table_size_t wasm_table_size(const wasm_table_t*); +WASM_API_EXTERN bool wasm_table_grow(wasm_table_t*, wasm_table_size_t delta, wasm_ref_t* init); + + +// Memory Instances + +WASM_DECLARE_REF(memory) + +typedef uint32_t wasm_memory_pages_t; + +static const size_t MEMORY_PAGE_SIZE = 0x10000; + +WASM_API_EXTERN own wasm_memory_t* wasm_memory_new(wasm_store_t*, const wasm_memorytype_t*); + +WASM_API_EXTERN own wasm_memorytype_t* wasm_memory_type(const wasm_memory_t*); + +WASM_API_EXTERN byte_t* wasm_memory_data(wasm_memory_t*); +WASM_API_EXTERN size_t wasm_memory_data_size(const wasm_memory_t*); + +WASM_API_EXTERN wasm_memory_pages_t wasm_memory_size(const wasm_memory_t*); +WASM_API_EXTERN bool wasm_memory_grow(wasm_memory_t*, wasm_memory_pages_t delta); + + +// Externals + +WASM_DECLARE_REF(extern) +WASM_DECLARE_VEC(extern, *) + +WASM_API_EXTERN wasm_externkind_t wasm_extern_kind(const wasm_extern_t*); +WASM_API_EXTERN own wasm_externtype_t* wasm_extern_type(const wasm_extern_t*); + +WASM_API_EXTERN wasm_extern_t* wasm_func_as_extern(wasm_func_t*); +WASM_API_EXTERN wasm_extern_t* wasm_global_as_extern(wasm_global_t*); +WASM_API_EXTERN wasm_extern_t* wasm_table_as_extern(wasm_table_t*); +WASM_API_EXTERN wasm_extern_t* wasm_memory_as_extern(wasm_memory_t*); + +WASM_API_EXTERN wasm_func_t* wasm_extern_as_func(wasm_extern_t*); +WASM_API_EXTERN wasm_global_t* wasm_extern_as_global(wasm_extern_t*); +WASM_API_EXTERN wasm_table_t* wasm_extern_as_table(wasm_extern_t*); +WASM_API_EXTERN wasm_memory_t* wasm_extern_as_memory(wasm_extern_t*); + +WASM_API_EXTERN const wasm_extern_t* wasm_func_as_extern_const(const wasm_func_t*); +WASM_API_EXTERN const wasm_extern_t* wasm_global_as_extern_const(const wasm_global_t*); +WASM_API_EXTERN const wasm_extern_t* wasm_table_as_extern_const(const wasm_table_t*); +WASM_API_EXTERN const wasm_extern_t* wasm_memory_as_extern_const(const wasm_memory_t*); + +WASM_API_EXTERN const wasm_func_t* wasm_extern_as_func_const(const wasm_extern_t*); +WASM_API_EXTERN const wasm_global_t* wasm_extern_as_global_const(const wasm_extern_t*); +WASM_API_EXTERN const wasm_table_t* wasm_extern_as_table_const(const wasm_extern_t*); +WASM_API_EXTERN const wasm_memory_t* wasm_extern_as_memory_const(const wasm_extern_t*); + + +// Module Instances + +WASM_DECLARE_REF(instance) + +WASM_API_EXTERN own wasm_instance_t* wasm_instance_new( + wasm_store_t*, const wasm_module_t*, const wasm_extern_t* const imports[], + own wasm_trap_t** +); + +WASM_API_EXTERN void wasm_instance_exports(const wasm_instance_t*, own wasm_extern_vec_t* out); + + +/////////////////////////////////////////////////////////////////////////////// +// Convenience + +// Value Type construction short-hands + +static inline own wasm_valtype_t* wasm_valtype_new_i32() { + return wasm_valtype_new(WASM_I32); +} +static inline own wasm_valtype_t* wasm_valtype_new_i64() { + return wasm_valtype_new(WASM_I64); +} +static inline own wasm_valtype_t* wasm_valtype_new_f32() { + return wasm_valtype_new(WASM_F32); +} +static inline own wasm_valtype_t* wasm_valtype_new_f64() { + return wasm_valtype_new(WASM_F64); +} + +static inline own wasm_valtype_t* wasm_valtype_new_anyref() { + return wasm_valtype_new(WASM_ANYREF); +} +static inline own wasm_valtype_t* wasm_valtype_new_funcref() { + return wasm_valtype_new(WASM_FUNCREF); +} + + +// Function Types construction short-hands + +static inline own wasm_functype_t* wasm_functype_new_0_0() { + wasm_valtype_vec_t params, results; + wasm_valtype_vec_new_empty(¶ms); + wasm_valtype_vec_new_empty(&results); + return wasm_functype_new(¶ms, &results); +} + +static inline own wasm_functype_t* wasm_functype_new_1_0( + own wasm_valtype_t* p +) { + wasm_valtype_t* ps[1] = {p}; + wasm_valtype_vec_t params, results; + wasm_valtype_vec_new(¶ms, 1, ps); + wasm_valtype_vec_new_empty(&results); + return wasm_functype_new(¶ms, &results); +} + +static inline own wasm_functype_t* wasm_functype_new_2_0( + own wasm_valtype_t* p1, own wasm_valtype_t* p2 +) { + wasm_valtype_t* ps[2] = {p1, p2}; + wasm_valtype_vec_t params, results; + wasm_valtype_vec_new(¶ms, 2, ps); + wasm_valtype_vec_new_empty(&results); + return wasm_functype_new(¶ms, &results); +} + +static inline own wasm_functype_t* wasm_functype_new_3_0( + own wasm_valtype_t* p1, own wasm_valtype_t* p2, own wasm_valtype_t* p3 +) { + wasm_valtype_t* ps[3] = {p1, p2, p3}; + wasm_valtype_vec_t params, results; + wasm_valtype_vec_new(¶ms, 3, ps); + wasm_valtype_vec_new_empty(&results); + return wasm_functype_new(¶ms, &results); +} + +static inline own wasm_functype_t* wasm_functype_new_0_1( + own wasm_valtype_t* r +) { + wasm_valtype_t* rs[1] = {r}; + wasm_valtype_vec_t params, results; + wasm_valtype_vec_new_empty(¶ms); + wasm_valtype_vec_new(&results, 1, rs); + return wasm_functype_new(¶ms, &results); +} + +static inline own wasm_functype_t* wasm_functype_new_1_1( + own wasm_valtype_t* p, own wasm_valtype_t* r +) { + wasm_valtype_t* ps[1] = {p}; + wasm_valtype_t* rs[1] = {r}; + wasm_valtype_vec_t params, results; + wasm_valtype_vec_new(¶ms, 1, ps); + wasm_valtype_vec_new(&results, 1, rs); + return wasm_functype_new(¶ms, &results); +} + +static inline own wasm_functype_t* wasm_functype_new_2_1( + own wasm_valtype_t* p1, own wasm_valtype_t* p2, own wasm_valtype_t* r +) { + wasm_valtype_t* ps[2] = {p1, p2}; + wasm_valtype_t* rs[1] = {r}; + wasm_valtype_vec_t params, results; + wasm_valtype_vec_new(¶ms, 2, ps); + wasm_valtype_vec_new(&results, 1, rs); + return wasm_functype_new(¶ms, &results); +} + +static inline own wasm_functype_t* wasm_functype_new_3_1( + own wasm_valtype_t* p1, own wasm_valtype_t* p2, own wasm_valtype_t* p3, + own wasm_valtype_t* r +) { + wasm_valtype_t* ps[3] = {p1, p2, p3}; + wasm_valtype_t* rs[1] = {r}; + wasm_valtype_vec_t params, results; + wasm_valtype_vec_new(¶ms, 3, ps); + wasm_valtype_vec_new(&results, 1, rs); + return wasm_functype_new(¶ms, &results); +} + +static inline own wasm_functype_t* wasm_functype_new_0_2( + own wasm_valtype_t* r1, own wasm_valtype_t* r2 +) { + wasm_valtype_t* rs[2] = {r1, r2}; + wasm_valtype_vec_t params, results; + wasm_valtype_vec_new_empty(¶ms); + wasm_valtype_vec_new(&results, 2, rs); + return wasm_functype_new(¶ms, &results); +} + +static inline own wasm_functype_t* wasm_functype_new_1_2( + own wasm_valtype_t* p, own wasm_valtype_t* r1, own wasm_valtype_t* r2 +) { + wasm_valtype_t* ps[1] = {p}; + wasm_valtype_t* rs[2] = {r1, r2}; + wasm_valtype_vec_t params, results; + wasm_valtype_vec_new(¶ms, 1, ps); + wasm_valtype_vec_new(&results, 2, rs); + return wasm_functype_new(¶ms, &results); +} + +static inline own wasm_functype_t* wasm_functype_new_2_2( + own wasm_valtype_t* p1, own wasm_valtype_t* p2, + own wasm_valtype_t* r1, own wasm_valtype_t* r2 +) { + wasm_valtype_t* ps[2] = {p1, p2}; + wasm_valtype_t* rs[2] = {r1, r2}; + wasm_valtype_vec_t params, results; + wasm_valtype_vec_new(¶ms, 2, ps); + wasm_valtype_vec_new(&results, 2, rs); + return wasm_functype_new(¶ms, &results); +} + +static inline own wasm_functype_t* wasm_functype_new_3_2( + own wasm_valtype_t* p1, own wasm_valtype_t* p2, own wasm_valtype_t* p3, + own wasm_valtype_t* r1, own wasm_valtype_t* r2 +) { + wasm_valtype_t* ps[3] = {p1, p2, p3}; + wasm_valtype_t* rs[2] = {r1, r2}; + wasm_valtype_vec_t params, results; + wasm_valtype_vec_new(¶ms, 3, ps); + wasm_valtype_vec_new(&results, 2, rs); + return wasm_functype_new(¶ms, &results); +} + + +// Value construction short-hands + +static inline void wasm_val_init_ptr(own wasm_val_t* out, void* p) { +#if UINTPTR_MAX == UINT32_MAX + out->kind = WASM_I32; + out->of.i32 = (intptr_t)p; +#elif UINTPTR_MAX == UINT64_MAX + out->kind = WASM_I64; + out->of.i64 = (intptr_t)p; +#endif +} + +static inline void* wasm_val_ptr(const wasm_val_t* val) { +#if UINTPTR_MAX == UINT32_MAX + return (void*)(intptr_t)val->of.i32; +#elif UINTPTR_MAX == UINT64_MAX + return (void*)(intptr_t)val->of.i64; +#endif +} + + +/////////////////////////////////////////////////////////////////////////////// + +#undef own + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // #ifdef WASM_H diff --git a/lib/c-api/tests/wasm-c-api/include/wasm.hh b/lib/c-api/tests/wasm-c-api/include/wasm.hh new file mode 100644 index 000000000..29b3798e3 --- /dev/null +++ b/lib/c-api/tests/wasm-c-api/include/wasm.hh @@ -0,0 +1,746 @@ +// WebAssembly C++ API + +#ifndef WASM_HH +#define WASM_HH + +#include +#include +#include +#include +#include +#include +#include + +#ifndef WASM_API_EXTERN +#ifdef _WIN32 +#define WASM_API_EXTERN __declspec(dllimport) +#else +#define WASM_API_EXTERN +#endif +#endif + + +/////////////////////////////////////////////////////////////////////////////// +// Auxiliaries + +// Machine types + +static_assert(sizeof(float) == sizeof(int32_t), "incompatible float type"); +static_assert(sizeof(double) == sizeof(int64_t), "incompatible double type"); +static_assert(sizeof(intptr_t) == sizeof(int32_t) || + sizeof(intptr_t) == sizeof(int64_t), "incompatible pointer type"); + +using byte_t = char; +using float32_t = float; +using float64_t = double; + + +namespace wasm { + +// Vectors + +template +class vec { + static const size_t invalid_size = SIZE_MAX; + + size_t size_; + std::unique_ptr data_; + +#ifdef WASM_API_DEBUG + WASM_API_EXTERN void make_data(); + WASM_API_EXTERN void free_data(); +#else + void make_data() {} + void free_data() {} +#endif + + vec(size_t size) : vec(size, size ? new(std::nothrow) T[size] : nullptr) { + make_data(); + } + + vec(size_t size, T* data) : size_(size), data_(data) { + assert(!!size_ == !!data_ || size_ == invalid_size); + } + +public: + using elem_type = T; + + vec(vec&& that) : vec(that.size_, that.data_.release()) { + that.size_ = invalid_size; + } + + ~vec() { + free_data(); + } + + operator bool() const { + return bool(size_ != invalid_size); + } + + auto size() const -> size_t { + return size_; + } + + auto get() const -> const T* { + return data_.get(); + } + + auto get() -> T* { + return data_.get(); + } + + auto release() -> T* { + size_ = invalid_size; + return data_.release(); + } + + void reset() { + free_data(); + size_ = invalid_size; + data_.reset(); + } + + void reset(vec& that) { + free_data(); + size_ = that.size_; + data_.reset(that.data_.release()); + that.size_ = invalid_size; + } + + auto operator=(vec&& that) -> vec& { + reset(that); + return *this; + } + + auto operator[](size_t i) -> T& { + assert(i < size_); + return data_[i]; + } + + auto operator[](size_t i) const -> const T& { + assert(i < size_); + return data_[i]; + } + + auto copy() const -> vec { + auto v = vec(size_); + if (v) for (size_t i = 0; i < size_; ++i) v.data_[i] = data_[i]; + return v; + } + + // TODO: This can't be used for e.g. vec + auto deep_copy() const -> vec { + auto v = vec(size_); + if (v) for (size_t i = 0; i < size_; ++i) v.data_[i] = data_[i]->copy(); + return v; + } + + static auto make_uninitialized(size_t size = 0) -> vec { + return vec(size); + } + + static auto make(size_t size, T init[]) -> vec { + auto v = vec(size); + if (v) for (size_t i = 0; i < size; ++i) v.data_[i] = std::move(init[i]); + return v; + } + + static auto make(std::string s) -> vec { + auto v = vec(s.length() + 1); + if (v) std::strcpy(v.get(), s.data()); + return v; + } + + // TODO(mvsc): MVSC requires this special case: + static auto make() -> vec { + return vec(0); + } + + template + static auto make(Ts&&... args) -> vec { + T data[] = { std::forward(args)... }; + return make(sizeof...(Ts), data); + } + + static auto adopt(size_t size, T data[]) -> vec { + return vec(size, data); + } + + static auto invalid() -> vec { + return vec(invalid_size, nullptr); + } +}; + + +// Ownership + +template using own = std::unique_ptr; +template using ownvec = vec>; + +template +auto make_own(T* x) -> own { return own(x); } + + +/////////////////////////////////////////////////////////////////////////////// +// Runtime Environment + +// Configuration + +class WASM_API_EXTERN Config { +public: + Config() = delete; + ~Config(); + void operator delete(void*); + + static auto make() -> own; + + // Implementations may provide custom methods for manipulating Configs. +}; + + +// Engine + +class WASM_API_EXTERN Engine { +public: + Engine() = delete; + ~Engine(); + void operator delete(void*); + + static auto make(own&& = Config::make()) -> own; +}; + + +// Store + +class WASM_API_EXTERN Store { +public: + Store() = delete; + ~Store(); + void operator delete(void*); + + static auto make(Engine*) -> own; +}; + + +/////////////////////////////////////////////////////////////////////////////// +// Type Representations + +// Type attributes + +enum class Mutability : uint8_t { CONST, VAR }; + +struct Limits { + uint32_t min; + uint32_t max; + + Limits(uint32_t min, uint32_t max = std::numeric_limits::max()) : + min(min), max(max) {} +}; + + +// Value Types + +enum class ValKind : uint8_t { + I32, I64, F32, F64, + ANYREF = 128, FUNCREF, +}; + +inline bool is_num(ValKind k) { return k < ValKind::ANYREF; } +inline bool is_ref(ValKind k) { return k >= ValKind::ANYREF; } + + +class WASM_API_EXTERN ValType { +public: + ValType() = delete; + ~ValType(); + void operator delete(void*); + + static auto make(ValKind) -> own; + auto copy() const -> own; + + auto kind() const -> ValKind; + auto is_num() const -> bool { return wasm::is_num(kind()); } + auto is_ref() const -> bool { return wasm::is_ref(kind()); } +}; + + +// External Types + +enum class ExternKind : uint8_t { + FUNC, GLOBAL, TABLE, MEMORY +}; + +class FuncType; +class GlobalType; +class TableType; +class MemoryType; + +class WASM_API_EXTERN ExternType { +public: + ExternType() = delete; + ~ExternType(); + void operator delete(void*); + + auto copy() const-> own; + + auto kind() const -> ExternKind; + + auto func() -> FuncType*; + auto global() -> GlobalType*; + auto table() -> TableType*; + auto memory() -> MemoryType*; + + auto func() const -> const FuncType*; + auto global() const -> const GlobalType*; + auto table() const -> const TableType*; + auto memory() const -> const MemoryType*; +}; + + +// Function Types + +class WASM_API_EXTERN FuncType : public ExternType { +public: + FuncType() = delete; + ~FuncType(); + + static auto make( + ownvec&& params = ownvec::make(), + ownvec&& results = ownvec::make() + ) -> own; + + auto copy() const -> own; + + auto params() const -> const ownvec&; + auto results() const -> const ownvec&; +}; + + +// Global Types + +class WASM_API_EXTERN GlobalType : public ExternType { +public: + GlobalType() = delete; + ~GlobalType(); + + static auto make(own&&, Mutability) -> own; + auto copy() const -> own; + + auto content() const -> const ValType*; + auto mutability() const -> Mutability; +}; + + +// Table Types + +class WASM_API_EXTERN TableType : public ExternType { +public: + TableType() = delete; + ~TableType(); + + static auto make(own&&, Limits) -> own; + auto copy() const -> own; + + auto element() const -> const ValType*; + auto limits() const -> const Limits&; +}; + + +// Memory Types + +class WASM_API_EXTERN MemoryType : public ExternType { +public: + MemoryType() = delete; + ~MemoryType(); + + static auto make(Limits) -> own; + auto copy() const -> own; + + auto limits() const -> const Limits&; +}; + + +// Import Types + +using Name = vec; + +class WASM_API_EXTERN ImportType { +public: + ImportType() = delete; + ~ImportType(); + void operator delete(void*); + + static auto make(Name&& module, Name&& name, own&&) -> + own; + auto copy() const -> own; + + auto module() const -> const Name&; + auto name() const -> const Name&; + auto type() const -> const ExternType*; +}; + + +// Export Types + +class WASM_API_EXTERN ExportType { +public: + ExportType() = delete; + ~ExportType(); + void operator delete(void*); + + static auto make(Name&&, own&&) -> own; + auto copy() const -> own; + + auto name() const -> const Name&; + auto type() const -> const ExternType*; +}; + + +/////////////////////////////////////////////////////////////////////////////// +// Runtime Objects + +// References + +class WASM_API_EXTERN Ref { +public: + Ref() = delete; + ~Ref(); + void operator delete(void*); + + auto copy() const -> own; + auto same(const Ref*) const -> bool; + + auto get_host_info() const -> void*; + void set_host_info(void* info, void (*finalizer)(void*) = nullptr); +}; + + +// Values + +class Val { + ValKind kind_; + union impl { + int32_t i32; + int64_t i64; + float32_t f32; + float64_t f64; + Ref* ref; + } impl_; + + Val(ValKind kind, impl impl) : kind_(kind), impl_(impl) {} + +public: + Val() : kind_(ValKind::ANYREF) { impl_.ref = nullptr; } + Val(int32_t i) : kind_(ValKind::I32) { impl_.i32 = i; } + Val(int64_t i) : kind_(ValKind::I64) { impl_.i64 = i; } + Val(float32_t z) : kind_(ValKind::F32) { impl_.f32 = z; } + Val(float64_t z) : kind_(ValKind::F64) { impl_.f64 = z; } + Val(own&& r) : kind_(ValKind::ANYREF) { impl_.ref = r.release(); } + + Val(Val&& that) : kind_(that.kind_), impl_(that.impl_) { + if (is_ref()) that.impl_.ref = nullptr; + } + + ~Val() { + reset(); + } + + auto is_num() const -> bool { return wasm::is_num(kind_); } + auto is_ref() const -> bool { return wasm::is_ref(kind_); } + + static auto i32(int32_t x) -> Val { return Val(x); } + static auto i64(int64_t x) -> Val { return Val(x); } + static auto f32(float32_t x) -> Val { return Val(x); } + static auto f64(float64_t x) -> Val { return Val(x); } + static auto ref(own&& x) -> Val { return Val(std::move(x)); } + template inline static auto make(T x) -> Val; + template inline static auto make(own&& x) -> Val; + + void reset() { + if (is_ref() && impl_.ref) { + delete impl_.ref; + impl_.ref = nullptr; + } + } + + void reset(Val& that) { + reset(); + kind_ = that.kind_; + impl_ = that.impl_; + if (is_ref()) that.impl_.ref = nullptr; + } + + auto operator=(Val&& that) -> Val& { + reset(that); + return *this; + } + + auto kind() const -> ValKind { return kind_; } + auto i32() const -> int32_t { assert(kind_ == ValKind::I32); return impl_.i32; } + auto i64() const -> int64_t { assert(kind_ == ValKind::I64); return impl_.i64; } + auto f32() const -> float32_t { assert(kind_ == ValKind::F32); return impl_.f32; } + auto f64() const -> float64_t { assert(kind_ == ValKind::F64); return impl_.f64; } + auto ref() const -> Ref* { assert(is_ref()); return impl_.ref; } + template inline auto get() const -> T; + + auto release_ref() -> own { + assert(is_ref()); + auto ref = impl_.ref; + impl_.ref = nullptr; + return own(ref); + } + + auto copy() const -> Val { + if (is_ref() && impl_.ref != nullptr) { + // TODO(mvsc): MVSC cannot handle this: + // impl impl = {.ref = impl_.ref->copy().release()}; + impl impl; + impl.ref = impl_.ref->copy().release(); + return Val(kind_, impl); + } else { + return Val(kind_, impl_); + } + } +}; + + +template<> inline auto Val::make(int32_t x) -> Val { return Val(x); } +template<> inline auto Val::make(int64_t x) -> Val { return Val(x); } +template<> inline auto Val::make(float32_t x) -> Val { return Val(x); } +template<> inline auto Val::make(float64_t x) -> Val { return Val(x); } +template<> inline auto Val::make(own&& x) -> Val { + return Val(std::move(x)); +} + +template<> inline auto Val::make(uint32_t x) -> Val { + return Val(static_cast(x)); +} +template<> inline auto Val::make(uint64_t x) -> Val { + return Val(static_cast(x)); +} + +template<> inline auto Val::get() const -> int32_t { return i32(); } +template<> inline auto Val::get() const -> int64_t { return i64(); } +template<> inline auto Val::get() const -> float32_t { return f32(); } +template<> inline auto Val::get() const -> float64_t { return f64(); } +template<> inline auto Val::get() const -> Ref* { return ref(); } + +template<> inline auto Val::get() const -> uint32_t { + return static_cast(i32()); +} +template<> inline auto Val::get() const -> uint64_t { + return static_cast(i64()); +} + + +// Traps + +using Message = vec; // null terminated + +class Instance; + +class WASM_API_EXTERN Frame { +public: + Frame() = delete; + ~Frame(); + void operator delete(void*); + + auto copy() const -> own; + + auto instance() const -> Instance*; + auto func_index() const -> uint32_t; + auto func_offset() const -> size_t; + auto module_offset() const -> size_t; +}; + +class WASM_API_EXTERN Trap : public Ref { +public: + Trap() = delete; + ~Trap(); + + static auto make(Store*, const Message& msg) -> own; + auto copy() const -> own; + + auto message() const -> Message; + auto origin() const -> own; // may be null + auto trace() const -> ownvec; // may be empty, origin first +}; + + +// Shared objects + +template +class WASM_API_EXTERN Shared { +public: + Shared() = delete; + ~Shared(); + void operator delete(void*); +}; + + +// Modules + +class WASM_API_EXTERN Module : public Ref { +public: + Module() = delete; + ~Module(); + + static auto validate(Store*, const vec& binary) -> bool; + static auto make(Store*, const vec& binary) -> own; + auto copy() const -> own; + + auto imports() const -> ownvec; + auto exports() const -> ownvec; + + auto share() const -> own>; + static auto obtain(Store*, const Shared*) -> own; + + auto serialize() const -> vec; + static auto deserialize(Store*, const vec&) -> own; +}; + + +// Foreign Objects + +class WASM_API_EXTERN Foreign : public Ref { +public: + Foreign() = delete; + ~Foreign(); + + static auto make(Store*) -> own; + auto copy() const -> own; +}; + + +// Externals + +class Func; +class Global; +class Table; +class Memory; + +class WASM_API_EXTERN Extern : public Ref { +public: + Extern() = delete; + ~Extern(); + + auto copy() const -> own; + + auto kind() const -> ExternKind; + auto type() const -> own; + + auto func() -> Func*; + auto global() -> Global*; + auto table() -> Table*; + auto memory() -> Memory*; + + auto func() const -> const Func*; + auto global() const -> const Global*; + auto table() const -> const Table*; + auto memory() const -> const Memory*; +}; + + +// Function Instances + +class WASM_API_EXTERN Func : public Extern { +public: + Func() = delete; + ~Func(); + + using callback = auto (*)(const Val[], Val[]) -> own; + using callback_with_env = auto (*)(void*, const Val[], Val[]) -> own; + + static auto make(Store*, const FuncType*, callback) -> own; + static auto make(Store*, const FuncType*, callback_with_env, + void*, void (*finalizer)(void*) = nullptr) -> own; + auto copy() const -> own; + + auto type() const -> own; + auto param_arity() const -> size_t; + auto result_arity() const -> size_t; + + auto call(const Val[] = nullptr, Val[] = nullptr) const -> own; +}; + + +// Global Instances + +class WASM_API_EXTERN Global : public Extern { +public: + Global() = delete; + ~Global(); + + static auto make(Store*, const GlobalType*, const Val&) -> own; + auto copy() const -> own; + + auto type() const -> own; + auto get() const -> Val; + void set(const Val&); +}; + + +// Table Instances + +class WASM_API_EXTERN Table : public Extern { +public: + Table() = delete; + ~Table(); + + using size_t = uint32_t; + + static auto make( + Store*, const TableType*, const Ref* init = nullptr) -> own
; + auto copy() const -> own
; + + auto type() const -> own; + auto get(size_t index) const -> own; + auto set(size_t index, const Ref*) -> bool; + auto size() const -> size_t; + auto grow(size_t delta, const Ref* init = nullptr) -> bool; +}; + + +// Memory Instances + +class WASM_API_EXTERN Memory : public Extern { +public: + Memory() = delete; + ~Memory(); + + static auto make(Store*, const MemoryType*) -> own; + auto copy() const -> own; + + using pages_t = uint32_t; + + static const size_t page_size = 0x10000; + + auto type() const -> own; + auto data() const -> byte_t*; + auto data_size() const -> size_t; + auto size() const -> pages_t; + auto grow(pages_t delta) -> bool; +}; + + +// Module Instances + +class WASM_API_EXTERN Instance : public Ref { +public: + Instance() = delete; + ~Instance(); + + static auto make( + Store*, const Module*, const Extern* const[], own* = nullptr + ) -> own; + auto copy() const -> own; + + auto exports() const -> ownvec; +}; + + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace wasm + +#endif // #ifdef WASM_HH diff --git a/lib/c-api/tests/wasm-c-api/patch/0001-BUILD.gn-add-wasm-v8-lowlevel.patch b/lib/c-api/tests/wasm-c-api/patch/0001-BUILD.gn-add-wasm-v8-lowlevel.patch new file mode 100644 index 000000000..fe7b35f9f --- /dev/null +++ b/lib/c-api/tests/wasm-c-api/patch/0001-BUILD.gn-add-wasm-v8-lowlevel.patch @@ -0,0 +1,28 @@ +diff --git a/BUILD.gn b/BUILD.gn +index deedf82..6dced15 100644 +--- a/BUILD.gn ++++ b/BUILD.gn +@@ -1679,6 +1679,7 @@ v8_header_set("v8_headers") { + + sources = [ + "include/v8.h", ++ "include/wasm-v8-lowlevel.hh", + "include/v8config.h", + ] + +@@ -2007,6 +2008,7 @@ v8_source_set("v8_base") { + "include/v8-testing.h", + "include/v8-util.h", + "include/v8.h", ++ "include/wasm-v8-lowlevel.hh", + "include/v8config.h", + "src/accessors.cc", + "src/accessors.h", +@@ -2020,6 +2022,7 @@ v8_source_set("v8_base") { + "src/api/api-natives.h", + "src/api/api.cc", + "src/api/api.h", ++ "src/wasm-v8-lowlevel.cc", + "src/asmjs/asm-js.cc", + "src/asmjs/asm-js.h", + "src/asmjs/asm-names.h", diff --git a/lib/c-api/tests/wasm-c-api/src/wasm-bin.cc b/lib/c-api/tests/wasm-c-api/src/wasm-bin.cc new file mode 100644 index 000000000..93f0b5847 --- /dev/null +++ b/lib/c-api/tests/wasm-c-api/src/wasm-bin.cc @@ -0,0 +1,570 @@ +#include "wasm-bin.hh" + +#include + +namespace wasm { +namespace bin { + +//////////////////////////////////////////////////////////////////////////////// +// Encoding + +void encode_header(char*& ptr) { + std::memcpy(ptr, "\x00""asm\x01\x00\x00\x00", 8); + ptr += 8; +} + +auto u64_size(uint64_t n) -> size_t { + bool done = false; + size_t size = 0; + do { + ++size; + done = n <= 0x7f; + n = n >> 7; + } while (!done); + return size; +} + +auto u32_size(uint64_t n) -> size_t { + return u64_size(n); +} + +void encode_u64(char*& ptr, uint64_t n) { + bool done = false; + do { + done = n <= 0x7f; + *ptr++ = (n & 0x7f) | (done ? 0x00 : 0x80); + n = n >> 7; + } while (!done); +} + +void encode_u32(char*& ptr, uint32_t n) { + encode_u64(ptr, n); +} + +void encode_size32(char*& ptr, size_t n) { + assert(n <= 0xffffffff); + for (int i = 0; i < 5; ++i) { + *ptr++ = (n & 0x7f) | (i == 4 ? 0x00 : 0x80); + n = n >> 7; + } +} + + +void encode_valtype(char*& ptr, const ValType* type) { + switch (type->kind()) { + case ValKind::I32: *ptr++ = 0x7f; break; + case ValKind::I64: *ptr++ = 0x7e; break; + case ValKind::F32: *ptr++ = 0x7d; break; + case ValKind::F64: *ptr++ = 0x7c; break; + case ValKind::FUNCREF: *ptr++ = 0x70; break; + case ValKind::ANYREF: *ptr++ = 0x6f; break; + default: assert(false); + } +} + +auto zero_size(const ValType* type) -> size_t { + switch (type->kind()) { + case ValKind::I32: return 1; + case ValKind::I64: return 1; + case ValKind::F32: return 4; + case ValKind::F64: return 8; + case ValKind::FUNCREF: return 0; + case ValKind::ANYREF: return 0; + default: assert(false); + } +} + +void encode_const_zero(char*& ptr, const ValType* type) { + switch (type->kind()) { + case ValKind::I32: *ptr++ = 0x41; break; + case ValKind::I64: *ptr++ = 0x42; break; + case ValKind::F32: *ptr++ = 0x43; break; + case ValKind::F64: *ptr++ = 0x44; break; + case ValKind::FUNCREF: *ptr++ = 0xd0; break; + case ValKind::ANYREF: *ptr++ = 0xd0; break; + default: assert(false); + } + for (int i = 0; i < zero_size(type); ++i) *ptr++ = 0; +} + + +auto wrapper(const FuncType* type) -> vec { + auto in_arity = type->params().size(); + auto out_arity = type->results().size(); + auto size = 39 + in_arity + out_arity; + auto binary = vec::make_uninitialized(size); + auto ptr = binary.get(); + + encode_header(ptr); + + *ptr++ = 0x01; // type section + encode_size32(ptr, 12 + in_arity + out_arity); // size + *ptr++ = 1; // length + *ptr++ = 0x60; // function + encode_size32(ptr, in_arity); + for (size_t i = 0; i < in_arity; ++i) { + encode_valtype(ptr, type->params()[i].get()); + } + encode_size32(ptr, out_arity); + for (size_t i = 0; i < out_arity; ++i) { + encode_valtype(ptr, type->results()[i].get()); + } + + *ptr++ = 0x02; // import section + *ptr++ = 5; // size + *ptr++ = 1; // length + *ptr++ = 0; // module length + *ptr++ = 0; // name length + *ptr++ = 0x00; // func + *ptr++ = 0; // type index + + *ptr++ = 0x07; // export section + *ptr++ = 4; // size + *ptr++ = 1; // length + *ptr++ = 0; // name length + *ptr++ = 0x00; // func + *ptr++ = 0; // func index + + assert(ptr - binary.get() == size); + return binary; +} + +auto wrapper(const GlobalType* type) -> vec { + auto size = 25 + zero_size(type->content()); + auto binary = vec::make_uninitialized(size); + auto ptr = binary.get(); + + encode_header(ptr); + + *ptr++ = 0x06; // global section + encode_size32(ptr, 5 + zero_size(type->content())); // size + *ptr++ = 1; // length + encode_valtype(ptr, type->content()); + *ptr++ = (type->mutability() == Mutability::VAR); + encode_const_zero(ptr, type->content()); + *ptr++ = 0x0b; // end + + *ptr++ = 0x07; // export section + *ptr++ = 4; // size + *ptr++ = 1; // length + *ptr++ = 0; // name length + *ptr++ = 0x03; // global + *ptr++ = 0; // func index + + assert(ptr - binary.get() == size); + return binary; +} + + +//////////////////////////////////////////////////////////////////////////////// +// Decoding + +// Numbers + +auto u32(const byte_t*& pos) -> uint32_t { + uint32_t n = 0; + uint32_t shift = 0; + byte_t b; + do { + b = *pos++; + n += (b & 0x7f) << shift; + shift += 7; + } while ((b & 0x80) != 0); + return n; +} + +auto u64(const byte_t*& pos) -> uint64_t { + uint64_t n = 0; + uint64_t shift = 0; + byte_t b; + do { + b = *pos++; + n += (b & 0x7f) << shift; + shift += 7; + } while ((b & 0x80) != 0); + return n; +} + +void u32_skip(const byte_t*& pos) { + bin::u32(pos); +} + + +// Names + +auto name(const byte_t*& pos) -> Name { + auto size = bin::u32(pos); + auto start = pos; + auto name = Name::make_uninitialized(size); + std::memcpy(name.get(), start, size); + pos += size; + return name; +} + +void name_skip(const byte_t*& pos) { + auto size = bin::u32(pos); + pos += size; +} + + +// Types + +auto valtype(const byte_t*& pos) -> own { + switch (*pos++) { + case 0x7f: return ValType::make(ValKind::I32); + case 0x7e: return ValType::make(ValKind::I64); + case 0x7d: return ValType::make(ValKind::F32); + case 0x7c: return ValType::make(ValKind::F64); + case 0x70: return ValType::make(ValKind::FUNCREF); + case 0x6f: return ValType::make(ValKind::ANYREF); + default: + // TODO(wasm+): support new value types + assert(false); + } +} + +auto mutability(const byte_t*& pos) -> Mutability { + return *pos++ ? Mutability::VAR : Mutability::CONST; +} + +auto limits(const byte_t*& pos) -> Limits { + auto tag = *pos++; + auto min = bin::u32(pos); + if ((tag & 0x01) == 0) { + return Limits(min); + } else { + auto max = bin::u32(pos); + return Limits(min, max); + } +} + +auto stacktype(const byte_t*& pos) -> ownvec { + size_t size = bin::u32(pos); + auto v = ownvec::make_uninitialized(size); + for (uint32_t i = 0; i < size; ++i) v[i] = bin::valtype(pos); + return v; +} + +auto functype(const byte_t*& pos) -> own { + assert(*pos == 0x60); + ++pos; + auto params = bin::stacktype(pos); + auto results = bin::stacktype(pos); + return FuncType::make(std::move(params), std::move(results)); +} + +auto globaltype(const byte_t*& pos) -> own { + auto content = bin::valtype(pos); + auto mutability = bin::mutability(pos); + return GlobalType::make(std::move(content), mutability); +} + +auto tabletype(const byte_t*& pos) -> own { + auto elem = bin::valtype(pos); + auto limits = bin::limits(pos); + return TableType::make(std::move(elem), limits); +} + +auto memorytype(const byte_t*& pos) -> own { + auto limits = bin::limits(pos); + return MemoryType::make(limits); +} + + +void mutability_skip(const byte_t*& pos) { + ++pos; +} + +void limits_skip(const byte_t*& pos) { + auto tag = *pos++; + bin::u32_skip(pos); + if ((tag & 0x01) != 0) bin::u32_skip(pos); +} + +void valtype_skip(const byte_t*& pos) { + // TODO(wasm+): support new value types + ++pos; +} + +void globaltype_skip(const byte_t*& pos) { + bin::valtype_skip(pos); + bin::mutability_skip(pos); +} + +void tabletype_skip(const byte_t*& pos) { + bin::valtype_skip(pos); + bin::limits_skip(pos); +} + +void memorytype_skip(const byte_t*& pos) { + bin::limits_skip(pos); +} + + +// Expressions + +void expr_skip(const byte_t*& pos) { + switch (*pos++ & 0xff) { + case 0x41: // i32.const + case 0x42: // i64.const + case 0x23: // get_global + case 0xd2: { // ref.func + bin::u32_skip(pos); + } break; + case 0x43: { // f32.const + pos += 4; + } break; + case 0x44: { // f64.const + pos += 8; + } break; + case 0xd0: { // ref.null + } break; + default: { + assert(false); + } + } + ++pos; // end +} + + +// Sections + +enum sec_t : byte_t { + SEC_TYPE = 1, + SEC_IMPORT = 2, + SEC_FUNC = 3, + SEC_TABLE = 4, + SEC_MEMORY = 5, + SEC_GLOBAL = 6, + SEC_EXPORT = 7 +}; + +auto section(const vec& binary, bin::sec_t sec) -> const byte_t* { + const byte_t* end = binary.get() + binary.size(); + const byte_t* pos = binary.get() + 8; // skip header + while (pos < end && *pos++ != sec) { + auto size = bin::u32(pos); + pos += size; + } + if (pos == end) return nullptr; + bin::u32_skip(pos); + return pos; +} + +auto section_end(const vec& binary, bin::sec_t sec) -> const byte_t* { + const byte_t* end = binary.get() + binary.size(); + const byte_t* pos = binary.get() + 8; // skip header + while (pos < end && *pos != sec) { + ++pos; + auto size = bin::u32(pos); + pos += size; + } + if (pos == end) return nullptr; + ++pos; + auto size = bin::u32(pos); + return pos + size; +} + + +// Type section + +auto types(const vec& binary) -> ownvec { + auto pos = bin::section(binary, SEC_TYPE); + if (pos == nullptr) return ownvec::make(); + size_t size = bin::u32(pos); + // TODO(wasm+): support new deftypes + auto v = ownvec::make_uninitialized(size); + for (uint32_t i = 0; i < size; ++i) { + v[i] = bin::functype(pos); + } + assert(pos = bin::section_end(binary, SEC_TYPE)); + return v; +} + + +// Import section + +auto imports( + const vec& binary, const ownvec& types +) -> ownvec { + auto pos = bin::section(binary, SEC_IMPORT); + if (pos == nullptr) return ownvec::make(); + size_t size = bin::u32(pos); + auto v = ownvec::make_uninitialized(size); + for (uint32_t i = 0; i < size; ++i) { + auto module = bin::name(pos); + auto name = bin::name(pos); + own type; + switch (*pos++) { + case 0x00: type = types[bin::u32(pos)]->copy(); break; + case 0x01: type = bin::tabletype(pos); break; + case 0x02: type = bin::memorytype(pos); break; + case 0x03: type = bin::globaltype(pos); break; + default: assert(false); + } + v[i] = ImportType::make( + std::move(module), std::move(name), std::move(type)); + } + assert(pos = bin::section_end(binary, SEC_IMPORT)); + return v; +} + +auto count(const ownvec& imports, ExternKind kind) -> uint32_t { + uint32_t n = 0; + for (uint32_t i = 0; i < imports.size(); ++i) { + if (imports[i]->type()->kind() == kind) ++n; + } + return n; +} + + +// Function section + +auto funcs( + const vec& binary, + const ownvec& imports, const ownvec& types +) -> ownvec { + auto pos = bin::section(binary, SEC_FUNC); + size_t size = pos != nullptr ? bin::u32(pos) : 0; + auto v = ownvec::make_uninitialized( + size + count(imports, ExternKind::FUNC)); + size_t j = 0; + for (uint32_t i = 0; i < imports.size(); ++i) { + auto et = imports[i]->type(); + if (et->kind() == ExternKind::FUNC) { + v[j++] = et->func()->copy(); + } + } + if (pos != nullptr) { + for (; j < v.size(); ++j) { + v[j] = types[bin::u32(pos)]->copy(); + } + assert(pos = bin::section_end(binary, SEC_FUNC)); + } + return v; +} + + +// Global section + +auto globals( + const vec& binary, const ownvec& imports +) -> ownvec { + auto pos = bin::section(binary, SEC_GLOBAL); + size_t size = pos != nullptr ? bin::u32(pos) : 0; + auto v = ownvec::make_uninitialized( + size + count(imports, ExternKind::GLOBAL)); + size_t j = 0; + for (uint32_t i = 0; i < imports.size(); ++i) { + auto et = imports[i]->type(); + if (et->kind() == ExternKind::GLOBAL) { + v[j++] = et->global()->copy(); + } + } + if (pos != nullptr) { + for (; j < v.size(); ++j) { + v[j] = bin::globaltype(pos); + expr_skip(pos); + } + assert(pos = bin::section_end(binary, SEC_GLOBAL)); + } + return v; +} + + +// Table section + +auto tables( + const vec& binary, const ownvec& imports +) -> ownvec { + auto pos = bin::section(binary, SEC_TABLE); + size_t size = pos != nullptr ? bin::u32(pos) : 0; + auto v = ownvec::make_uninitialized( + size + count(imports, ExternKind::TABLE)); + size_t j = 0; + for (uint32_t i = 0; i < imports.size(); ++i) { + auto et = imports[i]->type(); + if (et->kind() == ExternKind::TABLE) { + v[j++] = et->table()->copy(); + } + } + if (pos != nullptr) { + for (; j < v.size(); ++j) { + v[j] = bin::tabletype(pos); + } + assert(pos = bin::section_end(binary, SEC_TABLE)); + } + return v; +} + + +// Memory section + +auto memories( + const vec& binary, const ownvec& imports +) -> ownvec { + auto pos = bin::section(binary, SEC_MEMORY); + size_t size = pos != nullptr ? bin::u32(pos) : 0; + auto v = ownvec::make_uninitialized( + size + count(imports, ExternKind::MEMORY)); + size_t j = 0; + for (uint32_t i = 0; i < imports.size(); ++i) { + auto et = imports[i]->type(); + if (et->kind() == ExternKind::MEMORY) { + v[j++] = et->memory()->copy(); + } + } + if (pos != nullptr) { + for (; j < v.size(); ++j) { + v[j] = bin::memorytype(pos); + } + assert(pos = bin::section_end(binary, SEC_MEMORY)); + } + return v; +} + + +// Export section + +auto exports(const vec& binary, + const ownvec& funcs, const ownvec& globals, + const ownvec& tables, const ownvec& memories +) -> ownvec { + auto pos = bin::section(binary, SEC_EXPORT); + if (pos == nullptr) return ownvec::make(); + size_t size = bin::u32(pos); + auto exports = ownvec::make_uninitialized(size); + for (uint32_t i = 0; i < size; ++i) { + auto name = bin::name(pos); + auto tag = *pos++; + auto index = bin::u32(pos); + own type; + switch (tag) { + case 0x00: type = funcs[index]->copy(); break; + case 0x01: type = tables[index]->copy(); break; + case 0x02: type = memories[index]->copy(); break; + case 0x03: type = globals[index]->copy(); break; + default: assert(false); + } + exports[i] = ExportType::make(std::move(name), std::move(type)); + } + assert(pos = bin::section_end(binary, SEC_EXPORT)); + return exports; +} + +auto imports(const vec& binary) -> ownvec { + return bin::imports(binary, bin::types(binary)); +} + +auto exports(const vec& binary) -> ownvec { + auto types = bin::types(binary); + auto imports = bin::imports(binary, types); + auto funcs = bin::funcs(binary, imports, types); + auto globals = bin::globals(binary, imports); + auto tables = bin::tables(binary, imports); + auto memories = bin::memories(binary, imports); + return bin::exports(binary, funcs, globals, tables, memories); +} + +} // namespace bin +} // namespace wasm diff --git a/lib/c-api/tests/wasm-c-api/src/wasm-bin.hh b/lib/c-api/tests/wasm-c-api/src/wasm-bin.hh new file mode 100644 index 000000000..15b2529d9 --- /dev/null +++ b/lib/c-api/tests/wasm-c-api/src/wasm-bin.hh @@ -0,0 +1,25 @@ +#ifndef __WASM_BIN_HH +#define __WASM_BIN_HH + +#include "wasm.hh" + +namespace wasm { +namespace bin { + +auto u32_size(uint32_t) -> size_t; +auto u64_size(uint64_t) -> size_t; +void encode_u32(char*& ptr, uint32_t n); +void encode_u64(char*& ptr, uint64_t n); +auto u32(const byte_t*& pos) -> uint32_t; +auto u64(const byte_t*& pos) -> uint64_t; + +auto wrapper(const FuncType*) -> vec; +auto wrapper(const GlobalType*) -> vec; + +auto imports(const vec& binary) -> ownvec; +auto exports(const vec& binary) -> ownvec; + +} // namespace bin +} // namespace wasm + +#endif // #ifdef __WASM_BIN_HH diff --git a/lib/c-api/tests/wasm-c-api/src/wasm-c.cc b/lib/c-api/tests/wasm-c-api/src/wasm-c.cc new file mode 100644 index 000000000..982cc8071 --- /dev/null +++ b/lib/c-api/tests/wasm-c-api/src/wasm-c.cc @@ -0,0 +1,1019 @@ +#include "wasm.h" +#include "wasm.hh" + +#include "wasm-v8.cc" + +using namespace wasm; + +extern "C" { + +/////////////////////////////////////////////////////////////////////////////// +// Auxiliaries + +// Backing implementation + +extern "C++" { + +template +struct borrowed_vec { + vec it; + borrowed_vec(vec&& v) : it(std::move(v)) {} + borrowed_vec(borrowed_vec&& that) : it(std::move(that.it)) {} + ~borrowed_vec() { it.release(); } +}; + +} // extern "C++" + + +#define WASM_DEFINE_OWN(name, Name) \ + struct wasm_##name##_t : Name {}; \ + \ + void wasm_##name##_delete(wasm_##name##_t* x) { \ + delete x; \ + } \ + \ + extern "C++" inline auto hide_##name(Name* x) -> wasm_##name##_t* { \ + return static_cast(x); \ + } \ + extern "C++" inline auto hide_##name(const Name* x) -> const wasm_##name##_t* { \ + return static_cast(x); \ + } \ + extern "C++" inline auto reveal_##name(wasm_##name##_t* x) -> Name* { \ + return x; \ + } \ + extern "C++" inline auto reveal_##name(const wasm_##name##_t* x) -> const Name* { \ + return x; \ + } \ + extern "C++" inline auto get_##name(own& x) -> wasm_##name##_t* { \ + return hide_##name(x.get()); \ + } \ + extern "C++" inline auto get_##name(const own& x) -> const wasm_##name##_t* { \ + return hide_##name(x.get()); \ + } \ + extern "C++" inline auto release_##name(own&& x) -> wasm_##name##_t* { \ + return hide_##name(x.release()); \ + } \ + extern "C++" inline auto adopt_##name(wasm_##name##_t* x) -> own { \ + return make_own(x); \ + } + + +// Vectors + +#define WASM_DEFINE_VEC_BASE(name, Name, vec, ptr_or_none) \ + static_assert( \ + sizeof(wasm_##name##_vec_t) == sizeof(vec), \ + "C/C++ incompatibility" \ + ); \ + static_assert( \ + sizeof(wasm_##name##_t ptr_or_none) == sizeof(vec::elem_type), \ + "C/C++ incompatibility" \ + ); \ + \ + extern "C++" inline auto hide_##name##_vec(vec& v) \ + -> wasm_##name##_vec_t* { \ + return reinterpret_cast(&v); \ + } \ + extern "C++" inline auto hide_##name##_vec(const vec& v) \ + -> const wasm_##name##_vec_t* { \ + return reinterpret_cast(&v); \ + } \ + extern "C++" inline auto hide_##name##_vec(vec::elem_type* v) \ + -> wasm_##name##_t ptr_or_none* { \ + return reinterpret_cast(v); \ + } \ + extern "C++" inline auto hide_##name##_vec(const vec::elem_type* v) \ + -> wasm_##name##_t ptr_or_none const* { \ + return reinterpret_cast(v); \ + } \ + extern "C++" inline auto reveal_##name##_vec(wasm_##name##_t ptr_or_none* v) \ + -> vec::elem_type* { \ + return reinterpret_cast::elem_type*>(v); \ + } \ + extern "C++" inline auto reveal_##name##_vec(wasm_##name##_t ptr_or_none const* v) \ + -> const vec::elem_type* { \ + return reinterpret_cast::elem_type*>(v); \ + } \ + extern "C++" inline auto get_##name##_vec(vec& v) \ + -> wasm_##name##_vec_t { \ + wasm_##name##_vec_t v2 = { v.size(), hide_##name##_vec(v.get()) }; \ + return v2; \ + } \ + extern "C++" inline auto get_##name##_vec(const vec& v) \ + -> const wasm_##name##_vec_t { \ + wasm_##name##_vec_t v2 = { \ + v.size(), const_cast(hide_##name##_vec(v.get())) }; \ + return v2; \ + } \ + extern "C++" inline auto release_##name##_vec(vec&& v) \ + -> wasm_##name##_vec_t { \ + wasm_##name##_vec_t v2 = { v.size(), hide_##name##_vec(v.release()) }; \ + return v2; \ + } \ + extern "C++" inline auto adopt_##name##_vec(wasm_##name##_vec_t* v) \ + -> vec { \ + return vec::adopt(v->size, reveal_##name##_vec(v->data)); \ + } \ + extern "C++" inline auto borrow_##name##_vec(const wasm_##name##_vec_t* v) \ + -> borrowed_vec::elem_type> { \ + return borrowed_vec::elem_type>(vec::adopt(v->size, reveal_##name##_vec(v->data))); \ + } \ + \ + void wasm_##name##_vec_new_uninitialized( \ + wasm_##name##_vec_t* out, size_t size \ + ) { \ + *out = release_##name##_vec(vec::make_uninitialized(size)); \ + } \ + void wasm_##name##_vec_new_empty(wasm_##name##_vec_t* out) { \ + wasm_##name##_vec_new_uninitialized(out, 0); \ + } \ + \ + void wasm_##name##_vec_delete(wasm_##name##_vec_t* v) { \ + adopt_##name##_vec(v); \ + } + +// Vectors with no ownership management of elements +#define WASM_DEFINE_VEC_PLAIN(name, Name) \ + WASM_DEFINE_VEC_BASE(name, Name, vec, ) \ + \ + void wasm_##name##_vec_new( \ + wasm_##name##_vec_t* out, \ + size_t size, \ + const wasm_##name##_t data[] \ + ) { \ + auto v2 = vec::make_uninitialized(size); \ + if (v2.size() != 0) { \ + memcpy(v2.get(), data, size * sizeof(wasm_##name##_t)); \ + } \ + *out = release_##name##_vec(std::move(v2)); \ + } \ + \ + void wasm_##name##_vec_copy( \ + wasm_##name##_vec_t* out, const wasm_##name##_vec_t* v \ + ) { \ + wasm_##name##_vec_new(out, v->size, v->data); \ + } + +// Vectors that own their elements +#define WASM_DEFINE_VEC_OWN(name, Name) \ + WASM_DEFINE_VEC_BASE(name, Name, ownvec, *) \ + \ + void wasm_##name##_vec_new( \ + wasm_##name##_vec_t* out, \ + size_t size, \ + wasm_##name##_t* const data[] \ + ) { \ + auto v2 = ownvec::make_uninitialized(size); \ + for (size_t i = 0; i < v2.size(); ++i) { \ + v2[i] = adopt_##name(data[i]); \ + } \ + *out = release_##name##_vec(std::move(v2)); \ + } \ + \ + void wasm_##name##_vec_copy( \ + wasm_##name##_vec_t* out, const wasm_##name##_vec_t* v \ + ) { \ + auto v2 = ownvec::make_uninitialized(v->size); \ + for (size_t i = 0; i < v2.size(); ++i) { \ + v2[i] = adopt_##name(wasm_##name##_copy(v->data[i])); \ + } \ + *out = release_##name##_vec(std::move(v2)); \ + } + +extern "C++" { +template +inline auto is_empty(T* p) -> bool { return !p; } +} + + +// Byte vectors + +using byte = byte_t; +WASM_DEFINE_VEC_PLAIN(byte, byte) + + +/////////////////////////////////////////////////////////////////////////////// +// Runtime Environment + +// Configuration + +WASM_DEFINE_OWN(config, Config) + +wasm_config_t* wasm_config_new() { + return release_config(Config::make()); +} + + +// Engine + +WASM_DEFINE_OWN(engine, Engine) + +wasm_engine_t* wasm_engine_new() { + return release_engine(Engine::make()); +} + +wasm_engine_t* wasm_engine_new_with_config(wasm_config_t* config) { + return release_engine(Engine::make(adopt_config(config))); +} + + +// Stores + +WASM_DEFINE_OWN(store, Store) + +wasm_store_t* wasm_store_new(wasm_engine_t* engine) { + return release_store(Store::make(engine)); +}; + + +/////////////////////////////////////////////////////////////////////////////// +// Type Representations + +// Type attributes + +extern "C++" inline auto hide_mutability(Mutability mutability) -> wasm_mutability_t { + return static_cast(mutability); +} + +extern "C++" inline auto reveal_mutability(wasm_mutability_t mutability) -> Mutability { + return static_cast(mutability); +} + + +extern "C++" inline auto hide_limits(const Limits& limits) -> const wasm_limits_t* { + return reinterpret_cast(&limits); +} + +extern "C++" inline auto reveal_limits(wasm_limits_t limits) -> Limits { + return Limits(limits.min, limits.max); +} + + +extern "C++" inline auto hide_valkind(ValKind kind) -> wasm_valkind_t { + return static_cast(kind); +} + +extern "C++" inline auto reveal_valkind(wasm_valkind_t kind) -> ValKind { + return static_cast(kind); +} + + +extern "C++" inline auto hide_externkind(ExternKind kind) -> wasm_externkind_t { + return static_cast(kind); +} + +extern "C++" inline auto reveal_externkind(wasm_externkind_t kind) -> ExternKind { + return static_cast(kind); +} + + + +// Generic + +#define WASM_DEFINE_TYPE(name, Name) \ + WASM_DEFINE_OWN(name, Name) \ + WASM_DEFINE_VEC_OWN(name, Name) \ + \ + wasm_##name##_t* wasm_##name##_copy(wasm_##name##_t* t) { \ + return release_##name(t->copy()); \ + } + + +// Value Types + +WASM_DEFINE_TYPE(valtype, ValType) + +wasm_valtype_t* wasm_valtype_new(wasm_valkind_t k) { + return release_valtype(ValType::make(reveal_valkind(k))); +} + +wasm_valkind_t wasm_valtype_kind(const wasm_valtype_t* t) { + return hide_valkind(t->kind()); +} + + +// Function Types + +WASM_DEFINE_TYPE(functype, FuncType) + +wasm_functype_t* wasm_functype_new( + wasm_valtype_vec_t* params, wasm_valtype_vec_t* results +) { + return release_functype( + FuncType::make(adopt_valtype_vec(params), adopt_valtype_vec(results))); +} + +const wasm_valtype_vec_t* wasm_functype_params(const wasm_functype_t* ft) { + return hide_valtype_vec(ft->params()); +} + +const wasm_valtype_vec_t* wasm_functype_results(const wasm_functype_t* ft) { + return hide_valtype_vec(ft->results()); +} + + +// Global Types + +WASM_DEFINE_TYPE(globaltype, GlobalType) + +wasm_globaltype_t* wasm_globaltype_new( + wasm_valtype_t* content, wasm_mutability_t mutability +) { + return release_globaltype(GlobalType::make( + adopt_valtype(content), + reveal_mutability(mutability) + )); +} + +const wasm_valtype_t* wasm_globaltype_content(const wasm_globaltype_t* gt) { + return hide_valtype(gt->content()); +} + +wasm_mutability_t wasm_globaltype_mutability(const wasm_globaltype_t* gt) { + return hide_mutability(gt->mutability()); +} + + +// Table Types + +WASM_DEFINE_TYPE(tabletype, TableType) + +wasm_tabletype_t* wasm_tabletype_new( + wasm_valtype_t* element, const wasm_limits_t* limits +) { + return release_tabletype(TableType::make(adopt_valtype(element), reveal_limits(*limits))); +} + +const wasm_valtype_t* wasm_tabletype_element(const wasm_tabletype_t* tt) { + return hide_valtype(tt->element()); +} + +const wasm_limits_t* wasm_tabletype_limits(const wasm_tabletype_t* tt) { + return hide_limits(tt->limits()); +} + + +// Memory Types + +WASM_DEFINE_TYPE(memorytype, MemoryType) + +wasm_memorytype_t* wasm_memorytype_new(const wasm_limits_t* limits) { + return release_memorytype(MemoryType::make(reveal_limits(*limits))); +} + +const wasm_limits_t* wasm_memorytype_limits(const wasm_memorytype_t* mt) { + return hide_limits(mt->limits()); +} + + +// Extern Types + +WASM_DEFINE_TYPE(externtype, ExternType) + +wasm_externkind_t wasm_externtype_kind(const wasm_externtype_t* et) { + return hide_externkind(et->kind()); +} + +wasm_externtype_t* wasm_functype_as_externtype(wasm_functype_t* ft) { + return hide_externtype(static_cast(ft)); +} +wasm_externtype_t* wasm_globaltype_as_externtype(wasm_globaltype_t* gt) { + return hide_externtype(static_cast(gt)); +} +wasm_externtype_t* wasm_tabletype_as_externtype(wasm_tabletype_t* tt) { + return hide_externtype(static_cast(tt)); +} +wasm_externtype_t* wasm_memorytype_as_externtype(wasm_memorytype_t* mt) { + return hide_externtype(static_cast(mt)); +} + +const wasm_externtype_t* wasm_functype_as_externtype_const( + const wasm_functype_t* ft +) { + return hide_externtype(static_cast(ft)); +} +const wasm_externtype_t* wasm_globaltype_as_externtype_const( + const wasm_globaltype_t* gt +) { + return hide_externtype(static_cast(gt)); +} +const wasm_externtype_t* wasm_tabletype_as_externtype_const( + const wasm_tabletype_t* tt +) { + return hide_externtype(static_cast(tt)); +} +const wasm_externtype_t* wasm_memorytype_as_externtype_const( + const wasm_memorytype_t* mt +) { + return hide_externtype(static_cast(mt)); +} + +wasm_functype_t* wasm_externtype_as_functype(wasm_externtype_t* et) { + return et->kind() == ExternKind::FUNC + ? hide_functype(static_cast(reveal_externtype(et))) : nullptr; +} +wasm_globaltype_t* wasm_externtype_as_globaltype(wasm_externtype_t* et) { + return et->kind() == ExternKind::GLOBAL + ? hide_globaltype(static_cast(reveal_externtype(et))) : nullptr; +} +wasm_tabletype_t* wasm_externtype_as_tabletype(wasm_externtype_t* et) { + return et->kind() == ExternKind::TABLE + ? hide_tabletype(static_cast(reveal_externtype(et))) : nullptr; +} +wasm_memorytype_t* wasm_externtype_as_memorytype(wasm_externtype_t* et) { + return et->kind() == ExternKind::MEMORY + ? hide_memorytype(static_cast(reveal_externtype(et))) : nullptr; +} + +const wasm_functype_t* wasm_externtype_as_functype_const( + const wasm_externtype_t* et +) { + return et->kind() == ExternKind::FUNC + ? hide_functype(static_cast(reveal_externtype(et))) : nullptr; +} +const wasm_globaltype_t* wasm_externtype_as_globaltype_const( + const wasm_externtype_t* et +) { + return et->kind() == ExternKind::GLOBAL + ? hide_globaltype(static_cast(reveal_externtype(et))) : nullptr; +} +const wasm_tabletype_t* wasm_externtype_as_tabletype_const( + const wasm_externtype_t* et +) { + return et->kind() == ExternKind::TABLE + ? hide_tabletype(static_cast(reveal_externtype(et))) : nullptr; +} +const wasm_memorytype_t* wasm_externtype_as_memorytype_const( + const wasm_externtype_t* et +) { + return et->kind() == ExternKind::MEMORY + ? hide_memorytype(static_cast(reveal_externtype(et))) : nullptr; +} + + +// Import Types + +WASM_DEFINE_TYPE(importtype, ImportType) + +wasm_importtype_t* wasm_importtype_new( + wasm_name_t* module, wasm_name_t* name, wasm_externtype_t* type +) { + return release_importtype( + ImportType::make(adopt_byte_vec(module), adopt_byte_vec(name), adopt_externtype(type))); +} + +const wasm_name_t* wasm_importtype_module(const wasm_importtype_t* it) { + return hide_byte_vec(it->module()); +} + +const wasm_name_t* wasm_importtype_name(const wasm_importtype_t* it) { + return hide_byte_vec(it->name()); +} + +const wasm_externtype_t* wasm_importtype_type(const wasm_importtype_t* it) { + return hide_externtype(it->type()); +} + + +// Export Types + +WASM_DEFINE_TYPE(exporttype, ExportType) + +wasm_exporttype_t* wasm_exporttype_new( + wasm_name_t* name, wasm_externtype_t* type +) { + return release_exporttype( + ExportType::make(adopt_byte_vec(name), adopt_externtype(type))); +} + +const wasm_name_t* wasm_exporttype_name(const wasm_exporttype_t* et) { + return hide_byte_vec(et->name()); +} + +const wasm_externtype_t* wasm_exporttype_type(const wasm_exporttype_t* et) { + return hide_externtype(et->type()); +} + + +/////////////////////////////////////////////////////////////////////////////// +// Runtime Values + +// References + +#define WASM_DEFINE_REF_BASE(name, Name) \ + WASM_DEFINE_OWN(name, Name) \ + \ + wasm_##name##_t* wasm_##name##_copy(const wasm_##name##_t* t) { \ + return release_##name(t->copy()); \ + } \ + \ + bool wasm_##name##_same(const wasm_##name##_t* t1, const wasm_##name##_t* t2) { \ + return t1->same(t2); \ + } \ + \ + void* wasm_##name##_get_host_info(const wasm_##name##_t* r) { \ + return r->get_host_info(); \ + } \ + void wasm_##name##_set_host_info(wasm_##name##_t* r, void* info) { \ + r->set_host_info(info); \ + } \ + void wasm_##name##_set_host_info_with_finalizer( \ + wasm_##name##_t* r, void* info, void (*finalizer)(void*) \ + ) { \ + r->set_host_info(info, finalizer); \ + } + +#define WASM_DEFINE_REF(name, Name) \ + WASM_DEFINE_REF_BASE(name, Name) \ + \ + wasm_ref_t* wasm_##name##_as_ref(wasm_##name##_t* r) { \ + return hide_ref(static_cast(reveal_##name(r))); \ + } \ + wasm_##name##_t* wasm_ref_as_##name(wasm_ref_t* r) { \ + return hide_##name(static_cast(reveal_ref(r))); \ + } \ + \ + const wasm_ref_t* wasm_##name##_as_ref_const(const wasm_##name##_t* r) { \ + return hide_ref(static_cast(reveal_##name(r))); \ + } \ + const wasm_##name##_t* wasm_ref_as_##name##_const(const wasm_ref_t* r) { \ + return hide_##name(static_cast(reveal_ref(r))); \ + } + +#define WASM_DEFINE_SHARABLE_REF(name, Name) \ + WASM_DEFINE_REF(name, Name) \ + WASM_DEFINE_OWN(shared_##name, Shared) + + +WASM_DEFINE_REF_BASE(ref, Ref) + + +// Values + +extern "C++" { + +inline auto is_empty(wasm_val_t v) -> bool { + return !is_ref(reveal_valkind(v.kind)) || !v.of.ref; +} + +inline auto hide_val(Val v) -> wasm_val_t { + wasm_val_t v2 = { hide_valkind(v.kind()) }; + switch (v.kind()) { + case ValKind::I32: v2.of.i32 = v.i32(); break; + case ValKind::I64: v2.of.i64 = v.i64(); break; + case ValKind::F32: v2.of.f32 = v.f32(); break; + case ValKind::F64: v2.of.f64 = v.f64(); break; + case ValKind::ANYREF: + case ValKind::FUNCREF: v2.of.ref = hide_ref(v.ref()); break; + default: assert(false); + } + return v2; +} + +inline auto release_val(Val v) -> wasm_val_t { + wasm_val_t v2 = { hide_valkind(v.kind()) }; + switch (v.kind()) { + case ValKind::I32: v2.of.i32 = v.i32(); break; + case ValKind::I64: v2.of.i64 = v.i64(); break; + case ValKind::F32: v2.of.f32 = v.f32(); break; + case ValKind::F64: v2.of.f64 = v.f64(); break; + case ValKind::ANYREF: + case ValKind::FUNCREF: v2.of.ref = release_ref(v.release_ref()); break; + default: assert(false); + } + return v2; +} + +inline auto adopt_val(wasm_val_t v) -> Val { + switch (reveal_valkind(v.kind)) { + case ValKind::I32: return Val(v.of.i32); + case ValKind::I64: return Val(v.of.i64); + case ValKind::F32: return Val(v.of.f32); + case ValKind::F64: return Val(v.of.f64); + case ValKind::ANYREF: + case ValKind::FUNCREF: return Val(adopt_ref(v.of.ref)); + default: assert(false); + } +} + +struct borrowed_val { + Val it; + borrowed_val(Val&& v) : it(std::move(v)) {} + borrowed_val(borrowed_val&& that) : it(std::move(that.it)) {} + ~borrowed_val() { if (it.is_ref()) it.release_ref().release(); } +}; + +inline auto borrow_val(const wasm_val_t* v) -> borrowed_val { + Val v2; + switch (reveal_valkind(v->kind)) { + case ValKind::I32: v2 = Val(v->of.i32); break; + case ValKind::I64: v2 = Val(v->of.i64); break; + case ValKind::F32: v2 = Val(v->of.f32); break; + case ValKind::F64: v2 = Val(v->of.f64); break; + case ValKind::ANYREF: + case ValKind::FUNCREF: v2 = Val(adopt_ref(v->of.ref)); break; + default: assert(false); + } + return borrowed_val(std::move(v2)); +} + +} // extern "C++" + + +WASM_DEFINE_VEC_BASE(val, Val, vec, ) + +void wasm_val_vec_new( + wasm_val_vec_t* out, size_t size, wasm_val_t const data[] +) { + auto v2 = vec::make_uninitialized(size); + for (size_t i = 0; i < v2.size(); ++i) { + v2[i] = adopt_val(data[i]); + } + *out = release_val_vec(std::move(v2)); +} + +void wasm_val_vec_copy(wasm_val_vec_t* out, const wasm_val_vec_t* v) { + auto v2 = vec::make_uninitialized(v->size); + for (size_t i = 0; i < v2.size(); ++i) { + wasm_val_t val; + wasm_val_copy(&v->data[i], &val); + v2[i] = adopt_val(val); + } + *out = release_val_vec(std::move(v2)); +} + + +void wasm_val_delete(wasm_val_t* v) { + if (is_ref(reveal_valkind(v->kind))) { + adopt_ref(v->of.ref); + } +} + +void wasm_val_copy(wasm_val_t* out, const wasm_val_t* v) { + *out = *v; + if (is_ref(reveal_valkind(v->kind))) { + out->of.ref = v->of.ref ? release_ref(v->of.ref->copy()) : nullptr; + } +} + + +/////////////////////////////////////////////////////////////////////////////// +// Runtime Objects + +// Frames + +WASM_DEFINE_OWN(frame, Frame) +WASM_DEFINE_VEC_OWN(frame, Frame) + +wasm_frame_t* wasm_frame_copy(const wasm_frame_t* frame) { + return release_frame(frame->copy()); +} + +wasm_instance_t* wasm_frame_instance(const wasm_frame_t* frame); +// Defined below along with wasm_instance_t. + +uint32_t wasm_frame_func_index(const wasm_frame_t* frame) { + return reveal_frame(frame)->func_index(); +} + +size_t wasm_frame_func_offset(const wasm_frame_t* frame) { + return reveal_frame(frame)->func_offset(); +} + +size_t wasm_frame_module_offset(const wasm_frame_t* frame) { + return reveal_frame(frame)->module_offset(); +} + + +// Traps + +WASM_DEFINE_REF(trap, Trap) + +wasm_trap_t* wasm_trap_new(wasm_store_t* store, const wasm_message_t* message) { + auto message_ = borrow_byte_vec(message); + return release_trap(Trap::make(store, message_.it)); +} + +void wasm_trap_message(const wasm_trap_t* trap, wasm_message_t* out) { + *out = release_byte_vec(reveal_trap(trap)->message()); +} + +wasm_frame_t* wasm_trap_origin(const wasm_trap_t* trap) { + return release_frame(reveal_trap(trap)->origin()); +} + +void wasm_trap_trace(const wasm_trap_t* trap, wasm_frame_vec_t* out) { + *out = release_frame_vec(reveal_trap(trap)->trace()); +} + + +// Foreign Objects + +WASM_DEFINE_REF(foreign, Foreign) + +wasm_foreign_t* wasm_foreign_new(wasm_store_t* store) { + return release_foreign(Foreign::make(store)); +} + + +// Modules + +WASM_DEFINE_SHARABLE_REF(module, Module) + +bool wasm_module_validate(wasm_store_t* store, const wasm_byte_vec_t* binary) { + auto binary_ = borrow_byte_vec(binary); + return Module::validate(store, binary_.it); +} + +wasm_module_t* wasm_module_new( + wasm_store_t* store, const wasm_byte_vec_t* binary +) { + auto binary_ = borrow_byte_vec(binary); + return release_module(Module::make(store, binary_.it)); +} + + +void wasm_module_imports( + const wasm_module_t* module, wasm_importtype_vec_t* out +) { + *out = release_importtype_vec(reveal_module(module)->imports()); +} + +void wasm_module_exports( + const wasm_module_t* module, wasm_exporttype_vec_t* out +) { + *out = release_exporttype_vec(reveal_module(module)->exports()); +} + +void wasm_module_serialize(const wasm_module_t* module, wasm_byte_vec_t* out) { + *out = release_byte_vec(reveal_module(module)->serialize()); +} + +wasm_module_t* wasm_module_deserialize( + wasm_store_t* store, const wasm_byte_vec_t* binary +) { + auto binary_ = borrow_byte_vec(binary); + return release_module(Module::deserialize(store, binary_.it)); +} + +wasm_shared_module_t* wasm_module_share(const wasm_module_t* module) { + return release_shared_module(reveal_module(module)->share()); +} + +wasm_module_t* wasm_module_obtain(wasm_store_t* store, const wasm_shared_module_t* shared) { + return release_module(Module::obtain(store, shared)); +} + + +// Function Instances + +WASM_DEFINE_REF(func, Func) + +extern "C++" { + +auto wasm_callback(void* env, const Val args[], Val results[]) -> own { + auto f = reinterpret_cast(env); + return adopt_trap(f(hide_val_vec(args), hide_val_vec(results))); +} + +struct wasm_callback_env_t { + wasm_func_callback_with_env_t callback; + void* env; + void (*finalizer)(void*); +}; + +auto wasm_callback_with_env( + void* env, const Val args[], Val results[] +) -> own { + auto t = static_cast(env); + return adopt_trap(t->callback(t->env, hide_val_vec(args), hide_val_vec(results))); +} + +void wasm_callback_env_finalizer(void* env) { + auto t = static_cast(env); + if (t->finalizer) t->finalizer(t->env); + delete t; +} + +} // extern "C++" + +wasm_func_t* wasm_func_new( + wasm_store_t* store, const wasm_functype_t* type, + wasm_func_callback_t callback +) { + return release_func(Func::make( + store, type, wasm_callback, reinterpret_cast(callback))); +} + +wasm_func_t *wasm_func_new_with_env( + wasm_store_t* store, const wasm_functype_t* type, + wasm_func_callback_with_env_t callback, void *env, void (*finalizer)(void*) +) { + auto env2 = new wasm_callback_env_t{callback, env, finalizer}; + return release_func(Func::make(store, type, wasm_callback_with_env, env2, wasm_callback_env_finalizer)); +} + +wasm_functype_t* wasm_func_type(const wasm_func_t* func) { + return release_functype(func->type()); +} + +size_t wasm_func_param_arity(const wasm_func_t* func) { + return func->param_arity(); +} + +size_t wasm_func_result_arity(const wasm_func_t* func) { + return func->result_arity(); +} + +wasm_trap_t* wasm_func_call( + const wasm_func_t* func, const wasm_val_t args[], wasm_val_t results[] +) { + return release_trap(func->call(reveal_val_vec(args), reveal_val_vec(results))); +} + + +// Global Instances + +WASM_DEFINE_REF(global, Global) + +wasm_global_t* wasm_global_new( + wasm_store_t* store, const wasm_globaltype_t* type, const wasm_val_t* val +) { + auto val_ = borrow_val(val); + return release_global(Global::make(store, type, val_.it)); +} + +wasm_globaltype_t* wasm_global_type(const wasm_global_t* global) { + return release_globaltype(global->type()); +} + +void wasm_global_get(const wasm_global_t* global, wasm_val_t* out) { + *out = release_val(global->get()); +} + +void wasm_global_set(wasm_global_t* global, const wasm_val_t* val) { + auto val_ = borrow_val(val); + global->set(val_.it); +} + + +// Table Instances + +WASM_DEFINE_REF(table, Table) + +wasm_table_t* wasm_table_new( + wasm_store_t* store, const wasm_tabletype_t* type, wasm_ref_t* ref +) { + return release_table(Table::make(store, type, ref)); +} + +wasm_tabletype_t* wasm_table_type(const wasm_table_t* table) { + return release_tabletype(table->type()); +} + +wasm_ref_t* wasm_table_get(const wasm_table_t* table, wasm_table_size_t index) { + return release_ref(table->get(index)); +} + +bool wasm_table_set( + wasm_table_t* table, wasm_table_size_t index, wasm_ref_t* ref +) { + return table->set(index, ref); +} + +wasm_table_size_t wasm_table_size(const wasm_table_t* table) { + return table->size(); +} + +bool wasm_table_grow( + wasm_table_t* table, wasm_table_size_t delta, wasm_ref_t* ref +) { + return table->grow(delta, ref); +} + + +// Memory Instances + +WASM_DEFINE_REF(memory, Memory) + +wasm_memory_t* wasm_memory_new( + wasm_store_t* store, const wasm_memorytype_t* type +) { + return release_memory(Memory::make(store, type)); +} + +wasm_memorytype_t* wasm_memory_type(const wasm_memory_t* memory) { + return release_memorytype(memory->type()); +} + +wasm_byte_t* wasm_memory_data(wasm_memory_t* memory) { + return memory->data(); +} + +size_t wasm_memory_data_size(const wasm_memory_t* memory) { + return memory->data_size(); +} + +wasm_memory_pages_t wasm_memory_size(const wasm_memory_t* memory) { + return memory->size(); +} + +bool wasm_memory_grow(wasm_memory_t* memory, wasm_memory_pages_t delta) { + return memory->grow(delta); +} + + +// Externals + +WASM_DEFINE_REF(extern, Extern) +WASM_DEFINE_VEC_OWN(extern, Extern) + +wasm_externkind_t wasm_extern_kind(const wasm_extern_t* external) { + return hide_externkind(external->kind()); +} +wasm_externtype_t* wasm_extern_type(const wasm_extern_t* external) { + return release_externtype(external->type()); +} + +wasm_extern_t* wasm_func_as_extern(wasm_func_t* func) { + return hide_extern(static_cast(reveal_func(func))); +} +wasm_extern_t* wasm_global_as_extern(wasm_global_t* global) { + return hide_extern(static_cast(reveal_global(global))); +} +wasm_extern_t* wasm_table_as_extern(wasm_table_t* table) { + return hide_extern(static_cast(reveal_table(table))); +} +wasm_extern_t* wasm_memory_as_extern(wasm_memory_t* memory) { + return hide_extern(static_cast(reveal_memory(memory))); +} + +const wasm_extern_t* wasm_func_as_extern_const(const wasm_func_t* func) { + return hide_extern(static_cast(reveal_func(func))); +} +const wasm_extern_t* wasm_global_as_extern_const(const wasm_global_t* global) { + return hide_extern(static_cast(reveal_global(global))); +} +const wasm_extern_t* wasm_table_as_extern_const(const wasm_table_t* table) { + return hide_extern(static_cast(reveal_table(table))); +} +const wasm_extern_t* wasm_memory_as_extern_const(const wasm_memory_t* memory) { + return hide_extern(static_cast(reveal_memory(memory))); +} + +wasm_func_t* wasm_extern_as_func(wasm_extern_t* external) { + return hide_func(external->func()); +} +wasm_global_t* wasm_extern_as_global(wasm_extern_t* external) { + return hide_global(external->global()); +} +wasm_table_t* wasm_extern_as_table(wasm_extern_t* external) { + return hide_table(external->table()); +} +wasm_memory_t* wasm_extern_as_memory(wasm_extern_t* external) { + return hide_memory(external->memory()); +} + +const wasm_func_t* wasm_extern_as_func_const(const wasm_extern_t* external) { + return hide_func(external->func()); +} +const wasm_global_t* wasm_extern_as_global_const(const wasm_extern_t* external) { + return hide_global(external->global()); +} +const wasm_table_t* wasm_extern_as_table_const(const wasm_extern_t* external) { + return hide_table(external->table()); +} +const wasm_memory_t* wasm_extern_as_memory_const(const wasm_extern_t* external) { + return hide_memory(external->memory()); +} + + +// Module Instances + +WASM_DEFINE_REF(instance, Instance) + +wasm_instance_t* wasm_instance_new( + wasm_store_t* store, + const wasm_module_t* module, + const wasm_extern_t* const imports[], + wasm_trap_t** trap +) { + own error; + auto instance = release_instance(Instance::make(store, module, + reinterpret_cast(imports), &error)); + if (trap) *trap = hide_trap(error.release()); + return instance; +} + +void wasm_instance_exports( + const wasm_instance_t* instance, wasm_extern_vec_t* out +) { + *out = release_extern_vec(instance->exports()); +} + + +wasm_instance_t* wasm_frame_instance(const wasm_frame_t* frame) { + return hide_instance(reveal_frame(frame)->instance()); +} + +} // extern "C" diff --git a/lib/c-api/tests/wasm-c-api/src/wasm-v8-lowlevel.cc b/lib/c-api/tests/wasm-c-api/src/wasm-v8-lowlevel.cc new file mode 100644 index 000000000..d00f48e19 --- /dev/null +++ b/lib/c-api/tests/wasm-c-api/src/wasm-v8-lowlevel.cc @@ -0,0 +1,443 @@ +#include "wasm-v8-lowlevel.hh" + +// TODO(v8): if we don't include these, api.h does not compile +#include "objects/objects.h" +#include "objects/bigint.h" +#include "objects/managed.h" +#include "objects/module.h" +#include "objects/shared-function-info.h" +#include "objects/templates.h" +#include "objects/fixed-array.h" +#include "objects/ordered-hash-table.h" +#include "objects/js-promise.h" +#include "objects/js-collection.h" + +#include "api/api.h" +#include "api/api-inl.h" +#include "wasm/wasm-objects.h" +#include "wasm/wasm-objects-inl.h" +#include "wasm/wasm-serialization.h" + + +namespace v8 { +namespace wasm { + + +// Objects + +auto object_isolate(v8::Local obj) -> v8::Isolate* { + auto v8_obj = v8::Utils::OpenHandle(*obj); + return reinterpret_cast(v8_obj->GetIsolate()); +} + +auto object_isolate(const v8::Persistent& obj) -> v8::Isolate* { + struct FakePersistent { v8::Object* val; }; + auto v8_obj = reinterpret_cast(&obj)->val; + return v8_obj->GetIsolate(); +} + +template +auto object_handle(T v8_obj) -> v8::internal::Handle { + return handle(v8_obj, v8_obj.GetIsolate()); +} + + +auto object_is_module(v8::Local obj) -> bool { + auto v8_obj = v8::Utils::OpenHandle(*obj); + return v8_obj->IsWasmModuleObject(); +} + +auto object_is_instance(v8::Local obj) -> bool { + auto v8_obj = v8::Utils::OpenHandle(*obj); + return v8_obj->IsWasmInstanceObject(); +} + +auto object_is_func(v8::Local obj) -> bool { + auto v8_obj = v8::Utils::OpenHandle(*obj); + return v8::internal::WasmExportedFunction::IsWasmExportedFunction(*v8_obj); +} + +auto object_is_global(v8::Local obj) -> bool { + auto v8_obj = v8::Utils::OpenHandle(*obj); + return v8_obj->IsWasmGlobalObject(); +} + +auto object_is_table(v8::Local obj) -> bool { + auto v8_obj = v8::Utils::OpenHandle(*obj); + return v8_obj->IsWasmTableObject(); +} + +auto object_is_memory(v8::Local obj) -> bool { + auto v8_obj = v8::Utils::OpenHandle(*obj); + return v8_obj->IsWasmMemoryObject(); +} + +auto object_is_error(v8::Local obj) -> bool { + auto v8_obj = v8::Utils::OpenHandle(*obj); + return v8_obj->IsJSError(); +} + + + +// Foreign pointers + +auto foreign_new(v8::Isolate* isolate, void* ptr) -> v8::Local { + auto foreign = v8::FromCData( + reinterpret_cast(isolate), + reinterpret_cast(ptr) + ); + return v8::Utils::ToLocal(foreign); +} + +auto foreign_get(v8::Local val) -> void* { + auto foreign = v8::Utils::OpenHandle(*val); + if (!foreign->IsForeign()) return nullptr; + auto addr = v8::ToCData(*foreign); + return reinterpret_cast(addr); +} + + +struct ManagedData { + ManagedData(void* info, void (*finalizer)(void*)) : + info(info), finalizer(finalizer) {} + + ~ManagedData() { + if (finalizer) (*finalizer)(info); + } + + void* info; + void (*finalizer)(void*); +}; + +auto managed_new(v8::Isolate* isolate, void* ptr, void (*finalizer)(void*)) -> v8::Local { + assert(ptr); + auto managed = v8::internal::Managed::FromUniquePtr( + reinterpret_cast(isolate), sizeof(ManagedData), + std::unique_ptr(new ManagedData(ptr, finalizer)) + ); + return v8::Utils::ToLocal(managed); +} + +auto managed_get(v8::Local val) -> void* { + auto v8_val = v8::Utils::OpenHandle(*val); + if (!v8_val->IsForeign()) return nullptr; + auto managed = + v8::internal::Handle>::cast(v8_val); + return managed->raw()->info; +} + + +// Types + +auto v8_valtype_to_wasm(v8::internal::wasm::ValueType v8_valtype) -> val_kind_t { + switch (v8_valtype) { + case v8::internal::wasm::kWasmI32: return I32; + case v8::internal::wasm::kWasmI64: return I64; + case v8::internal::wasm::kWasmF32: return F32; + case v8::internal::wasm::kWasmF64: return F64; + case v8::internal::wasm::kWasmAnyRef: return ANYREF; + case v8::internal::wasm::kWasmAnyFunc: return FUNCREF; + default: + UNREACHABLE(); + } +} + +auto func_type_param_arity(v8::Local function) -> uint32_t { + auto v8_object = v8::Utils::OpenHandle(function); + auto v8_function = v8::internal::Handle::cast(v8_object); + v8::internal::wasm::FunctionSig* sig = + v8_function->instance().module()->functions[v8_function->function_index()].sig; + return static_cast(sig->parameter_count()); +} + +auto func_type_result_arity(v8::Local function) -> uint32_t { + auto v8_object = v8::Utils::OpenHandle(function); + auto v8_function = v8::internal::Handle::cast(v8_object); + v8::internal::wasm::FunctionSig* sig = + v8_function->instance().module()->functions[v8_function->function_index()].sig; + return static_cast(sig->return_count()); +} + +auto func_type_param(v8::Local function, size_t i) -> val_kind_t { + auto v8_object = v8::Utils::OpenHandle(function); + auto v8_function = v8::internal::Handle::cast(v8_object); + v8::internal::wasm::FunctionSig* sig = + v8_function->instance().module()->functions[v8_function->function_index()].sig; + return v8_valtype_to_wasm(sig->GetParam(i)); +} + +auto func_type_result(v8::Local function, size_t i) -> val_kind_t { + auto v8_object = v8::Utils::OpenHandle(function); + auto v8_function = v8::internal::Handle::cast(v8_object); + v8::internal::wasm::FunctionSig* sig = + v8_function->instance().module()->functions[v8_function->function_index()].sig; + return v8_valtype_to_wasm(sig->GetReturn(i)); +} + +auto global_type_content(v8::Local global) -> val_kind_t { + auto v8_object = v8::Utils::OpenHandle(global); + auto v8_global = v8::internal::Handle::cast(v8_object); + return v8_valtype_to_wasm(v8_global->type()); +} + +auto global_type_mutable(v8::Local global) -> bool { + auto v8_object = v8::Utils::OpenHandle(global); + auto v8_global = v8::internal::Handle::cast(v8_object); + return v8_global->is_mutable(); +} + +auto table_type_min(v8::Local table) -> uint32_t { + auto v8_object = v8::Utils::OpenHandle(table); + auto v8_table = v8::internal::Handle::cast(v8_object); + return v8_table->current_length(); +} + +auto table_type_max(v8::Local table) -> uint32_t { + auto v8_object = v8::Utils::OpenHandle(table); + auto v8_table = v8::internal::Handle::cast(v8_object); + auto v8_max_obj = v8_table->maximum_length(); + uint32_t max; + return v8_max_obj.ToUint32(&max) ? max : 0xffffffffu; +} + +auto memory_type_min(v8::Local memory) -> uint32_t { + return memory_size(memory); +} + +auto memory_type_max(v8::Local memory) -> uint32_t { + auto v8_object = v8::Utils::OpenHandle(memory); + auto v8_memory = v8::internal::Handle::cast(v8_object); + return v8_memory->has_maximum_pages() ? v8_memory->maximum_pages() : 0xffffffffu; +} + + +// Modules + +auto module_binary_size(v8::Local module) -> size_t { + auto v8_object = v8::Utils::OpenHandle(module); + auto v8_module = v8::internal::Handle::cast(v8_object); + return v8_module->native_module()->wire_bytes().size(); +} + +auto module_binary(v8::Local module) -> const char* { + auto v8_object = v8::Utils::OpenHandle(module); + auto v8_module = v8::internal::Handle::cast(v8_object); + return reinterpret_cast(v8_module->native_module()->wire_bytes().begin()); +} + +auto module_serialize_size(v8::Local module) -> size_t { + auto v8_object = v8::Utils::OpenHandle(module); + auto v8_module = v8::internal::Handle::cast(v8_object); + v8::internal::wasm::WasmSerializer serializer(v8_module->native_module()); + return serializer.GetSerializedNativeModuleSize(); +} + +auto module_serialize(v8::Local module, char* buffer, size_t size) -> bool { + auto v8_object = v8::Utils::OpenHandle(module); + auto v8_module = v8::internal::Handle::cast(v8_object); + v8::internal::wasm::WasmSerializer serializer(v8_module->native_module()); + return serializer.SerializeNativeModule({reinterpret_cast(buffer), size}); +} + +auto module_deserialize( + v8::Isolate* isolate, + const char* binary, size_t binary_size, + const char* buffer, size_t buffer_size +) -> v8::MaybeLocal { + auto v8_isolate = reinterpret_cast(isolate); + auto maybe_v8_module = + v8::internal::wasm::DeserializeNativeModule(v8_isolate, + {reinterpret_cast(buffer), buffer_size}, + {reinterpret_cast(binary), binary_size}); + if (maybe_v8_module.is_null()) return v8::MaybeLocal(); + auto v8_module = v8::internal::Handle::cast(maybe_v8_module.ToHandleChecked()); + return v8::MaybeLocal(v8::Utils::ToLocal(v8_module)); +} + + +// Instances + +auto instance_module(v8::Local instance) -> v8::Local { + auto v8_object = v8::Utils::OpenHandle(instance); + auto v8_instance = v8::internal::Handle::cast(v8_object); + auto v8_module = object_handle(v8::internal::JSObject::cast(v8_instance->module_object())); + return v8::Utils::ToLocal(v8_module); +} + +auto instance_exports(v8::Local instance) -> v8::Local { + auto v8_object = v8::Utils::OpenHandle(instance); + auto v8_instance = v8::internal::Handle::cast(v8_object); + auto v8_exports = object_handle(v8_instance->exports_object()); + return v8::Utils::ToLocal(v8_exports); +} + + +// Externals + +auto extern_kind(v8::Local external) -> extern_kind_t { + auto v8_object = v8::Utils::OpenHandle(external); + + if (v8::internal::WasmExportedFunction::IsWasmExportedFunction(*v8_object)) return EXTERN_FUNC; + if (v8_object->IsWasmGlobalObject()) return EXTERN_GLOBAL; + if (v8_object->IsWasmTableObject()) return EXTERN_TABLE; + if (v8_object->IsWasmMemoryObject()) return EXTERN_MEMORY; + UNREACHABLE(); +} + + +// Functions + +auto func_instance(v8::Local function) -> v8::Local { + auto v8_function = v8::Utils::OpenHandle(*function); + auto v8_func = v8::internal::Handle::cast(v8_function); + auto v8_instance = object_handle(v8::internal::JSObject::cast(v8_func->instance())); + return v8::Utils::ToLocal(v8_instance); +} + + +// Globals + +auto global_get_i32(v8::Local global) -> int32_t { + auto v8_object = v8::Utils::OpenHandle(global); + auto v8_global = v8::internal::Handle::cast(v8_object); + return v8_global->GetI32(); +} +auto global_get_i64(v8::Local global) -> int64_t { + auto v8_object = v8::Utils::OpenHandle(global); + auto v8_global = v8::internal::Handle::cast(v8_object); + return v8_global->GetI64(); +} +auto global_get_f32(v8::Local global) -> float { + auto v8_object = v8::Utils::OpenHandle(global); + auto v8_global = v8::internal::Handle::cast(v8_object); + return v8_global->GetF32(); +} +auto global_get_f64(v8::Local global) -> double { + auto v8_object = v8::Utils::OpenHandle(global); + auto v8_global = v8::internal::Handle::cast(v8_object); + return v8_global->GetF64(); +} +auto global_get_ref(v8::Local global) -> v8::Local { + auto v8_object = v8::Utils::OpenHandle(global); + auto v8_global = v8::internal::Handle::cast(v8_object); + return v8::Utils::ToLocal(v8_global->GetRef()); +} + +void global_set_i32(v8::Local global, int32_t val) { + auto v8_object = v8::Utils::OpenHandle(global); + auto v8_global = v8::internal::Handle::cast(v8_object); + v8_global->SetI32(val); +} +void global_set_i64(v8::Local global, int64_t val) { + auto v8_object = v8::Utils::OpenHandle(global); + auto v8_global = v8::internal::Handle::cast(v8_object); + v8_global->SetI64(val); +} +void global_set_f32(v8::Local global, float val) { + auto v8_object = v8::Utils::OpenHandle(global); + auto v8_global = v8::internal::Handle::cast(v8_object); + v8_global->SetF32(val); +} +void global_set_f64(v8::Local global, double val) { + auto v8_object = v8::Utils::OpenHandle(global); + auto v8_global = v8::internal::Handle::cast(v8_object); + v8_global->SetF64(val); +} +void global_set_ref(v8::Local global, v8::Local val) { + auto v8_object = v8::Utils::OpenHandle(global); + auto v8_global = v8::internal::Handle::cast(v8_object); + v8_global->SetAnyRef(v8::Utils::OpenHandle(val)); +} + + +// Tables + +auto table_get(v8::Local table, size_t index) -> v8::MaybeLocal { + auto v8_object = v8::Utils::OpenHandle(table); + auto v8_table = v8::internal::Handle::cast(v8_object); + // TODO(v8): This should happen in WasmTableObject::Get. + if (index > v8_table->current_length()) return v8::MaybeLocal(); + + v8::internal::Handle v8_value = + v8::internal::WasmTableObject::Get( + v8_table->GetIsolate(), v8_table, static_cast(index)); + return v8::Utils::ToLocal(v8::internal::Handle::cast(v8_value)); +} + +auto table_set( + v8::Local table, size_t index, v8::Local value +) -> bool { + auto v8_object = v8::Utils::OpenHandle(table); + auto v8_table = v8::internal::Handle::cast(v8_object); + auto v8_value = v8::Utils::OpenHandle(value); + // TODO(v8): This should happen in WasmTableObject::Set. + if (index >= v8_table->current_length()) return false; + + { v8::TryCatch handler(table->GetIsolate()); + v8::internal::WasmTableObject::Set(v8_table->GetIsolate(), v8_table, + static_cast(index), v8_value); + if (handler.HasCaught()) return false; + } + + return true; +} + +auto table_size(v8::Local table) -> size_t { + auto v8_object = v8::Utils::OpenHandle(table); + auto v8_table = v8::internal::Handle::cast(v8_object); + return v8_table->current_length(); +} + +auto table_grow( + v8::Local table, size_t delta, v8::Local init +) -> bool { + auto v8_object = v8::Utils::OpenHandle(table); + auto v8_table = v8::internal::Handle::cast(v8_object); + if (delta > 0xfffffffflu) return false; + auto old_size = v8_table->current_length(); + auto new_size = old_size + static_cast(delta); + // TODO(v8): This should happen in WasmTableObject::Grow. + if (new_size > table_type_max(table)) return false; + + { v8::TryCatch handler(table->GetIsolate()); + v8::internal::WasmTableObject::Grow( + v8_table->GetIsolate(), v8_table, static_cast(delta), + v8::Utils::OpenHandle(init)); + if (handler.HasCaught()) return false; + } + + return true; +} + + +// Memory + +auto memory_data(v8::Local memory) -> char* { + auto v8_object = v8::Utils::OpenHandle(memory); + auto v8_memory = v8::internal::Handle::cast(v8_object); + return reinterpret_cast(v8_memory->array_buffer().backing_store()); +} + +auto memory_data_size(v8::Local memory)-> size_t { + auto v8_object = v8::Utils::OpenHandle(memory); + auto v8_memory = v8::internal::Handle::cast(v8_object); + return v8_memory->array_buffer().byte_length(); +} + +auto memory_size(v8::Local memory) -> uint32_t { + auto v8_object = v8::Utils::OpenHandle(memory); + auto v8_memory = v8::internal::Handle::cast(v8_object); + return static_cast( + v8_memory->array_buffer().byte_length() / v8::internal::wasm::kWasmPageSize); +} + +auto memory_grow(v8::Local memory, uint32_t delta) -> bool { + auto v8_object = v8::Utils::OpenHandle(memory); + auto v8_memory = v8::internal::Handle::cast(v8_object); + auto old = v8::internal::WasmMemoryObject::Grow( + v8_memory->GetIsolate(), v8_memory, delta); + return old != -1; +} + +} // namespace wasm +} // namespace v8 diff --git a/lib/c-api/tests/wasm-c-api/src/wasm-v8-lowlevel.hh b/lib/c-api/tests/wasm-c-api/src/wasm-v8-lowlevel.hh new file mode 100644 index 000000000..5b2969fe7 --- /dev/null +++ b/lib/c-api/tests/wasm-c-api/src/wasm-v8-lowlevel.hh @@ -0,0 +1,79 @@ +#ifndef __WASM_V8_LOWLEVEL_HH +#define __WASM_V8_LOWLEVEL_HH + +#include "v8.h" + +namespace v8 { +namespace wasm { + +auto object_isolate(v8::Local) -> v8::Isolate*; +auto object_isolate(const v8::Persistent&) -> v8::Isolate*; + +auto object_is_module(v8::Local) -> bool; +auto object_is_instance(v8::Local) -> bool; +auto object_is_func(v8::Local) -> bool; +auto object_is_global(v8::Local) -> bool; +auto object_is_table(v8::Local) -> bool; +auto object_is_memory(v8::Local) -> bool; +auto object_is_error(v8::Local) -> bool; + +auto foreign_new(v8::Isolate*, void*) -> v8::Local; +auto foreign_get(v8::Local) -> void*; + +auto managed_new(v8::Isolate*, void*, void (*)(void*)) -> v8::Local; +auto managed_get(v8::Local) -> void*; + +enum val_kind_t { I32, I64, F32, F64, ANYREF = 128, FUNCREF }; +auto func_type_param_arity(v8::Local global) -> uint32_t; +auto func_type_result_arity(v8::Local global) -> uint32_t; +auto func_type_param(v8::Local global, size_t) -> val_kind_t; +auto func_type_result(v8::Local global, size_t) -> val_kind_t; + +auto global_type_content(v8::Local global) -> val_kind_t; +auto global_type_mutable(v8::Local global) -> bool; + +auto table_type_min(v8::Local table) -> uint32_t; +auto table_type_max(v8::Local table) -> uint32_t; + +auto memory_type_min(v8::Local memory) -> uint32_t; +auto memory_type_max(v8::Local memory) -> uint32_t; + +auto module_binary_size(v8::Local module) -> size_t; +auto module_binary(v8::Local module) -> const char*; +auto module_serialize_size(v8::Local module) -> size_t; +auto module_serialize(v8::Local module, char*, size_t) -> bool; +auto module_deserialize(v8::Isolate*, const char*, size_t, const char*, size_t) -> v8::MaybeLocal; + +auto instance_module(v8::Local instance) -> v8::Local; +auto instance_exports(v8::Local instance) -> v8::Local; + +enum extern_kind_t { EXTERN_FUNC, EXTERN_GLOBAL, EXTERN_TABLE, EXTERN_MEMORY }; +auto extern_kind(v8::Local external) -> extern_kind_t; + +auto func_instance(v8::Local) -> v8::Local; + +auto global_get_i32(v8::Local global) -> int32_t; +auto global_get_i64(v8::Local global) -> int64_t; +auto global_get_f32(v8::Local global) -> float; +auto global_get_f64(v8::Local global) -> double; +auto global_get_ref(v8::Local global) -> v8::Local; +void global_set_i32(v8::Local global, int32_t); +void global_set_i64(v8::Local global, int64_t); +void global_set_f32(v8::Local global, float); +void global_set_f64(v8::Local global, double); +void global_set_ref(v8::Local global, v8::Local); + +auto table_get(v8::Local table, size_t index) -> v8::MaybeLocal; +auto table_set(v8::Local table, size_t index, v8::Local) -> bool; +auto table_size(v8::Local table) -> size_t; +auto table_grow(v8::Local table, size_t delta, v8::Local) -> bool; + +auto memory_data(v8::Local memory) -> char*; +auto memory_data_size(v8::Local memory)-> size_t; +auto memory_size(v8::Local memory) -> uint32_t; +auto memory_grow(v8::Local memory, uint32_t delta) -> bool; + +} // namespace wasm +} // namespace v8 + +#endif // #define __WASM_V8_LOWLEVEL_HH diff --git a/lib/c-api/tests/wasm-c-api/src/wasm-v8.cc b/lib/c-api/tests/wasm-c-api/src/wasm-v8.cc new file mode 100644 index 000000000..75b9e6644 --- /dev/null +++ b/lib/c-api/tests/wasm-c-api/src/wasm-v8.cc @@ -0,0 +1,2136 @@ +#include "wasm.hh" +#include "wasm-bin.hh" +#include "wasm-v8-lowlevel.hh" + +#include "v8.h" +#include "libplatform/libplatform.h" + +#include + +#ifdef WASM_API_DEBUG +#include +#endif + + +namespace wasm_v8 { + using namespace v8::wasm; +} + +namespace v8 { + namespace internal { + extern bool FLAG_expose_gc; + extern bool FLAG_experimental_wasm_bigint; + extern bool FLAG_experimental_wasm_mv; + extern bool FLAG_experimental_wasm_anyref; + extern bool FLAG_experimental_wasm_bulk_memory; + extern bool FLAG_experimental_wasm_return_call; + } +} + +namespace wasm { + +/////////////////////////////////////////////////////////////////////////////// +// Auxiliaries + +[[noreturn]] void UNIMPLEMENTED(const char* s) { + std::cerr << "Wasm API: " << s << " not supported yet!\n"; + exit(1); +} + +template +void ignore(T) {} + + +template struct implement; + +template +auto impl(C* x) -> typename implement ::type* { + return reinterpret_cast::type*>(x); +} + +template +auto impl(const C* x) -> const typename implement::type* { + return reinterpret_cast::type*>(x); +} + +template +auto seal(typename implement ::type* x) -> C* { + return reinterpret_cast(x); +} + +template +auto seal(const typename implement ::type* x) -> const C* { + return reinterpret_cast(x); +} + + +/////////////////////////////////////////////////////////////////////////////// +// Debug aids + +struct Stats { + enum category_t { + BYTE, CONFIG, ENGINE, STORE, FRAME, + VALTYPE, FUNCTYPE, GLOBALTYPE, TABLETYPE, MEMORYTYPE, + EXTERNTYPE, IMPORTTYPE, EXPORTTYPE, + VAL, REF, TRAP, + MODULE, INSTANCE, FUNC, GLOBAL, TABLE, MEMORY, EXTERN, + STRONG_COUNT, + FUNCDATA_FUNCTYPE, FUNCDATA_VALTYPE, + CATEGORY_COUNT + }; + enum cardinality_t { + OWN, VEC, SHARED, CARDINALITY_COUNT + }; + +#ifdef WASM_API_DEBUG + static const char* name[STRONG_COUNT]; + static const char* left[CARDINALITY_COUNT]; + static const char* right[CARDINALITY_COUNT]; + + std::atomic made[CATEGORY_COUNT][CARDINALITY_COUNT]; + std::atomic freed[CATEGORY_COUNT][CARDINALITY_COUNT]; + + Stats() { + for (int i = 0; i < CATEGORY_COUNT; ++i) { + for (int j = 0; j < CARDINALITY_COUNT; ++j) { + made[i][j] = freed[i][j] = 0; + } + } + } + + ~Stats() { + // Hack for func data weakly owned by V8 heap. + freed[FUNCTYPE][OWN] += + made[FUNCDATA_FUNCTYPE][OWN] - freed[FUNCDATA_FUNCTYPE][OWN]; + freed[VALTYPE][OWN] += + made[FUNCDATA_VALTYPE][OWN] - freed[FUNCDATA_VALTYPE][OWN]; + freed[VALTYPE][VEC] += + made[FUNCDATA_VALTYPE][VEC] - freed[FUNCDATA_VALTYPE][VEC]; + // Hack for shared modules. + freed[BYTE][VEC] += made[MODULE][SHARED] - freed[MODULE][SHARED]; + + bool leak = false; + for (int i = 0; i < STRONG_COUNT; ++i) { + for (int j = 0; j < CARDINALITY_COUNT; ++j) { + assert(made[i][j] >= freed[i][j]); + auto live = made[i][j] - freed[i][j]; + if (live) { + std::cerr << "Leaked " << live << " instances of wasm::" + << left[j] << name[i] << right[j] + << ", made " << made[i][j] << ", freed " << freed[i][j] << "!" + << std::endl; + leak = true; + } + } + } + if (leak) exit(1); + } +#endif + + void make(category_t i, void* ptr, cardinality_t j = OWN, size_t n = 1) { +#ifdef WASM_API_DEBUG +#ifdef WASM_API_DEBUG_LOG + if (ptr) { + std::clog << "[make] " << ptr + << " wasm::" << left[j] << name[i] << right[j] << std::endl; + } +#endif + made[i][j] += n; +#endif + } + + void free(category_t i, void* ptr, cardinality_t j = OWN, size_t n = 1) { +#ifdef WASM_API_DEBUG +#ifdef WASM_API_DEBUG_LOG + if (ptr) { + std::clog << "[free] " << ptr + << " wasm::" << left[j] << name[i] << right[j] << std::endl; + } +#endif + freed[i][j] += n; + if (freed[i][j] > made[i][j]) { + std::cerr << "Deleting instance of wasm::" + << left[j] << name[i] << right[j] << " when none is alive" + << ", made " << made[i][j] << ", freed " << freed[i][j] << "!" + << std::endl; + exit(1); + } +#endif + } + + static category_t categorize(const v8::Persistent& pobj) { +#ifdef WASM_API_DEBUG + auto isolate = wasm_v8::object_isolate(pobj); + v8::HandleScope handle_scope(isolate); + auto obj = pobj.Get(isolate); + if (wasm_v8::object_is_func(obj)) return FUNC; + if (wasm_v8::object_is_global(obj)) return GLOBAL; + if (wasm_v8::object_is_table(obj)) return TABLE; + if (wasm_v8::object_is_memory(obj)) return MEMORY; + if (wasm_v8::object_is_module(obj)) return MODULE; + if (wasm_v8::object_is_instance(obj)) return INSTANCE; + if (wasm_v8::object_is_error(obj)) return TRAP; +#endif + return REF; + } +}; + +#ifdef WASM_API_DEBUG +const char* Stats::name[STRONG_COUNT] = { + "byte_t", "Config", "Engine", "Store", "Frame", + "ValType", "FuncType", "GlobalType", "TableType", "MemoryType", + "ExternType", "ImportType", "ExportType", + "Val", "Ref", "Trap", + "Module", "Instance", "Func", "Global", "Table", "Memory", "Extern" +}; + +const char* Stats::left[CARDINALITY_COUNT] = { + "", "vec<", "Shared<" +}; + +const char* Stats::right[CARDINALITY_COUNT] = { + "", ">", ">" +}; +#endif + + +Stats stats; + + +// Vectors + +#ifdef WASM_API_DEBUG + +#define DEFINE_VEC(type, vec, STAT) \ + template<> void vec::make_data() { \ + if (data_) stats.make(Stats::STAT, data_.get(), Stats::VEC); \ + } \ + \ + template<> void vec::free_data() { \ + if (data_) stats.free(Stats::STAT, data_.get(), Stats::VEC); \ + } + +DEFINE_VEC(byte_t, vec, BYTE) +DEFINE_VEC(Frame, ownvec, FRAME) +DEFINE_VEC(ValType, ownvec, VALTYPE) +DEFINE_VEC(FuncType, ownvec, FUNCTYPE) +DEFINE_VEC(GlobalType, ownvec, GLOBALTYPE) +DEFINE_VEC(TableType, ownvec, TABLETYPE) +DEFINE_VEC(MemoryType, ownvec, MEMORYTYPE) +DEFINE_VEC(ExternType, ownvec, EXTERNTYPE) +DEFINE_VEC(ImportType, ownvec, IMPORTTYPE) +DEFINE_VEC(ExportType, ownvec, EXPORTTYPE) +DEFINE_VEC(Ref, ownvec, REF) +DEFINE_VEC(Trap, ownvec, TRAP) +DEFINE_VEC(Module, ownvec, MODULE) +DEFINE_VEC(Instance, ownvec, INSTANCE) +DEFINE_VEC(Func, ownvec, FUNC) +DEFINE_VEC(Global, ownvec, GLOBAL) +DEFINE_VEC(Table, ownvec, TABLE) +DEFINE_VEC(Memory, ownvec, MEMORY) +DEFINE_VEC(Extern, ownvec, EXTERN) +DEFINE_VEC(Val, vec, VAL) + +#endif // #ifdef WASM_API_DEBUG + + +/////////////////////////////////////////////////////////////////////////////// +// Runtime Environment + +// Configuration + +struct ConfigImpl { + ConfigImpl() { stats.make(Stats::CONFIG, this); } + ~ConfigImpl() { stats.free(Stats::CONFIG, this); } +}; + +template<> struct implement { using type = ConfigImpl; }; + + +Config::~Config() { + impl(this)->~ConfigImpl(); +} + +void Config::operator delete(void *p) { + ::operator delete(p); +} + +auto Config::make() -> own { + return own(seal(new(std::nothrow) ConfigImpl())); +} + + +// Engine + +struct EngineImpl { + static bool created; + + std::unique_ptr platform; + + EngineImpl() { + assert(!created); + created = true; + stats.make(Stats::ENGINE, this); + } + + ~EngineImpl() { + v8::V8::Dispose(); + v8::V8::ShutdownPlatform(); + stats.free(Stats::ENGINE, this); + } +}; + +bool EngineImpl::created = false; + +template<> struct implement { using type = EngineImpl; }; + + +Engine::~Engine() { + impl(this)->~EngineImpl(); +} + +void Engine::operator delete(void *p) { + ::operator delete(p); +} + +auto Engine::make(own&& config) -> own { + v8::internal::FLAG_expose_gc = true; + v8::internal::FLAG_experimental_wasm_bigint = true; + v8::internal::FLAG_experimental_wasm_mv = true; + v8::internal::FLAG_experimental_wasm_anyref = true; + v8::internal::FLAG_experimental_wasm_bulk_memory = true; + v8::internal::FLAG_experimental_wasm_return_call = true; + // v8::V8::SetFlagsFromCommandLine(&argc, const_cast(argv), false); + auto engine = new(std::nothrow) EngineImpl; + if (!engine) return own(); + // v8::V8::InitializeICUDefaultLocation(argv[0]); + // v8::V8::InitializeExternalStartupData(argv[0]); + engine->platform = v8::platform::NewDefaultPlatform(); + v8::V8::InitializePlatform(engine->platform.get()); + v8::V8::Initialize(); + return make_own(seal(engine)); +} + + +// Stores + +enum v8_string_t { + V8_S_EMPTY, + V8_S_I32, V8_S_I64, V8_S_F32, V8_S_F64, V8_S_ANYREF, V8_S_ANYFUNC, + V8_S_VALUE, V8_S_MUTABLE, V8_S_ELEMENT, V8_S_MINIMUM, V8_S_MAXIMUM, + V8_S_COUNT +}; + +enum v8_symbol_t { + V8_Y_CALLBACK, V8_Y_ENV, + V8_Y_COUNT +}; + +enum v8_function_t { + V8_F_WEAKMAP, V8_F_WEAKMAP_PROTO, V8_F_WEAKMAP_GET, V8_F_WEAKMAP_SET, + V8_F_MODULE, V8_F_GLOBAL, V8_F_TABLE, V8_F_MEMORY, + V8_F_INSTANCE, V8_F_VALIDATE, + V8_F_COUNT, +}; + +class StoreImpl { + friend own Store::make(Engine*); + + v8::Isolate::CreateParams create_params_; + v8::Isolate *isolate_; + v8::Eternal context_; + v8::Eternal strings_[V8_S_COUNT]; + v8::Eternal symbols_[V8_Y_COUNT]; + v8::Eternal functions_[V8_F_COUNT]; + v8::Eternal host_data_map_; + v8::Eternal callback_symbol_; + v8::Persistent* handle_pool_ = nullptr; // TODO: use v8::Value + +public: + StoreImpl() { + stats.make(Stats::STORE, this); + } + + ~StoreImpl() { +#ifdef WASM_API_DEBUG + isolate_->RequestGarbageCollectionForTesting( + v8::Isolate::kFullGarbageCollection); +#endif + { + v8::HandleScope scope(isolate_); + while (handle_pool_ != nullptr) { + auto handle = handle_pool_; + handle_pool_ = reinterpret_cast*>( + wasm_v8::foreign_get(handle->Get(isolate_))); + delete handle; + } + } + context()->Exit(); + isolate_->Exit(); + isolate_->Dispose(); + delete create_params_.array_buffer_allocator; + stats.free(Stats::STORE, this); + } + + auto isolate() const -> v8::Isolate* { + return isolate_; + } + + auto context() const -> v8::Local { + return context_.Get(isolate_); + } + + auto v8_string(v8_string_t i) const -> v8::Local { + return strings_[i].Get(isolate_); + } + auto v8_string(v8_symbol_t i) const -> v8::Local { + return symbols_[i].Get(isolate_); + } + auto v8_function(v8_function_t i) const -> v8::Local { + return functions_[i].Get(isolate_); + } + + auto host_data_map() const -> v8::Local { + return host_data_map_.Get(isolate_); + } + + static auto get(v8::Isolate* isolate) -> StoreImpl* { + return static_cast(isolate->GetData(0)); + } + + auto make_handle() -> v8::Persistent* { + if (handle_pool_ == nullptr) { + static const size_t n = 100; + for (size_t i = 0; i < n; ++i) { + auto v8_next = wasm_v8::foreign_new(isolate_, handle_pool_); + handle_pool_ = new(std::nothrow) v8::Persistent(); + if (!handle_pool_) return nullptr; + handle_pool_->Reset(isolate_, v8::Local::Cast(v8_next)); + } + } + auto handle = handle_pool_; + handle_pool_ = reinterpret_cast*>( + wasm_v8::foreign_get(handle->Get(isolate_))); + return handle; + } + + void free_handle(v8::Persistent* handle) { + // TODO: shrink pool? + auto next = wasm_v8::foreign_new(isolate_, handle_pool_); + handle->Reset(isolate_, v8::Local::Cast(next)); + handle_pool_ = handle; + } +}; + +template<> struct implement { using type = StoreImpl; }; + + +Store::~Store() { + impl(this)->~StoreImpl(); +} + +void Store::operator delete(void *p) { + ::operator delete(p); +} + +auto Store::make(Engine*) -> own { + auto store = make_own(new(std::nothrow) StoreImpl()); + if (!store) return own(); + + // Create isolate. + store->create_params_.array_buffer_allocator = + v8::ArrayBuffer::Allocator::NewDefaultAllocator(); + auto isolate = v8::Isolate::New(store->create_params_); + if (!isolate) return own(); + + { + v8::Isolate::Scope isolate_scope(isolate); + v8::HandleScope handle_scope(isolate); + + // Create context. + auto context = v8::Context::New(isolate); + if (context.IsEmpty()) return own(); + v8::Context::Scope context_scope(context); + + store->isolate_ = isolate; + store->context_ = v8::Eternal(isolate, context); + + // Create strings. + static const char* const raw_strings[V8_S_COUNT] = { + "", + "i32", "i64", "f32", "f64", "anyref", "anyfunc", + "value", "mutable", "element", "initial", "maximum", + }; + for (int i = 0; i < V8_S_COUNT; ++i) { + auto maybe = v8::String::NewFromUtf8(isolate, raw_strings[i], + v8::NewStringType::kNormal); + if (maybe.IsEmpty()) return own(); + auto string = maybe.ToLocalChecked(); + store->strings_[i] = v8::Eternal(isolate, string); + } + + for (int i = 0; i < V8_Y_COUNT; ++i) { + auto symbol = v8::Symbol::New(isolate); + store->symbols_[i] = v8::Eternal(isolate, symbol); + } + + // Extract functions. + auto global = context->Global(); + auto maybe_wasm_name = v8::String::NewFromUtf8(isolate, "WebAssembly", + v8::NewStringType::kNormal); + if (maybe_wasm_name.IsEmpty()) return own(); + auto wasm_name = maybe_wasm_name.ToLocalChecked(); + auto maybe_wasm = global->Get(context, wasm_name); + if (maybe_wasm.IsEmpty()) return own(); + auto wasm = v8::Local::Cast(maybe_wasm.ToLocalChecked()); + v8::Local weakmap; + v8::Local weakmap_proto; + + struct { + const char* name; + v8::Local* carrier; + } raw_functions[V8_F_COUNT] = { + {"WeakMap", &global}, {"prototype", &weakmap}, + {"get", &weakmap_proto}, {"set", &weakmap_proto}, + {"Module", &wasm}, {"Global", &wasm}, {"Table", &wasm}, {"Memory", &wasm}, + {"Instance", &wasm}, {"validate", &wasm}, + }; + for (int i = 0; i < V8_F_COUNT; ++i) { + auto maybe_name = v8::String::NewFromUtf8(isolate, raw_functions[i].name, + v8::NewStringType::kNormal); + if (maybe_name.IsEmpty()) return own(); + auto name = maybe_name.ToLocalChecked(); + assert(!raw_functions[i].carrier->IsEmpty()); + // TODO(wasm+): remove + if ((*raw_functions[i].carrier)->IsUndefined()) continue; + auto maybe_obj = (*raw_functions[i].carrier)->Get(context, name); + if (maybe_obj.IsEmpty()) return own(); + auto obj = v8::Local::Cast(maybe_obj.ToLocalChecked()); + if (i == V8_F_WEAKMAP_PROTO) { + assert(obj->IsObject()); + weakmap_proto = obj; + } else { + assert(obj->IsFunction()); + auto function = v8::Local::Cast(obj); + store->functions_[i] = v8::Eternal(isolate, function); + if (i == V8_F_WEAKMAP) weakmap = function; + } + } + + // Create host data weak map. + v8::Local empty_args[] = {}; + auto maybe_weakmap = + store->v8_function(V8_F_WEAKMAP)->NewInstance(context, 0, empty_args); + if (maybe_weakmap.IsEmpty()) return own(); + auto map = v8::Local::Cast(maybe_weakmap.ToLocalChecked()); + assert(map->IsWeakMap()); + store->host_data_map_ = v8::Eternal(isolate, map); + } + + store->isolate()->Enter(); + store->context()->Enter(); + isolate->SetData(0, store.get()); + + return make_own(seal(store.release())); +}; + + +/////////////////////////////////////////////////////////////////////////////// +// Type Representations + +// Value Types + +struct ValTypeImpl { + ValKind kind; + + ValTypeImpl(ValKind kind) : kind(kind) {} +}; + +template<> struct implement { using type = ValTypeImpl; }; + +ValTypeImpl* valtype_i32 = new ValTypeImpl(ValKind::I32); +ValTypeImpl* valtype_i64 = new ValTypeImpl(ValKind::I64); +ValTypeImpl* valtype_f32 = new ValTypeImpl(ValKind::F32); +ValTypeImpl* valtype_f64 = new ValTypeImpl(ValKind::F64); +ValTypeImpl* valtype_anyref = new ValTypeImpl(ValKind::ANYREF); +ValTypeImpl* valtype_funcref = new ValTypeImpl(ValKind::FUNCREF); + + +ValType::~ValType() { + stats.free(Stats::VALTYPE, this); +} + +void ValType::operator delete(void*) {} + +auto ValType::make(ValKind k) -> own { + ValTypeImpl* valtype; + switch (k) { + case ValKind::I32: valtype = valtype_i32; break; + case ValKind::I64: valtype = valtype_i64; break; + case ValKind::F32: valtype = valtype_f32; break; + case ValKind::F64: valtype = valtype_f64; break; + case ValKind::ANYREF: valtype = valtype_anyref; break; + case ValKind::FUNCREF: valtype = valtype_funcref; break; + default: + // TODO(wasm+): support new value types + assert(false); + }; + auto result = seal(valtype); + stats.make(Stats::VALTYPE, result); + return own(result); +} + +auto ValType::copy() const -> own { + return make(kind()); +} + +auto ValType::kind() const -> ValKind { + return impl(this)->kind; +} + + +// Extern Types + +struct ExternTypeImpl { + ExternKind kind; + + explicit ExternTypeImpl(ExternKind kind) : kind(kind) {} + virtual ~ExternTypeImpl() {} +}; + +template<> struct implement { using type = ExternTypeImpl; }; + + +ExternType::~ExternType() { + impl(this)->~ExternTypeImpl(); +} + +void ExternType::operator delete(void *p) { + ::operator delete(p); +} + +auto ExternType::copy() const -> own { + switch (kind()) { + case ExternKind::FUNC: return func()->copy(); + case ExternKind::GLOBAL: return global()->copy(); + case ExternKind::TABLE: return table()->copy(); + case ExternKind::MEMORY: return memory()->copy(); + } +} + +auto ExternType::kind() const -> ExternKind { + return impl(this)->kind; +} + + +// Function Types + +struct FuncTypeImpl : ExternTypeImpl { + ownvec params; + ownvec results; + + FuncTypeImpl(ownvec& params, ownvec& results) : + ExternTypeImpl(ExternKind::FUNC), + params(std::move(params)), results(std::move(results)) + { + stats.make(Stats::FUNCTYPE, this); + } + + ~FuncTypeImpl() { + stats.free(Stats::FUNCTYPE, this); + } +}; + +template<> struct implement { using type = FuncTypeImpl; }; + + +FuncType::~FuncType() {} + +auto FuncType::make(ownvec&& params, ownvec&& results) + -> own { + return params && results + ? own( + seal(new(std::nothrow) FuncTypeImpl(params, results))) + : own(); +} + +auto FuncType::copy() const -> own { + return make(params().deep_copy(), results().deep_copy()); +} + +auto FuncType::params() const -> const ownvec& { + return impl(this)->params; +} + +auto FuncType::results() const -> const ownvec& { + return impl(this)->results; +} + + +auto ExternType::func() -> FuncType* { + return kind() == ExternKind::FUNC + ? seal(static_cast(impl(this))) + : nullptr; +} + +auto ExternType::func() const -> const FuncType* { + return kind() == ExternKind::FUNC + ? seal(static_cast(impl(this))) + : nullptr; +} + + +// Global Types + +struct GlobalTypeImpl : ExternTypeImpl { + own content; + Mutability mutability; + + GlobalTypeImpl(own& content, Mutability mutability) : + ExternTypeImpl(ExternKind::GLOBAL), + content(std::move(content)), mutability(mutability) + { + stats.make(Stats::GLOBALTYPE, this); + } + + ~GlobalTypeImpl() { + stats.free(Stats::GLOBALTYPE, this); + } +}; + +template<> struct implement { using type = GlobalTypeImpl; }; + + +GlobalType::~GlobalType() {} + +auto GlobalType::make( + own&& content, Mutability mutability +) -> own { + return content + ? own( + seal(new(std::nothrow) GlobalTypeImpl(content, mutability))) + : own(); +} + +auto GlobalType::copy() const -> own { + return make(content()->copy(), mutability()); +} + +auto GlobalType::content() const -> const ValType* { + return impl(this)->content.get(); +} + +auto GlobalType::mutability() const -> Mutability { + return impl(this)->mutability; +} + + +auto ExternType::global() -> GlobalType* { + return kind() == ExternKind::GLOBAL + ? seal(static_cast(impl(this))) + : nullptr; +} + +auto ExternType::global() const -> const GlobalType* { + return kind() == ExternKind::GLOBAL + ? seal(static_cast(impl(this))) + : nullptr; +} + + +// Table Types + +struct TableTypeImpl : ExternTypeImpl { + own element; + Limits limits; + + TableTypeImpl(own& element, Limits limits) : + ExternTypeImpl(ExternKind::TABLE), element(std::move(element)), limits(limits) + { + stats.make(Stats::TABLETYPE, this); + } + + ~TableTypeImpl() { + stats.free(Stats::TABLETYPE, this); + } +}; + +template<> struct implement { using type = TableTypeImpl; }; + + +TableType::~TableType() {} + +auto TableType::make(own&& element, Limits limits) -> own { + return element + ? own( + seal(new(std::nothrow) TableTypeImpl(element, limits))) + : own(); +} + +auto TableType::copy() const -> own { + return make(element()->copy(), limits()); +} + +auto TableType::element() const -> const ValType* { + return impl(this)->element.get(); +} + +auto TableType::limits() const -> const Limits& { + return impl(this)->limits; +} + + +auto ExternType::table() -> TableType* { + return kind() == ExternKind::TABLE + ? seal(static_cast(impl(this))) + : nullptr; +} + +auto ExternType::table() const -> const TableType* { + return kind() == ExternKind::TABLE + ? seal(static_cast(impl(this))) + : nullptr; +} + + +// Memory Types + +struct MemoryTypeImpl : ExternTypeImpl { + Limits limits; + + MemoryTypeImpl(Limits limits) : + ExternTypeImpl(ExternKind::MEMORY), limits(limits) + { + stats.make(Stats::MEMORYTYPE, this); + } + + ~MemoryTypeImpl() { + stats.free(Stats::MEMORYTYPE, this); + } +}; + +template<> struct implement { using type = MemoryTypeImpl; }; + + +MemoryType::~MemoryType() {} + +auto MemoryType::make(Limits limits) -> own { + return own( + seal(new(std::nothrow) MemoryTypeImpl(limits))); +} + +auto MemoryType::copy() const -> own { + return MemoryType::make(limits()); +} + +auto MemoryType::limits() const -> const Limits& { + return impl(this)->limits; +} + + +auto ExternType::memory() -> MemoryType* { + return kind() == ExternKind::MEMORY + ? seal(static_cast(impl(this))) + : nullptr; +} + +auto ExternType::memory() const -> const MemoryType* { + return kind() == ExternKind::MEMORY + ? seal(static_cast(impl(this))) + : nullptr; +} + + +// Import Types + +struct ImportTypeImpl { + Name module; + Name name; + own type; + + ImportTypeImpl(Name& module, Name& name, own& type) : + module(std::move(module)), name(std::move(name)), type(std::move(type)) + { + stats.make(Stats::IMPORTTYPE, this); + } + + ~ImportTypeImpl() { + stats.free(Stats::IMPORTTYPE, this); + } +}; + +template<> struct implement { using type = ImportTypeImpl; }; + + +ImportType::~ImportType() { + impl(this)->~ImportTypeImpl(); +} + +void ImportType::operator delete(void *p) { + ::operator delete(p); +} + +auto ImportType::make( + Name&& module, Name&& name, own&& type +) -> own { + return module && name && type + ? own( + seal(new(std::nothrow) ImportTypeImpl(module, name, type))) + : own(); +} + +auto ImportType::copy() const -> own { + return make(module().copy(), name().copy(), type()->copy()); +} + +auto ImportType::module() const -> const Name& { + return impl(this)->module; +} + +auto ImportType::name() const -> const Name& { + return impl(this)->name; +} + +auto ImportType::type() const -> const ExternType* { + return impl(this)->type.get(); +} + + +// Export Types + +struct ExportTypeImpl { + Name name; + own type; + + ExportTypeImpl(Name& name, own& type) : + name(std::move(name)), type(std::move(type)) + { + stats.make(Stats::EXPORTTYPE, this); + } + + ~ExportTypeImpl() { + stats.free(Stats::EXPORTTYPE, this); + } +}; + +template<> struct implement { using type = ExportTypeImpl; }; + + +ExportType::~ExportType() { + impl(this)->~ExportTypeImpl(); +} + +void ExportType::operator delete(void *p) { + ::operator delete(p); +} + +auto ExportType::make( + Name&& name, own&& type +) -> own { + return name && type + ? own( + seal(new(std::nothrow) ExportTypeImpl(name, type))) + : own(); +} + +auto ExportType::copy() const -> own { + return make(name().copy(), type()->copy()); +} + +auto ExportType::name() const -> const Name& { + return impl(this)->name; +} + +auto ExportType::type() const -> const ExternType* { + return impl(this)->type.get(); +} + + +/////////////////////////////////////////////////////////////////////////////// +// Conversions of types from and to V8 objects + +// Types + +auto valtype_to_v8( + StoreImpl* store, const ValType* type +) -> v8::Local { + v8_string_t string; + switch (type->kind()) { + case ValKind::I32: string = V8_S_I32; break; + case ValKind::I64: string = V8_S_I64; break; + case ValKind::F32: string = V8_S_F32; break; + case ValKind::F64: string = V8_S_F64; break; + case ValKind::ANYREF: string = V8_S_ANYREF; break; + case ValKind::FUNCREF: string = V8_S_ANYFUNC; break; + default: + // TODO(wasm+): support new value types + assert(false); + } + return store->v8_string(string); +} + +auto mutability_to_v8( + StoreImpl* store, Mutability mutability +) -> v8::Local { + return v8::Boolean::New(store->isolate(), mutability == Mutability::VAR); +} + +void limits_to_v8(StoreImpl* store, Limits limits, v8::Local desc) { + auto isolate = store->isolate(); + auto context = store->context(); + ignore(desc->DefineOwnProperty(context, store->v8_string(V8_S_MINIMUM), + v8::Integer::NewFromUnsigned(isolate, limits.min))); + if (limits.max != Limits(0).max) { + ignore(desc->DefineOwnProperty(context, store->v8_string(V8_S_MAXIMUM), + v8::Integer::NewFromUnsigned(isolate, limits.max))); + } +} + +auto globaltype_to_v8( + StoreImpl* store, const GlobalType* type +) -> v8::Local { + auto isolate = store->isolate(); + auto context = store->context(); + auto desc = v8::Object::New(isolate); + ignore(desc->DefineOwnProperty(context, store->v8_string(V8_S_VALUE), + valtype_to_v8(store, type->content()))); + ignore(desc->DefineOwnProperty(context, store->v8_string(V8_S_MUTABLE), + mutability_to_v8(store, type->mutability()))); + return desc; +} + +auto tabletype_to_v8( + StoreImpl* store, const TableType* type +) -> v8::Local { + auto isolate = store->isolate(); + auto context = store->context(); + auto desc = v8::Object::New(isolate); + ignore(desc->DefineOwnProperty(context, store->v8_string(V8_S_ELEMENT), + valtype_to_v8(store, type->element()))); + limits_to_v8(store, type->limits(), desc); + return desc; +} + +auto memorytype_to_v8( + StoreImpl* store, const MemoryType* type +) -> v8::Local { + auto isolate = store->isolate(); + auto desc = v8::Object::New(isolate); + limits_to_v8(store, type->limits(), desc); + return desc; +} + + +/////////////////////////////////////////////////////////////////////////////// +// Runtime Values + +// References + +template +class RefImpl : public v8::Persistent { +public: + RefImpl() = delete; + ~RefImpl() = delete; + + static auto make(StoreImpl* store, v8::Local obj) -> own { + static_assert(sizeof(RefImpl) == sizeof(v8::Persistent), + "incompatible object layout"); + auto self = static_cast(store->make_handle()); + if (!self) return nullptr; + self->Reset(store->isolate(), obj); + stats.make(Stats::categorize(*self), self); + return make_own(seal(self)); + } + + auto copy() const -> own { + v8::HandleScope handle_scope(isolate()); + return make(store(), v8_object()); + } + + auto store() const -> StoreImpl* { + return StoreImpl::get(isolate()); + } + + auto isolate() const -> v8::Isolate* { + return wasm_v8::object_isolate(*this); + } + + auto v8_object() const -> v8::Local { + return Get(isolate()); + } + + auto get_host_info() const -> void* { + v8::HandleScope handle_scope(isolate()); + auto store = this->store(); + + v8::Local args[] = { v8_object() }; + auto maybe_result = store->v8_function(V8_F_WEAKMAP_GET)->Call( + store->context(), store->host_data_map(), 1, args); + if (maybe_result.IsEmpty()) return nullptr; + return wasm_v8::managed_get(maybe_result.ToLocalChecked()); + } + + void set_host_info(void* info, void (*finalizer)(void*)) { + v8::HandleScope handle_scope(isolate()); + auto store = this->store(); + auto managed = wasm_v8::managed_new(store->isolate(), info, finalizer); + v8::Local args[] = { v8_object(), managed }; + auto maybe_result = store->v8_function(V8_F_WEAKMAP_SET)->Call( + store->context(), store->host_data_map(), 2, args); + if (maybe_result.IsEmpty()) return; + } +}; + +template<> struct implement { using type = RefImpl; }; + + +Ref::~Ref() { + stats.free(Stats::categorize(*impl(this)), this); + v8::HandleScope handle_scope(impl(this)->isolate()); + impl(this)->store()->free_handle(impl(this)); +} + +void Ref::operator delete(void *p) {} + +auto Ref::copy() const -> own { + return impl(this)->copy(); +} + +auto Ref::same(const Ref* that) const -> bool { + v8::HandleScope handle_scope(impl(this)->isolate()); + return impl(this)->v8_object()->SameValue(impl(that)->v8_object()); +} + +auto Ref::get_host_info() const -> void* { + return impl(this)->get_host_info(); +} + +void Ref::set_host_info(void* info, void (*finalizer)(void*)) { + impl(this)->set_host_info(info, finalizer); +} + + +// Value Conversion + +auto ref_to_v8(StoreImpl* store, const Ref* r) -> v8::Local { + if (r == nullptr) { + return v8::Null(store->isolate()); + } else { + return impl(r)->v8_object(); + } +} + +auto val_to_v8(StoreImpl* store, const Val& v) -> v8::Local { + auto isolate = store->isolate(); + switch (v.kind()) { + case ValKind::I32: return v8::Integer::NewFromUnsigned(isolate, v.i32()); + case ValKind::I64: return v8::BigInt::New(isolate, v.i64()); + case ValKind::F32: return v8::Number::New(isolate, v.f32()); + case ValKind::F64: return v8::Number::New(isolate, v.f64()); + case ValKind::ANYREF: + case ValKind::FUNCREF: + return ref_to_v8(store, v.ref()); + default: assert(false); + } +} + +auto v8_to_ref(StoreImpl* store, v8::Local value) -> own { + if (value->IsNull()) { + return nullptr; + } else if (value->IsObject()) { + return RefImpl::make(store, v8::Local::Cast(value)); + } else { + UNIMPLEMENTED("JS primitive ref value"); + } +} + +auto v8_to_val( + StoreImpl* store, v8::Local value, const ValType* t +) -> Val { + auto context = store->context(); + switch (t->kind()) { + case ValKind::I32: return Val(value->Int32Value(context).ToChecked()); + case ValKind::I64: { + auto bigint = value->ToBigInt(context).ToLocalChecked(); + return Val(bigint->Int64Value()); + } + case ValKind::F32: { + auto number = value->NumberValue(context).ToChecked(); + return Val(static_cast(number)); + } + case ValKind::F64: return Val(value->NumberValue(context).ToChecked()); + case ValKind::ANYREF: + case ValKind::FUNCREF: { + return Val(v8_to_ref(store, value)); + } + } +} + + +/////////////////////////////////////////////////////////////////////////////// +// Runtime Objects + +// Frames + +struct FrameImpl { + FrameImpl( + own&& instance, uint32_t func_index, + size_t func_offset, size_t module_offset + ) : + instance(std::move(instance)), + func_index(func_index), + func_offset(func_offset), + module_offset(module_offset) + { + stats.make(Stats::FRAME, this); + } + + ~FrameImpl() { stats.free(Stats::FRAME, this); } + + own instance; + uint32_t func_index; + size_t func_offset; + size_t module_offset; +}; + +template<> struct implement { using type = FrameImpl; }; + + +Frame::~Frame() { + impl(this)->~FrameImpl(); +} + +void Frame::operator delete(void *p) { + ::operator delete(p); +} + +auto Frame::copy() const -> own { + auto self = impl(this); + return own(seal(new(std::nothrow) FrameImpl( + self->instance->copy(), self->func_index, self->func_offset, + self->module_offset))); +} + +auto Frame::instance() const -> Instance* { + return impl(this)->instance.get(); +} + +auto Frame::func_index() const -> uint32_t { + return impl(this)->func_index; +} + +auto Frame::func_offset() const -> size_t { + return impl(this)->func_offset; +} + +auto Frame::module_offset() const -> size_t { + return impl(this)->module_offset; +} + + +// Traps + +template<> struct implement { using type = RefImpl; }; + + +Trap::~Trap() {} + +auto Trap::copy() const -> own { + return impl(this)->copy(); +} + +auto Trap::make(Store* store_abs, const Message& message) -> own { + auto store = impl(store_abs); + v8::Isolate* isolate = store->isolate(); + v8::HandleScope handle_scope(isolate); + + auto maybe_string = v8::String::NewFromUtf8(isolate, message.get(), + v8::NewStringType::kNormal, message.size()); + if (maybe_string.IsEmpty()) return own(); + auto exception = v8::Exception::Error(maybe_string.ToLocalChecked()); + return RefImpl::make(store, v8::Local::Cast(exception)); +} + +auto Trap::message() const -> Message { + auto isolate = impl(this)->isolate(); + v8::HandleScope handle_scope(isolate); + + auto message = v8::Exception::CreateMessage(isolate, impl(this)->v8_object()); + v8::String::Utf8Value string(isolate, message->Get()); + return vec::make(std::string(*string)); +} + +auto Trap::origin() const -> own { + // TODO(v8): implement + return own(nullptr); +} + +auto Trap::trace() const -> ownvec { + // TODO(v8): implement + return ownvec::make(); +} + + +// Foreign Objects + +template<> struct implement { using type = RefImpl; }; + + +Foreign::~Foreign() {} + +auto Foreign::copy() const -> own { + return impl(this)->copy(); +} + +auto Foreign::make(Store* store_abs) -> own { + auto store = impl(store_abs); + auto isolate = store->isolate(); + v8::HandleScope handle_scope(isolate); + + auto obj = v8::Object::New(isolate); + return RefImpl::make(store, obj); +} + + +// Modules + +template<> struct implement { using type = RefImpl; }; + + +Module::~Module() {} + +auto Module::copy() const -> own { + return impl(this)->copy(); +} + +auto Module::validate(Store* store_abs, const vec& binary) -> bool { + auto store = impl(store_abs); + v8::Isolate* isolate = store->isolate(); + v8::HandleScope handle_scope(isolate); + + auto array_buffer = v8::ArrayBuffer::New( + isolate, const_cast(binary.get()), binary.size()); + + v8::Local args[] = {array_buffer}; + auto result = store->v8_function(V8_F_VALIDATE)->Call( + store->context(), v8::Undefined(isolate), 1, args); + if (result.IsEmpty()) return false; + + return result.ToLocalChecked()->IsTrue(); +} + +auto Module::make(Store* store_abs, const vec& binary) -> own { + auto store = impl(store_abs); + auto isolate = store->isolate(); + auto context = store->context(); + v8::HandleScope handle_scope(isolate); + + auto array_buffer = v8::ArrayBuffer::New( + isolate, const_cast(binary.get()), binary.size()); + + v8::Local args[] = {array_buffer}; + auto maybe_obj = + store->v8_function(V8_F_MODULE)->NewInstance(context, 1, args); + if (maybe_obj.IsEmpty()) return nullptr; + return RefImpl::make(store, maybe_obj.ToLocalChecked()); +} + +auto Module::imports() const -> ownvec { + v8::HandleScope handle_scope(impl(this)->isolate()); + auto module = impl(this)->v8_object(); + auto binary = vec::adopt( + wasm_v8::module_binary_size(module), + const_cast(wasm_v8::module_binary(module)) + ); + auto imports = wasm::bin::imports(binary); + binary.release(); + return imports; + // return impl(this)->data->imports.copy(); +/* OBSOLETE? + auto store = module->store(); + auto isolate = store->isolate(); + auto context = store->context(); + v8::HandleScope handle_scope(isolate); + + v8::Local args[] = { module->v8_object() }; + auto result = store->v8_function(V8_F_IMPORTS)->Call( + context, v8::Undefined(isolate), 1, args); + if (result.IsEmpty()) return wasm_importtype_vec_empty(); + auto array = v8::Local::Cast(result.ToLocalChecked()); + size_t size = array->Length(); + + wasm_importtype_vec_t imports = wasm_importtype_vec_new_uninitialized(size); + for (size_t i = 0; i < size; ++i) { + auto desc = v8::Local::Cast(array->Get(i)); + auto module_str = v8::Local::Cast( + desc->Get(context, store->v8_string(V8_S_MODULE)).ToLocalChecked()); + auto name_str = v8::Local::Cast( + desc->Get(context, store->v8_string(V8_S_NAME)).ToLocalChecked()); + auto kind_str = v8::Local::Cast( + desc->Get(context, store->v8_string(V8_S_KIND)).ToLocalChecked()); + + auto type = wasm_externtype_new_from_v8_kind(store, kind_str); + auto module = wasm_byte_vec_new_from_v8_string(module_str); + auto name = wasm_byte_vec_new_from_v8_string(name_str); + imports.data[i] = wasm_importtype_new(module, name, type); + } + + return imports; +*/ +} + +auto Module::exports() const -> ownvec { + v8::HandleScope handle_scope(impl(this)->isolate()); + auto module = impl(this)->v8_object(); + auto binary = vec::adopt( + wasm_v8::module_binary_size(module), + const_cast(wasm_v8::module_binary(module)) + ); + auto exports = wasm::bin::exports(binary); + binary.release(); + return exports; + // return impl(this)->data->exports.copy(); +/* OBSOLETE? + auto store = module->store(); + auto isolate = store->isolate(); + auto context = store->context(); + v8::HandleScope handle_scope(isolate); + + v8::Local args[] = { module->v8_object() }; + auto result = store->v8_function(V8_F_EXPORTS)->Call( + context, v8::Undefined(isolate), 1, args); + if (result.IsEmpty()) return wasm_exporttype_vec_empty(); + auto array = v8::Local::Cast(result.ToLocalChecked()); + size_t size = array->Length(); + + wasm_exporttype_vec_t exports = wasm_exporttype_vec_new_uninitialized(size); + for (size_t i = 0; i < size; ++i) { + auto desc = v8::Local::Cast(array->Get(i)); + auto name_str = v8::Local::Cast( + desc->Get(context, store->v8_string(V8_S_NAME)).ToLocalChecked()); + auto kind_str = v8::Local::Cast( + desc->Get(context, store->v8_string(V8_S_KIND)).ToLocalChecked()); + + auto type = wasm_externtype_new_from_v8_kind(store, kind_str); + auto name = wasm_byte_vec_new_from_v8_string(name_str); + exports.data[i] = wasm_exporttype_new(name, type); + } + + return exports; +*/ +} + +auto Module::serialize() const -> vec { + v8::HandleScope handle_scope(impl(this)->isolate()); + auto module = impl(this)->v8_object(); + auto binary_size = wasm_v8::module_binary_size(module); + auto serial_size = wasm_v8::module_serialize_size(module); + auto size_size = wasm::bin::u64_size(binary_size); + auto buffer = vec::make_uninitialized( + size_size + binary_size + serial_size); + auto ptr = buffer.get(); + wasm::bin::encode_u64(ptr, binary_size); + std::memcpy(ptr, wasm_v8::module_binary(module), binary_size); + ptr += binary_size; + if (!wasm_v8::module_serialize(module, ptr, serial_size)) buffer.reset(); + return buffer; +} + +auto Module::deserialize(Store* store_abs, const vec& serialized) -> own { + auto store = impl(store_abs); + auto isolate = store->isolate(); + v8::HandleScope handle_scope(isolate); + auto ptr = serialized.get(); + auto binary_size = wasm::bin::u64(ptr); + auto size_size = ptr - serialized.get(); + auto serial_size = serialized.size() - size_size - binary_size; + auto maybe_obj = wasm_v8::module_deserialize( + isolate, ptr, binary_size, ptr + binary_size, serial_size); + if (maybe_obj.IsEmpty()) return nullptr; + return RefImpl::make(store, maybe_obj.ToLocalChecked()); +} + + +// TODO(v8): do better when V8 can do better. +template<> struct implement> { using type = vec; }; + +template<> +Shared::~Shared() { + stats.free(Stats::MODULE, this, Stats::SHARED); + impl(this)->~vec(); +} + +template<> +void Shared::operator delete(void* p) { + ::operator delete(p); +} + +auto Module::share() const -> own> { + auto shared = seal>(new vec(serialize())); + stats.make(Stats::MODULE, shared, Stats::SHARED); + return make_own(shared); +} + +auto Module::obtain(Store* store, const Shared* shared) -> own { + return Module::deserialize(store, *impl(shared)); +} + + + + +// Externals + +template<> struct implement { using type = RefImpl; }; + + +Extern::~Extern() {} + +auto Extern::copy() const -> own { + return impl(this)->copy(); +} + +auto Extern::kind() const -> ExternKind { + v8::HandleScope handle_scope(impl(this)->isolate()); + return static_cast(wasm_v8::extern_kind(impl(this)->v8_object())); +} + +auto Extern::type() const -> own { + switch (kind()) { + case ExternKind::FUNC: return func()->type(); + case ExternKind::GLOBAL: return global()->type(); + case ExternKind::TABLE: return table()->type(); + case ExternKind::MEMORY: return memory()->type(); + } +} + +auto Extern::func() -> Func* { + return kind() == ExternKind::FUNC ? static_cast(this) : nullptr; +} + +auto Extern::global() -> Global* { + return kind() == ExternKind::GLOBAL ? static_cast(this) : nullptr; +} + +auto Extern::table() -> Table* { + return kind() == ExternKind::TABLE ? static_cast(this) : nullptr; +} + +auto Extern::memory() -> Memory* { + return kind() == ExternKind::MEMORY ? static_cast(this) : nullptr; +} + +auto Extern::func() const -> const Func* { + return kind() == ExternKind::FUNC ? static_cast(this) : nullptr; +} + +auto Extern::global() const -> const Global* { + return kind() == ExternKind::GLOBAL ? static_cast(this) : nullptr; +} + +auto Extern::table() const -> const Table* { + return kind() == ExternKind::TABLE ? static_cast(this) : nullptr; +} + +auto Extern::memory() const -> const Memory* { + return kind() == ExternKind::MEMORY ? static_cast(this) : nullptr; +} + +auto extern_to_v8(const Extern* ex) -> v8::Local { + return impl(ex)->v8_object(); +} + + +// Function Instances + +template<> struct implement { using type = RefImpl; }; + + +Func::~Func() {} + +auto Func::copy() const -> own { + return impl(this)->copy(); +} + +struct FuncData { + Store* store; + own type; + enum Kind { CALLBACK, CALLBACK_WITH_ENV } kind; + union { + Func::callback callback; + Func::callback_with_env callback_with_env; + }; + void (*finalizer)(void*); + void* env; + + FuncData(Store* store, const FuncType* type, Kind kind) : + store(store), type(type->copy()), kind(kind), finalizer(nullptr) + { + stats.make(Stats::FUNCDATA_FUNCTYPE, nullptr); + stats.make(Stats::FUNCDATA_VALTYPE, nullptr, Stats::OWN, type->params().size()); + stats.make(Stats::FUNCDATA_VALTYPE, nullptr, Stats::OWN, type->results().size()); + if (type->params().get()) stats.make(Stats::FUNCDATA_VALTYPE, nullptr, Stats::VEC); + if (type->results().get()) stats.make(Stats::FUNCDATA_VALTYPE, nullptr, Stats::VEC); + } + + ~FuncData() { + stats.free(Stats::FUNCDATA_FUNCTYPE, nullptr); + stats.free(Stats::FUNCDATA_VALTYPE, nullptr, Stats::OWN, type->params().size()); + stats.free(Stats::FUNCDATA_VALTYPE, nullptr, Stats::OWN, type->results().size()); + if (type->params().get()) stats.free(Stats::FUNCDATA_VALTYPE, nullptr, Stats::VEC); + if (type->results().get()) stats.free(Stats::FUNCDATA_VALTYPE, nullptr, Stats::VEC); + if (finalizer) (*finalizer)(env); + } + + static void v8_callback(const v8::FunctionCallbackInfo&); + static void finalize_func_data(void* data); +}; + +namespace { + +auto make_func(Store* store_abs, FuncData* data) -> own { + auto store = impl(store_abs); + auto isolate = store->isolate(); + v8::HandleScope handle_scope(isolate); + auto context = store->context(); + + // Create V8 function + auto v8_data = wasm_v8::foreign_new(isolate, data); + auto function_template = v8::FunctionTemplate::New( + isolate, &FuncData::v8_callback, v8_data); + auto maybe_func_obj = function_template->GetFunction(context); + if (maybe_func_obj.IsEmpty()) return own(); + auto func_obj = maybe_func_obj.ToLocalChecked(); + + // Create wrapper instance + auto binary = wasm::bin::wrapper(data->type.get()); + auto module = Module::make(store_abs, binary); + + auto imports_obj = v8::Object::New(isolate); + auto module_obj = v8::Object::New(isolate); + auto str = store->v8_string(V8_S_EMPTY); + ignore(imports_obj->DefineOwnProperty(context, str, module_obj)); + ignore(module_obj->DefineOwnProperty(context, str, func_obj)); + + v8::Local instantiate_args[] = { + impl(module.get())->v8_object(), imports_obj + }; + auto instance_obj = store->v8_function(V8_F_INSTANCE)->NewInstance( + context, 2, instantiate_args).ToLocalChecked(); + assert(!instance_obj.IsEmpty()); + assert(instance_obj->IsObject()); + auto exports_obj = wasm_v8::instance_exports(instance_obj); + assert(!exports_obj.IsEmpty()); + assert(exports_obj->IsObject()); + auto wrapped_func_obj = v8::Local::Cast( + exports_obj->Get(context, str).ToLocalChecked()); + assert(!wrapped_func_obj.IsEmpty()); + assert(wrapped_func_obj->IsFunction()); + + auto func = RefImpl::make(store, wrapped_func_obj); + func->set_host_info(data, &FuncData::finalize_func_data); + return func; +} + +auto func_type(v8::Local v8_func) -> own { + // return impl(this)->data->type->copy(); + auto param_arity = wasm_v8::func_type_param_arity(v8_func); + auto result_arity = wasm_v8::func_type_result_arity(v8_func); + auto params = ownvec::make_uninitialized(param_arity); + auto results = ownvec::make_uninitialized(result_arity); + + for (size_t i = 0; i < params.size(); ++i) { + auto kind = static_cast(wasm_v8::func_type_param(v8_func, i)); + params[i] = ValType::make(kind); + } + for (size_t i = 0; i < results.size(); ++i) { + auto kind = static_cast(wasm_v8::func_type_result(v8_func, i)); + results[i] = ValType::make(kind); + } + + return FuncType::make(std::move(params), std::move(results)); +} + +} // namespace + +auto Func::make( + Store* store, const FuncType* type, Func::callback callback +) -> own { + auto data = new FuncData(store, type, FuncData::CALLBACK); + data->callback = callback; + return make_func(store, data); +} + +auto Func::make( + Store* store, const FuncType* type, + callback_with_env callback, void* env, void (*finalizer)(void*) +) -> own { + auto data = new FuncData(store, type, FuncData::CALLBACK_WITH_ENV); + data->callback_with_env = callback; + data->env = env; + data->finalizer = finalizer; + return make_func(store, data); +} + +auto Func::type() const -> own { + // return impl(this)->data->type->copy(); + v8::HandleScope handle_scope(impl(this)->isolate()); + return func_type(impl(this)->v8_object()); +} + +auto Func::param_arity() const -> size_t { + v8::HandleScope handle_scope(impl(this)->isolate()); + return wasm_v8::func_type_param_arity(impl(this)->v8_object()); +} + +auto Func::result_arity() const -> size_t { + v8::HandleScope handle_scope(impl(this)->isolate()); + return wasm_v8::func_type_result_arity(impl(this)->v8_object()); +} + +auto Func::call(const Val args[], Val results[]) const -> own { + auto func = impl(this); + auto store = func->store(); + auto isolate = store->isolate(); + v8::HandleScope handle_scope(isolate); + + auto context = store->context(); + auto type = this->type(); + auto& param_types = type->params(); + auto& result_types = type->results(); + + // TODO: cache v8_args array per thread. + auto v8_args = std::unique_ptr[]>( + new(std::nothrow) v8::Local[param_types.size()]); + for (size_t i = 0; i < param_types.size(); ++i) { + assert(args[i].kind() == param_types[i]->kind()); + v8_args[i] = val_to_v8(store, args[i]); + } + + v8::TryCatch handler(isolate); + auto v8_function = v8::Local::Cast(func->v8_object()); + auto maybe_val = v8_function->Call( + context, v8::Undefined(isolate), param_types.size(), v8_args.get()); + + if (handler.HasCaught()) { + auto exception = handler.Exception(); + if (!exception->IsObject()) { + auto maybe_string = exception->ToString(store->context()); + auto string = maybe_string.IsEmpty() + ? store->v8_string(V8_S_EMPTY) : maybe_string.ToLocalChecked(); + exception = v8::Exception::Error(string); + } + return RefImpl::make(store, v8::Local::Cast(exception)); + } + + auto val = maybe_val.ToLocalChecked(); + if (result_types.size() == 0) { + assert(val->IsUndefined()); + } else if (result_types.size() == 1) { + assert(!val->IsUndefined()); + new (&results[0]) Val(v8_to_val(store, val, result_types[0].get())); + } else { + assert(val->IsArray()); + auto array = v8::Handle::Cast(val); + for (size_t i = 0; i < result_types.size(); ++i) { + auto maybe = array->Get(context, i); + assert(!maybe.IsEmpty()); + new (&results[i]) Val(v8_to_val( + store, maybe.ToLocalChecked(), result_types[i].get())); + } + } + return nullptr; +} + +void FuncData::v8_callback(const v8::FunctionCallbackInfo& info) { + auto v8_data = v8::Local::Cast(info.Data()); + auto self = reinterpret_cast(wasm_v8::foreign_get(v8_data)); + auto store = impl(self->store); + auto isolate = store->isolate(); + v8::HandleScope handle_scope(isolate); + + auto& param_types = self->type->params(); + auto& result_types = self->type->results(); + + assert(param_types.size() == info.Length()); + + // TODO: cache params and result arrays per thread. + auto args = std::unique_ptr(new Val[param_types.size()]); + auto results = std::unique_ptr(new Val[result_types.size()]); + for (size_t i = 0; i < param_types.size(); ++i) { + args[i] = v8_to_val(store, info[i], param_types[i].get()); + } + + own trap; + if (self->kind == CALLBACK_WITH_ENV) { + trap = self->callback_with_env(self->env, args.get(), results.get()); + } else { + trap = self->callback(args.get(), results.get()); + } + + if (trap) { + isolate->ThrowException(impl(trap.get())->v8_object()); + return; + } + + auto ret = info.GetReturnValue(); + if (result_types.size() == 0) { + ret.SetUndefined(); + } else if (result_types.size() == 1) { + assert(results[0].kind() == result_types[0]->kind()); + ret.Set(val_to_v8(store, results[0])); + } else { + auto context = store->context(); + auto array = v8::Array::New(isolate, result_types.size()); + for (size_t i = 0; i < result_types.size(); ++i) { + auto success = array->Set(context, i, val_to_v8(store, results[i])); + assert(success.IsJust() && success.ToChecked()); + } + ret.Set(array); + } +} + +void FuncData::finalize_func_data(void* data) { + delete reinterpret_cast(data); +} + + +// Global Instances + +template<> struct implement { using type = RefImpl; }; + + +Global::~Global() {} + +auto Global::copy() const -> own { + return impl(this)->copy(); +} + +auto Global::make( + Store* store_abs, const GlobalType* type, const Val& val +) -> own { + auto store = impl(store_abs); + auto isolate = store->isolate(); + v8::HandleScope handle_scope(isolate); + auto context = store->context(); + + assert(type->content()->kind() == val.kind()); + + // Create wrapper instance + auto binary = wasm::bin::wrapper(type); + auto module = Module::make(store_abs, binary); + + v8::Local instantiate_args[] = { impl(module.get())->v8_object() }; + auto instance_obj = store->v8_function(V8_F_INSTANCE)->NewInstance( + context, 1, instantiate_args).ToLocalChecked(); + auto exports_obj = wasm_v8::instance_exports(instance_obj); + auto obj = v8::Local::Cast( + exports_obj->Get(context, store->v8_string(V8_S_EMPTY)).ToLocalChecked()); + assert(!obj.IsEmpty() && obj->IsObject()); + + auto global = RefImpl::make(store, obj); + assert(global); + global->set(val); + return global; +} + +auto Global::type() const -> own { + // return impl(this)->data->type->copy(); + v8::HandleScope handle_scope(impl(this)->isolate()); + auto v8_global = impl(this)->v8_object(); + auto kind = static_cast(wasm_v8::global_type_content(v8_global)); + auto mutability = wasm_v8::global_type_mutable(v8_global) + ? Mutability::VAR : Mutability::CONST; + return GlobalType::make(ValType::make(kind), mutability); +} + +auto Global::get() const -> Val { + v8::HandleScope handle_scope(impl(this)->isolate()); + auto v8_global = impl(this)->v8_object(); + switch (type()->content()->kind()) { + case ValKind::I32: return Val(wasm_v8::global_get_i32(v8_global)); + case ValKind::I64: return Val(wasm_v8::global_get_i64(v8_global)); + case ValKind::F32: return Val(wasm_v8::global_get_f32(v8_global)); + case ValKind::F64: return Val(wasm_v8::global_get_f64(v8_global)); + case ValKind::ANYREF: + case ValKind::FUNCREF: { + auto store = impl(this)->store(); + return Val(v8_to_ref(store, wasm_v8::global_get_ref(v8_global))); + } + default: + assert(false); + } +} + +void Global::set(const Val& val) { + v8::HandleScope handle_scope(impl(this)->isolate()); + auto v8_global = impl(this)->v8_object(); + switch (val.kind()) { + case ValKind::I32: return wasm_v8::global_set_i32(v8_global, val.i32()); + case ValKind::I64: return wasm_v8::global_set_i64(v8_global, val.i64()); + case ValKind::F32: return wasm_v8::global_set_f32(v8_global, val.f32()); + case ValKind::F64: return wasm_v8::global_set_f64(v8_global, val.f64()); + case ValKind::ANYREF: + case ValKind::FUNCREF: { + auto store = impl(this)->store(); + return wasm_v8::global_set_ref(v8_global, ref_to_v8(store, val.ref())); + } + default: + assert(false); + } +} + + +// Table Instances + +template<> struct implement
{ using type = RefImpl
; }; + + +Table::~Table() {} + +auto Table::copy() const -> own
{ + return impl(this)->copy(); +} + +auto Table::make( + Store* store_abs, const TableType* type, const Ref* ref +) -> own
{ + auto store = impl(store_abs); + auto isolate = store->isolate(); + v8::HandleScope handle_scope(isolate); + auto context = store->context(); + + v8::Local init = v8::Null(isolate); + if (ref) init = impl(ref)->v8_object(); + v8::Local args[] = {tabletype_to_v8(store, type), init}; + auto maybe_obj = + store->v8_function(V8_F_TABLE)->NewInstance(context, 2, args); + if (maybe_obj.IsEmpty()) return own
(); + auto table = RefImpl
::make(store, maybe_obj.ToLocalChecked()); + // TODO(wasm+): pass reference initialiser as parameter + if (table && ref) { + auto size = type->limits().min; + auto obj = maybe_obj.ToLocalChecked(); + for (size_t i = 0; i < size; ++i) { + wasm_v8::table_set(obj, i, v8::Local::Cast(init)); + } + } + return table; +} + +auto Table::type() const -> own { + // return impl(this)->data->type->copy(); + v8::HandleScope handle_scope(impl(this)->isolate()); + auto v8_table = impl(this)->v8_object(); + uint32_t min = wasm_v8::table_type_min(v8_table); + uint32_t max = wasm_v8::table_type_max(v8_table); + // TODO(wasm+): support new element types. + return TableType::make(ValType::make(ValKind::FUNCREF), Limits(min, max)); +} + +auto Table::get(size_t index) const -> own { + v8::HandleScope handle_scope(impl(this)->isolate()); + auto maybe = wasm_v8::table_get(impl(this)->v8_object(), index); + if (maybe.IsEmpty()) return own(); + auto obj = v8::Local::Cast(maybe.ToLocalChecked()); + return v8_to_ref(impl(this)->store(), obj); +} + +auto Table::set(size_t index, const Ref* ref) -> bool { + v8::HandleScope handle_scope(impl(this)->isolate()); + auto val = ref_to_v8(impl(this)->store(), ref); + return wasm_v8::table_set(impl(this)->v8_object(), index, val); +} + +auto Table::size() const -> size_t { + v8::HandleScope handle_scope(impl(this)->isolate()); + return wasm_v8::table_size(impl(this)->v8_object()); +} + +auto Table::grow(size_t delta, const Ref* ref) -> bool { + v8::HandleScope handle_scope(impl(this)->isolate()); + auto val = ref_to_v8(impl(this)->store(), ref); + return wasm_v8::table_grow(impl(this)->v8_object(), delta, val); +} + + +// Memory Instances + +template<> struct implement { using type = RefImpl; }; + + +Memory::~Memory() {} + +auto Memory::copy() const -> own { + return impl(this)->copy(); +} + +auto Memory::make(Store* store_abs, const MemoryType* type) -> own { + auto store = impl(store_abs); + auto isolate = store->isolate(); + v8::HandleScope handle_scope(isolate); + auto context = store->context(); + + v8::Local args[] = { memorytype_to_v8(store, type) }; + auto maybe_obj = + store->v8_function(V8_F_MEMORY)->NewInstance(context, 1, args); + if (maybe_obj.IsEmpty()) return own(); + return RefImpl::make(store, maybe_obj.ToLocalChecked()); +} + +auto Memory::type() const -> own { + // return impl(this)->data->type->copy(); + v8::HandleScope handle_scope(impl(this)->isolate()); + auto v8_memory = impl(this)->v8_object(); + uint32_t min = wasm_v8::memory_type_min(v8_memory); + uint32_t max = wasm_v8::memory_type_max(v8_memory); + return MemoryType::make(Limits(min, max)); +} + +auto Memory::data() const -> byte_t* { + v8::HandleScope handle_scope(impl(this)->isolate()); + return wasm_v8::memory_data(impl(this)->v8_object()); +} + +auto Memory::data_size() const -> size_t { + v8::HandleScope handle_scope(impl(this)->isolate()); + return wasm_v8::memory_data_size(impl(this)->v8_object()); +} + +auto Memory::size() const -> pages_t { + v8::HandleScope handle_scope(impl(this)->isolate()); + return wasm_v8::memory_size(impl(this)->v8_object()); +} + +auto Memory::grow(pages_t delta) -> bool { + v8::HandleScope handle_scope(impl(this)->isolate()); + return wasm_v8::memory_grow(impl(this)->v8_object(), delta); +} + + +// Module Instances + +template<> struct implement { using type = RefImpl; }; + + +Instance::~Instance() {} + +auto Instance::copy() const -> own { + return impl(this)->copy(); +} + +auto Instance::make( + Store* store_abs, const Module* module_abs, const Extern* const imports[], + own* trap +) -> own { + auto store = impl(store_abs); + auto module = impl(module_abs); + auto isolate = store->isolate(); + auto context = store->context(); + v8::HandleScope handle_scope(isolate); + + assert(wasm_v8::object_isolate(module->v8_object()) == isolate); + + if (trap) *trap = nullptr; + auto import_types = module_abs->imports(); + auto imports_obj = v8::Object::New(isolate); + for (size_t i = 0; i < import_types.size(); ++i) { + auto type = import_types[i].get(); + auto maybe_module = v8::String::NewFromOneByte( + isolate, reinterpret_cast(type->module().get()), + v8::NewStringType::kNormal, type->module().size() + ); + if (maybe_module.IsEmpty()) return own(); + auto module_str = maybe_module.ToLocalChecked(); + auto maybe_name = v8::String::NewFromOneByte( + isolate, reinterpret_cast(type->name().get()), + v8::NewStringType::kNormal, type->name().size() + ); + if (maybe_name.IsEmpty()) return own(); + auto name_str = maybe_name.ToLocalChecked(); + + v8::Local module_obj; + if (imports_obj->HasOwnProperty(context, module_str).ToChecked()) { + module_obj = v8::Local::Cast( + imports_obj->Get(context, module_str).ToLocalChecked()); + } else { + module_obj = v8::Object::New(isolate); + ignore(imports_obj->DefineOwnProperty(context, module_str, module_obj)); + } + + ignore(module_obj->DefineOwnProperty( + context, name_str, extern_to_v8(imports[i]))); + } + + v8::TryCatch handler(isolate); + v8::Local instantiate_args[] = {module->v8_object(), imports_obj}; + auto obj = store->v8_function(V8_F_INSTANCE)->NewInstance( + context, 2, instantiate_args).ToLocalChecked(); + + if (handler.HasCaught() && trap) { + auto exception = handler.Exception(); + if (!exception->IsObject()) { + auto maybe_string = exception->ToString(store->context()); + auto string = maybe_string.IsEmpty() + ? store->v8_string(V8_S_EMPTY) : maybe_string.ToLocalChecked(); + exception = v8::Exception::Error(string); + } + *trap = RefImpl::make(store, v8::Local::Cast(exception)); + return nullptr; + } + + return RefImpl::make(store, obj); +} + +auto Instance::exports() const -> ownvec { + auto instance = impl(this); + auto store = instance->store(); + auto isolate = store->isolate(); + auto context = store->context(); + v8::HandleScope handle_scope(isolate); + + auto module_obj = wasm_v8::instance_module(instance->v8_object()); + auto exports_obj = wasm_v8::instance_exports(instance->v8_object()); + assert(!module_obj.IsEmpty() && module_obj->IsObject()); + assert(!exports_obj.IsEmpty() && exports_obj->IsObject()); + + auto module = RefImpl::make(store, module_obj); + auto export_types = module->exports(); + auto exports = ownvec::make_uninitialized(export_types.size()); + if (!exports) return ownvec::invalid(); + + for (size_t i = 0; i < export_types.size(); ++i) { + auto& name = export_types[i]->name(); + auto maybe_name_obj = v8::String::NewFromUtf8(isolate, name.get(), + v8::NewStringType::kNormal, name.size()); + if (maybe_name_obj.IsEmpty()) return ownvec::invalid(); + auto name_obj = maybe_name_obj.ToLocalChecked(); + auto obj = v8::Local::Cast( + exports_obj->Get(context, name_obj).ToLocalChecked()); + + auto type = export_types[i]->type(); + switch (type->kind()) { + case ExternKind::FUNC: { + assert(wasm_v8::extern_kind(obj) == wasm_v8::EXTERN_FUNC); + exports[i] = RefImpl::make(store, obj); + } break; + case ExternKind::GLOBAL: { + assert(wasm_v8::extern_kind(obj) == wasm_v8::EXTERN_GLOBAL); + exports[i] = RefImpl::make(store, obj); + } break; + case ExternKind::TABLE: { + assert(wasm_v8::extern_kind(obj) == wasm_v8::EXTERN_TABLE); + exports[i] = RefImpl
::make(store, obj); + } break; + case ExternKind::MEMORY: { + assert(wasm_v8::extern_kind(obj) == wasm_v8::EXTERN_MEMORY); + exports[i] = RefImpl::make(store, obj); + } break; + } + } + + return exports; +} + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace wasm diff --git a/lib/c-api/wasmer.h b/lib/c-api/wasmer.h new file mode 100644 index 000000000..aadf841c1 --- /dev/null +++ b/lib/c-api/wasmer.h @@ -0,0 +1,1469 @@ + +#if !defined(WASMER_H_MACROS) + +#define WASMER_H_MACROS + +// Define the `ARCH_X86_X64` constant. +#if defined(MSVC) && defined(_M_AMD64) +# define ARCH_X86_64 +#elif (defined(GCC) || defined(__GNUC__) || defined(__clang__)) && defined(__x86_64__) +# define ARCH_X86_64 +#endif + +// Compatibility with non-Clang compilers. +#if !defined(__has_attribute) +# define __has_attribute(x) 0 +#endif + +// Compatibility with non-Clang compilers. +#if !defined(__has_declspec_attribute) +# define __has_declspec_attribute(x) 0 +#endif + +// Define the `DEPRECATED` macro. +#if defined(GCC) || defined(__GNUC__) || __has_attribute(deprecated) +# define DEPRECATED(message) __attribute__((deprecated(message))) +#elif defined(MSVC) || __has_declspec_attribute(deprecated) +# define DEPRECATED(message) __declspec(deprecated(message)) +#endif + +#define WASMER_WASI_ENABLED +#endif // WASMER_H_MACROS + + +#ifndef WASMER_H +#define WASMER_H + +#include +#include +#include +#include + +#if defined(WASMER_WASI_ENABLED) +enum Version { +#if defined(WASMER_WASI_ENABLED) + /** + * Version cannot be detected or is unknown. + */ + Unknown = 0, +#endif +#if defined(WASMER_WASI_ENABLED) + /** + * Latest version. See `wasmer_wasi::WasiVersion::Latest` to + * learn more. + */ + Latest = 1, +#endif +#if defined(WASMER_WASI_ENABLED) + /** + * `wasi_unstable`. + */ + Snapshot0 = 2, +#endif +#if defined(WASMER_WASI_ENABLED) + /** + * `wasi_snapshot_preview1`. + */ + Snapshot1 = 3, +#endif +}; +typedef uint8_t Version; +#endif + +/** + * List of export/import kinds. + */ +enum wasmer_import_export_kind { + /** + * The export/import is a function. + */ + WASM_FUNCTION = 0, + /** + * The export/import is a global. + */ + WASM_GLOBAL = 1, + /** + * The export/import is a memory. + */ + WASM_MEMORY = 2, + /** + * The export/import is a table. + */ + WASM_TABLE = 3, +}; +typedef uint32_t wasmer_import_export_kind; + +/** + * The `wasmer_result_t` enum is a type that represents either a + * success, or a failure. + */ +typedef enum { + /** + * Represents a success. + */ + WASMER_OK = 1, + /** + * Represents a failure. + */ + WASMER_ERROR = 2, +} wasmer_result_t; + +/** + * Represents all possibles WebAssembly value types. + * + * See `wasmer_value_t` to get a complete example. + */ +enum wasmer_value_tag { + /** + * Represents the `i32` WebAssembly type. + */ + WASM_I32, + /** + * Represents the `i64` WebAssembly type. + */ + WASM_I64, + /** + * Represents the `f32` WebAssembly type. + */ + WASM_F32, + /** + * Represents the `f64` WebAssembly type. + */ + WASM_F64, +}; +typedef uint32_t wasmer_value_tag; + +typedef struct { + +} wasmer_module_t; + +/** + * Opaque pointer to a `wasmer_runtime::Instance` value in Rust. + * + * A `wasmer_runtime::Instance` represents a WebAssembly instance. It + * is generally generated by the `wasmer_instantiate()` function, or by + * the `wasmer_module_instantiate()` function for the most common paths. + */ +typedef struct { + +} wasmer_instance_t; + +typedef struct { + const uint8_t *bytes; + uint32_t bytes_len; +} wasmer_byte_array; + +#if defined(WASMER_EMSCRIPTEN_ENABLED) +/** + * Type used to construct an import_object_t with Emscripten imports. + */ +typedef struct { + +} wasmer_emscripten_globals_t; +#endif + +typedef struct { + +} wasmer_import_object_t; + +/** + * Opaque pointer to `NamedExportDescriptor`. + */ +typedef struct { + +} wasmer_export_descriptor_t; + +/** + * Opaque pointer to `NamedExportDescriptors`. + */ +typedef struct { + +} wasmer_export_descriptors_t; + +/** + * Opaque pointer to `wasmer_export_t`. + */ +typedef struct { + +} wasmer_export_func_t; + +/** + * Represents a WebAssembly value. + * + * This is a [Rust union][rust-union], which is equivalent to the C + * union. See `wasmer_value_t` to get a complete example. + * + * [rust-union]: https://doc.rust-lang.org/reference/items/unions.html + */ +typedef union { + int32_t I32; + int64_t I64; + float F32; + double F64; +} wasmer_value; + +/** + * Represents a WebAssembly type and value pair, + * i.e. `wasmer_value_tag` and `wasmer_value`. Since the latter is an + * union, it's the safe way to read or write a WebAssembly value in + * C. + * + * Example: + * + * ```c + * // Create a WebAssembly value. + * wasmer_value_t wasm_value = { + * .tag = WASM_I32, + * .value.I32 = 42, + * }; + * + * // Read a WebAssembly value. + * if (wasm_value.tag == WASM_I32) { + * int32_t x = wasm_value.value.I32; + * // … + * } + * ``` + */ +typedef struct { + /** + * The value type. + */ + wasmer_value_tag tag; + /** + * The value. + */ + wasmer_value value; +} wasmer_value_t; + +/** + * Opaque pointer to `ImportType`. + */ +typedef struct { + +} wasmer_export_t; + +/** + * Opaque pointer to a `wasmer_runtime::Memory` value in Rust. + * + * A `wasmer_runtime::Memory` represents a WebAssembly memory. It is + * possible to create one with `wasmer_memory_new()` and pass it as + * imports of an instance, or to read it from exports of an instance + * with `wasmer_export_to_memory()`. + */ +typedef struct { + +} wasmer_memory_t; + +/** + * Opaque pointer to the opaque structure `crate::NamedExports`, + * which is a wrapper around a vector of the opaque structure + * `crate::NamedExport`. + * + * Check the `wasmer_instance_exports()` function to learn more. + */ +typedef struct { + +} wasmer_exports_t; + +typedef struct { + +} wasmer_global_t; + +typedef struct { + bool mutable_; + wasmer_value_tag kind; +} wasmer_global_descriptor_t; + +typedef struct { + +} wasmer_import_descriptor_t; + +typedef struct { + +} wasmer_import_descriptors_t; + +typedef struct { + +} wasmer_import_func_t; + +typedef struct { + +} wasmer_table_t; + +/** + * Union of import/export value. + */ +typedef union { + const wasmer_import_func_t *func; + const wasmer_table_t *table; + const wasmer_memory_t *memory; + const wasmer_global_t *global; +} wasmer_import_export_value; + +typedef struct { + wasmer_byte_array module_name; + wasmer_byte_array import_name; + wasmer_import_export_kind tag; + wasmer_import_export_value value; +} wasmer_import_t; + +typedef struct { + +} wasmer_import_object_iter_t; + +/** + * Opaque pointer to a `wasmer_runtime::Ctx` value in Rust. + * + * An instance context is passed to any host function (aka imported + * function) as the first argument. It is necessary to read the + * instance data or the memory, respectively with the + * `wasmer_instance_context_data_get()` function, and the + * `wasmer_instance_context_memory()` function. + * + * It is also possible to get the instance context outside a host + * function by using the `wasmer_instance_context_get()` + * function. See also `wasmer_instance_context_data_set()` to set the + * instance context data. + * + * Example: + * + * ```c + * // A host function that prints data from the WebAssembly memory to + * // the standard output. + * void print(wasmer_instance_context_t *context, int32_t pointer, int32_t length) { + * // Use `wasmer_instance_context` to get back the first instance memory. + * const wasmer_memory_t *memory = wasmer_instance_context_memory(context, 0); + * + * // Continue… + * } + * ``` + */ +typedef struct { + +} wasmer_instance_context_t; + +/** + * The `wasmer_limit_option_t` struct represents an optional limit + * for `wasmer_limits_t`. + */ +typedef struct { + /** + * Whether the limit is set. + */ + bool has_some; + /** + * The limit value. + */ + uint32_t some; +} wasmer_limit_option_t; + +/** + * The `wasmer_limits_t` struct is a type that describes the limits of something + * such as a memory or a table. See the `wasmer_memory_new()` function to get + * more information. + */ +typedef struct { + /** + * The minimum number of allowed pages. + */ + uint32_t min; + /** + * The maximum number of allowed pages. + */ + wasmer_limit_option_t max; +} wasmer_limits_t; + +typedef struct { + +} wasmer_serialized_module_t; + +#if defined(WASMER_WASI_ENABLED) +/** + * 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; +#endif + +/** + * Creates a new Module from the given wasm bytes. + * + * Returns `wasmer_result_t::WASMER_OK` upon success. + * + * Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` + * and `wasmer_last_error_message` to get an error message. + */ +wasmer_result_t wasmer_compile(wasmer_module_t **module, + uint8_t *wasm_bytes, + uint32_t wasm_bytes_len); + +#if defined(WASMER_EMSCRIPTEN_ENABLED) +/** + * Convenience function for setting up arguments and calling the Emscripten + * main function. + * + * WARNING: + * + * Do not call this function on untrusted code when operating without + * additional sandboxing in place. + * Emscripten has access to many host system calls and therefore may do very + * bad things. + */ +wasmer_result_t wasmer_emscripten_call_main(wasmer_instance_t *instance, + const wasmer_byte_array *args, + unsigned int args_len); +#endif + +#if defined(WASMER_EMSCRIPTEN_ENABLED) +/** + * Destroy `wasmer_emscrpten_globals_t` created by + * `wasmer_emscripten_get_emscripten_globals`. + */ +void wasmer_emscripten_destroy_globals(wasmer_emscripten_globals_t *globals); +#endif + +#if defined(WASMER_EMSCRIPTEN_ENABLED) +/** + * Create a `wasmer_import_object_t` with Emscripten imports, use + * `wasmer_emscripten_get_emscripten_globals` to get a + * `wasmer_emscripten_globals_t` from a `wasmer_module_t`. + * + * WARNING: + * + * This `import_object_t` contains thin-wrappers around host system calls. + * Do not use this to execute untrusted code without additional sandboxing. + */ +wasmer_import_object_t *wasmer_emscripten_generate_import_object(wasmer_emscripten_globals_t *globals); +#endif + +#if defined(WASMER_EMSCRIPTEN_ENABLED) +/** + * Create a `wasmer_emscripten_globals_t` from a Wasm module. + */ +wasmer_emscripten_globals_t *wasmer_emscripten_get_globals(const wasmer_module_t *module); +#endif + +#if defined(WASMER_EMSCRIPTEN_ENABLED) +/** + * Execute global constructors (required if the module is compiled from C++) + * and sets up the internal environment. + * + * This function sets the data pointer in the same way that + * [`wasmer_instance_context_data_set`] does. + */ +wasmer_result_t wasmer_emscripten_set_up(wasmer_instance_t *instance, + wasmer_emscripten_globals_t *globals); +#endif + +/** + * Gets export descriptor kind + */ +wasmer_import_export_kind wasmer_export_descriptor_kind(wasmer_export_descriptor_t *export_); + +/** + * Gets name for the export descriptor + */ +wasmer_byte_array wasmer_export_descriptor_name(wasmer_export_descriptor_t *export_descriptor); + +/** + * Gets export descriptors for the given module + * + * The caller owns the object and should call `wasmer_export_descriptors_destroy` to free it. + */ +void wasmer_export_descriptors(const wasmer_module_t *module, + wasmer_export_descriptors_t **export_descriptors); + +/** + * Frees the memory for the given export descriptors + */ +void wasmer_export_descriptors_destroy(wasmer_export_descriptors_t *export_descriptors); + +/** + * Gets export descriptor by index + */ +wasmer_export_descriptor_t *wasmer_export_descriptors_get(wasmer_export_descriptors_t *export_descriptors, + int idx); + +/** + * Gets the length of the export descriptors + */ +int wasmer_export_descriptors_len(wasmer_export_descriptors_t *exports); + +/** + * Calls a `func` with the provided parameters. + * Results are set using the provided `results` pointer. + * + * Returns `wasmer_result_t::WASMER_OK` upon success. + * + * Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` + * and `wasmer_last_error_message` to get an error message. + */ +wasmer_result_t wasmer_export_func_call(const wasmer_export_func_t *func, + const wasmer_value_t *params, + unsigned int params_len, + wasmer_value_t *results, + unsigned int results_len); + +/** + * Sets the params buffer to the parameter types of the given wasmer_export_func_t + * + * Returns `wasmer_result_t::WASMER_OK` upon success. + * + * Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` + * and `wasmer_last_error_message` to get an error message. + */ +wasmer_result_t wasmer_export_func_params(const wasmer_export_func_t *func, + wasmer_value_tag *params, + uint32_t params_len); + +/** + * Sets the result parameter to the arity of the params of the wasmer_export_func_t + * + * Returns `wasmer_result_t::WASMER_OK` upon success. + * + * Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` + * and `wasmer_last_error_message` to get an error message. + */ +wasmer_result_t wasmer_export_func_params_arity(const wasmer_export_func_t *func, uint32_t *result); + +/** + * Sets the returns buffer to the parameter types of the given wasmer_export_func_t + * + * Returns `wasmer_result_t::WASMER_OK` upon success. + * + * Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` + * and `wasmer_last_error_message` to get an error message. + */ +wasmer_result_t wasmer_export_func_returns(const wasmer_export_func_t *func, + wasmer_value_tag *returns, + uint32_t returns_len); + +/** + * Sets the result parameter to the arity of the returns of the wasmer_export_func_t + * + * Returns `wasmer_result_t::WASMER_OK` upon success. + * + * Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` + * and `wasmer_last_error_message` to get an error message. + */ +wasmer_result_t wasmer_export_func_returns_arity(const wasmer_export_func_t *func, + uint32_t *result); + +/** + * Gets wasmer_export kind + */ +wasmer_import_export_kind wasmer_export_kind(wasmer_export_t *export_); + +/** + * Gets name from wasmer_export + */ +wasmer_byte_array wasmer_export_name(wasmer_export_t *export_); + +/** + * Gets export func from export + */ +const wasmer_export_func_t *wasmer_export_to_func(const wasmer_export_t *export_); + +/** + * Gets a memory pointer from an export pointer. + * + * Returns `wasmer_result_t::WASMER_OK` upon success. + * + * Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` + * and `wasmer_last_error_message` to get an error message. + */ +wasmer_result_t wasmer_export_to_memory(const wasmer_export_t *export_, wasmer_memory_t **memory); + +/** + * Frees the memory for the given exports. + * + * Check the `wasmer_instance_exports()` function to get a complete + * example. + * + * If `exports` is a null pointer, this function does nothing. + * + * Example: + * + * ```c + * // Get some exports. + * wasmer_exports_t *exports = NULL; + * wasmer_instance_exports(instance, &exports); + * + * // Destroy the exports. + * wasmer_exports_destroy(exports); + * ``` + */ +void wasmer_exports_destroy(wasmer_exports_t *exports); + +/** + * Gets wasmer_export by index + */ +wasmer_export_t *wasmer_exports_get(wasmer_exports_t *exports, int idx); + +/** + * Gets the length of the exports + */ +int wasmer_exports_len(wasmer_exports_t *exports); + +/** + * Frees memory for the given Global + */ +void wasmer_global_destroy(wasmer_global_t *global); + +/** + * Gets the value stored by the given Global + */ +wasmer_value_t wasmer_global_get(wasmer_global_t *global); + +/** + * Returns a descriptor (type, mutability) of the given Global + */ +wasmer_global_descriptor_t wasmer_global_get_descriptor(wasmer_global_t *global); + +/** + * Creates a new Global and returns a pointer to it. + * The caller owns the object and should call `wasmer_global_destroy` to free it. + */ +wasmer_global_t *wasmer_global_new(wasmer_value_t value, bool mutable_); + +/** + * Sets the value stored by the given Global + */ +void wasmer_global_set(wasmer_global_t *global, wasmer_value_t value); + +/** + * Gets export descriptor kind + */ +wasmer_import_export_kind wasmer_import_descriptor_kind(wasmer_import_descriptor_t *export_); + +/** + * Gets module name for the import descriptor + */ +wasmer_byte_array wasmer_import_descriptor_module_name(wasmer_import_descriptor_t *import_descriptor); + +/** + * Gets name for the import descriptor + */ +wasmer_byte_array wasmer_import_descriptor_name(wasmer_import_descriptor_t *import_descriptor); + +/** + * Gets import descriptors for the given module + * + * The caller owns the object and should call `wasmer_import_descriptors_destroy` to free it. + */ +void wasmer_import_descriptors(const wasmer_module_t *module, + wasmer_import_descriptors_t **import_descriptors); + +/** + * Frees the memory for the given import descriptors + */ +void wasmer_import_descriptors_destroy(wasmer_import_descriptors_t *import_descriptors); + +/** + * Gets import descriptor by index + */ +wasmer_import_descriptor_t *wasmer_import_descriptors_get(wasmer_import_descriptors_t *import_descriptors, + unsigned int idx); + +/** + * Gets the length of the import descriptors + */ +unsigned int wasmer_import_descriptors_len(wasmer_import_descriptors_t *exports); + +/** + * Frees memory for the given Func + */ +void wasmer_import_func_destroy(wasmer_import_func_t *func); + +/** + * Creates new host function, aka imported function. `func` is a + * function pointer, where the first argument is the famous `vm::Ctx` + * (in Rust), or `wasmer_instance_context_t` (in C). All arguments + * must be typed with compatible WebAssembly native types: + * + * | WebAssembly type | C/C++ type | + * | ---------------- | ---------- | + * | `i32` | `int32_t` | + * | `i64` | `int64_t` | + * | `f32` | `float` | + * | `f64` | `double` | + * + * The function pointer must have a lifetime greater than the + * WebAssembly instance lifetime. + * + * The caller owns the object and should call + * `wasmer_import_func_destroy` to free it. + */ +wasmer_import_func_t *wasmer_import_func_new(void (*func)(void *data), + const wasmer_value_tag *params, + unsigned int params_len, + const wasmer_value_tag *returns, + unsigned int returns_len); + +/** + * Sets the params buffer to the parameter types of the given wasmer_import_func_t + * + * Returns `wasmer_result_t::WASMER_OK` upon success. + * + * Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` + * and `wasmer_last_error_message` to get an error message. + */ +wasmer_result_t wasmer_import_func_params(const wasmer_import_func_t *func, + wasmer_value_tag *params, + unsigned int params_len); + +/** + * Sets the result parameter to the arity of the params of the wasmer_import_func_t + * + * Returns `wasmer_result_t::WASMER_OK` upon success. + * + * Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` + * and `wasmer_last_error_message` to get an error message. + */ +wasmer_result_t wasmer_import_func_params_arity(const wasmer_import_func_t *func, uint32_t *result); + +/** + * Sets the returns buffer to the parameter types of the given wasmer_import_func_t + * + * Returns `wasmer_result_t::WASMER_OK` upon success. + * + * Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` + * and `wasmer_last_error_message` to get an error message. + */ +wasmer_result_t wasmer_import_func_returns(const wasmer_import_func_t *func, + wasmer_value_tag *returns, + unsigned int returns_len); + +/** + * Sets the result parameter to the arity of the returns of the wasmer_import_func_t + * + * Returns `wasmer_result_t::WASMER_OK` upon success. + * + * Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` + * and `wasmer_last_error_message` to get an error message. + */ +wasmer_result_t wasmer_import_func_returns_arity(const wasmer_import_func_t *func, + uint32_t *result); + +/** + * Frees memory of the given ImportObject + */ +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, + 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(void); + +/** + * Calls an exported function of a WebAssembly instance by `name` + * with the provided parameters. The exported function results are + * stored on the provided `results` pointer. + * + * This function returns `wasmer_result_t::WASMER_OK` upon success, + * `wasmer_result_t::WASMER_ERROR` otherwise. You can use + * `wasmer_last_error_message()` to get the generated error message. + * + * Potential errors are the following: + * + * * `instance` is a null pointer, + * * `name` is a null pointer, + * * `params` is a null pointer. + * + * Example of calling an exported function that needs two parameters, and returns one value: + * + * ```c + * // First argument. + * wasmer_value_t argument_one = { + * .tag = WASM_I32, + * .value.I32 = 3, + * }; + * + * // Second argument. + * wasmer_value_t argument_two = { + * .tag = WASM_I32, + * .value.I32 = 4, + * }; + * + * // First result. + * wasmer_value_t result_one; + * + * // All arguments and results. + * wasmer_value_t arguments[] = {argument_one, argument_two}; + * wasmer_value_t results[] = {result_one}; + * + * wasmer_result_t call_result = wasmer_instance_call( + * instance, // instance pointer + * "sum", // the exported function name + * arguments, // the arguments + * 2, // the number of arguments + * results, // the results + * 1 // the number of results + * ); + * + * if (call_result == WASMER_OK) { + * printf("Result is: %d\n", results[0].value.I32); + * } + * ``` + */ +wasmer_result_t wasmer_instance_call(wasmer_instance_t *instance, + const char *name, + const wasmer_value_t *params, + uint32_t params_len, + wasmer_value_t *results, + uint32_t results_len); + +/** + * Gets the data that can be hold by an instance. + * + * This function is complementary of + * `wasmer_instance_context_data_set()`. Please read its + * documentation. You can also read the documentation of + * `wasmer_instance_context_t` to get other examples. + * + * This function returns nothing if `ctx` is a null pointer. + */ +void *wasmer_instance_context_data_get(const wasmer_instance_context_t *ctx); + +/** + * Sets the data that can be hold by an instance context. + * + * An instance context (represented by the opaque + * `wasmer_instance_context_t` structure) can hold user-defined + * data. This function sets the data. This function is complementary + * of `wasmer_instance_context_data_get()`. + * + * This function does nothing if `instance` is a null pointer. + * + * Example: + * + * ```c + * // Define your own data. + * typedef struct { + * // … + * } my_data; + * + * // Allocate them and set them on the given instance. + * my_data *data = malloc(sizeof(my_data)); + * data->… = …; + * wasmer_instance_context_data_set(instance, (void*) data); + * + * // You can read your data. + * { + * my_data *data = (my_data*) wasmer_instance_context_data_get(wasmer_instance_context_get(instance)); + * // … + * } + * ``` + */ +void wasmer_instance_context_data_set(wasmer_instance_t *instance, + void *data_ptr); + +/** + * Returns the instance context. Learn more by looking at the + * `wasmer_instance_context_t` struct. + * + * This function returns `null` if `instance` is a null pointer. + * + * Example: + * + * ```c + * const wasmer_instance_context_get *context = wasmer_instance_context_get(instance); + * my_data *data = (my_data *) wasmer_instance_context_data_get(context); + * // Do something with `my_data`. + * ``` + * + * It is often useful with `wasmer_instance_context_data_set()`. + */ +const wasmer_instance_context_t *wasmer_instance_context_get(wasmer_instance_t *instance); + +/** + * Gets the `memory_idx`th memory of the instance. + * + * Note that the index is always `0` until multiple memories are supported. + * + * This function is mostly used inside host functions (aka imported + * functions) to read the instance memory. + * + * Example of a _host function_ that reads and prints a string based on a pointer and a length: + * + * ```c + * void print_string(const wasmer_instance_context_t *context, int32_t pointer, int32_t length) { + * // Get the 0th memory. + * const wasmer_memory_t *memory = wasmer_instance_context_memory(context, 0); + * + * // Get the memory data as a pointer. + * uint8_t *memory_bytes = wasmer_memory_data(memory); + * + * // Print what we assumed to be a string! + * printf("%.*s", length, memory_bytes + pointer); + * } + * ``` + */ +const wasmer_memory_t *wasmer_instance_context_memory(const wasmer_instance_context_t *ctx, + uint32_t _memory_idx); + +/** + * Frees memory for the given `wasmer_instance_t`. + * + * Check the `wasmer_instantiate()` function to get a complete + * example. + * + * If `instance` is a null pointer, this function does nothing. + * + * Example: + * + * ```c + * // Get an instance. + * wasmer_instance_t *instance = NULL; + * wasmer_instantiate(&instance, bytes, bytes_length, imports, 0); + * + * // Destroy the instance. + * wasmer_instance_destroy(instance); + * ``` + */ +void wasmer_instance_destroy(wasmer_instance_t *instance); + +/** + * Gets all the exports of the given WebAssembly instance. + * + * This function stores a Rust vector of exports into `exports` as an + * opaque pointer of kind `wasmer_exports_t`. + * + * As is, you can do anything with `exports` except using the + * companion functions, like `wasmer_exports_len()`, + * `wasmer_exports_get()` or `wasmer_export_kind()`. See the example below. + * + * **Warning**: The caller owns the object and should call + * `wasmer_exports_destroy()` to free it. + * + * Example: + * + * ```c + * // Get the exports. + * wasmer_exports_t *exports = NULL; + * wasmer_instance_exports(instance, &exports); + * + * // Get the number of exports. + * int exports_length = wasmer_exports_len(exports); + * printf("Number of exports: %d\n", exports_length); + * + * // Read the first export. + * wasmer_export_t *export = wasmer_exports_get(exports, 0); + * + * // Get the kind of the export. + * wasmer_import_export_kind export_kind = wasmer_export_kind(export); + * + * // Assert it is a function (why not). + * assert(export_kind == WASM_FUNCTION); + * + * // Read the export name. + * wasmer_byte_array name_bytes = wasmer_export_name(export); + * + * assert(name_bytes.bytes_len == sizeof("sum") - 1); + * assert(memcmp(name_bytes.bytes, "sum", sizeof("sum") - 1) == 0); + * + * // Destroy the exports. + * wasmer_exports_destroy(exports); + * ``` + */ +void wasmer_instance_exports(wasmer_instance_t *instance, wasmer_exports_t **exports); + +/** + * Creates a new WebAssembly instance from the given bytes and imports. + * + * The result is stored in the first argument `instance` if + * successful, i.e. when the function returns + * `wasmer_result_t::WASMER_OK`. Otherwise + * `wasmer_result_t::WASMER_ERROR` is returned, and + * `wasmer_last_error_length()` with `wasmer_last_error_message()` must + * be used to read the error message. + * + * The caller is responsible to free the instance with + * `wasmer_instance_destroy()`. + * + * Example: + * + * ```c + * // 1. Read a WebAssembly module from a file. + * FILE *file = fopen("sum.wasm", "r"); + * fseek(file, 0, SEEK_END); + * long bytes_length = ftell(file); + * uint8_t *bytes = malloc(bytes_length); + * fseek(file, 0, SEEK_SET); + * fread(bytes, 1, bytes_length, file); + * fclose(file); + * + * // 2. Declare the imports (here, none). + * wasmer_import_t imports[] = {}; + * + * // 3. Instantiate the WebAssembly module. + * wasmer_instance_t *instance = NULL; + * wasmer_result_t result = wasmer_instantiate(&instance, bytes, bytes_length, imports, 0); + * + * // 4. Check for errors. + * if (result != WASMER_OK) { + * int error_length = wasmer_last_error_length(); + * char *error = malloc(error_length); + * wasmer_last_error_message(error, error_length); + * // Do something with `error`… + * } + * + * // 5. Free the memory! + * wasmer_instance_destroy(instance); + * ``` + */ +wasmer_result_t wasmer_instantiate(wasmer_instance_t **instance, + uint8_t *wasm_bytes, + uint32_t wasm_bytes_len, + wasmer_import_t *imports, + int imports_len); + +/** + * Gets the length in bytes of the last error if any. + * + * This can be used to dynamically allocate a buffer with the correct number of + * bytes needed to store a message. + * + * See `wasmer_last_error_message()` to get a full example. + */ +int wasmer_last_error_length(void); + +/** + * Gets the last error message if any into the provided buffer + * `buffer` up to the given `length`. + * + * The `length` parameter must be large enough to store the last + * error message. Ideally, the value should come from + * `wasmer_last_error_length()`. + * + * The function returns the length of the string in bytes, `-1` if an + * error occurs. Potential errors are: + * + * * The buffer is a null pointer, + * * The buffer is too smal to hold the error message. + * + * Note: The error message always has a trailing null character. + * + * Example: + * + * ```c + * int error_length = wasmer_last_error_length(); + * + * if (error_length > 0) { + * char *error_message = malloc(error_length); + * wasmer_last_error_message(error_message, error_length); + * printf("Error message: `%s`\n", error_message); + * } else { + * printf("No error message\n"); + * } + * ``` + */ +int wasmer_last_error_message(char *buffer, int length); + +/** + * Gets a pointer to the beginning of the contiguous memory data + * bytes. + * + * The function returns `NULL` if `memory` is a null pointer. + * + * Note that when the memory grows, it can be reallocated, and thus + * the returned pointer can be invalidated. + * + * Example: + * + * ```c + * uint8_t *memory_data = wasmer_memory_data(memory); + * char *str = (char*) malloc(sizeof(char) * 7); + * + * for (uint32_t nth = 0; nth < 7; ++nth) { + * str[nth] = (char) memory_data[nth]; + * } + * ``` + */ +uint8_t *wasmer_memory_data(const wasmer_memory_t *memory); + +/** + * Gets the size in bytes of the memory data. + * + * This function returns 0 if `memory` is a null pointer. + * + * Example: + * + * ```c + * uint32_t memory_data_length = wasmer_memory_data_length(memory); + * ``` + */ +uint32_t wasmer_memory_data_length(const wasmer_memory_t *memory); + +/** + * Frees memory for the given `wasmer_memory_t`. + * + * Check the `wasmer_memory_new()` function to get a complete + * example. + * + * If `memory` is a null pointer, this function does nothing. + * + * Example: + * + * ```c + * // Get a memory. + * wasmer_memory_t *memory = NULL; + * wasmer_result_t result = wasmer_memory_new(&memory, memory_descriptor); + * + * // Destroy the memory. + * wasmer_memory_destroy(memory); + * ``` + */ +void wasmer_memory_destroy(wasmer_memory_t *memory); + +/** + * Grows a memory by the given number of pages (of 65Kb each). + * + * The functions return `wasmer_result_t::WASMER_OK` upon success, + * `wasmer_result_t::WASMER_ERROR` otherwise. Use + * `wasmer_last_error_length()` with `wasmer_last_error_message()` to + * read the error message. + * + * Example: + * + * ```c + * wasmer_result_t result = wasmer_memory_grow(memory, 10); + * + * if (result != WASMER_OK) { + * // … + * } + * ``` + */ +wasmer_result_t wasmer_memory_grow(wasmer_memory_t *memory, uint32_t delta); + +/** + * Reads the current length (in pages) of the given memory. + * + * The function returns zero if `memory` is a null pointer. + * + * Example: + * + * ```c + * uint32_t memory_length = wasmer_memory_length(memory); + * + * printf("Memory pages length: %d\n", memory_length); + * ``` + */ +uint32_t wasmer_memory_length(const wasmer_memory_t *memory); + +/** + * Creates a new empty WebAssembly memory for the given descriptor. + * + * The result is stored in the first argument `memory` if successful, + * i.e. when the function returns + * `wasmer_result_t::WASMER_OK`. Otherwise, + * `wasmer_result_t::WASMER_ERROR` is returned, and + * `wasmer_last_error_length()` with `wasmer_last_error_message()` + * must be used to read the error message. + * + * The caller owns the memory and is responsible to free it with + * `wasmer_memory_destroy()`. + * + * Example: + * + * ```c + * // 1. The memory object. + * wasmer_memory_t *memory = NULL; + * + * // 2. The memory descriptor. + * wasmer_limits_t memory_descriptor = { + * .min = 10, + * .max = { + * .has_some = true, + * .some = 15, + * }, + * }; + * + * // 3. Initialize the memory. + * wasmer_result_t result = wasmer_memory_new(&memory, memory_descriptor); + * + * if (result != WASMER_OK) { + * int error_length = wasmer_last_error_length(); + * char *error = malloc(error_length); + * wasmer_last_error_message(error, error_length); + * // Do something with `error`… + * } + * + * // 4. Free the memory! + * wasmer_memory_destroy(memory); + * ``` + */ +wasmer_result_t wasmer_memory_new(wasmer_memory_t **memory, wasmer_limits_t limits); + +/** + * Deserialize the given serialized module. + * + * Returns `wasmer_result_t::WASMER_OK` upon success. + * + * Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` + * and `wasmer_last_error_message` to get an error message. + */ +wasmer_result_t wasmer_module_deserialize(wasmer_module_t **module, + const wasmer_serialized_module_t *serialized_module); + +/** + * Frees memory for the given Module + */ +void wasmer_module_destroy(wasmer_module_t *module); + +/** + * Given: + * * A prepared `wasmer` import-object + * * A compiled wasmer module + * + * Instantiates a wasmer instance + */ +wasmer_result_t wasmer_module_import_instantiate(wasmer_instance_t **instance, + const wasmer_module_t *module, + const wasmer_import_object_t *import_object); + +/** + * Creates a new Instance from the given module and imports. + * + * Returns `wasmer_result_t::WASMER_OK` upon success. + * + * Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` + * and `wasmer_last_error_message` to get an error message. + */ +wasmer_result_t wasmer_module_instantiate(const wasmer_module_t *module, + wasmer_instance_t **instance, + wasmer_import_t *imports, + int imports_len); + +/** + * Serialize the given Module. + * + * The caller owns the object and should call `wasmer_serialized_module_destroy` to free it. + * + * Returns `wasmer_result_t::WASMER_OK` upon success. + * + * Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` + * and `wasmer_last_error_message` to get an error message. + */ +wasmer_result_t wasmer_module_serialize(wasmer_serialized_module_t **serialized_module_out, + const wasmer_module_t *module); + +/** + * Get bytes of the serialized module. + */ +wasmer_byte_array wasmer_serialized_module_bytes(const wasmer_serialized_module_t *serialized_module); + +/** + * Frees memory for the given serialized Module. + */ +void wasmer_serialized_module_destroy(wasmer_serialized_module_t *serialized_module); + +/** + * Transform a sequence of bytes into a serialized module. + * + * The caller owns the object and should call `wasmer_serialized_module_destroy` to free it. + * + * Returns `wasmer_result_t::WASMER_OK` upon success. + * + * Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` + * and `wasmer_last_error_message` to get an error message. + */ +wasmer_result_t wasmer_serialized_module_from_bytes(wasmer_serialized_module_t **serialized_module, + const uint8_t *serialized_module_bytes, + uint32_t serialized_module_bytes_length); + +/** + * Frees memory for the given Table + */ +void wasmer_table_destroy(wasmer_table_t *table); + +/** + * Grows a Table by the given number of elements. + * + * Returns `wasmer_result_t::WASMER_OK` upon success. + * + * Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` + * and `wasmer_last_error_message` to get an error message. + */ +wasmer_result_t wasmer_table_grow(wasmer_table_t *table, uint32_t delta); + +/** + * Returns the current length of the given Table + */ +uint32_t wasmer_table_length(wasmer_table_t *table); + +/** + * Creates a new Table for the given descriptor and initializes the given + * pointer to pointer to a pointer to the new Table. + * + * The caller owns the object and should call `wasmer_table_destroy` to free it. + * + * Returns `wasmer_result_t::WASMER_OK` upon success. + * + * Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` + * and `wasmer_last_error_message` to get an error message. + */ +wasmer_result_t wasmer_table_new(wasmer_table_t **table, wasmer_limits_t limits); + +/** + * Stop the execution of a host function, aka imported function. The + * function must be used _only_ inside a host function. + * + * The pointer to `wasmer_instance_context_t` is received by the host + * function as its first argument. Just passing it to `ctx` is fine. + * + * The error message must have a greater lifetime than the host + * function itself since the error is read outside the host function + * with `wasmer_last_error_message`. + * + * This function returns `wasmer_result_t::WASMER_ERROR` if `ctx` or + * `error_message` are null. + * + * This function never returns otherwise. + */ +wasmer_result_t wasmer_trap(const wasmer_instance_context_t *ctx, const char *error_message); + +/** + * Validates a sequence of bytes hoping it represents a valid WebAssembly module. + * + * The function returns true if the bytes are valid, false otherwise. + * + * Example: + * + * ```c + * bool result = wasmer_validate(bytes, bytes_length); + * + * if (false == result) { + * // Do something… + * } + * ``` + */ +bool wasmer_validate(const uint8_t *wasm_bytes, uint32_t wasm_bytes_len); + +#if defined(WASMER_WASI_ENABLED) +/** + * 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); +#endif + +#if defined(WASMER_WASI_ENABLED) +/** + * 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 + +#if defined(WASMER_WASI_ENABLED) +/** + * Creates a WASI import object for a specific version. + * + * This function is similar to `wasmer_wasi_generate_import_object` + * except that the first argument describes the WASI version. + * + * The version is expected to be of kind `Version`. + */ +wasmer_import_object_t *wasmer_wasi_generate_import_object_for_version(unsigned char version, + const wasmer_byte_array *args, + unsigned int args_len, + const wasmer_byte_array *envs, + unsigned int envs_len, + const wasmer_byte_array *preopened_files, + unsigned int preopened_files_len, + const wasmer_wasi_map_dir_entry_t *mapped_dirs, + unsigned int mapped_dirs_len); +#endif + +#if defined(WASMER_WASI_ENABLED) +/** + * Find the version of WASI used by the module. + * + * In case of error, the returned version is `Version::Unknown`. + */ +Version wasmer_wasi_get_version(const wasmer_module_t *module); +#endif + +#endif /* WASMER_H */ diff --git a/lib/c-api/wasmer.hh b/lib/c-api/wasmer.hh new file mode 100644 index 000000000..535118866 --- /dev/null +++ b/lib/c-api/wasmer.hh @@ -0,0 +1,1220 @@ + +#if !defined(WASMER_H_MACROS) + +#define WASMER_H_MACROS + +// Define the `ARCH_X86_X64` constant. +#if defined(MSVC) && defined(_M_AMD64) +# define ARCH_X86_64 +#elif (defined(GCC) || defined(__GNUC__) || defined(__clang__)) && defined(__x86_64__) +# define ARCH_X86_64 +#endif + +// Compatibility with non-Clang compilers. +#if !defined(__has_attribute) +# define __has_attribute(x) 0 +#endif + +// Compatibility with non-Clang compilers. +#if !defined(__has_declspec_attribute) +# define __has_declspec_attribute(x) 0 +#endif + +// Define the `DEPRECATED` macro. +#if defined(GCC) || defined(__GNUC__) || __has_attribute(deprecated) +# define DEPRECATED(message) __attribute__((deprecated(message))) +#elif defined(MSVC) || __has_declspec_attribute(deprecated) +# define DEPRECATED(message) __declspec(deprecated(message)) +#endif + +#define WASMER_WASI_ENABLED +#endif // WASMER_H_MACROS + + +#ifndef WASMER_H +#define WASMER_H + +#include +#include +#include +#include + +#if defined(WASMER_WASI_ENABLED) +enum class Version : uint8_t { +#if defined(WASMER_WASI_ENABLED) + /// Version cannot be detected or is unknown. + Unknown = 0, +#endif +#if defined(WASMER_WASI_ENABLED) + /// Latest version. See `wasmer_wasi::WasiVersion::Latest` to + /// learn more. + Latest = 1, +#endif +#if defined(WASMER_WASI_ENABLED) + /// `wasi_unstable`. + Snapshot0 = 2, +#endif +#if defined(WASMER_WASI_ENABLED) + /// `wasi_snapshot_preview1`. + Snapshot1 = 3, +#endif +}; +#endif + +/// List of export/import kinds. +enum class wasmer_import_export_kind : uint32_t { + /// The export/import is a function. + WASM_FUNCTION = 0, + /// The export/import is a global. + WASM_GLOBAL = 1, + /// The export/import is a memory. + WASM_MEMORY = 2, + /// The export/import is a table. + WASM_TABLE = 3, +}; + +/// The `wasmer_result_t` enum is a type that represents either a +/// success, or a failure. +enum class wasmer_result_t { + /// Represents a success. + WASMER_OK = 1, + /// Represents a failure. + WASMER_ERROR = 2, +}; + +/// Represents all possibles WebAssembly value types. +/// +/// See `wasmer_value_t` to get a complete example. +enum class wasmer_value_tag : uint32_t { + /// Represents the `i32` WebAssembly type. + WASM_I32, + /// Represents the `i64` WebAssembly type. + WASM_I64, + /// Represents the `f32` WebAssembly type. + WASM_F32, + /// Represents the `f64` WebAssembly type. + WASM_F64, +}; + +struct wasmer_module_t { + +}; + +/// Opaque pointer to a `wasmer_runtime::Instance` value in Rust. +/// +/// A `wasmer_runtime::Instance` represents a WebAssembly instance. It +/// is generally generated by the `wasmer_instantiate()` function, or by +/// the `wasmer_module_instantiate()` function for the most common paths. +struct wasmer_instance_t { + +}; + +struct wasmer_byte_array { + const uint8_t *bytes; + uint32_t bytes_len; +}; + +#if defined(WASMER_EMSCRIPTEN_ENABLED) +/// Type used to construct an import_object_t with Emscripten imports. +struct wasmer_emscripten_globals_t { + +}; +#endif + +struct wasmer_import_object_t { + +}; + +/// Opaque pointer to `NamedExportDescriptor`. +struct wasmer_export_descriptor_t { + +}; + +/// Opaque pointer to `NamedExportDescriptors`. +struct wasmer_export_descriptors_t { + +}; + +/// Opaque pointer to `wasmer_export_t`. +struct wasmer_export_func_t { + +}; + +/// Represents a WebAssembly value. +/// +/// This is a [Rust union][rust-union], which is equivalent to the C +/// union. See `wasmer_value_t` to get a complete example. +/// +/// [rust-union]: https://doc.rust-lang.org/reference/items/unions.html +union wasmer_value { + int32_t I32; + int64_t I64; + float F32; + double F64; +}; + +/// Represents a WebAssembly type and value pair, +/// i.e. `wasmer_value_tag` and `wasmer_value`. Since the latter is an +/// union, it's the safe way to read or write a WebAssembly value in +/// C. +/// +/// Example: +/// +/// ```c +/// // Create a WebAssembly value. +/// wasmer_value_t wasm_value = { +/// .tag = WASM_I32, +/// .value.I32 = 42, +/// }; +/// +/// // Read a WebAssembly value. +/// if (wasm_value.tag == WASM_I32) { +/// int32_t x = wasm_value.value.I32; +/// // … +/// } +/// ``` +struct wasmer_value_t { + /// The value type. + wasmer_value_tag tag; + /// The value. + wasmer_value value; +}; + +/// Opaque pointer to `ImportType`. +struct wasmer_export_t { + +}; + +/// Opaque pointer to a `wasmer_runtime::Memory` value in Rust. +/// +/// A `wasmer_runtime::Memory` represents a WebAssembly memory. It is +/// possible to create one with `wasmer_memory_new()` and pass it as +/// imports of an instance, or to read it from exports of an instance +/// with `wasmer_export_to_memory()`. +struct wasmer_memory_t { + +}; + +/// Opaque pointer to the opaque structure `crate::NamedExports`, +/// which is a wrapper around a vector of the opaque structure +/// `crate::NamedExport`. +/// +/// Check the `wasmer_instance_exports()` function to learn more. +struct wasmer_exports_t { + +}; + +struct wasmer_global_t { + +}; + +struct wasmer_global_descriptor_t { + bool mutable_; + wasmer_value_tag kind; +}; + +struct wasmer_import_descriptor_t { + +}; + +struct wasmer_import_descriptors_t { + +}; + +struct wasmer_import_func_t { + +}; + +struct wasmer_table_t { + +}; + +/// Union of import/export value. +union wasmer_import_export_value { + const wasmer_import_func_t *func; + const wasmer_table_t *table; + const wasmer_memory_t *memory; + const wasmer_global_t *global; +}; + +struct wasmer_import_t { + wasmer_byte_array module_name; + wasmer_byte_array import_name; + wasmer_import_export_kind tag; + wasmer_import_export_value value; +}; + +struct wasmer_import_object_iter_t { + +}; + +/// Opaque pointer to a `wasmer_runtime::Ctx` value in Rust. +/// +/// An instance context is passed to any host function (aka imported +/// function) as the first argument. It is necessary to read the +/// instance data or the memory, respectively with the +/// `wasmer_instance_context_data_get()` function, and the +/// `wasmer_instance_context_memory()` function. +/// +/// It is also possible to get the instance context outside a host +/// function by using the `wasmer_instance_context_get()` +/// function. See also `wasmer_instance_context_data_set()` to set the +/// instance context data. +/// +/// Example: +/// +/// ```c +/// // A host function that prints data from the WebAssembly memory to +/// // the standard output. +/// void print(wasmer_instance_context_t *context, int32_t pointer, int32_t length) { +/// // Use `wasmer_instance_context` to get back the first instance memory. +/// const wasmer_memory_t *memory = wasmer_instance_context_memory(context, 0); +/// +/// // Continue… +/// } +/// ``` +struct wasmer_instance_context_t { + +}; + +/// The `wasmer_limit_option_t` struct represents an optional limit +/// for `wasmer_limits_t`. +struct wasmer_limit_option_t { + /// Whether the limit is set. + bool has_some; + /// The limit value. + uint32_t some; +}; + +/// The `wasmer_limits_t` struct is a type that describes the limits of something +/// such as a memory or a table. See the `wasmer_memory_new()` function to get +/// more information. +struct wasmer_limits_t { + /// The minimum number of allowed pages. + uint32_t min; + /// The maximum number of allowed pages. + wasmer_limit_option_t max; +}; + +struct wasmer_serialized_module_t { + +}; + +#if defined(WASMER_WASI_ENABLED) +/// 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; +}; +#endif + +extern "C" { + +/// Creates a new Module from the given wasm bytes. +/// +/// Returns `wasmer_result_t::WASMER_OK` upon success. +/// +/// Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` +/// and `wasmer_last_error_message` to get an error message. +wasmer_result_t wasmer_compile(wasmer_module_t **module, + uint8_t *wasm_bytes, + uint32_t wasm_bytes_len); + +#if defined(WASMER_EMSCRIPTEN_ENABLED) +/// Convenience function for setting up arguments and calling the Emscripten +/// main function. +/// +/// WARNING: +/// +/// Do not call this function on untrusted code when operating without +/// additional sandboxing in place. +/// Emscripten has access to many host system calls and therefore may do very +/// bad things. +wasmer_result_t wasmer_emscripten_call_main(wasmer_instance_t *instance, + const wasmer_byte_array *args, + unsigned int args_len); +#endif + +#if defined(WASMER_EMSCRIPTEN_ENABLED) +/// Destroy `wasmer_emscrpten_globals_t` created by +/// `wasmer_emscripten_get_emscripten_globals`. +void wasmer_emscripten_destroy_globals(wasmer_emscripten_globals_t *globals); +#endif + +#if defined(WASMER_EMSCRIPTEN_ENABLED) +/// Create a `wasmer_import_object_t` with Emscripten imports, use +/// `wasmer_emscripten_get_emscripten_globals` to get a +/// `wasmer_emscripten_globals_t` from a `wasmer_module_t`. +/// +/// WARNING: +/// +/// This `import_object_t` contains thin-wrappers around host system calls. +/// Do not use this to execute untrusted code without additional sandboxing. +wasmer_import_object_t *wasmer_emscripten_generate_import_object(wasmer_emscripten_globals_t *globals); +#endif + +#if defined(WASMER_EMSCRIPTEN_ENABLED) +/// Create a `wasmer_emscripten_globals_t` from a Wasm module. +wasmer_emscripten_globals_t *wasmer_emscripten_get_globals(const wasmer_module_t *module); +#endif + +#if defined(WASMER_EMSCRIPTEN_ENABLED) +/// Execute global constructors (required if the module is compiled from C++) +/// and sets up the internal environment. +/// +/// This function sets the data pointer in the same way that +/// [`wasmer_instance_context_data_set`] does. +wasmer_result_t wasmer_emscripten_set_up(wasmer_instance_t *instance, + wasmer_emscripten_globals_t *globals); +#endif + +/// Gets export descriptor kind +wasmer_import_export_kind wasmer_export_descriptor_kind(wasmer_export_descriptor_t *export_); + +/// Gets name for the export descriptor +wasmer_byte_array wasmer_export_descriptor_name(wasmer_export_descriptor_t *export_descriptor); + +/// Gets export descriptors for the given module +/// +/// The caller owns the object and should call `wasmer_export_descriptors_destroy` to free it. +void wasmer_export_descriptors(const wasmer_module_t *module, + wasmer_export_descriptors_t **export_descriptors); + +/// Frees the memory for the given export descriptors +void wasmer_export_descriptors_destroy(wasmer_export_descriptors_t *export_descriptors); + +/// Gets export descriptor by index +wasmer_export_descriptor_t *wasmer_export_descriptors_get(wasmer_export_descriptors_t *export_descriptors, + int idx); + +/// Gets the length of the export descriptors +int wasmer_export_descriptors_len(wasmer_export_descriptors_t *exports); + +/// Calls a `func` with the provided parameters. +/// Results are set using the provided `results` pointer. +/// +/// Returns `wasmer_result_t::WASMER_OK` upon success. +/// +/// Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` +/// and `wasmer_last_error_message` to get an error message. +wasmer_result_t wasmer_export_func_call(const wasmer_export_func_t *func, + const wasmer_value_t *params, + unsigned int params_len, + wasmer_value_t *results, + unsigned int results_len); + +/// Sets the params buffer to the parameter types of the given wasmer_export_func_t +/// +/// Returns `wasmer_result_t::WASMER_OK` upon success. +/// +/// Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` +/// and `wasmer_last_error_message` to get an error message. +wasmer_result_t wasmer_export_func_params(const wasmer_export_func_t *func, + wasmer_value_tag *params, + uint32_t params_len); + +/// Sets the result parameter to the arity of the params of the wasmer_export_func_t +/// +/// Returns `wasmer_result_t::WASMER_OK` upon success. +/// +/// Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` +/// and `wasmer_last_error_message` to get an error message. +wasmer_result_t wasmer_export_func_params_arity(const wasmer_export_func_t *func, uint32_t *result); + +/// Sets the returns buffer to the parameter types of the given wasmer_export_func_t +/// +/// Returns `wasmer_result_t::WASMER_OK` upon success. +/// +/// Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` +/// and `wasmer_last_error_message` to get an error message. +wasmer_result_t wasmer_export_func_returns(const wasmer_export_func_t *func, + wasmer_value_tag *returns, + uint32_t returns_len); + +/// Sets the result parameter to the arity of the returns of the wasmer_export_func_t +/// +/// Returns `wasmer_result_t::WASMER_OK` upon success. +/// +/// Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` +/// and `wasmer_last_error_message` to get an error message. +wasmer_result_t wasmer_export_func_returns_arity(const wasmer_export_func_t *func, + uint32_t *result); + +/// Gets wasmer_export kind +wasmer_import_export_kind wasmer_export_kind(wasmer_export_t *export_); + +/// Gets name from wasmer_export +wasmer_byte_array wasmer_export_name(wasmer_export_t *export_); + +/// Gets export func from export +const wasmer_export_func_t *wasmer_export_to_func(const wasmer_export_t *export_); + +/// Gets a memory pointer from an export pointer. +/// +/// Returns `wasmer_result_t::WASMER_OK` upon success. +/// +/// Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` +/// and `wasmer_last_error_message` to get an error message. +wasmer_result_t wasmer_export_to_memory(const wasmer_export_t *export_, wasmer_memory_t **memory); + +/// Frees the memory for the given exports. +/// +/// Check the `wasmer_instance_exports()` function to get a complete +/// example. +/// +/// If `exports` is a null pointer, this function does nothing. +/// +/// Example: +/// +/// ```c +/// // Get some exports. +/// wasmer_exports_t *exports = NULL; +/// wasmer_instance_exports(instance, &exports); +/// +/// // Destroy the exports. +/// wasmer_exports_destroy(exports); +/// ``` +void wasmer_exports_destroy(wasmer_exports_t *exports); + +/// Gets wasmer_export by index +wasmer_export_t *wasmer_exports_get(wasmer_exports_t *exports, int idx); + +/// Gets the length of the exports +int wasmer_exports_len(wasmer_exports_t *exports); + +/// Frees memory for the given Global +void wasmer_global_destroy(wasmer_global_t *global); + +/// Gets the value stored by the given Global +wasmer_value_t wasmer_global_get(wasmer_global_t *global); + +/// Returns a descriptor (type, mutability) of the given Global +wasmer_global_descriptor_t wasmer_global_get_descriptor(wasmer_global_t *global); + +/// Creates a new Global and returns a pointer to it. +/// The caller owns the object and should call `wasmer_global_destroy` to free it. +wasmer_global_t *wasmer_global_new(wasmer_value_t value, bool mutable_); + +/// Sets the value stored by the given Global +void wasmer_global_set(wasmer_global_t *global, wasmer_value_t value); + +/// Gets export descriptor kind +wasmer_import_export_kind wasmer_import_descriptor_kind(wasmer_import_descriptor_t *export_); + +/// Gets module name for the import descriptor +wasmer_byte_array wasmer_import_descriptor_module_name(wasmer_import_descriptor_t *import_descriptor); + +/// Gets name for the import descriptor +wasmer_byte_array wasmer_import_descriptor_name(wasmer_import_descriptor_t *import_descriptor); + +/// Gets import descriptors for the given module +/// +/// The caller owns the object and should call `wasmer_import_descriptors_destroy` to free it. +void wasmer_import_descriptors(const wasmer_module_t *module, + wasmer_import_descriptors_t **import_descriptors); + +/// Frees the memory for the given import descriptors +void wasmer_import_descriptors_destroy(wasmer_import_descriptors_t *import_descriptors); + +/// Gets import descriptor by index +wasmer_import_descriptor_t *wasmer_import_descriptors_get(wasmer_import_descriptors_t *import_descriptors, + unsigned int idx); + +/// Gets the length of the import descriptors +unsigned int wasmer_import_descriptors_len(wasmer_import_descriptors_t *exports); + +/// Frees memory for the given Func +void wasmer_import_func_destroy(wasmer_import_func_t *func); + +/// Creates new host function, aka imported function. `func` is a +/// function pointer, where the first argument is the famous `vm::Ctx` +/// (in Rust), or `wasmer_instance_context_t` (in C). All arguments +/// must be typed with compatible WebAssembly native types: +/// +/// | WebAssembly type | C/C++ type | +/// | ---------------- | ---------- | +/// | `i32` | `int32_t` | +/// | `i64` | `int64_t` | +/// | `f32` | `float` | +/// | `f64` | `double` | +/// +/// The function pointer must have a lifetime greater than the +/// WebAssembly instance lifetime. +/// +/// The caller owns the object and should call +/// `wasmer_import_func_destroy` to free it. +wasmer_import_func_t *wasmer_import_func_new(void (*func)(void *data), + const wasmer_value_tag *params, + unsigned int params_len, + const wasmer_value_tag *returns, + unsigned int returns_len); + +/// Sets the params buffer to the parameter types of the given wasmer_import_func_t +/// +/// Returns `wasmer_result_t::WASMER_OK` upon success. +/// +/// Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` +/// and `wasmer_last_error_message` to get an error message. +wasmer_result_t wasmer_import_func_params(const wasmer_import_func_t *func, + wasmer_value_tag *params, + unsigned int params_len); + +/// Sets the result parameter to the arity of the params of the wasmer_import_func_t +/// +/// Returns `wasmer_result_t::WASMER_OK` upon success. +/// +/// Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` +/// and `wasmer_last_error_message` to get an error message. +wasmer_result_t wasmer_import_func_params_arity(const wasmer_import_func_t *func, uint32_t *result); + +/// Sets the returns buffer to the parameter types of the given wasmer_import_func_t +/// +/// Returns `wasmer_result_t::WASMER_OK` upon success. +/// +/// Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` +/// and `wasmer_last_error_message` to get an error message. +wasmer_result_t wasmer_import_func_returns(const wasmer_import_func_t *func, + wasmer_value_tag *returns, + unsigned int returns_len); + +/// Sets the result parameter to the arity of the returns of the wasmer_import_func_t +/// +/// Returns `wasmer_result_t::WASMER_OK` upon success. +/// +/// Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` +/// and `wasmer_last_error_message` to get an error message. +wasmer_result_t wasmer_import_func_returns_arity(const wasmer_import_func_t *func, + uint32_t *result); + +/// Frees memory of the given ImportObject +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, + 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(); + +/// Calls an exported function of a WebAssembly instance by `name` +/// with the provided parameters. The exported function results are +/// stored on the provided `results` pointer. +/// +/// This function returns `wasmer_result_t::WASMER_OK` upon success, +/// `wasmer_result_t::WASMER_ERROR` otherwise. You can use +/// `wasmer_last_error_message()` to get the generated error message. +/// +/// Potential errors are the following: +/// +/// * `instance` is a null pointer, +/// * `name` is a null pointer, +/// * `params` is a null pointer. +/// +/// Example of calling an exported function that needs two parameters, and returns one value: +/// +/// ```c +/// // First argument. +/// wasmer_value_t argument_one = { +/// .tag = WASM_I32, +/// .value.I32 = 3, +/// }; +/// +/// // Second argument. +/// wasmer_value_t argument_two = { +/// .tag = WASM_I32, +/// .value.I32 = 4, +/// }; +/// +/// // First result. +/// wasmer_value_t result_one; +/// +/// // All arguments and results. +/// wasmer_value_t arguments[] = {argument_one, argument_two}; +/// wasmer_value_t results[] = {result_one}; +/// +/// wasmer_result_t call_result = wasmer_instance_call( +/// instance, // instance pointer +/// "sum", // the exported function name +/// arguments, // the arguments +/// 2, // the number of arguments +/// results, // the results +/// 1 // the number of results +/// ); +/// +/// if (call_result == WASMER_OK) { +/// printf("Result is: %d\n", results[0].value.I32); +/// } +/// ``` +wasmer_result_t wasmer_instance_call(wasmer_instance_t *instance, + const char *name, + const wasmer_value_t *params, + uint32_t params_len, + wasmer_value_t *results, + uint32_t results_len); + +/// Gets the data that can be hold by an instance. +/// +/// This function is complementary of +/// `wasmer_instance_context_data_set()`. Please read its +/// documentation. You can also read the documentation of +/// `wasmer_instance_context_t` to get other examples. +/// +/// This function returns nothing if `ctx` is a null pointer. +void *wasmer_instance_context_data_get(const wasmer_instance_context_t *ctx); + +/// Sets the data that can be hold by an instance context. +/// +/// An instance context (represented by the opaque +/// `wasmer_instance_context_t` structure) can hold user-defined +/// data. This function sets the data. This function is complementary +/// of `wasmer_instance_context_data_get()`. +/// +/// This function does nothing if `instance` is a null pointer. +/// +/// Example: +/// +/// ```c +/// // Define your own data. +/// typedef struct { +/// // … +/// } my_data; +/// +/// // Allocate them and set them on the given instance. +/// my_data *data = malloc(sizeof(my_data)); +/// data->… = …; +/// wasmer_instance_context_data_set(instance, (void*) data); +/// +/// // You can read your data. +/// { +/// my_data *data = (my_data*) wasmer_instance_context_data_get(wasmer_instance_context_get(instance)); +/// // … +/// } +/// ``` +void wasmer_instance_context_data_set(wasmer_instance_t *instance, + void *data_ptr); + +/// Returns the instance context. Learn more by looking at the +/// `wasmer_instance_context_t` struct. +/// +/// This function returns `null` if `instance` is a null pointer. +/// +/// Example: +/// +/// ```c +/// const wasmer_instance_context_get *context = wasmer_instance_context_get(instance); +/// my_data *data = (my_data *) wasmer_instance_context_data_get(context); +/// // Do something with `my_data`. +/// ``` +/// +/// It is often useful with `wasmer_instance_context_data_set()`. +const wasmer_instance_context_t *wasmer_instance_context_get(wasmer_instance_t *instance); + +/// Gets the `memory_idx`th memory of the instance. +/// +/// Note that the index is always `0` until multiple memories are supported. +/// +/// This function is mostly used inside host functions (aka imported +/// functions) to read the instance memory. +/// +/// Example of a _host function_ that reads and prints a string based on a pointer and a length: +/// +/// ```c +/// void print_string(const wasmer_instance_context_t *context, int32_t pointer, int32_t length) { +/// // Get the 0th memory. +/// const wasmer_memory_t *memory = wasmer_instance_context_memory(context, 0); +/// +/// // Get the memory data as a pointer. +/// uint8_t *memory_bytes = wasmer_memory_data(memory); +/// +/// // Print what we assumed to be a string! +/// printf("%.*s", length, memory_bytes + pointer); +/// } +/// ``` +const wasmer_memory_t *wasmer_instance_context_memory(const wasmer_instance_context_t *ctx, + uint32_t _memory_idx); + +/// Frees memory for the given `wasmer_instance_t`. +/// +/// Check the `wasmer_instantiate()` function to get a complete +/// example. +/// +/// If `instance` is a null pointer, this function does nothing. +/// +/// Example: +/// +/// ```c +/// // Get an instance. +/// wasmer_instance_t *instance = NULL; +/// wasmer_instantiate(&instance, bytes, bytes_length, imports, 0); +/// +/// // Destroy the instance. +/// wasmer_instance_destroy(instance); +/// ``` +void wasmer_instance_destroy(wasmer_instance_t *instance); + +/// Gets all the exports of the given WebAssembly instance. +/// +/// This function stores a Rust vector of exports into `exports` as an +/// opaque pointer of kind `wasmer_exports_t`. +/// +/// As is, you can do anything with `exports` except using the +/// companion functions, like `wasmer_exports_len()`, +/// `wasmer_exports_get()` or `wasmer_export_kind()`. See the example below. +/// +/// **Warning**: The caller owns the object and should call +/// `wasmer_exports_destroy()` to free it. +/// +/// Example: +/// +/// ```c +/// // Get the exports. +/// wasmer_exports_t *exports = NULL; +/// wasmer_instance_exports(instance, &exports); +/// +/// // Get the number of exports. +/// int exports_length = wasmer_exports_len(exports); +/// printf("Number of exports: %d\n", exports_length); +/// +/// // Read the first export. +/// wasmer_export_t *export = wasmer_exports_get(exports, 0); +/// +/// // Get the kind of the export. +/// wasmer_import_export_kind export_kind = wasmer_export_kind(export); +/// +/// // Assert it is a function (why not). +/// assert(export_kind == WASM_FUNCTION); +/// +/// // Read the export name. +/// wasmer_byte_array name_bytes = wasmer_export_name(export); +/// +/// assert(name_bytes.bytes_len == sizeof("sum") - 1); +/// assert(memcmp(name_bytes.bytes, "sum", sizeof("sum") - 1) == 0); +/// +/// // Destroy the exports. +/// wasmer_exports_destroy(exports); +/// ``` +void wasmer_instance_exports(wasmer_instance_t *instance, wasmer_exports_t **exports); + +/// Creates a new WebAssembly instance from the given bytes and imports. +/// +/// The result is stored in the first argument `instance` if +/// successful, i.e. when the function returns +/// `wasmer_result_t::WASMER_OK`. Otherwise +/// `wasmer_result_t::WASMER_ERROR` is returned, and +/// `wasmer_last_error_length()` with `wasmer_last_error_message()` must +/// be used to read the error message. +/// +/// The caller is responsible to free the instance with +/// `wasmer_instance_destroy()`. +/// +/// Example: +/// +/// ```c +/// // 1. Read a WebAssembly module from a file. +/// FILE *file = fopen("sum.wasm", "r"); +/// fseek(file, 0, SEEK_END); +/// long bytes_length = ftell(file); +/// uint8_t *bytes = malloc(bytes_length); +/// fseek(file, 0, SEEK_SET); +/// fread(bytes, 1, bytes_length, file); +/// fclose(file); +/// +/// // 2. Declare the imports (here, none). +/// wasmer_import_t imports[] = {}; +/// +/// // 3. Instantiate the WebAssembly module. +/// wasmer_instance_t *instance = NULL; +/// wasmer_result_t result = wasmer_instantiate(&instance, bytes, bytes_length, imports, 0); +/// +/// // 4. Check for errors. +/// if (result != WASMER_OK) { +/// int error_length = wasmer_last_error_length(); +/// char *error = malloc(error_length); +/// wasmer_last_error_message(error, error_length); +/// // Do something with `error`… +/// } +/// +/// // 5. Free the memory! +/// wasmer_instance_destroy(instance); +/// ``` +wasmer_result_t wasmer_instantiate(wasmer_instance_t **instance, + uint8_t *wasm_bytes, + uint32_t wasm_bytes_len, + wasmer_import_t *imports, + int imports_len); + +/// Gets the length in bytes of the last error if any. +/// +/// This can be used to dynamically allocate a buffer with the correct number of +/// bytes needed to store a message. +/// +/// See `wasmer_last_error_message()` to get a full example. +int wasmer_last_error_length(); + +/// Gets the last error message if any into the provided buffer +/// `buffer` up to the given `length`. +/// +/// The `length` parameter must be large enough to store the last +/// error message. Ideally, the value should come from +/// `wasmer_last_error_length()`. +/// +/// The function returns the length of the string in bytes, `-1` if an +/// error occurs. Potential errors are: +/// +/// * The buffer is a null pointer, +/// * The buffer is too smal to hold the error message. +/// +/// Note: The error message always has a trailing null character. +/// +/// Example: +/// +/// ```c +/// int error_length = wasmer_last_error_length(); +/// +/// if (error_length > 0) { +/// char *error_message = malloc(error_length); +/// wasmer_last_error_message(error_message, error_length); +/// printf("Error message: `%s`\n", error_message); +/// } else { +/// printf("No error message\n"); +/// } +/// ``` +int wasmer_last_error_message(char *buffer, int length); + +/// Gets a pointer to the beginning of the contiguous memory data +/// bytes. +/// +/// The function returns `NULL` if `memory` is a null pointer. +/// +/// Note that when the memory grows, it can be reallocated, and thus +/// the returned pointer can be invalidated. +/// +/// Example: +/// +/// ```c +/// uint8_t *memory_data = wasmer_memory_data(memory); +/// char *str = (char*) malloc(sizeof(char) * 7); +/// +/// for (uint32_t nth = 0; nth < 7; ++nth) { +/// str[nth] = (char) memory_data[nth]; +/// } +/// ``` +uint8_t *wasmer_memory_data(const wasmer_memory_t *memory); + +/// Gets the size in bytes of the memory data. +/// +/// This function returns 0 if `memory` is a null pointer. +/// +/// Example: +/// +/// ```c +/// uint32_t memory_data_length = wasmer_memory_data_length(memory); +/// ``` +uint32_t wasmer_memory_data_length(const wasmer_memory_t *memory); + +/// Frees memory for the given `wasmer_memory_t`. +/// +/// Check the `wasmer_memory_new()` function to get a complete +/// example. +/// +/// If `memory` is a null pointer, this function does nothing. +/// +/// Example: +/// +/// ```c +/// // Get a memory. +/// wasmer_memory_t *memory = NULL; +/// wasmer_result_t result = wasmer_memory_new(&memory, memory_descriptor); +/// +/// // Destroy the memory. +/// wasmer_memory_destroy(memory); +/// ``` +void wasmer_memory_destroy(wasmer_memory_t *memory); + +/// Grows a memory by the given number of pages (of 65Kb each). +/// +/// The functions return `wasmer_result_t::WASMER_OK` upon success, +/// `wasmer_result_t::WASMER_ERROR` otherwise. Use +/// `wasmer_last_error_length()` with `wasmer_last_error_message()` to +/// read the error message. +/// +/// Example: +/// +/// ```c +/// wasmer_result_t result = wasmer_memory_grow(memory, 10); +/// +/// if (result != WASMER_OK) { +/// // … +/// } +/// ``` +wasmer_result_t wasmer_memory_grow(wasmer_memory_t *memory, uint32_t delta); + +/// Reads the current length (in pages) of the given memory. +/// +/// The function returns zero if `memory` is a null pointer. +/// +/// Example: +/// +/// ```c +/// uint32_t memory_length = wasmer_memory_length(memory); +/// +/// printf("Memory pages length: %d\n", memory_length); +/// ``` +uint32_t wasmer_memory_length(const wasmer_memory_t *memory); + +/// Creates a new empty WebAssembly memory for the given descriptor. +/// +/// The result is stored in the first argument `memory` if successful, +/// i.e. when the function returns +/// `wasmer_result_t::WASMER_OK`. Otherwise, +/// `wasmer_result_t::WASMER_ERROR` is returned, and +/// `wasmer_last_error_length()` with `wasmer_last_error_message()` +/// must be used to read the error message. +/// +/// The caller owns the memory and is responsible to free it with +/// `wasmer_memory_destroy()`. +/// +/// Example: +/// +/// ```c +/// // 1. The memory object. +/// wasmer_memory_t *memory = NULL; +/// +/// // 2. The memory descriptor. +/// wasmer_limits_t memory_descriptor = { +/// .min = 10, +/// .max = { +/// .has_some = true, +/// .some = 15, +/// }, +/// }; +/// +/// // 3. Initialize the memory. +/// wasmer_result_t result = wasmer_memory_new(&memory, memory_descriptor); +/// +/// if (result != WASMER_OK) { +/// int error_length = wasmer_last_error_length(); +/// char *error = malloc(error_length); +/// wasmer_last_error_message(error, error_length); +/// // Do something with `error`… +/// } +/// +/// // 4. Free the memory! +/// wasmer_memory_destroy(memory); +/// ``` +wasmer_result_t wasmer_memory_new(wasmer_memory_t **memory, wasmer_limits_t limits); + +/// Deserialize the given serialized module. +/// +/// Returns `wasmer_result_t::WASMER_OK` upon success. +/// +/// Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` +/// and `wasmer_last_error_message` to get an error message. +wasmer_result_t wasmer_module_deserialize(wasmer_module_t **module, + const wasmer_serialized_module_t *serialized_module); + +/// Frees memory for the given Module +void wasmer_module_destroy(wasmer_module_t *module); + +/// Given: +/// * A prepared `wasmer` import-object +/// * A compiled wasmer module +/// +/// Instantiates a wasmer instance +wasmer_result_t wasmer_module_import_instantiate(wasmer_instance_t **instance, + const wasmer_module_t *module, + const wasmer_import_object_t *import_object); + +/// Creates a new Instance from the given module and imports. +/// +/// Returns `wasmer_result_t::WASMER_OK` upon success. +/// +/// Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` +/// and `wasmer_last_error_message` to get an error message. +wasmer_result_t wasmer_module_instantiate(const wasmer_module_t *module, + wasmer_instance_t **instance, + wasmer_import_t *imports, + int imports_len); + +/// Serialize the given Module. +/// +/// The caller owns the object and should call `wasmer_serialized_module_destroy` to free it. +/// +/// Returns `wasmer_result_t::WASMER_OK` upon success. +/// +/// Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` +/// and `wasmer_last_error_message` to get an error message. +wasmer_result_t wasmer_module_serialize(wasmer_serialized_module_t **serialized_module_out, + const wasmer_module_t *module); + +/// Get bytes of the serialized module. +wasmer_byte_array wasmer_serialized_module_bytes(const wasmer_serialized_module_t *serialized_module); + +/// Frees memory for the given serialized Module. +void wasmer_serialized_module_destroy(wasmer_serialized_module_t *serialized_module); + +/// Transform a sequence of bytes into a serialized module. +/// +/// The caller owns the object and should call `wasmer_serialized_module_destroy` to free it. +/// +/// Returns `wasmer_result_t::WASMER_OK` upon success. +/// +/// Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` +/// and `wasmer_last_error_message` to get an error message. +wasmer_result_t wasmer_serialized_module_from_bytes(wasmer_serialized_module_t **serialized_module, + const uint8_t *serialized_module_bytes, + uint32_t serialized_module_bytes_length); + +/// Frees memory for the given Table +void wasmer_table_destroy(wasmer_table_t *table); + +/// Grows a Table by the given number of elements. +/// +/// Returns `wasmer_result_t::WASMER_OK` upon success. +/// +/// Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` +/// and `wasmer_last_error_message` to get an error message. +wasmer_result_t wasmer_table_grow(wasmer_table_t *table, uint32_t delta); + +/// Returns the current length of the given Table +uint32_t wasmer_table_length(wasmer_table_t *table); + +/// Creates a new Table for the given descriptor and initializes the given +/// pointer to pointer to a pointer to the new Table. +/// +/// The caller owns the object and should call `wasmer_table_destroy` to free it. +/// +/// Returns `wasmer_result_t::WASMER_OK` upon success. +/// +/// Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` +/// and `wasmer_last_error_message` to get an error message. +wasmer_result_t wasmer_table_new(wasmer_table_t **table, wasmer_limits_t limits); + +/// Stop the execution of a host function, aka imported function. The +/// function must be used _only_ inside a host function. +/// +/// The pointer to `wasmer_instance_context_t` is received by the host +/// function as its first argument. Just passing it to `ctx` is fine. +/// +/// The error message must have a greater lifetime than the host +/// function itself since the error is read outside the host function +/// with `wasmer_last_error_message`. +/// +/// This function returns `wasmer_result_t::WASMER_ERROR` if `ctx` or +/// `error_message` are null. +/// +/// This function never returns otherwise. +wasmer_result_t wasmer_trap(const wasmer_instance_context_t *ctx, const char *error_message); + +/// Validates a sequence of bytes hoping it represents a valid WebAssembly module. +/// +/// The function returns true if the bytes are valid, false otherwise. +/// +/// Example: +/// +/// ```c +/// bool result = wasmer_validate(bytes, bytes_length); +/// +/// if (false == result) { +/// // Do something… +/// } +/// ``` +bool wasmer_validate(const uint8_t *wasm_bytes, uint32_t wasm_bytes_len); + +#if defined(WASMER_WASI_ENABLED) +/// 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(); +#endif + +#if defined(WASMER_WASI_ENABLED) +/// 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 + +#if defined(WASMER_WASI_ENABLED) +/// Creates a WASI import object for a specific version. +/// +/// This function is similar to `wasmer_wasi_generate_import_object` +/// except that the first argument describes the WASI version. +/// +/// The version is expected to be of kind `Version`. +wasmer_import_object_t *wasmer_wasi_generate_import_object_for_version(unsigned char version, + const wasmer_byte_array *args, + unsigned int args_len, + const wasmer_byte_array *envs, + unsigned int envs_len, + const wasmer_byte_array *preopened_files, + unsigned int preopened_files_len, + const wasmer_wasi_map_dir_entry_t *mapped_dirs, + unsigned int mapped_dirs_len); +#endif + +#if defined(WASMER_WASI_ENABLED) +/// Find the version of WASI used by the module. +/// +/// In case of error, the returned version is `Version::Unknown`. +Version wasmer_wasi_get_version(const wasmer_module_t *module); +#endif + +} // extern "C" + +#endif // WASMER_H diff --git a/lib/compiler-cranelift/src/config.rs b/lib/compiler-cranelift/src/config.rs index af277e114..656039777 100644 --- a/lib/compiler-cranelift/src/config.rs +++ b/lib/compiler-cranelift/src/config.rs @@ -179,7 +179,7 @@ impl CompilerConfig for CraneliftConfig { } /// Transform it into the compiler - fn compiler(&self) -> Box { + fn compiler(&self) -> Box { Box::new(CraneliftCompiler::new(&self)) } } diff --git a/lib/compiler-llvm/src/config.rs b/lib/compiler-llvm/src/config.rs index fbf4ac5cd..87a886527 100644 --- a/lib/compiler-llvm/src/config.rs +++ b/lib/compiler-llvm/src/config.rs @@ -142,7 +142,7 @@ impl CompilerConfig for LLVMConfig { } /// Transform it into the compiler - fn compiler(&self) -> Box { + fn compiler(&self) -> Box { Box::new(LLVMCompiler::new(&self)) } } diff --git a/lib/compiler/src/compiler.rs b/lib/compiler/src/compiler.rs index 33000dc76..cf3774241 100644 --- a/lib/compiler/src/compiler.rs +++ b/lib/compiler/src/compiler.rs @@ -27,7 +27,7 @@ pub trait CompilerConfig { fn target(&self) -> &Target; /// Gets the custom compiler config - fn compiler(&self) -> Box; + fn compiler(&self) -> Box; } /// An implementation of a Compiler from parsed WebAssembly module to Compiled native code. diff --git a/lib/engine-jit/src/engine.rs b/lib/engine-jit/src/engine.rs index b9e0e55a3..10d55b846 100644 --- a/lib/engine-jit/src/engine.rs +++ b/lib/engine-jit/src/engine.rs @@ -1,9 +1,8 @@ //! JIT compilation. use crate::{CodeMemory, CompiledModule}; -use std::cell::RefCell; use std::collections::HashMap; -use std::sync::Arc; +use std::sync::{Arc, Mutex}; use wasm_common::entity::PrimaryMap; use wasm_common::{FunctionType, LocalFunctionIndex, MemoryIndex, SignatureIndex, TableIndex}; use wasmer_compiler::{Compilation, CompileError, FunctionBody, Target}; @@ -21,8 +20,8 @@ use wasmer_runtime::{ /// A WebAssembly `JIT` Engine. #[derive(Clone)] pub struct JITEngine { - inner: Arc>, - tunables: Arc>, + inner: Arc>, + tunables: Arc, } impl JITEngine { @@ -30,19 +29,22 @@ impl JITEngine { /// Create a new `JITEngine` with the given config #[cfg(feature = "compiler")] - pub fn new(config: &C, tunables: impl Tunables + 'static) -> Self + pub fn new( + config: &C, + tunables: impl Tunables + 'static + Send + Sync, + ) -> Self where C: ?Sized, { let compiler = config.compiler(); Self { - inner: Arc::new(RefCell::new(JITEngineInner { + inner: Arc::new(Mutex::new(JITEngineInner { compiler: Some(compiler), trampolines: HashMap::new(), code_memory: CodeMemory::new(), signatures: SignatureRegistry::new(), })), - tunables: Arc::new(Box::new(tunables)), + tunables: Arc::new(tunables), } } @@ -59,25 +61,25 @@ impl JITEngine { /// /// Headless engines can't compile or validate any modules, /// they just take already processed Modules (via `Module::serialize`). - pub fn headless(tunables: impl Tunables + 'static) -> Self { + pub fn headless(tunables: impl Tunables + 'static + Send + Sync) -> Self { Self { - inner: Arc::new(RefCell::new(JITEngineInner { + inner: Arc::new(Mutex::new(JITEngineInner { #[cfg(feature = "compiler")] compiler: None, trampolines: HashMap::new(), code_memory: CodeMemory::new(), signatures: SignatureRegistry::new(), })), - tunables: Arc::new(Box::new(tunables)), + tunables: Arc::new(tunables), } } - pub(crate) fn compiler(&self) -> std::cell::Ref<'_, JITEngineInner> { - self.inner.borrow() + pub(crate) fn compiler(&self) -> std::sync::MutexGuard<'_, JITEngineInner> { + self.inner.lock().unwrap() } - pub(crate) fn compiler_mut(&self) -> std::cell::RefMut<'_, JITEngineInner> { - self.inner.borrow_mut() + pub(crate) fn compiler_mut(&self) -> std::sync::MutexGuard<'_, JITEngineInner> { + self.inner.lock().unwrap() } /// Check if the provided bytes look like a serialized @@ -90,7 +92,7 @@ impl JITEngine { impl Engine for JITEngine { /// Get the tunables fn tunables(&self) -> &dyn Tunables { - &**self.tunables + &*self.tunables } /// Register a signature @@ -170,7 +172,7 @@ impl Engine for JITEngine { pub struct JITEngineInner { /// The compiler #[cfg(feature = "compiler")] - compiler: Option>, + compiler: Option>, /// Pointers to trampoline functions used to enter particular signatures trampolines: HashMap, /// The code memory is responsible of publishing the compiled diff --git a/lib/runtime/src/table.rs b/lib/runtime/src/table.rs index 6ec58f962..0ad3952c9 100644 --- a/lib/runtime/src/table.rs +++ b/lib/runtime/src/table.rs @@ -24,6 +24,14 @@ impl Table { Type::FuncRef => (), ty => return Err(format!("tables of types other than anyfunc ({})", ty)), }; + if let Some(max) = plan.table.maximum { + if max < plan.table.minimum { + return Err(format!( + "Table minimum ({}) is larger than maximum ({})!", + plan.table.minimum, max + )); + } + } match plan.style { TableStyle::CallerChecksSignature => Ok(Self { vec: RefCell::new(vec![ diff --git a/lib/wasm-common/src/types.rs b/lib/wasm-common/src/types.rs index 4c3529b54..178cd63c3 100644 --- a/lib/wasm-common/src/types.rs +++ b/lib/wasm-common/src/types.rs @@ -210,6 +210,8 @@ impl ExternType { } } +// TODO: `shrink_to_fit` these or change it to `Box<[Type]>` if not using +// Cow or something else /// The signature of a function that is either implemented /// in a Wasm module or exposed to Wasm by the host. /// @@ -332,10 +334,7 @@ impl GlobalType { /// let global = GlobalType::new(Type::I64, Mutability::Var); /// ``` pub fn new(ty: Type, mutability: Mutability) -> Self { - Self { - ty: ty, - mutability: mutability, - } + Self { ty, mutability } } }