diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index f8479573..6be4ae55 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -2,7 +2,7 @@ name: Rust CI on: push: - branches: ["**"] + branches: ["next", "main"] pull_request: branches: ["next", "main"] schedule: @@ -12,6 +12,23 @@ permissions: contents: read jobs: + c-api: + name: C API + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - uses: actions-rust-lang/setup-rust-toolchain@ecabd13d1c56bd1345c230e542e9144811ad706f # v2.0.0 + with: + toolchain: nightly + components: miri + rustflags: "" + build-warnings: warn + - run: cargo miri test -p tinywasm-c-api --lib + - run: make -C crates/c-api test + - run: make -C crates/c-api test TINYWASM_C_API_PREFIX=test_ TARGET_DIR=../../target/c-api-prefixed + run-tests: strategy: matrix: diff --git a/Cargo.lock b/Cargo.lock index c20ddfc0..3b2eaf05 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -999,6 +999,13 @@ dependencies = [ "wat", ] +[[package]] +name = "tinywasm-c-api" +version = "0.11.0" +dependencies = [ + "tinywasm", +] + [[package]] name = "tinywasm-cli" version = "0.11.0" diff --git a/crates/c-api/Cargo.toml b/crates/c-api/Cargo.toml new file mode 100644 index 00000000..63a1c476 --- /dev/null +++ b/crates/c-api/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "tinywasm-c-api" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +description = "WebAssembly C API for TinyWasm" +repository.workspace = true +license.workspace = true +publish = false + +[lib] +crate-type = ["cdylib", "staticlib"] +name = "tinywasm" + +[dependencies] +tinywasm = { workspace = true, features = ["archive", "parser", "std", "validate"] } + +[features] +custom-prefix = [] + +[lints] +workspace = true diff --git a/crates/c-api/Makefile b/crates/c-api/Makefile new file mode 100644 index 00000000..dfe2b607 --- /dev/null +++ b/crates/c-api/Makefile @@ -0,0 +1,50 @@ +# Optional convenience targets. Cargo remains the library build system. +CARGO ?= cargo +CC ?= cc +CXX ?= c++ +PREFIX ?= /usr/local +DESTDIR ?= +LIBDIR ?= $(PREFIX)/lib +PROFILE ?= release +TARGET_DIR ?= $(if $(CARGO_TARGET_DIR),$(CARGO_TARGET_DIR),../../target) +BUILD_LIBDIR := $(abspath $(TARGET_DIR)/$(if $(filter dev,$(PROFILE)),debug,$(PROFILE))) +BUILDDIR := $(abspath $(TARGET_DIR)/c-api) +CARGO_FLAGS := --profile $(PROFILE) +ifneq ($(TINYWASM_C_API_PREFIX),) +export TINYWASM_C_API_PREFIX +CARGO_FLAGS += --features custom-prefix +CPPFLAGS += -DTINYWASM_C_API_PREFIX=$(TINYWASM_C_API_PREFIX) +endif +CFLAGS ?= -O2 -Wall -Wextra -Werror +UNAME := $(shell uname -s) +SHARED_EXT := $(if $(filter Darwin,$(UNAME)),dylib,so) +# Native libraries reported by `cargo rustc -- --print native-static-libs`. +NATIVE_LIBS ?= $(if $(filter Darwin,$(UNAME)),-framework Security -framework CoreFoundation -liconv -lSystem,-lgcc_s -lutil -lrt -lpthread -lm -ldl -lc) +VERSION := $(shell sed -n '/^\[workspace.package\]/,/^\[/s/^version = "\(.*\)"/\1/p' ../../Cargo.toml) + +.PHONY: all build example test install +all: build + +build: + $(CARGO) build -p tinywasm-c-api $(CARGO_FLAGS) --target-dir "$(abspath $(TARGET_DIR))" + +example: build + mkdir -p "$(BUILDDIR)" + $(CC) $(CPPFLAGS) $(CFLAGS) -std=c11 -Iinclude examples/add.c -L"$(BUILD_LIBDIR)" -Wl,-rpath,"$(BUILD_LIBDIR)" -ltinywasm $(LDFLAGS) -o "$(BUILDDIR)/add" + "$(BUILDDIR)/add" + +test: example + $(CXX) $(CPPFLAGS) -std=c++17 -Wall -Wextra -Werror -fsyntax-only -x c++ -Iinclude include/tinywasm.h + $(CC) $(CPPFLAGS) $(CFLAGS) -std=c11 -Iinclude tests/api.c -L"$(BUILD_LIBDIR)" -Wl,-rpath,"$(BUILD_LIBDIR)" -ltinywasm $(LDFLAGS) -o "$(BUILDDIR)/api" + "$(BUILDDIR)/api" tests/fixtures + $(CC) $(CPPFLAGS) $(CFLAGS) -std=c11 -Iinclude examples/add.c "$(BUILD_LIBDIR)/libtinywasm.a" $(NATIVE_LIBS) $(LDFLAGS) -o "$(BUILDDIR)/add-static" + "$(BUILDDIR)/add-static" + $(CC) $(CPPFLAGS) $(CFLAGS) -std=c11 -Iinclude tests/api.c "$(BUILD_LIBDIR)/libtinywasm.a" $(NATIVE_LIBS) $(LDFLAGS) -o "$(BUILDDIR)/api-static" + "$(BUILDDIR)/api-static" tests/fixtures + +install: build + install -d "$(DESTDIR)$(PREFIX)/include" "$(DESTDIR)$(LIBDIR)/pkgconfig" "$(DESTDIR)$(PREFIX)/share/licenses/tinywasm" + install -m644 include/wasm.h include/tinywasm.h include/tinywasm-prefix.h "$(DESTDIR)$(PREFIX)/include/" + install -m644 "$(BUILD_LIBDIR)/libtinywasm.a" "$(BUILD_LIBDIR)/libtinywasm.$(SHARED_EXT)" "$(DESTDIR)$(LIBDIR)/" + install -m644 ../../LICENSE-APACHE ../../LICENSE-MIT "$(DESTDIR)$(PREFIX)/share/licenses/tinywasm/" + sed -e 's|@PREFIX@|$(PREFIX)|g' -e 's|@LIBDIR@|$(LIBDIR)|g' -e 's|@VERSION@|$(VERSION)|g' -e 's|@NATIVE_LIBS@|$(NATIVE_LIBS)|g' -e 's|@PREFIX_FLAG@|$(if $(TINYWASM_C_API_PREFIX),-DTINYWASM_C_API_PREFIX=$(TINYWASM_C_API_PREFIX))|g' tinywasm.pc.in > "$(DESTDIR)$(LIBDIR)/pkgconfig/tinywasm.pc" diff --git a/crates/c-api/README.md b/crates/c-api/README.md new file mode 100644 index 00000000..c885ede7 --- /dev/null +++ b/crates/c-api/README.md @@ -0,0 +1,52 @@ +# TinyWasm C API + +Experimental [WebAssembly C API](https://github.com/WebAssembly/wasm-c-api) support for TinyWasm. +Use `wasm.h` to embed it in C or C++. See [Wasmtime's C API documentation](https://docs.wasmtime.dev/c-api/wasm_8h.html) +for the standard interface and [`examples/add.c`](examples/add.c) for an example. + +## Build + +```sh +make -C crates/c-api +``` + +This builds `libtinywasm.so` (or `.dylib` on macOS) and `libtinywasm.a` in +`target/release`. Headers are in `crates/c-api/include`. To install them and a +`pkg-config` file, run `make -C crates/c-api install`. Run `make -C crates/c-api example` to +build and run the C example. + +## Symbol prefix + +By default, the library exports the names in `wasm.h`. To avoid symbol +collisions with another WebAssembly C API implementation, build with a prefix: + +```sh +make -C crates/c-api TINYWASM_C_API_PREFIX=my_ +``` + +Define the same prefix before including `tinywasm.h` in C or C++: + +```c +#define TINYWASM_C_API_PREFIX my_ +#include "tinywasm.h" +``` + +When building with Cargo directly, enable `custom-prefix` and set the +`TINYWASM_C_API_PREFIX` environment variable. Include `tinywasm.h` before +`wasm.h` so the symbol aliases take effect. + +## Notes + +- Follow the ownership annotations in `wasm.h` and use the matching delete + functions. Keep a store and its objects on the same thread. +- Load `.wasm` bytes with `wasm_module_new`. Imports are positional, in module + import order. +- Include `tinywasm.h` for `tinywasm_last_error_message`, which copies the + calling thread's last error into a vector you delete with `wasm_byte_vec_delete`. +- Module imports and exports must have types representable in `wasm.h`. SIMD + signatures, shared memory, memory64/table64, GC references, and tags are not supported at + the module boundary. + +`wasm.h` is vendored from WebAssembly/wasm-c-api commit +`9d6b93764ac96cdd9db51081c363e09d2d488b4d`, under +[Apache 2.0](../../LICENSE-APACHE). diff --git a/crates/c-api/build.rs b/crates/c-api/build.rs new file mode 100644 index 00000000..e78bb837 --- /dev/null +++ b/crates/c-api/build.rs @@ -0,0 +1,8 @@ +fn main() { + if std::env::var_os("CARGO_FEATURE_CUSTOM_PREFIX").is_some() { + println!("cargo:rerun-if-env-changed=TINYWASM_C_API_PREFIX"); + if std::env::var_os("TINYWASM_C_API_PREFIX").is_none() { + println!("cargo:rustc-env=TINYWASM_C_API_PREFIX=tinywasm_"); + } + } +} diff --git a/crates/c-api/examples/add.c b/crates/c-api/examples/add.c new file mode 100644 index 00000000..8ca41367 --- /dev/null +++ b/crates/c-api/examples/add.c @@ -0,0 +1,51 @@ +#include +#include +#include "tinywasm.h" + +int main(void) { + /* (module (func (export "add") (param i32 i32) (result i32) + * local.get 0 local.get 1 i32.add)) */ + const unsigned char binary[] = { + 0,97,115,109,1,0,0,0,1,7,1,96,2,127,127,1,127, + 3,2,1,0,7,7,1,3,97,100,100,0,0,10,9,1,7,0,32,0,32,1,106,11 + }; + wasm_engine_t* engine = wasm_engine_new(); + wasm_store_t* store = wasm_store_new(engine); + wasm_byte_vec_t bytes; + wasm_byte_vec_new(&bytes, sizeof(binary), (const char*)binary); + wasm_module_t* module = wasm_module_new(store, &bytes); + wasm_byte_vec_delete(&bytes); + if (!module) { + wasm_message_t error; + tinywasm_last_error_message(&error); + fprintf(stderr, "%s\n", error.data); + wasm_byte_vec_delete(&error); + wasm_store_delete(store); + wasm_engine_delete(engine); + return 1; + } + + wasm_extern_vec_t imports = WASM_EMPTY_VEC; + wasm_trap_t* trap = NULL; + wasm_instance_t* instance = wasm_instance_new(store, module, &imports, &trap); + assert(instance && !trap); + wasm_extern_vec_t exports; + wasm_instance_exports(instance, &exports); + /* The export vector owns its handles. wasm_extern_as_func borrows one. */ + wasm_val_t arguments[] = { WASM_I32_VAL(20), WASM_I32_VAL(22) }; + wasm_val_t result[1]; + /* Stack-backed vectors need no vector delete. The call writes the result slot. */ + wasm_val_vec_t args = WASM_ARRAY_VEC(arguments); + wasm_val_vec_t results = WASM_ARRAY_VEC(result); + trap = wasm_func_call(wasm_extern_as_func(exports.data[0]), &args, &results); + assert(!trap); + printf("20 + 22 = %d\n", result[0].of.i32); + assert(result[0].of.i32 == 42); + + wasm_extern_vec_delete(&exports); + wasm_instance_delete(instance); + wasm_module_delete(module); + wasm_store_delete(store); + wasm_engine_delete(engine); + return 0; +} diff --git a/crates/c-api/include/tinywasm-prefix.h b/crates/c-api/include/tinywasm-prefix.h new file mode 100644 index 00000000..ba967725 --- /dev/null +++ b/crates/c-api/include/tinywasm-prefix.h @@ -0,0 +1,308 @@ +/* Symbol aliases for the pinned wasm.h and TinyWasm extensions. */ +#ifndef TINYWASM_PREFIX_H +#define TINYWASM_PREFIX_H +#ifdef WASM_H +#error "Include tinywasm.h before wasm.h when using a custom prefix" +#endif +#ifndef TINYWASM_C_API_PREFIX +#error "Define TINYWASM_C_API_PREFIX to match the library build" +#endif +#define TINYWASM_JOIN_INNER(a, b) a##b +#define TINYWASM_JOIN(a, b) TINYWASM_JOIN_INNER(a, b) +#define TINYWASM_SYMBOL(name) TINYWASM_JOIN(TINYWASM_C_API_PREFIX, name) + +#define tinywasm_last_error_message TINYWASM_SYMBOL(tinywasm_last_error_message) +#define wasm_byte_vec_copy TINYWASM_SYMBOL(wasm_byte_vec_copy) +#define wasm_byte_vec_delete TINYWASM_SYMBOL(wasm_byte_vec_delete) +#define wasm_byte_vec_new TINYWASM_SYMBOL(wasm_byte_vec_new) +#define wasm_byte_vec_new_empty TINYWASM_SYMBOL(wasm_byte_vec_new_empty) +#define wasm_byte_vec_new_uninitialized TINYWASM_SYMBOL(wasm_byte_vec_new_uninitialized) +#define wasm_config_delete TINYWASM_SYMBOL(wasm_config_delete) +#define wasm_config_new TINYWASM_SYMBOL(wasm_config_new) +#define wasm_engine_delete TINYWASM_SYMBOL(wasm_engine_delete) +#define wasm_engine_new TINYWASM_SYMBOL(wasm_engine_new) +#define wasm_engine_new_with_config TINYWASM_SYMBOL(wasm_engine_new_with_config) +#define wasm_exporttype_copy TINYWASM_SYMBOL(wasm_exporttype_copy) +#define wasm_exporttype_delete TINYWASM_SYMBOL(wasm_exporttype_delete) +#define wasm_exporttype_name TINYWASM_SYMBOL(wasm_exporttype_name) +#define wasm_exporttype_new TINYWASM_SYMBOL(wasm_exporttype_new) +#define wasm_exporttype_type TINYWASM_SYMBOL(wasm_exporttype_type) +#define wasm_exporttype_vec_copy TINYWASM_SYMBOL(wasm_exporttype_vec_copy) +#define wasm_exporttype_vec_delete TINYWASM_SYMBOL(wasm_exporttype_vec_delete) +#define wasm_exporttype_vec_new TINYWASM_SYMBOL(wasm_exporttype_vec_new) +#define wasm_exporttype_vec_new_empty TINYWASM_SYMBOL(wasm_exporttype_vec_new_empty) +#define wasm_exporttype_vec_new_uninitialized TINYWASM_SYMBOL(wasm_exporttype_vec_new_uninitialized) +#define wasm_extern_as_func TINYWASM_SYMBOL(wasm_extern_as_func) +#define wasm_extern_as_func_const TINYWASM_SYMBOL(wasm_extern_as_func_const) +#define wasm_extern_as_global TINYWASM_SYMBOL(wasm_extern_as_global) +#define wasm_extern_as_global_const TINYWASM_SYMBOL(wasm_extern_as_global_const) +#define wasm_extern_as_memory TINYWASM_SYMBOL(wasm_extern_as_memory) +#define wasm_extern_as_memory_const TINYWASM_SYMBOL(wasm_extern_as_memory_const) +#define wasm_extern_as_ref TINYWASM_SYMBOL(wasm_extern_as_ref) +#define wasm_extern_as_ref_const TINYWASM_SYMBOL(wasm_extern_as_ref_const) +#define wasm_extern_as_table TINYWASM_SYMBOL(wasm_extern_as_table) +#define wasm_extern_as_table_const TINYWASM_SYMBOL(wasm_extern_as_table_const) +#define wasm_extern_copy TINYWASM_SYMBOL(wasm_extern_copy) +#define wasm_extern_delete TINYWASM_SYMBOL(wasm_extern_delete) +#define wasm_extern_get_host_info TINYWASM_SYMBOL(wasm_extern_get_host_info) +#define wasm_extern_kind TINYWASM_SYMBOL(wasm_extern_kind) +#define wasm_extern_same TINYWASM_SYMBOL(wasm_extern_same) +#define wasm_extern_set_host_info TINYWASM_SYMBOL(wasm_extern_set_host_info) +#define wasm_extern_set_host_info_with_finalizer TINYWASM_SYMBOL(wasm_extern_set_host_info_with_finalizer) +#define wasm_extern_type TINYWASM_SYMBOL(wasm_extern_type) +#define wasm_extern_vec_copy TINYWASM_SYMBOL(wasm_extern_vec_copy) +#define wasm_extern_vec_delete TINYWASM_SYMBOL(wasm_extern_vec_delete) +#define wasm_extern_vec_new TINYWASM_SYMBOL(wasm_extern_vec_new) +#define wasm_extern_vec_new_empty TINYWASM_SYMBOL(wasm_extern_vec_new_empty) +#define wasm_extern_vec_new_uninitialized TINYWASM_SYMBOL(wasm_extern_vec_new_uninitialized) +#define wasm_externtype_as_functype TINYWASM_SYMBOL(wasm_externtype_as_functype) +#define wasm_externtype_as_functype_const TINYWASM_SYMBOL(wasm_externtype_as_functype_const) +#define wasm_externtype_as_globaltype TINYWASM_SYMBOL(wasm_externtype_as_globaltype) +#define wasm_externtype_as_globaltype_const TINYWASM_SYMBOL(wasm_externtype_as_globaltype_const) +#define wasm_externtype_as_memorytype TINYWASM_SYMBOL(wasm_externtype_as_memorytype) +#define wasm_externtype_as_memorytype_const TINYWASM_SYMBOL(wasm_externtype_as_memorytype_const) +#define wasm_externtype_as_tabletype TINYWASM_SYMBOL(wasm_externtype_as_tabletype) +#define wasm_externtype_as_tabletype_const TINYWASM_SYMBOL(wasm_externtype_as_tabletype_const) +#define wasm_externtype_as_tagtype TINYWASM_SYMBOL(wasm_externtype_as_tagtype) +#define wasm_externtype_as_tagtype_const TINYWASM_SYMBOL(wasm_externtype_as_tagtype_const) +#define wasm_externtype_copy TINYWASM_SYMBOL(wasm_externtype_copy) +#define wasm_externtype_delete TINYWASM_SYMBOL(wasm_externtype_delete) +#define wasm_externtype_kind TINYWASM_SYMBOL(wasm_externtype_kind) +#define wasm_externtype_vec_copy TINYWASM_SYMBOL(wasm_externtype_vec_copy) +#define wasm_externtype_vec_delete TINYWASM_SYMBOL(wasm_externtype_vec_delete) +#define wasm_externtype_vec_new TINYWASM_SYMBOL(wasm_externtype_vec_new) +#define wasm_externtype_vec_new_empty TINYWASM_SYMBOL(wasm_externtype_vec_new_empty) +#define wasm_externtype_vec_new_uninitialized TINYWASM_SYMBOL(wasm_externtype_vec_new_uninitialized) +#define wasm_foreign_as_ref TINYWASM_SYMBOL(wasm_foreign_as_ref) +#define wasm_foreign_as_ref_const TINYWASM_SYMBOL(wasm_foreign_as_ref_const) +#define wasm_foreign_copy TINYWASM_SYMBOL(wasm_foreign_copy) +#define wasm_foreign_delete TINYWASM_SYMBOL(wasm_foreign_delete) +#define wasm_foreign_get_host_info TINYWASM_SYMBOL(wasm_foreign_get_host_info) +#define wasm_foreign_new TINYWASM_SYMBOL(wasm_foreign_new) +#define wasm_foreign_same TINYWASM_SYMBOL(wasm_foreign_same) +#define wasm_foreign_set_host_info TINYWASM_SYMBOL(wasm_foreign_set_host_info) +#define wasm_foreign_set_host_info_with_finalizer TINYWASM_SYMBOL(wasm_foreign_set_host_info_with_finalizer) +#define wasm_frame_copy TINYWASM_SYMBOL(wasm_frame_copy) +#define wasm_frame_delete TINYWASM_SYMBOL(wasm_frame_delete) +#define wasm_frame_func_index TINYWASM_SYMBOL(wasm_frame_func_index) +#define wasm_frame_func_offset TINYWASM_SYMBOL(wasm_frame_func_offset) +#define wasm_frame_instance TINYWASM_SYMBOL(wasm_frame_instance) +#define wasm_frame_module_offset TINYWASM_SYMBOL(wasm_frame_module_offset) +#define wasm_frame_vec_copy TINYWASM_SYMBOL(wasm_frame_vec_copy) +#define wasm_frame_vec_delete TINYWASM_SYMBOL(wasm_frame_vec_delete) +#define wasm_frame_vec_new TINYWASM_SYMBOL(wasm_frame_vec_new) +#define wasm_frame_vec_new_empty TINYWASM_SYMBOL(wasm_frame_vec_new_empty) +#define wasm_frame_vec_new_uninitialized TINYWASM_SYMBOL(wasm_frame_vec_new_uninitialized) +#define wasm_func_as_extern TINYWASM_SYMBOL(wasm_func_as_extern) +#define wasm_func_as_extern_const TINYWASM_SYMBOL(wasm_func_as_extern_const) +#define wasm_func_as_ref TINYWASM_SYMBOL(wasm_func_as_ref) +#define wasm_func_as_ref_const TINYWASM_SYMBOL(wasm_func_as_ref_const) +#define wasm_func_call TINYWASM_SYMBOL(wasm_func_call) +#define wasm_func_copy TINYWASM_SYMBOL(wasm_func_copy) +#define wasm_func_delete TINYWASM_SYMBOL(wasm_func_delete) +#define wasm_func_get_host_info TINYWASM_SYMBOL(wasm_func_get_host_info) +#define wasm_func_new TINYWASM_SYMBOL(wasm_func_new) +#define wasm_func_new_with_env TINYWASM_SYMBOL(wasm_func_new_with_env) +#define wasm_func_param_arity TINYWASM_SYMBOL(wasm_func_param_arity) +#define wasm_func_result_arity TINYWASM_SYMBOL(wasm_func_result_arity) +#define wasm_func_same TINYWASM_SYMBOL(wasm_func_same) +#define wasm_func_set_host_info TINYWASM_SYMBOL(wasm_func_set_host_info) +#define wasm_func_set_host_info_with_finalizer TINYWASM_SYMBOL(wasm_func_set_host_info_with_finalizer) +#define wasm_func_type TINYWASM_SYMBOL(wasm_func_type) +#define wasm_functype_as_externtype TINYWASM_SYMBOL(wasm_functype_as_externtype) +#define wasm_functype_as_externtype_const TINYWASM_SYMBOL(wasm_functype_as_externtype_const) +#define wasm_functype_copy TINYWASM_SYMBOL(wasm_functype_copy) +#define wasm_functype_delete TINYWASM_SYMBOL(wasm_functype_delete) +#define wasm_functype_new TINYWASM_SYMBOL(wasm_functype_new) +#define wasm_functype_params TINYWASM_SYMBOL(wasm_functype_params) +#define wasm_functype_results TINYWASM_SYMBOL(wasm_functype_results) +#define wasm_functype_vec_copy TINYWASM_SYMBOL(wasm_functype_vec_copy) +#define wasm_functype_vec_delete TINYWASM_SYMBOL(wasm_functype_vec_delete) +#define wasm_functype_vec_new TINYWASM_SYMBOL(wasm_functype_vec_new) +#define wasm_functype_vec_new_empty TINYWASM_SYMBOL(wasm_functype_vec_new_empty) +#define wasm_functype_vec_new_uninitialized TINYWASM_SYMBOL(wasm_functype_vec_new_uninitialized) +#define wasm_global_as_extern TINYWASM_SYMBOL(wasm_global_as_extern) +#define wasm_global_as_extern_const TINYWASM_SYMBOL(wasm_global_as_extern_const) +#define wasm_global_as_ref TINYWASM_SYMBOL(wasm_global_as_ref) +#define wasm_global_as_ref_const TINYWASM_SYMBOL(wasm_global_as_ref_const) +#define wasm_global_copy TINYWASM_SYMBOL(wasm_global_copy) +#define wasm_global_delete TINYWASM_SYMBOL(wasm_global_delete) +#define wasm_global_get TINYWASM_SYMBOL(wasm_global_get) +#define wasm_global_get_host_info TINYWASM_SYMBOL(wasm_global_get_host_info) +#define wasm_global_new TINYWASM_SYMBOL(wasm_global_new) +#define wasm_global_same TINYWASM_SYMBOL(wasm_global_same) +#define wasm_global_set TINYWASM_SYMBOL(wasm_global_set) +#define wasm_global_set_host_info TINYWASM_SYMBOL(wasm_global_set_host_info) +#define wasm_global_set_host_info_with_finalizer TINYWASM_SYMBOL(wasm_global_set_host_info_with_finalizer) +#define wasm_global_type TINYWASM_SYMBOL(wasm_global_type) +#define wasm_globaltype_as_externtype TINYWASM_SYMBOL(wasm_globaltype_as_externtype) +#define wasm_globaltype_as_externtype_const TINYWASM_SYMBOL(wasm_globaltype_as_externtype_const) +#define wasm_globaltype_content TINYWASM_SYMBOL(wasm_globaltype_content) +#define wasm_globaltype_copy TINYWASM_SYMBOL(wasm_globaltype_copy) +#define wasm_globaltype_delete TINYWASM_SYMBOL(wasm_globaltype_delete) +#define wasm_globaltype_mutability TINYWASM_SYMBOL(wasm_globaltype_mutability) +#define wasm_globaltype_new TINYWASM_SYMBOL(wasm_globaltype_new) +#define wasm_globaltype_vec_copy TINYWASM_SYMBOL(wasm_globaltype_vec_copy) +#define wasm_globaltype_vec_delete TINYWASM_SYMBOL(wasm_globaltype_vec_delete) +#define wasm_globaltype_vec_new TINYWASM_SYMBOL(wasm_globaltype_vec_new) +#define wasm_globaltype_vec_new_empty TINYWASM_SYMBOL(wasm_globaltype_vec_new_empty) +#define wasm_globaltype_vec_new_uninitialized TINYWASM_SYMBOL(wasm_globaltype_vec_new_uninitialized) +#define wasm_importtype_copy TINYWASM_SYMBOL(wasm_importtype_copy) +#define wasm_importtype_delete TINYWASM_SYMBOL(wasm_importtype_delete) +#define wasm_importtype_module TINYWASM_SYMBOL(wasm_importtype_module) +#define wasm_importtype_name TINYWASM_SYMBOL(wasm_importtype_name) +#define wasm_importtype_new TINYWASM_SYMBOL(wasm_importtype_new) +#define wasm_importtype_type TINYWASM_SYMBOL(wasm_importtype_type) +#define wasm_importtype_vec_copy TINYWASM_SYMBOL(wasm_importtype_vec_copy) +#define wasm_importtype_vec_delete TINYWASM_SYMBOL(wasm_importtype_vec_delete) +#define wasm_importtype_vec_new TINYWASM_SYMBOL(wasm_importtype_vec_new) +#define wasm_importtype_vec_new_empty TINYWASM_SYMBOL(wasm_importtype_vec_new_empty) +#define wasm_importtype_vec_new_uninitialized TINYWASM_SYMBOL(wasm_importtype_vec_new_uninitialized) +#define wasm_instance_as_ref TINYWASM_SYMBOL(wasm_instance_as_ref) +#define wasm_instance_as_ref_const TINYWASM_SYMBOL(wasm_instance_as_ref_const) +#define wasm_instance_copy TINYWASM_SYMBOL(wasm_instance_copy) +#define wasm_instance_delete TINYWASM_SYMBOL(wasm_instance_delete) +#define wasm_instance_exports TINYWASM_SYMBOL(wasm_instance_exports) +#define wasm_instance_get_host_info TINYWASM_SYMBOL(wasm_instance_get_host_info) +#define wasm_instance_new TINYWASM_SYMBOL(wasm_instance_new) +#define wasm_instance_same TINYWASM_SYMBOL(wasm_instance_same) +#define wasm_instance_set_host_info TINYWASM_SYMBOL(wasm_instance_set_host_info) +#define wasm_instance_set_host_info_with_finalizer TINYWASM_SYMBOL(wasm_instance_set_host_info_with_finalizer) +#define wasm_memory_as_extern TINYWASM_SYMBOL(wasm_memory_as_extern) +#define wasm_memory_as_extern_const TINYWASM_SYMBOL(wasm_memory_as_extern_const) +#define wasm_memory_as_ref TINYWASM_SYMBOL(wasm_memory_as_ref) +#define wasm_memory_as_ref_const TINYWASM_SYMBOL(wasm_memory_as_ref_const) +#define wasm_memory_copy TINYWASM_SYMBOL(wasm_memory_copy) +#define wasm_memory_data TINYWASM_SYMBOL(wasm_memory_data) +#define wasm_memory_data_size TINYWASM_SYMBOL(wasm_memory_data_size) +#define wasm_memory_delete TINYWASM_SYMBOL(wasm_memory_delete) +#define wasm_memory_get_host_info TINYWASM_SYMBOL(wasm_memory_get_host_info) +#define wasm_memory_grow TINYWASM_SYMBOL(wasm_memory_grow) +#define wasm_memory_new TINYWASM_SYMBOL(wasm_memory_new) +#define wasm_memory_same TINYWASM_SYMBOL(wasm_memory_same) +#define wasm_memory_set_host_info TINYWASM_SYMBOL(wasm_memory_set_host_info) +#define wasm_memory_set_host_info_with_finalizer TINYWASM_SYMBOL(wasm_memory_set_host_info_with_finalizer) +#define wasm_memory_size TINYWASM_SYMBOL(wasm_memory_size) +#define wasm_memory_type TINYWASM_SYMBOL(wasm_memory_type) +#define wasm_memorytype_as_externtype TINYWASM_SYMBOL(wasm_memorytype_as_externtype) +#define wasm_memorytype_as_externtype_const TINYWASM_SYMBOL(wasm_memorytype_as_externtype_const) +#define wasm_memorytype_copy TINYWASM_SYMBOL(wasm_memorytype_copy) +#define wasm_memorytype_delete TINYWASM_SYMBOL(wasm_memorytype_delete) +#define wasm_memorytype_limits TINYWASM_SYMBOL(wasm_memorytype_limits) +#define wasm_memorytype_new TINYWASM_SYMBOL(wasm_memorytype_new) +#define wasm_memorytype_vec_copy TINYWASM_SYMBOL(wasm_memorytype_vec_copy) +#define wasm_memorytype_vec_delete TINYWASM_SYMBOL(wasm_memorytype_vec_delete) +#define wasm_memorytype_vec_new TINYWASM_SYMBOL(wasm_memorytype_vec_new) +#define wasm_memorytype_vec_new_empty TINYWASM_SYMBOL(wasm_memorytype_vec_new_empty) +#define wasm_memorytype_vec_new_uninitialized TINYWASM_SYMBOL(wasm_memorytype_vec_new_uninitialized) +#define wasm_module_as_ref TINYWASM_SYMBOL(wasm_module_as_ref) +#define wasm_module_as_ref_const TINYWASM_SYMBOL(wasm_module_as_ref_const) +#define wasm_module_copy TINYWASM_SYMBOL(wasm_module_copy) +#define wasm_module_delete TINYWASM_SYMBOL(wasm_module_delete) +#define wasm_module_deserialize TINYWASM_SYMBOL(wasm_module_deserialize) +#define wasm_module_exports TINYWASM_SYMBOL(wasm_module_exports) +#define wasm_module_get_host_info TINYWASM_SYMBOL(wasm_module_get_host_info) +#define wasm_module_imports TINYWASM_SYMBOL(wasm_module_imports) +#define wasm_module_new TINYWASM_SYMBOL(wasm_module_new) +#define wasm_module_obtain TINYWASM_SYMBOL(wasm_module_obtain) +#define wasm_module_same TINYWASM_SYMBOL(wasm_module_same) +#define wasm_module_serialize TINYWASM_SYMBOL(wasm_module_serialize) +#define wasm_module_set_host_info TINYWASM_SYMBOL(wasm_module_set_host_info) +#define wasm_module_set_host_info_with_finalizer TINYWASM_SYMBOL(wasm_module_set_host_info_with_finalizer) +#define wasm_module_share TINYWASM_SYMBOL(wasm_module_share) +#define wasm_module_validate TINYWASM_SYMBOL(wasm_module_validate) +#define wasm_ref_as_extern TINYWASM_SYMBOL(wasm_ref_as_extern) +#define wasm_ref_as_extern_const TINYWASM_SYMBOL(wasm_ref_as_extern_const) +#define wasm_ref_as_foreign TINYWASM_SYMBOL(wasm_ref_as_foreign) +#define wasm_ref_as_foreign_const TINYWASM_SYMBOL(wasm_ref_as_foreign_const) +#define wasm_ref_as_func TINYWASM_SYMBOL(wasm_ref_as_func) +#define wasm_ref_as_func_const TINYWASM_SYMBOL(wasm_ref_as_func_const) +#define wasm_ref_as_global TINYWASM_SYMBOL(wasm_ref_as_global) +#define wasm_ref_as_global_const TINYWASM_SYMBOL(wasm_ref_as_global_const) +#define wasm_ref_as_instance TINYWASM_SYMBOL(wasm_ref_as_instance) +#define wasm_ref_as_instance_const TINYWASM_SYMBOL(wasm_ref_as_instance_const) +#define wasm_ref_as_memory TINYWASM_SYMBOL(wasm_ref_as_memory) +#define wasm_ref_as_memory_const TINYWASM_SYMBOL(wasm_ref_as_memory_const) +#define wasm_ref_as_module TINYWASM_SYMBOL(wasm_ref_as_module) +#define wasm_ref_as_module_const TINYWASM_SYMBOL(wasm_ref_as_module_const) +#define wasm_ref_as_table TINYWASM_SYMBOL(wasm_ref_as_table) +#define wasm_ref_as_table_const TINYWASM_SYMBOL(wasm_ref_as_table_const) +#define wasm_ref_as_trap TINYWASM_SYMBOL(wasm_ref_as_trap) +#define wasm_ref_as_trap_const TINYWASM_SYMBOL(wasm_ref_as_trap_const) +#define wasm_ref_copy TINYWASM_SYMBOL(wasm_ref_copy) +#define wasm_ref_delete TINYWASM_SYMBOL(wasm_ref_delete) +#define wasm_ref_get_host_info TINYWASM_SYMBOL(wasm_ref_get_host_info) +#define wasm_ref_same TINYWASM_SYMBOL(wasm_ref_same) +#define wasm_ref_set_host_info TINYWASM_SYMBOL(wasm_ref_set_host_info) +#define wasm_ref_set_host_info_with_finalizer TINYWASM_SYMBOL(wasm_ref_set_host_info_with_finalizer) +#define wasm_shared_module_delete TINYWASM_SYMBOL(wasm_shared_module_delete) +#define wasm_store_delete TINYWASM_SYMBOL(wasm_store_delete) +#define wasm_store_new TINYWASM_SYMBOL(wasm_store_new) +#define wasm_table_as_extern TINYWASM_SYMBOL(wasm_table_as_extern) +#define wasm_table_as_extern_const TINYWASM_SYMBOL(wasm_table_as_extern_const) +#define wasm_table_as_ref TINYWASM_SYMBOL(wasm_table_as_ref) +#define wasm_table_as_ref_const TINYWASM_SYMBOL(wasm_table_as_ref_const) +#define wasm_table_copy TINYWASM_SYMBOL(wasm_table_copy) +#define wasm_table_delete TINYWASM_SYMBOL(wasm_table_delete) +#define wasm_table_get TINYWASM_SYMBOL(wasm_table_get) +#define wasm_table_get_host_info TINYWASM_SYMBOL(wasm_table_get_host_info) +#define wasm_table_grow TINYWASM_SYMBOL(wasm_table_grow) +#define wasm_table_new TINYWASM_SYMBOL(wasm_table_new) +#define wasm_table_same TINYWASM_SYMBOL(wasm_table_same) +#define wasm_table_set TINYWASM_SYMBOL(wasm_table_set) +#define wasm_table_set_host_info TINYWASM_SYMBOL(wasm_table_set_host_info) +#define wasm_table_set_host_info_with_finalizer TINYWASM_SYMBOL(wasm_table_set_host_info_with_finalizer) +#define wasm_table_size TINYWASM_SYMBOL(wasm_table_size) +#define wasm_table_type TINYWASM_SYMBOL(wasm_table_type) +#define wasm_tabletype_as_externtype TINYWASM_SYMBOL(wasm_tabletype_as_externtype) +#define wasm_tabletype_as_externtype_const TINYWASM_SYMBOL(wasm_tabletype_as_externtype_const) +#define wasm_tabletype_copy TINYWASM_SYMBOL(wasm_tabletype_copy) +#define wasm_tabletype_delete TINYWASM_SYMBOL(wasm_tabletype_delete) +#define wasm_tabletype_element TINYWASM_SYMBOL(wasm_tabletype_element) +#define wasm_tabletype_limits TINYWASM_SYMBOL(wasm_tabletype_limits) +#define wasm_tabletype_new TINYWASM_SYMBOL(wasm_tabletype_new) +#define wasm_tabletype_vec_copy TINYWASM_SYMBOL(wasm_tabletype_vec_copy) +#define wasm_tabletype_vec_delete TINYWASM_SYMBOL(wasm_tabletype_vec_delete) +#define wasm_tabletype_vec_new TINYWASM_SYMBOL(wasm_tabletype_vec_new) +#define wasm_tabletype_vec_new_empty TINYWASM_SYMBOL(wasm_tabletype_vec_new_empty) +#define wasm_tabletype_vec_new_uninitialized TINYWASM_SYMBOL(wasm_tabletype_vec_new_uninitialized) +#define wasm_tagtype_as_externtype TINYWASM_SYMBOL(wasm_tagtype_as_externtype) +#define wasm_tagtype_as_externtype_const TINYWASM_SYMBOL(wasm_tagtype_as_externtype_const) +#define wasm_tagtype_copy TINYWASM_SYMBOL(wasm_tagtype_copy) +#define wasm_tagtype_delete TINYWASM_SYMBOL(wasm_tagtype_delete) +#define wasm_tagtype_functype TINYWASM_SYMBOL(wasm_tagtype_functype) +#define wasm_tagtype_new TINYWASM_SYMBOL(wasm_tagtype_new) +#define wasm_tagtype_vec_copy TINYWASM_SYMBOL(wasm_tagtype_vec_copy) +#define wasm_tagtype_vec_delete TINYWASM_SYMBOL(wasm_tagtype_vec_delete) +#define wasm_tagtype_vec_new TINYWASM_SYMBOL(wasm_tagtype_vec_new) +#define wasm_tagtype_vec_new_empty TINYWASM_SYMBOL(wasm_tagtype_vec_new_empty) +#define wasm_tagtype_vec_new_uninitialized TINYWASM_SYMBOL(wasm_tagtype_vec_new_uninitialized) +#define wasm_trap_as_ref TINYWASM_SYMBOL(wasm_trap_as_ref) +#define wasm_trap_as_ref_const TINYWASM_SYMBOL(wasm_trap_as_ref_const) +#define wasm_trap_copy TINYWASM_SYMBOL(wasm_trap_copy) +#define wasm_trap_delete TINYWASM_SYMBOL(wasm_trap_delete) +#define wasm_trap_get_host_info TINYWASM_SYMBOL(wasm_trap_get_host_info) +#define wasm_trap_message TINYWASM_SYMBOL(wasm_trap_message) +#define wasm_trap_new TINYWASM_SYMBOL(wasm_trap_new) +#define wasm_trap_origin TINYWASM_SYMBOL(wasm_trap_origin) +#define wasm_trap_same TINYWASM_SYMBOL(wasm_trap_same) +#define wasm_trap_set_host_info TINYWASM_SYMBOL(wasm_trap_set_host_info) +#define wasm_trap_set_host_info_with_finalizer TINYWASM_SYMBOL(wasm_trap_set_host_info_with_finalizer) +#define wasm_trap_trace TINYWASM_SYMBOL(wasm_trap_trace) +#define wasm_val_copy TINYWASM_SYMBOL(wasm_val_copy) +#define wasm_val_delete TINYWASM_SYMBOL(wasm_val_delete) +#define wasm_val_vec_copy TINYWASM_SYMBOL(wasm_val_vec_copy) +#define wasm_val_vec_delete TINYWASM_SYMBOL(wasm_val_vec_delete) +#define wasm_val_vec_new TINYWASM_SYMBOL(wasm_val_vec_new) +#define wasm_val_vec_new_empty TINYWASM_SYMBOL(wasm_val_vec_new_empty) +#define wasm_val_vec_new_uninitialized TINYWASM_SYMBOL(wasm_val_vec_new_uninitialized) +#define wasm_valtype_copy TINYWASM_SYMBOL(wasm_valtype_copy) +#define wasm_valtype_delete TINYWASM_SYMBOL(wasm_valtype_delete) +#define wasm_valtype_kind TINYWASM_SYMBOL(wasm_valtype_kind) +#define wasm_valtype_new TINYWASM_SYMBOL(wasm_valtype_new) +#define wasm_valtype_vec_copy TINYWASM_SYMBOL(wasm_valtype_vec_copy) +#define wasm_valtype_vec_delete TINYWASM_SYMBOL(wasm_valtype_vec_delete) +#define wasm_valtype_vec_new TINYWASM_SYMBOL(wasm_valtype_vec_new) +#define wasm_valtype_vec_new_empty TINYWASM_SYMBOL(wasm_valtype_vec_new_empty) +#define wasm_valtype_vec_new_uninitialized TINYWASM_SYMBOL(wasm_valtype_vec_new_uninitialized) +#endif diff --git a/crates/c-api/include/tinywasm.h b/crates/c-api/include/tinywasm.h new file mode 100644 index 00000000..18a96788 --- /dev/null +++ b/crates/c-api/include/tinywasm.h @@ -0,0 +1,21 @@ +#ifndef TINYWASM_H +#define TINYWASM_H + +#ifdef TINYWASM_C_API_PREFIX +#include "tinywasm-prefix.h" +#endif +#include "wasm.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Copies this thread's most recent API error into an owned, nul-terminated + * vector. Successful operations do not clear it. Delete with wasm_byte_vec_delete. + * An empty diagnostic is returned as a single nul byte. */ +WASM_API_EXTERN void tinywasm_last_error_message(wasm_message_t* out); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/crates/c-api/include/wasm.h b/crates/c-api/include/wasm.h new file mode 100644 index 00000000..41f7fa3a --- /dev/null +++ b/crates/c-api/include/wasm.h @@ -0,0 +1,737 @@ +// WebAssembly C API + +#ifndef WASM_H +#define WASM_H + +#include +#include +#include +#include +#include + +#ifndef WASM_API_EXTERN +#if defined(_WIN32) && !defined(__MINGW32__) && !defined(LIBWASM_STATIC) +#define WASM_API_EXTERN __declspec(dllimport) +#else +#define WASM_API_EXTERN +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/////////////////////////////////////////////////////////////////////////////// +// Auxiliaries + +// Machine types + +inline void assertions(void) { + 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_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), s); +} + +static inline void wasm_name_new_from_string_nt( + 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(void); + +// Embedders may provide custom functions for manipulating configs. + + +// Engine + +WASM_DECLARE_OWN(engine) + +WASM_API_EXTERN own wasm_engine_t* wasm_engine_new(void); +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(const 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_EXTERNREF = 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_EXTERNREF; +} +static inline bool wasm_valkind_is_ref(wasm_valkind_t k) { + return k >= WASM_EXTERNREF; +} + +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*); + + +// Tag Types + +WASM_DECLARE_TYPE(tagtype) + +WASM_API_EXTERN own wasm_tagtype_t* wasm_tagtype_new(own wasm_functype_t*); + +WASM_API_EXTERN const wasm_functype_t* wasm_tagtype_functype(const wasm_tagtype_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_EXTERN_TAG, +}; + +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_externtype_t* wasm_tagtype_as_externtype(wasm_tagtype_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 wasm_tagtype_t* wasm_externtype_as_tagtype(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_externtype_t* wasm_tagtype_as_externtype_const(const wasm_tagtype_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*); +WASM_API_EXTERN const wasm_tagtype_t* wasm_externtype_as_tagtype_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_vec_t* args, own wasm_val_vec_t* results); +typedef own wasm_trap_t* (*wasm_func_callback_with_env_t)( + void* env, const wasm_val_vec_t* args, wasm_val_vec_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_vec_t* args, wasm_val_vec_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_vec_t* imports, + own wasm_trap_t** +); + +WASM_API_EXTERN void wasm_instance_exports(const wasm_instance_t*, own wasm_extern_vec_t* out); + + +/////////////////////////////////////////////////////////////////////////////// +// Convenience + +// Vectors + +#define WASM_EMPTY_VEC {0, NULL} +#define WASM_ARRAY_VEC(array) {sizeof(array)/sizeof(*(array)), array} + + +// Value Type construction short-hands + +static inline own wasm_valtype_t* wasm_valtype_new_i32(void) { + return wasm_valtype_new(WASM_I32); +} +static inline own wasm_valtype_t* wasm_valtype_new_i64(void) { + return wasm_valtype_new(WASM_I64); +} +static inline own wasm_valtype_t* wasm_valtype_new_f32(void) { + return wasm_valtype_new(WASM_F32); +} +static inline own wasm_valtype_t* wasm_valtype_new_f64(void) { + return wasm_valtype_new(WASM_F64); +} + +static inline own wasm_valtype_t* wasm_valtype_new_externref(void) { + return wasm_valtype_new(WASM_EXTERNREF); +} +static inline own wasm_valtype_t* wasm_valtype_new_funcref(void) { + return wasm_valtype_new(WASM_FUNCREF); +} + + +// Function Types construction short-hands + +static inline own wasm_functype_t* wasm_functype_new_0_0(void) { + 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 +} + +#define WASM_I32_VAL(i) {.kind = WASM_I32, .of = {.i32 = i}} +#define WASM_I64_VAL(i) {.kind = WASM_I64, .of = {.i64 = i}} +#define WASM_F32_VAL(z) {.kind = WASM_F32, .of = {.f32 = z}} +#define WASM_F64_VAL(z) {.kind = WASM_F64, .of = {.f64 = z}} +#define WASM_REF_VAL(r) {.kind = WASM_EXTERNREF, .of = {.ref = r}} +#define WASM_INIT_VAL {.kind = WASM_EXTERNREF, .of = {.ref = NULL}} + + +/////////////////////////////////////////////////////////////////////////////// + +#undef own + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // #ifdef WASM_H diff --git a/crates/c-api/src/externals.rs b/crates/c-api/src/externals.rs new file mode 100644 index 00000000..71da5823 --- /dev/null +++ b/crates/c-api/src/externals.rs @@ -0,0 +1,130 @@ +use crate::{boxed, failure, objects::*, runtime::*, types::*, values::*}; +use std::{ptr, rc::Rc}; +use tinywasm::{Global, Memory, Table, WasmValue}; + +export! { pub unsafe extern "C" fn wasm_extern_kind(value: *const wasm_extern_t) -> u8 { unsafe { &*value }.0.kind.extern_kind().expect("expected extern") }} +export! { pub unsafe extern "C" fn wasm_extern_type(value: *const wasm_extern_t) -> *mut wasm_externtype_t { + unsafe { match wasm_extern_kind(value) { + 0 => crate::function::wasm_func_type(value), 1 => wasm_global_type(value), + 2 => wasm_table_type(value), 3 => wasm_memory_type(value), _ => unreachable!(), + } } +}} + +macro_rules! object_type { + ($name:ident, $variant:ident, $convert:ident) => { + export! { pub unsafe extern "C" fn $name(value: *const wasm_ref_t) -> *mut wasm_externtype_t { + let object = unsafe { &(*value).0 }; + with_object(object, |_, access| { + let ObjectKind::$variant(item) = &object.kind else { return Err(tinywasm::Error::Other("incorrect object kind".into())); }; + wasm_externtype_t::$convert(item.ty(access.store())?) + .map(boxed).ok_or_else(|| tinywasm::Error::Other("type is not representable by wasm.h".into())) + }) + }} + }; +} +object_type!(wasm_memory_type, Memory, from_memory); +object_type!(wasm_table_type, Table, from_table); +object_type!(wasm_global_type, Global, from_global); + +export! { pub unsafe extern "C" fn wasm_memory_new(store: *mut wasm_store_t, ty: *const wasm_memorytype_t) -> *mut wasm_memory_t { + let state = unsafe { &(*store).0 }; + state.access(|access| Ok(state.intern(ObjectKind::Memory(Memory::try_new(access.store(), unsafe { (*ty).memory() })?)))) + .map_or_else(failure, |object| boxed(wasm_ref_t(object))) +}} +export! { pub unsafe extern "C" fn wasm_global_new(store: *mut wasm_store_t, ty: *const wasm_globaltype_t, value: *const wasm_val_t) -> *mut wasm_global_t { + let state = unsafe { &(*store).0 }; + state.access(|access| { + let value = unsafe { (*value).to_runtime(state, access.store()) }?; + Ok(state.intern(ObjectKind::Global(Global::try_new(access.store(), unsafe { (*ty).global() }, value)?))) + }).map_or_else(failure, |object| boxed(wasm_ref_t(object))) +}} +export! { pub unsafe extern "C" fn wasm_table_new(store: *mut wasm_store_t, ty: *const wasm_tabletype_t, init: *mut wasm_ref_t) -> *mut wasm_table_t { + let state = unsafe { &(*store).0 }; + let ty = unsafe { (*ty).table() }; + state.access(|access| { + let kind = value_kind(tinywasm::types::WasmType::Ref(ty.element_type)).unwrap(); + let init = unsafe { reference_to_runtime(init, kind, state, access.store()) }?; + Ok(state.intern(ObjectKind::Table(Table::try_new(access.store(), ty, init.into())?))) + }).map_or_else(failure, |object| boxed(wasm_ref_t(object))) +}} + +fn with_object( + object: &Object, + action: impl FnOnce(&Rc, &mut Access<'_>) -> tinywasm::Result, +) -> T { + let result = object.state().and_then(|state| state.access(|access| action(&state, access))); + result.unwrap_or_else(failure) +} + +export! { pub unsafe extern "C" fn wasm_memory_data(value: *mut wasm_memory_t) -> *mut u8 { + let object = unsafe { &(*value).0 }; + let ObjectKind::Memory(memory) = &object.kind else { return ptr::null_mut(); }; + with_object(object, |_, access| memory.data_ptr(access.store())) +}} +export! { pub unsafe extern "C" fn wasm_memory_data_size(value: *const wasm_memory_t) -> usize { + let object = unsafe { &(*value).0 }; + let ObjectKind::Memory(memory) = &object.kind else { return 0; }; + with_object(object, |_, access| memory.len(access.store())) +}} +export! { pub unsafe extern "C" fn wasm_memory_size(value: *const wasm_memory_t) -> u32 { + let object = unsafe { &(*value).0 }; + let ObjectKind::Memory(memory) = &object.kind else { return 0; }; + with_object(object, |_, access| Ok(memory.page_count(access.store())? as u32)) +}} +export! { pub unsafe extern "C" fn wasm_memory_grow(value: *mut wasm_memory_t, delta: u32) -> bool { + let object = unsafe { &(*value).0 }; + let ObjectKind::Memory(memory) = &object.kind else { return false; }; + with_object(object, |_, access| Ok(memory.grow(access.store(), delta as i64)?.is_some())) +}} +export! { pub unsafe extern "C" fn wasm_global_get(value: *const wasm_global_t, out: *mut wasm_val_t) { + let object = unsafe { &(*value).0 }; + let ObjectKind::Global(global) = &object.kind else { return; }; + let result = with_object(object, |state, access| { + let kind = value_kind(global.ty(access.store())?.ty).ok_or_else(|| tinywasm::Error::Other("unsupported global type".into()))?; + let value = global.get(access.store())?; + wasm_val_t::from_runtime(&value, kind, state, access.store()) + }); + unsafe { out.write(result) }; +}} +export! { pub unsafe extern "C" fn wasm_global_set(value: *mut wasm_global_t, new_value: *const wasm_val_t) { + let object = unsafe { &(*value).0 }; + let ObjectKind::Global(global) = &object.kind else { return; }; + with_object(object, |state, access| { + let value = unsafe { (*new_value).to_runtime(state, access.store()) }?; + global.set(access.store(), value) + }); +}} +export! { pub unsafe extern "C" fn wasm_table_size(value: *const wasm_table_t) -> u32 { + let object = unsafe { &(*value).0 }; + let ObjectKind::Table(table) = &object.kind else { return 0; }; + with_object(object, |_, access| Ok(table.size(access.store())? as u32)) +}} +export! { pub unsafe extern "C" fn wasm_table_get(value: *const wasm_table_t, index: u32) -> *mut wasm_ref_t { + let object = unsafe { &(*value).0 }; + let ObjectKind::Table(table) = &object.kind else { return ptr::null_mut(); }; + with_object(object, |state, access| { + let kind = value_kind(tinywasm::types::WasmType::Ref(table.ty(access.store())?.element_type)).unwrap(); + let value = table.get(access.store(), index)?; + let result = wasm_val_t::from_runtime(&value, kind, state, access.store())?; + Ok(unsafe { result.of.reference }) + }) +}} +export! { pub unsafe extern "C" fn wasm_table_set(value: *mut wasm_table_t, index: u32, element: *mut wasm_ref_t) -> bool { + let object = unsafe { &(*value).0 }; + let ObjectKind::Table(table) = &object.kind else { return false; }; + with_object(object, |state, access| { + let kind = value_kind(tinywasm::types::WasmType::Ref(table.ty(access.store())?.element_type)).unwrap(); + let value = unsafe { reference_to_runtime(element, kind, state, access.store()) }?; + table.set(access.store(), index, WasmValue::Ref(value))?; + Ok(true) + }) +}} +export! { pub unsafe extern "C" fn wasm_table_grow(value: *mut wasm_table_t, delta: u32, init: *mut wasm_ref_t) -> bool { + let object = unsafe { &(*value).0 }; + let ObjectKind::Table(table) = &object.kind else { return false; }; + with_object(object, |state, access| { + let kind = value_kind(tinywasm::types::WasmType::Ref(table.ty(access.store())?.element_type)).unwrap(); + let init = unsafe { reference_to_runtime(init, kind, state, access.store()) }?; + Ok(table.grow(access.store(), delta as i32, init.into())?.is_some()) + }) +}} diff --git a/crates/c-api/src/function.rs b/crates/c-api/src/function.rs new file mode 100644 index 00000000..e6cd58a2 --- /dev/null +++ b/crates/c-api/src/function.rs @@ -0,0 +1,165 @@ +use crate::{boxed, failure, objects::*, runtime::*, types::*, values::*, vectors::*}; +use std::{ffi::c_void, ptr, rc::Rc}; +use tinywasm::types::FuncType; +use tinywasm::{HostFunction, WasmValue}; + +type Callback = unsafe extern "C" fn(*const wasm_val_vec_t, *mut wasm_val_vec_t) -> *mut wasm_trap_t; +type CallbackWithEnv = + unsafe extern "C" fn(*mut c_void, *const wasm_val_vec_t, *mut wasm_val_vec_t) -> *mut wasm_trap_t; + +enum CallbackFn { + Plain(Callback), + WithEnv(CallbackWithEnv), +} +struct CallbackData { + function: CallbackFn, + environment: HostInfo, +} + +/// Adapts thread-confined C callback data when TinyWasm's `send` feature is +/// enabled through Cargo feature unification. +/// +/// This does not make C API stores thread-safe. Their callbacks and objects +/// must still be accessed and destroyed on the store's creating thread. +struct ThreadConfined(T); + +// SAFETY: no safe Rust API exposes C stores or their host callbacks. The C API +// requires a store and all of its objects to remain on their creating thread. +unsafe impl Send for ThreadConfined {} +unsafe impl Sync for ThreadConfined {} + +impl ThreadConfined { + /// Returns the wrapped value on its originating thread. + /// + /// # Safety + /// The caller must be running on the thread that created the C API store. + unsafe fn get(&self) -> &T { + &self.0 + } +} + +impl CallbackData { + /// # Safety + /// The C callback and its environment must remain valid for this call, and + /// `args` and `results` must point to valid callback vectors. + unsafe fn call(&self, args: *const wasm_val_vec_t, results: *mut wasm_val_vec_t) -> *mut wasm_trap_t { + unsafe { + match self.function { + CallbackFn::Plain(function) => function(args, results), + CallbackFn::WithEnv(function) => function(self.environment.data, args, results), + } + } + } +} + +fn new_function(state: &Rc, ty: &wasm_functype_t, callback: CallbackData) -> *mut wasm_func_t { + let ty = ty.func(); + let params = ty.params().iter().map(|ty| value_kind(*ty).unwrap()).collect::>(); + let results = ty.results().iter().map(|ty| value_kind(*ty).unwrap()).collect::>(); + let callback = ThreadConfined((Rc::downgrade(state), callback)); + let host = HostFunction::from_untyped(&ty, move |mut context, args, out| { + // SAFETY: invoking a C host callback through its store is restricted to + // the thread that created that store. + let (weak, callback) = unsafe { callback.get() }; + let state = weak.upgrade().ok_or_else(|| tinywasm::Error::Other("store has been deleted".into()))?; + let mut c_args = Vector::from_vec((0..args.len()).map(|_| wasm_val_t::default()).collect()); + for ((out, value), kind) in unsafe { c_args.as_mut_slice() }.iter_mut().zip(args).zip(¶ms) { + *out = wasm_val_t::from_runtime(value, *kind, &state, context.store())?; + } + let mut c_results = Vector::from_vec( + results.iter().map(|kind| wasm_val_t { kind: *kind, of: wasm_val_union { i64: 0 } }).collect(), + ); + let result_data = c_results.data; + let returned_trap = state.callback(&mut context, || unsafe { callback.call(&c_args, &mut c_results) }); + // The result vector is caller-provided storage, not replaceable ownership. + assert_eq!(c_results.data, result_data, "callback replaced result storage"); + assert_eq!(c_results.size, results.len(), "callback changed result count"); + if !returned_trap.is_null() { + let returned_trap = unsafe { Box::from_raw(returned_trap) }; + if !returned_trap.0.store.ptr_eq(&Rc::downgrade(&state)) { + return Err(tinywasm::Trap::InvalidStore.into()); + } + *state.trap.borrow_mut() = Some(returned_trap.0); + return Err(tinywasm::Error::Other("C host callback trapped".into())); + } + for ((out, value), expected) in out.iter_mut().zip(unsafe { c_results.as_slice() }).zip(&results) { + if value.kind != *expected { + return Err(tinywasm::Error::Other("C callback returned an incorrect value type".into())); + } + *out = unsafe { value.to_runtime(&state, context.store_mut()) }?; + } + Ok(()) + }); + let result = state.access(|access| { + let function = host.instantiate(access.store())?; + let reference = function.as_func_ref(access.store())?; + Ok(state.intern(ObjectKind::Func(function, reference))) + }); + result.map_or_else(failure, |object| boxed(wasm_ref_t(object))) +} + +fn function_type(object: &Object) -> tinywasm::Result { + let state = object.state()?; + let ObjectKind::Func(func, _) = &object.kind else { + return Err(tinywasm::Error::Other("expected function".into())); + }; + state.access(|access| Ok(func.ty(access.store())?.clone())) +} + +export! { pub unsafe extern "C" fn wasm_func_new(store: *mut wasm_store_t, ty: *const wasm_functype_t, callback: Callback) -> *mut wasm_func_t { + unsafe { new_function(&(*store).0, &*ty, CallbackData { function: CallbackFn::Plain(callback), environment: HostInfo::default() }) } +}} +export! { pub unsafe extern "C" fn wasm_func_new_with_env(store: *mut wasm_store_t, ty: *const wasm_functype_t, callback: CallbackWithEnv, env: *mut c_void, finalizer: Option) -> *mut wasm_func_t { + unsafe { new_function(&(*store).0, &*ty, CallbackData { function: CallbackFn::WithEnv(callback), environment: HostInfo { data: env, finalizer } }) } +}} +export! { pub unsafe extern "C" fn wasm_func_type(value: *const wasm_func_t) -> *mut wasm_functype_t { + function_type(unsafe { &(*value).0 }) + .and_then(|ty| wasm_externtype_t::from_func(&ty).ok_or_else(|| tinywasm::Error::Other("function type is not representable by wasm.h".into()))) + .map_or_else(failure, boxed) +}} +export! { pub unsafe extern "C" fn wasm_func_param_arity(value: *const wasm_func_t) -> usize { + function_type(unsafe { &(*value).0 }).map(|ty| ty.params().len()).unwrap_or_else(failure) +}} +export! { pub unsafe extern "C" fn wasm_func_result_arity(value: *const wasm_func_t) -> usize { + function_type(unsafe { &(*value).0 }).map(|ty| ty.results().len()).unwrap_or_else(failure) +}} + +export! { pub unsafe extern "C" fn wasm_func_call(value: *const wasm_func_t, args: *const wasm_val_vec_t, results: *mut wasm_val_vec_t) -> *mut wasm_trap_t { + let object = unsafe { (*value).0.clone() }; + let state = match object.state() { + Ok(state) => state, + Err(error) => return failure(error), + }; + let result = state.access(|access| { + let ObjectKind::Func(function, _) = &object.kind else { return Err(tinywasm::Error::Other("expected function".into())); }; + let ty = function.ty(access.store())?.clone(); + let args = unsafe { (*args).as_slice() }; + if args.len() != ty.params().len() || unsafe { (*results).size } != ty.results().len() { + return Err(tinywasm::Error::Other("function argument or result count mismatch".into())); + } + let args = args.iter().zip(ty.params()).map(|(value, ty)| { + if Some(value.kind) != value_kind(*ty) { return Err(tinywasm::Error::Other("function argument type mismatch".into())); } + unsafe { value.to_runtime(&state, access.store()) } + }).collect::>>()?; + let kinds = ty.results().iter().map(|ty| value_kind(*ty).ok_or_else(|| tinywasm::Error::Other("result type is not representable by wasm.h".into()))).collect::>>()?; + let mut values = vec![WasmValue::I32(0); kinds.len()]; + access.call(function, &args, &mut values)?; + let mut converted = Vector::from_vec((0..values.len()).map(|_| wasm_val_t::default()).collect()); + for ((out, value), kind) in unsafe { converted.as_mut_slice() }.iter_mut().zip(&values).zip(kinds) { + *out = wasm_val_t::from_runtime(value, kind, &state, access.store())?; + } + // Results may be uninitialized C storage. Transfer values with raw writes, + // rather than reading or dropping the previous contents. + for (index, value) in unsafe { converted.as_mut_slice() }.iter_mut().enumerate() { + unsafe { (*results).data.add(index).write(std::mem::take(value)) }; + } + Ok(()) + }); + match result { + Ok(()) => ptr::null_mut(), + Err(error) => { + failure::<()>(&error); + state.trap.borrow_mut().take().map_or_else(|| trap(&state, error.to_string()), |trap| boxed(wasm_ref_t(trap))) + } + } +}} diff --git a/crates/c-api/src/lib.rs b/crates/c-api/src/lib.rs new file mode 100644 index 00000000..484de1b5 --- /dev/null +++ b/crates/c-api/src/lib.rs @@ -0,0 +1,80 @@ +//! C API implementation for TinyWasm. +//! +//! See `include/wasm.h` and `include/tinywasm.h` for the C interface and ownership rules. +//! Use a store and its objects only on the thread that created the store. +//! +//! # Symbol prefix +//! +//! By default, the library exports the names in `wasm.h`. To avoid collisions +//! with another WebAssembly C API implementation, build with a prefix: +//! +//! ```sh +//! make -C crates/c-api TINYWASM_C_API_PREFIX=my_ +//! ``` +//! +//! Define the same prefix before including `tinywasm.h` in C or C++: +//! +//! ```c +//! #define TINYWASM_C_API_PREFIX my_ +//! #include "tinywasm.h" +//! ``` +//! +//! When building with Cargo directly, enable `custom-prefix` and set +//! `TINYWASM_C_API_PREFIX`. Include `tinywasm.h` before `wasm.h` so the aliases +//! take effect. +#![allow(non_camel_case_types)] +#![deny(unsafe_op_in_unsafe_fn)] + +#[macro_use] +mod macros; +mod externals; +mod function; +mod module; +mod objects; +mod runtime; +mod types; +mod values; +mod vectors; + +#[cfg(test)] +mod tests; + +use std::cell::RefCell; + +thread_local! { + static LAST_ERROR: RefCell> = const { RefCell::new(Vec::new()) }; +} + +/// Records an error for the calling thread and returns the ABI failure value. +fn failure(error: impl std::fmt::Display) -> T { + LAST_ERROR.with(|slot| *slot.borrow_mut() = error.to_string().into_bytes()); + T::default() +} + +/// Allocates an owned opaque handle. +fn boxed(value: T) -> *mut T { + Box::into_raw(Box::new(value)) +} + +/// Deletes a nullable owned opaque handle. +/// +/// # Safety +/// `value` must be null or a live handle returned by `boxed` whose ownership +/// has been transferred to this call exactly once. +unsafe fn delete(value: *mut T) { + if !value.is_null() { + unsafe { drop(Box::from_raw(value)) }; + } +} + +export! { +/// Copies the calling thread's last error into an owned, nul-terminated byte vector. +/// +/// # Safety +/// `out` must point to writable vector storage that does not own an allocation. +pub unsafe extern "C" fn tinywasm_last_error_message(out: *mut vectors::wasm_byte_vec_t) { + let mut message = LAST_ERROR.with(|slot| slot.borrow().clone()); + message.push(0); + unsafe { out.write(vectors::wasm_byte_vec_t::from_vec(message)) }; +} +} diff --git a/crates/c-api/src/macros.rs b/crates/c-api/src/macros.rs new file mode 100644 index 00000000..cb05a538 --- /dev/null +++ b/crates/c-api/src/macros.rs @@ -0,0 +1,8 @@ +macro_rules! export { + ($(#[$meta:meta])* pub unsafe extern "C" fn $name:ident($($args:tt)*) $(-> $result:ty)? $body:block) => { + $(#[$meta])* + #[cfg_attr(not(feature = "custom-prefix"), unsafe(no_mangle))] + #[cfg_attr(feature = "custom-prefix", unsafe(export_name = concat!(env!("TINYWASM_C_API_PREFIX"), stringify!($name))))] + pub unsafe extern "C" fn $name($($args)*) $(-> $result)? $body + }; +} diff --git a/crates/c-api/src/module.rs b/crates/c-api/src/module.rs new file mode 100644 index 00000000..02c346b0 --- /dev/null +++ b/crates/c-api/src/module.rs @@ -0,0 +1,137 @@ +use crate::{boxed, failure, objects::*, runtime::*, types::*, vectors::*}; +use std::{ptr, rc::Rc}; +use tinywasm::{ + Extern, ExternItem, Module, ModuleInstance, + types::{ExportType, ImportType}, +}; + +fn import_type(ty: ImportType<'_>) -> Option { + match ty { + ImportType::Func(ty) => wasm_externtype_t::from_func(ty), + ImportType::Global(ty) => wasm_externtype_t::from_global(*ty), + ImportType::Table(ty) => wasm_externtype_t::from_table(*ty), + ImportType::Memory(ty) => wasm_externtype_t::from_memory(*ty), + ImportType::Tag(_) => None, + } +} + +fn export_type(ty: ExportType<'_>) -> Option { + match ty { + ExportType::Func(ty) => wasm_externtype_t::from_func(ty), + ExportType::Global(ty) => wasm_externtype_t::from_global(*ty), + ExportType::Table(ty) => wasm_externtype_t::from_table(*ty), + ExportType::Memory(ty) => wasm_externtype_t::from_memory(*ty), + ExportType::Tag(_) => None, + } +} + +fn check_interface(module: Module) -> tinywasm::Result { + if module.imports().all(|item| import_type(item.ty).is_some()) + && module.exports().all(|item| export_type(item.ty).is_some()) + { + Ok(module) + } else { + Err(tinywasm::Error::Other("module interface contains types not representable by wasm.h".into())) + } +} + +fn module_handle(state: &Rc, module: Module) -> *mut wasm_module_t { + boxed(wasm_ref_t(Rc::new(Object::new(Rc::downgrade(state), ObjectKind::Module(module))))) +} + +export! { pub unsafe extern "C" fn wasm_module_new(store: *mut wasm_store_t, binary: *const wasm_byte_vec_t) -> *mut wasm_module_t { + match tinywasm::parse_bytes(unsafe { (*binary).as_slice() }).map_err(tinywasm::Error::from).and_then(check_interface) { + Ok(module) => module_handle(unsafe { &(*store).0 }, module), Err(error) => failure(error), + } +}} +export! { pub unsafe extern "C" fn wasm_module_validate(_: *mut wasm_store_t, binary: *const wasm_byte_vec_t) -> bool { + match tinywasm::parse_bytes(unsafe { (*binary).as_slice() }).map_err(tinywasm::Error::from).and_then(check_interface) { + Ok(_) => true, Err(error) => failure(error), + } +}} +export! { pub unsafe extern "C" fn wasm_module_imports(value: *const wasm_module_t, out: *mut wasm_importtype_vec_t) { + let ObjectKind::Module(module) = &(unsafe { &*value }).0.kind else { panic!("expected module") }; + let values = module.imports().map(|item| boxed(wasm_importtype_t { + module: Vector::from_vec(item.module.as_bytes().to_vec()), name: Vector::from_vec(item.name.as_bytes().to_vec()), + ty: Box::new(import_type(item.ty).expect("checked module interface")), + })).collect(); + unsafe { out.write(Vector::from_vec(values)) }; +}} +export! { pub unsafe extern "C" fn wasm_module_exports(value: *const wasm_module_t, out: *mut wasm_exporttype_vec_t) { + let ObjectKind::Module(module) = &(unsafe { &*value }).0.kind else { panic!("expected module") }; + let values = module.exports().map(|item| boxed(wasm_exporttype_t { + name: Vector::from_vec(item.name.as_bytes().to_vec()), ty: Box::new(export_type(item.ty).expect("checked module interface")), + })).collect(); + unsafe { out.write(Vector::from_vec(values)) }; +}} + +pub struct wasm_shared_module_t(Module); +export! { pub unsafe extern "C" fn wasm_module_share(value: *const wasm_module_t) -> *mut wasm_shared_module_t { + let ObjectKind::Module(module) = &(unsafe { &*value }).0.kind else { panic!("expected module") }; + boxed(wasm_shared_module_t(module.clone())) +}} +export! { pub unsafe extern "C" fn wasm_module_obtain(store: *mut wasm_store_t, value: *const wasm_shared_module_t) -> *mut wasm_module_t { unsafe { module_handle(&(*store).0, (*value).0.clone()) } }} +export! { pub unsafe extern "C" fn wasm_shared_module_delete(value: *mut wasm_shared_module_t) { unsafe { crate::delete(value) }; }} +export! { pub unsafe extern "C" fn wasm_module_serialize(value: *const wasm_module_t, out: *mut wasm_byte_vec_t) { + let ObjectKind::Module(module) = &(unsafe { &*value }).0.kind else { panic!("expected module") }; + let bytes = module.serialize_twasm().unwrap_or_else(failure); + unsafe { out.write(Vector::from_vec(bytes)) }; +}} +export! { pub unsafe extern "C" fn wasm_module_deserialize(store: *mut wasm_store_t, bytes: *const wasm_byte_vec_t) -> *mut wasm_module_t { + // As with the Rust archive API, these bytes must come from a trusted serializer. + match Module::try_from_twasm(unsafe { (*bytes).as_slice() }).map_err(tinywasm::Error::from).and_then(check_interface) { + Ok(module) => module_handle(unsafe { &(*store).0 }, module), Err(error) => failure(error), + } +}} + +export! { pub unsafe extern "C" fn wasm_instance_new(store: *mut wasm_store_t, module: *const wasm_module_t, imports: *const wasm_extern_vec_t, trap_out: *mut *mut wasm_trap_t) -> *mut wasm_instance_t { + if !trap_out.is_null() { unsafe { trap_out.write(ptr::null_mut()) }; } + let state = unsafe { &(*store).0 }; + let object = unsafe { &(*module).0 }; + let result = state.access(|access| { + if access.is_callback() { return Err(tinywasm::Error::Other("instantiation during a callback is not supported".into())); } + if !object.store.ptr_eq(&Rc::downgrade(state)) { return Err(tinywasm::Trap::InvalidStore.into()); } + let ObjectKind::Module(module) = &object.kind else { return Err(tinywasm::Error::Other("expected module".into())); }; + let imports = unsafe { (*imports).as_slice() }.iter().map(|value| { + let object = unsafe { &(**value).0 }; + if !object.store.ptr_eq(&Rc::downgrade(state)) { return Err(tinywasm::Trap::InvalidStore.into()); } + Ok(match &object.kind { + ObjectKind::Func(func, _) => Extern::Function(func.clone()), ObjectKind::Global(global) => Extern::Global(*global), + ObjectKind::Memory(memory) => Extern::Memory(*memory), ObjectKind::Table(table) => Extern::Table(*table), + _ => return Err(tinywasm::Error::Other("expected external import".into())), + }) + }).collect::>>()?; + ModuleInstance::instantiate_ordered(access.store(), module, &imports) + }); + match result { + Ok(instance) => boxed(wasm_ref_t(state.intern(ObjectKind::Instance(instance)))), + Err(error) => { + let pending = state.trap.borrow_mut().take(); + if !trap_out.is_null() { + let value = pending.map_or_else(|| trap(state, error.to_string()), |trap| boxed(wasm_ref_t(trap))); + unsafe { trap_out.write(value) }; + } + failure(error) + } + } +}} + +export! { pub unsafe extern "C" fn wasm_instance_exports(value: *const wasm_instance_t, out: *mut wasm_extern_vec_t) { + let object = unsafe { &(*value).0 }; + let result = (|| { + let state = object.state()?; + let ObjectKind::Instance(instance) = &object.kind else { return Err(tinywasm::Error::Other("expected instance".into())); }; + state.access(|access| { + let kinds = instance.exports().map(|(_, item)| { + Ok(match item { + ExternItem::Func(func) => { let reference = func.as_func_ref(access.store())?; ObjectKind::Func(func, reference) }, + ExternItem::Global(global) => ObjectKind::Global(global), ExternItem::Memory(memory) => ObjectKind::Memory(memory), + ExternItem::Table(table) => ObjectKind::Table(table), ExternItem::Tag(_) => return Err(tinywasm::Error::Other("tag exports are unsupported".into())), + ExternItem::MemoryShared(_) => return Err(tinywasm::Error::Other("shared memory exports are unsupported".into())), + }) + }).collect::>>()?; + Ok(Vector::from_vec(kinds.into_iter().map(|kind| boxed(wasm_ref_t(state.intern(kind)))).collect())) + }) + })(); + unsafe { out.write(result.unwrap_or_else(failure)) }; +}} diff --git a/crates/c-api/src/objects.rs b/crates/c-api/src/objects.rs new file mode 100644 index 00000000..bb717997 --- /dev/null +++ b/crates/c-api/src/objects.rs @@ -0,0 +1,330 @@ +use crate::{ + boxed, + runtime::{StoreState, wasm_store_t}, + vectors::*, +}; +use std::{ + cell::RefCell, + ffi::c_void, + ptr, + rc::{Rc, Weak}, +}; +use tinywasm::{FuncRef, Function, Global, Memory, Module, ModuleInstance, Table}; + +/// Calls the optional finalizer when replaced or dropped. +pub(crate) struct HostInfo { + pub(crate) data: *mut c_void, + pub(crate) finalizer: Option, +} +impl Default for HostInfo { + fn default() -> Self { + Self { data: ptr::null_mut(), finalizer: None } + } +} +impl Drop for HostInfo { + fn drop(&mut self) { + if let Some(finalizer) = self.finalizer { + unsafe { finalizer(self.data) }; + } + } +} + +/// Runtime payload shared by all C views of an object. +pub(crate) enum ObjectKind { + Func(Function, FuncRef), + Global(Global), + Table(Table), + Memory(Memory), + Module(Module), + Instance(ModuleInstance), + Foreign, + Trap(Vec), +} +impl ObjectKind { + pub(crate) fn same(&self, other: &Self) -> bool { + match (self, other) { + (Self::Func(_, a), Self::Func(_, b)) => a == b, + (Self::Global(a), Self::Global(b)) => a == b, + (Self::Memory(a), Self::Memory(b)) => a == b, + (Self::Table(a), Self::Table(b)) => a == b, + (Self::Instance(a), Self::Instance(b)) => a.id() == b.id(), + _ => false, + } + } + + pub(crate) fn extern_kind(&self) -> Option { + match self { + Self::Func(..) => Some(0), + Self::Global(_) => Some(1), + Self::Table(_) => Some(2), + Self::Memory(_) => Some(3), + _ => None, + } + } +} + +/// Object identity, independent of the allocation of each copied C handle. +pub(crate) struct Object { + pub(crate) store: Weak, + pub(crate) kind: ObjectKind, + host: RefCell, +} +impl Object { + pub(crate) fn new(store: Weak, kind: ObjectKind) -> Self { + Self { store, kind, host: RefCell::new(HostInfo::default()) } + } + pub(crate) fn state(&self) -> tinywasm::Result> { + self.store.upgrade().ok_or_else(|| tinywasm::Error::Other("store has been deleted".into())) + } +} + +#[derive(Clone)] +pub struct wasm_ref_t(pub(crate) Rc); +pub type wasm_func_t = wasm_ref_t; +pub type wasm_global_t = wasm_ref_t; +pub type wasm_table_t = wasm_ref_t; +pub type wasm_memory_t = wasm_ref_t; +pub type wasm_extern_t = wasm_ref_t; +pub type wasm_module_t = wasm_ref_t; +pub type wasm_instance_t = wasm_ref_t; +pub type wasm_foreign_t = wasm_ref_t; +pub type wasm_trap_t = wasm_ref_t; + +/// Creates a standalone trap handle. Its metadata survives propagation through C callbacks. +pub(crate) fn trap(store: &Rc, message: impl AsRef<[u8]>) -> *mut wasm_trap_t { + let mut message = message.as_ref().to_vec(); + if message.last() != Some(&0) { + message.push(0); + } + boxed(wasm_ref_t(Rc::new(Object::new(Rc::downgrade(store), ObjectKind::Trap(message))))) +} + +macro_rules! reference_api { + ($copy:ident, $delete:ident, $same:ident, $get:ident, $set:ident, $set_final:ident) => { + export! { pub unsafe extern "C" fn $copy(value: *const wasm_ref_t) -> *mut wasm_ref_t { unsafe { value.as_ref().map_or(ptr::null_mut(), |value| boxed(value.clone())) } }} + export! { pub unsafe extern "C" fn $delete(value: *mut wasm_ref_t) { unsafe { crate::delete(value) }; }} + export! { pub unsafe extern "C" fn $same(a: *const wasm_ref_t, b: *const wasm_ref_t) -> bool { + if a.is_null() || b.is_null() { return a == b; } + unsafe { Rc::ptr_eq(&(*a).0, &(*b).0) } + }} + export! { pub unsafe extern "C" fn $get(value: *const wasm_ref_t) -> *mut c_void { unsafe { &*value }.0.host.borrow().data }} + export! { pub unsafe extern "C" fn $set(value: *mut wasm_ref_t, data: *mut c_void) { unsafe { $set_final(value, data, None) }; }} + export! { pub unsafe extern "C" fn $set_final(value: *mut wasm_ref_t, data: *mut c_void, finalizer: Option) { + // Release the RefCell borrow before invoking the previous C finalizer. + let previous = unsafe { &*value }.0.host.replace(HostInfo { data, finalizer }); + drop(previous); + }} + }; +} +reference_api!( + wasm_ref_copy, + wasm_ref_delete, + wasm_ref_same, + wasm_ref_get_host_info, + wasm_ref_set_host_info, + wasm_ref_set_host_info_with_finalizer +); +reference_api!( + wasm_func_copy, + wasm_func_delete, + wasm_func_same, + wasm_func_get_host_info, + wasm_func_set_host_info, + wasm_func_set_host_info_with_finalizer +); +reference_api!( + wasm_global_copy, + wasm_global_delete, + wasm_global_same, + wasm_global_get_host_info, + wasm_global_set_host_info, + wasm_global_set_host_info_with_finalizer +); +reference_api!( + wasm_table_copy, + wasm_table_delete, + wasm_table_same, + wasm_table_get_host_info, + wasm_table_set_host_info, + wasm_table_set_host_info_with_finalizer +); +reference_api!( + wasm_memory_copy, + wasm_memory_delete, + wasm_memory_same, + wasm_memory_get_host_info, + wasm_memory_set_host_info, + wasm_memory_set_host_info_with_finalizer +); +reference_api!( + wasm_extern_copy, + wasm_extern_delete, + wasm_extern_same, + wasm_extern_get_host_info, + wasm_extern_set_host_info, + wasm_extern_set_host_info_with_finalizer +); +reference_api!( + wasm_module_copy, + wasm_module_delete, + wasm_module_same, + wasm_module_get_host_info, + wasm_module_set_host_info, + wasm_module_set_host_info_with_finalizer +); +reference_api!( + wasm_instance_copy, + wasm_instance_delete, + wasm_instance_same, + wasm_instance_get_host_info, + wasm_instance_set_host_info, + wasm_instance_set_host_info_with_finalizer +); +reference_api!( + wasm_foreign_copy, + wasm_foreign_delete, + wasm_foreign_same, + wasm_foreign_get_host_info, + wasm_foreign_set_host_info, + wasm_foreign_set_host_info_with_finalizer +); +reference_api!( + wasm_trap_copy, + wasm_trap_delete, + wasm_trap_same, + wasm_trap_get_host_info, + wasm_trap_set_host_info, + wasm_trap_set_host_info_with_finalizer +); + +macro_rules! reference_casts { + ($pattern:pat, $up:ident, $down:ident, $up_const:ident, $down_const:ident) => { + export! { pub unsafe extern "C" fn $up(value: *mut wasm_ref_t) -> *mut wasm_ref_t { value }} + export! { pub unsafe extern "C" fn $up_const(value: *const wasm_ref_t) -> *const wasm_ref_t { value }} + export! { pub unsafe extern "C" fn $down(value: *mut wasm_ref_t) -> *mut wasm_ref_t { + if unsafe { value.as_ref() }.is_some_and(|value| matches!(&value.0.kind, $pattern)) { value } else { ptr::null_mut() } + }} + export! { pub unsafe extern "C" fn $down_const(value: *const wasm_ref_t) -> *const wasm_ref_t { unsafe { $down(value.cast_mut()) } }} + }; +} +reference_casts!( + ObjectKind::Func(..), + wasm_func_as_ref, + wasm_ref_as_func, + wasm_func_as_ref_const, + wasm_ref_as_func_const +); +reference_casts!( + ObjectKind::Global(_), + wasm_global_as_ref, + wasm_ref_as_global, + wasm_global_as_ref_const, + wasm_ref_as_global_const +); +reference_casts!( + ObjectKind::Table(_), + wasm_table_as_ref, + wasm_ref_as_table, + wasm_table_as_ref_const, + wasm_ref_as_table_const +); +reference_casts!( + ObjectKind::Memory(_), + wasm_memory_as_ref, + wasm_ref_as_memory, + wasm_memory_as_ref_const, + wasm_ref_as_memory_const +); +reference_casts!( + ObjectKind::Func(..) | ObjectKind::Global(_) | ObjectKind::Table(_) | ObjectKind::Memory(_), + wasm_extern_as_ref, + wasm_ref_as_extern, + wasm_extern_as_ref_const, + wasm_ref_as_extern_const +); +reference_casts!( + ObjectKind::Module(_), + wasm_module_as_ref, + wasm_ref_as_module, + wasm_module_as_ref_const, + wasm_ref_as_module_const +); +reference_casts!( + ObjectKind::Instance(_), + wasm_instance_as_ref, + wasm_ref_as_instance, + wasm_instance_as_ref_const, + wasm_ref_as_instance_const +); +reference_casts!( + ObjectKind::Foreign, + wasm_foreign_as_ref, + wasm_ref_as_foreign, + wasm_foreign_as_ref_const, + wasm_ref_as_foreign_const +); +reference_casts!( + ObjectKind::Trap(_), + wasm_trap_as_ref, + wasm_ref_as_trap, + wasm_trap_as_ref_const, + wasm_ref_as_trap_const +); +reference_casts!( + ObjectKind::Func(..), + wasm_func_as_extern, + wasm_extern_as_func, + wasm_func_as_extern_const, + wasm_extern_as_func_const +); +reference_casts!( + ObjectKind::Global(_), + wasm_global_as_extern, + wasm_extern_as_global, + wasm_global_as_extern_const, + wasm_extern_as_global_const +); +reference_casts!( + ObjectKind::Table(_), + wasm_table_as_extern, + wasm_extern_as_table, + wasm_table_as_extern_const, + wasm_extern_as_table_const +); +reference_casts!( + ObjectKind::Memory(_), + wasm_memory_as_extern, + wasm_extern_as_memory, + wasm_memory_as_extern_const, + wasm_extern_as_memory_const +); + +export! { pub unsafe extern "C" fn wasm_foreign_new(store: *mut wasm_store_t) -> *mut wasm_foreign_t { + unsafe { boxed(wasm_ref_t((*store).0.intern(ObjectKind::Foreign))) } +}} +export! { pub unsafe extern "C" fn wasm_trap_new(store: *mut wasm_store_t, message: *const wasm_byte_vec_t) -> *mut wasm_trap_t { + unsafe { trap(&(*store).0, (*message).as_slice()) } +}} +export! { pub unsafe extern "C" fn wasm_trap_message(value: *const wasm_trap_t, out: *mut wasm_byte_vec_t) { + let ObjectKind::Trap(message) = &(unsafe { &*value }).0.kind else { panic!("expected trap") }; + unsafe { out.write(Vector::from_vec(message.clone())) }; +}} + +// TinyWasm currently has no public source-frame capture. The standard permits +// an unavailable origin and an empty trace. Frame accessors are provided for ABI +// completeness, but this implementation does not create frame objects yet. +#[derive(Clone)] +pub struct wasm_frame_t { + instance: wasm_instance_t, + index: u32, + func_offset: usize, + module_offset: usize, +} +export! { pub unsafe extern "C" fn wasm_trap_origin(_: *const wasm_trap_t) -> *mut wasm_frame_t { ptr::null_mut() }} +export! { pub unsafe extern "C" fn wasm_trap_trace(_: *const wasm_trap_t, out: *mut wasm_frame_vec_t) { unsafe { out.write(Default::default()) }; }} +export! { pub unsafe extern "C" fn wasm_frame_copy(value: *const wasm_frame_t) -> *mut wasm_frame_t { unsafe { boxed((*value).clone()) } }} +export! { pub unsafe extern "C" fn wasm_frame_delete(value: *mut wasm_frame_t) { unsafe { crate::delete(value) }; }} +export! { pub unsafe extern "C" fn wasm_frame_instance(value: *const wasm_frame_t) -> *mut wasm_instance_t { unsafe { ptr::addr_of!((*value).instance).cast_mut() } }} +export! { pub unsafe extern "C" fn wasm_frame_func_index(value: *const wasm_frame_t) -> u32 { unsafe { (*value).index } }} +export! { pub unsafe extern "C" fn wasm_frame_func_offset(value: *const wasm_frame_t) -> usize { unsafe { (*value).func_offset } }} +export! { pub unsafe extern "C" fn wasm_frame_module_offset(value: *const wasm_frame_t) -> usize { unsafe { (*value).module_offset } }} diff --git a/crates/c-api/src/runtime.rs b/crates/c-api/src/runtime.rs new file mode 100644 index 00000000..8d064055 --- /dev/null +++ b/crates/c-api/src/runtime.rs @@ -0,0 +1,135 @@ +use crate::{ + boxed, + objects::{Object, ObjectKind}, +}; +use std::{ + cell::{Cell, RefCell, UnsafeCell}, + ptr, + rc::Rc, +}; +use tinywasm::{Engine, FuncContext, Function, Store, WasmValue}; + +pub struct wasm_config_t(pub(crate) tinywasm::engine::Config); +pub struct wasm_engine_t(Engine); +pub struct wasm_store_t(pub(crate) Rc); + +/// Thread-confined store ownership and the dynamic callback borrow stack. +pub(crate) struct StoreState { + store: UnsafeCell, + busy: Cell, + active: Cell<*mut FuncContext<'static>>, + pub(crate) objects: RefCell>>, + pub(crate) trap: RefCell>>, +} + +/// An exclusive reborrow from either the idle store or the current host callback. +pub(crate) enum Access<'a> { + Store(&'a mut Store), + Callback(&'a mut FuncContext<'static>), +} + +impl Access<'_> { + pub(crate) fn store(&mut self) -> &mut Store { + match self { + Self::Store(store) => store, + Self::Callback(ctx) => ctx.store_mut(), + } + } + + pub(crate) fn call( + &mut self, + function: &Function, + args: &[WasmValue], + results: &mut [WasmValue], + ) -> tinywasm::Result<()> { + match self { + Self::Store(store) => function.call(store, args, results), + Self::Callback(ctx) => ctx.call_untyped(function, args, results), + } + } + + pub(crate) fn is_callback(&self) -> bool { + matches!(self, Self::Callback(_)) + } +} + +impl StoreState { + fn new(engine: Engine) -> Self { + Self { + store: UnsafeCell::new(Store::new(engine)), + busy: Cell::new(false), + active: Cell::new(ptr::null_mut()), + objects: RefCell::new(Vec::new()), + trap: RefCell::new(None), + } + } + + /// Acquires one exclusive borrow without aliasing an executing interpreter. + pub(crate) fn access(&self, action: impl FnOnce(&mut Access<'_>) -> tinywasm::Result) -> tinywasm::Result { + if self.busy.replace(true) { + return Err(tinywasm::Error::Other("store is already borrowed".into())); + } + struct Reset<'a>(&'a Cell); + impl Drop for Reset<'_> { + fn drop(&mut self) { + self.0.set(false); + } + } + let _reset = Reset(&self.busy); + // SAFETY: the store is thread-confined. `busy` excludes simultaneous access. + // During C callbacks, only the explicitly suspended FuncContext is reborrowed, + // never the original Store pointer. The guard ends the reborrow before reuse. + unsafe { + if self.active.get().is_null() { + action(&mut Access::Store(&mut *self.store.get())) + } else { + action(&mut Access::Callback(&mut *self.active.get())) + } + } + } + + /// Suspends the Rust callback context while C runs and may synchronously reenter. + pub(crate) fn callback(&self, context: &mut FuncContext<'_>, action: impl FnOnce() -> T) -> T { + struct Restore<'a> { + state: &'a StoreState, + active: *mut FuncContext<'static>, + busy: bool, + } + impl Drop for Restore<'_> { + fn drop(&mut self) { + self.state.active.set(self.active); + self.state.busy.set(self.busy); + } + } + // Erasure is confined to this dynamic extent. `action` cannot retain a Rust + // reference to context, and context is not touched until the old pointer is restored. + let active = self.active.replace((context as *mut FuncContext<'_>).cast()); + let busy = self.busy.replace(false); + let _restore = Restore { state: self, active, busy }; + action() + } + + /// Interns store objects so independent export and table lookups share identity. + pub(crate) fn intern(self: &Rc, kind: ObjectKind) -> Rc { + let mut objects = self.objects.borrow_mut(); + if let Some(existing) = objects.iter().find(|object| object.kind.same(&kind)) { + return existing.clone(); + } + let object = Rc::new(Object::new(Rc::downgrade(self), kind)); + objects.push(object.clone()); + object + } +} + +export! { pub unsafe extern "C" fn wasm_config_new() -> *mut wasm_config_t { boxed(wasm_config_t(Default::default())) }} +export! { pub unsafe extern "C" fn wasm_config_delete(value: *mut wasm_config_t) { unsafe { crate::delete(value) }; }} +export! { pub unsafe extern "C" fn wasm_engine_new() -> *mut wasm_engine_t { boxed(wasm_engine_t(Engine::default())) }} +export! { pub unsafe extern "C" fn wasm_engine_new_with_config(config: *mut wasm_config_t) -> *mut wasm_engine_t { + let config = unsafe { Box::from_raw(config) }; + boxed(wasm_engine_t(Engine::new(config.0))) +}} +export! { pub unsafe extern "C" fn wasm_engine_delete(value: *mut wasm_engine_t) { unsafe { crate::delete(value) }; }} +export! { pub unsafe extern "C" fn wasm_store_new(engine: *mut wasm_engine_t) -> *mut wasm_store_t { + boxed(wasm_store_t(Rc::new(StoreState::new(unsafe { (*engine).0.clone() })))) +}} +export! { pub unsafe extern "C" fn wasm_store_delete(value: *mut wasm_store_t) { unsafe { crate::delete(value) }; }} diff --git a/crates/c-api/src/tests.rs b/crates/c-api/src/tests.rs new file mode 100644 index 00000000..c7d95159 --- /dev/null +++ b/crates/c-api/src/tests.rs @@ -0,0 +1,161 @@ +use crate::{externals::*, function::*, module::*, objects::*, runtime::*, types::*, values::*, vectors::*}; +use std::{cell::Cell, ffi::c_void, ptr}; + +struct Environment { + nested: Cell<*mut wasm_func_t>, + memory: Cell<*mut wasm_memory_t>, + depth: Cell, + calls: Cell, + finalized: Cell, +} + +unsafe extern "C" fn callback( + env: *mut c_void, + args: *const wasm_val_vec_t, + results: *mut wasm_val_vec_t, +) -> *mut wasm_trap_t { + let env = unsafe { &*env.cast::() }; + env.depth.set(env.depth.get() + 1); + env.calls.set(env.calls.get() + 1); + unsafe { + if env.depth.get() == 3 { + (*results).data.write(wasm_val_t { kind: 0, of: wasm_val_union { i32: (*(*args).data).of.i32 + 1 } }); + } else { + assert!(wasm_func_call(env.nested.get(), args, results).is_null()); + } + let data = wasm_memory_data(env.memory.get()); + *data += 1; + assert_eq!(wasm_memory_size(env.memory.get()), 1); + assert!(*data > 0); + } + env.depth.set(env.depth.get() - 1); + ptr::null_mut() +} + +unsafe extern "C" fn finalize(env: *mut c_void) { + unsafe { &*env.cast::() }.finalized.set(true); +} + +#[test] +fn callback_reentry() { + let environment = Box::new(Environment { + nested: Cell::new(ptr::null_mut()), + memory: Cell::new(ptr::null_mut()), + depth: Cell::new(0), + calls: Cell::new(0), + finalized: Cell::new(false), + }); + let binary = Vector::from_vec(include_bytes!("../tests/fixtures/callback-reentry.wasm").to_vec()); + unsafe { + let engine = wasm_engine_new(); + let store = wasm_store_new(engine); + let module = wasm_module_new(store, &binary); + assert!(!module.is_null()); + let mut params = Vector::from_vec(vec![wasm_valtype_new(0)]); + let mut results = params.clone(); + let ty = wasm_functype_new(&mut params, &mut results); + let env = (&*environment as *const Environment).cast_mut().cast(); + let host = wasm_func_new_with_env(store, ty, callback, env, Some(finalize)); + wasm_functype_delete(ty); + let imports = Vector::from_vec(vec![host]); + let instance = wasm_instance_new(store, module, &imports, ptr::null_mut()); + assert!(!instance.is_null()); + let mut exports = Vector::default(); + wasm_instance_exports(instance, &mut exports); + environment.memory.set(wasm_extern_as_memory(exports.as_slice()[0])); + environment.nested.set(wasm_extern_as_func(exports.as_slice()[1])); + let args = Vector::from_vec(vec![wasm_val_t { kind: 0, of: wasm_val_union { i32: 41 } }]); + let mut results = Vector::from_vec(vec![wasm_val_t::default()]); + assert!(wasm_func_call(environment.nested.get(), &args, &mut results).is_null()); + assert_eq!(results.as_slice()[0].of.i32, 42); + assert_eq!(environment.calls.get(), 3); + assert_eq!(*wasm_memory_data(environment.memory.get()), 3); + wasm_instance_delete(instance); + wasm_module_delete(module); + drop(exports); + drop(imports); + wasm_store_delete(store); + wasm_engine_delete(engine); + assert!(environment.finalized.get()); + } +} + +#[test] +fn vector_ownership() { + unsafe { + let mut bytes = Vector::default(); + wasm_byte_vec_new(&mut bytes, 0, ptr::null()); + assert!(bytes.data.is_null()); + wasm_byte_vec_delete(&mut bytes); + let mut params = Vector::from_vec(vec![wasm_valtype_new(0), wasm_valtype_new(1)]); + let mut results = Vector::default(); + let ty = wasm_functype_new(&mut params, &mut results); + assert!(params.data.is_null()); + let copy = wasm_functype_copy(ty); + wasm_functype_delete(ty); + let params = &*wasm_functype_params(copy); + assert_eq!(params.size, 2); + assert_eq!(wasm_valtype_kind(params.as_slice()[1]), 1); + wasm_externtype_delete(wasm_functype_as_externtype(copy)); + } +} + +#[test] +fn memory_pointer_lifetime() { + let binary = Vector::from_vec(include_bytes!("../tests/fixtures/memory-write.wasm").to_vec()); + unsafe { + let engine = wasm_engine_new(); + let store = wasm_store_new(engine); + let module = wasm_module_new(store, &binary); + let imports = Vector::default(); + let instance = wasm_instance_new(store, module, &imports, ptr::null_mut()); + let mut exports = Vector::default(); + wasm_instance_exports(instance, &mut exports); + let memory = wasm_extern_as_memory(exports.as_slice()[0]); + let data = wasm_memory_data(memory); + *data = 7; + let second = wasm_memory_data(memory); + assert_eq!(data, second); + assert_eq!(*data, 7); + let mut empty = Vector::default(); + let empty_ptr = &raw mut empty; + assert!(wasm_func_call(wasm_extern_as_func(exports.as_slice()[1]), empty_ptr, empty_ptr).is_null()); + assert_eq!(*data, 42); + *data = 12; + assert_eq!(*wasm_memory_data(memory), 12); + drop(exports); + wasm_instance_delete(instance); + wasm_module_delete(module); + wasm_store_delete(store); + wasm_engine_delete(engine); + } +} + +#[test] +fn function_call_after_store_delete() { + let binary = Vector::from_vec(include_bytes!("../tests/fixtures/noop.wasm").to_vec()); + unsafe { + let engine = wasm_engine_new(); + let store = wasm_store_new(engine); + let module = wasm_module_new(store, &binary); + let imports = Vector::default(); + let instance = wasm_instance_new(store, module, &imports, ptr::null_mut()); + let mut exports = Vector::default(); + wasm_instance_exports(instance, &mut exports); + let function = wasm_func_copy(wasm_extern_as_func(exports.as_slice()[0])); + drop(exports); + wasm_instance_delete(instance); + wasm_module_delete(module); + wasm_store_delete(store); + + let empty = Vector::default(); + let mut results = Vector::default(); + assert!(wasm_func_call(function, &empty, &mut results).is_null()); + let mut message = Vector::default(); + crate::tinywasm_last_error_message(&mut message); + assert!(message.as_slice().ends_with(b"store has been deleted\0")); + + wasm_func_delete(function); + wasm_engine_delete(engine); + } +} diff --git a/crates/c-api/src/types.rs b/crates/c-api/src/types.rs new file mode 100644 index 00000000..94b37146 --- /dev/null +++ b/crates/c-api/src/types.rs @@ -0,0 +1,277 @@ +use crate::{boxed, failure, vectors::*}; +use std::ptr; +use tinywasm::types::{FuncType, GlobalType, MemoryArch, MemoryType, RefType, TableType, WasmType}; + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct wasm_limits_t { + pub min: u32, + pub max: u32, +} + +#[derive(Clone)] +pub struct wasm_valtype_t(pub(crate) WasmType); + +/// All extern type views use the same allocation, so casts remain borrowed views. +#[derive(Clone)] +pub enum wasm_externtype_t { + Func { params: wasm_valtype_vec_t, results: wasm_valtype_vec_t }, + Global { content: Box, mutable: bool }, + Table { element: Box, limits: wasm_limits_t }, + Memory(wasm_limits_t), + Tag(Box), +} + +pub type wasm_functype_t = wasm_externtype_t; +pub type wasm_globaltype_t = wasm_externtype_t; +pub type wasm_tabletype_t = wasm_externtype_t; +pub type wasm_memorytype_t = wasm_externtype_t; +pub type wasm_tagtype_t = wasm_externtype_t; + +#[derive(Clone)] +pub struct wasm_importtype_t { + pub(crate) module: wasm_byte_vec_t, + pub(crate) name: wasm_byte_vec_t, + pub(crate) ty: Box, +} + +#[derive(Clone)] +pub struct wasm_exporttype_t { + pub(crate) name: wasm_byte_vec_t, + pub(crate) ty: Box, +} + +/// Converts a C value kind, rejecting kinds outside the pinned header. +pub(crate) fn value_type(kind: u8) -> Option { + Some(match kind { + 0 => WasmType::I32, + 1 => WasmType::I64, + 2 => WasmType::F32, + 3 => WasmType::F64, + 128 => WasmType::Ref(RefType::EXTERNREF), + 129 => WasmType::Ref(RefType::FUNCREF), + _ => return None, + }) +} + +/// Converts only types that the standard header can represent exactly. +pub(crate) fn value_kind(ty: WasmType) -> Option { + Some(match ty { + WasmType::I32 => 0, + WasmType::I64 => 1, + WasmType::F32 => 2, + WasmType::F64 => 3, + WasmType::Ref(ty) if ty == RefType::EXTERNREF => 128, + WasmType::Ref(ty) if ty == RefType::FUNCREF => 129, + _ => return None, + }) +} + +impl wasm_externtype_t { + pub(crate) fn kind(&self) -> u8 { + match self { + Self::Func { .. } => 0, + Self::Global { .. } => 1, + Self::Table { .. } => 2, + Self::Memory(_) => 3, + Self::Tag(_) => 4, + } + } + + pub(crate) fn from_func(ty: &FuncType) -> Option { + if !ty.params().iter().chain(ty.results()).all(|ty| value_kind(*ty).is_some()) { + return None; + } + let values = |types: &[WasmType]| Vector::from_vec(types.iter().map(|ty| boxed(wasm_valtype_t(*ty))).collect()); + Some(Self::Func { params: values(ty.params()), results: values(ty.results()) }) + } + + pub(crate) fn from_global(ty: GlobalType) -> Option { + value_kind(ty.ty)?; + Some(Self::Global { content: Box::new(wasm_valtype_t(ty.ty)), mutable: ty.mutable }) + } + + pub(crate) fn from_memory(ty: MemoryType) -> Option { + if ty.arch() != MemoryArch::I32 || ty.page_size() != 65536 || ty.shared() { + return None; + } + Some(Self::Memory(wasm_limits_t { + min: ty.page_count_initial().try_into().ok()?, + max: match ty.page_count_max_declared() { + Some(max) => max.try_into().ok()?, + None => u32::MAX, + }, + })) + } + + pub(crate) fn from_table(ty: TableType) -> Option { + if ty.arch() != MemoryArch::I32 { + return None; + } + value_kind(WasmType::Ref(ty.element_type))?; + Some(Self::Table { + element: Box::new(wasm_valtype_t(WasmType::Ref(ty.element_type))), + limits: wasm_limits_t { + min: ty.size_initial.try_into().ok()?, + max: match ty.size_max { + Some(max) => max.try_into().ok()?, + None => u32::MAX, + }, + }, + }) + } + + pub(crate) fn func(&self) -> FuncType { + let Self::Func { params, results } = self else { panic!("expected function type") }; + let types = |values: &wasm_valtype_vec_t| unsafe { + values.as_slice().iter().map(|value| (**value).0).collect::>() + }; + FuncType::new(&types(params), &types(results)) + } + + pub(crate) fn global(&self) -> GlobalType { + let Self::Global { content, mutable } = self else { panic!("expected global type") }; + GlobalType::new(content.0, *mutable) + } + + pub(crate) fn memory(&self) -> MemoryType { + let Self::Memory(limits) = self else { panic!("expected memory type") }; + MemoryType::default() + .with_page_count_initial(limits.min as u64) + .with_page_count_max((limits.max != u32::MAX).then_some(limits.max as u64)) + } + + pub(crate) fn table(&self) -> TableType { + let Self::Table { element, limits } = self else { panic!("expected table type") }; + let WasmType::Ref(element) = element.0 else { panic!("expected reference type") }; + TableType::new(element, limits.min as u64, (limits.max != u32::MAX).then_some(limits.max as u64)) + } +} + +macro_rules! owned_type { + ($ty:ty, $copy:ident, $delete:ident) => { + export! { pub unsafe extern "C" fn $copy(value: *const $ty) -> *mut $ty { unsafe { boxed((*value).clone()) } }} + export! { pub unsafe extern "C" fn $delete(value: *mut $ty) { unsafe { crate::delete(value) }; }} + }; +} +owned_type!(wasm_valtype_t, wasm_valtype_copy, wasm_valtype_delete); +owned_type!(wasm_functype_t, wasm_functype_copy, wasm_functype_delete); +owned_type!(wasm_globaltype_t, wasm_globaltype_copy, wasm_globaltype_delete); +owned_type!(wasm_tabletype_t, wasm_tabletype_copy, wasm_tabletype_delete); +owned_type!(wasm_memorytype_t, wasm_memorytype_copy, wasm_memorytype_delete); +owned_type!(wasm_tagtype_t, wasm_tagtype_copy, wasm_tagtype_delete); +owned_type!(wasm_externtype_t, wasm_externtype_copy, wasm_externtype_delete); +owned_type!(wasm_importtype_t, wasm_importtype_copy, wasm_importtype_delete); +owned_type!(wasm_exporttype_t, wasm_exporttype_copy, wasm_exporttype_delete); + +macro_rules! type_casts { + ($kind:literal, $up:ident, $down:ident, $up_const:ident, $down_const:ident) => { + export! { pub unsafe extern "C" fn $up(value: *mut wasm_externtype_t) -> *mut wasm_externtype_t { value }} + export! { pub unsafe extern "C" fn $up_const(value: *const wasm_externtype_t) -> *const wasm_externtype_t { value }} + export! { pub unsafe extern "C" fn $down(value: *mut wasm_externtype_t) -> *mut wasm_externtype_t { + if unsafe { value.as_ref() }.is_some_and(|ty| ty.kind() == $kind) { value } else { ptr::null_mut() } + }} + export! { pub unsafe extern "C" fn $down_const(value: *const wasm_externtype_t) -> *const wasm_externtype_t { unsafe { $down(value.cast_mut()) } }} + }; +} +type_casts!( + 0, + wasm_functype_as_externtype, + wasm_externtype_as_functype, + wasm_functype_as_externtype_const, + wasm_externtype_as_functype_const +); +type_casts!( + 1, + wasm_globaltype_as_externtype, + wasm_externtype_as_globaltype, + wasm_globaltype_as_externtype_const, + wasm_externtype_as_globaltype_const +); +type_casts!( + 2, + wasm_tabletype_as_externtype, + wasm_externtype_as_tabletype, + wasm_tabletype_as_externtype_const, + wasm_externtype_as_tabletype_const +); +type_casts!( + 3, + wasm_memorytype_as_externtype, + wasm_externtype_as_memorytype, + wasm_memorytype_as_externtype_const, + wasm_externtype_as_memorytype_const +); +type_casts!( + 4, + wasm_tagtype_as_externtype, + wasm_externtype_as_tagtype, + wasm_tagtype_as_externtype_const, + wasm_externtype_as_tagtype_const +); + +export! { pub unsafe extern "C" fn wasm_valtype_new(kind: u8) -> *mut wasm_valtype_t { + value_type(kind).map_or_else(|| failure("invalid value kind"), |ty| boxed(wasm_valtype_t(ty))) +}} +export! { pub unsafe extern "C" fn wasm_valtype_kind(ty: *const wasm_valtype_t) -> u8 { unsafe { value_kind((*ty).0).unwrap() } }} +export! { pub unsafe extern "C" fn wasm_externtype_kind(ty: *const wasm_externtype_t) -> u8 { unsafe { (*ty).kind() } }} + +export! { pub unsafe extern "C" fn wasm_functype_new(params: *mut wasm_valtype_vec_t, results: *mut wasm_valtype_vec_t) -> *mut wasm_functype_t { + unsafe { boxed(wasm_externtype_t::Func { params: ptr::replace(params, Default::default()), results: ptr::replace(results, Default::default()) }) } +}} +export! { pub unsafe extern "C" fn wasm_functype_params(ty: *const wasm_functype_t) -> *const wasm_valtype_vec_t { + let wasm_externtype_t::Func { params, .. } = (unsafe { &*ty }) else { return ptr::null(); }; params +}} +export! { pub unsafe extern "C" fn wasm_functype_results(ty: *const wasm_functype_t) -> *const wasm_valtype_vec_t { + let wasm_externtype_t::Func { results, .. } = (unsafe { &*ty }) else { return ptr::null(); }; results +}} +export! { pub unsafe extern "C" fn wasm_globaltype_new(content: *mut wasm_valtype_t, mutable: u8) -> *mut wasm_globaltype_t { + let content = unsafe { Box::from_raw(content) }; + if mutable > 1 { return failure("invalid mutability"); } + boxed(wasm_externtype_t::Global { content, mutable: mutable == 1 }) +}} +export! { pub unsafe extern "C" fn wasm_globaltype_content(ty: *const wasm_globaltype_t) -> *const wasm_valtype_t { + let wasm_externtype_t::Global { content, .. } = (unsafe { &*ty }) else { return ptr::null(); }; &**content +}} +export! { pub unsafe extern "C" fn wasm_globaltype_mutability(ty: *const wasm_globaltype_t) -> u8 { + let wasm_externtype_t::Global { mutable, .. } = (unsafe { &*ty }) else { return 0; }; u8::from(*mutable) +}} +export! { pub unsafe extern "C" fn wasm_tabletype_new(element: *mut wasm_valtype_t, limits: *const wasm_limits_t) -> *mut wasm_tabletype_t { + let element = unsafe { Box::from_raw(element) }; + let limits = unsafe { *limits }; + if !matches!(element.0, WasmType::Ref(_)) || limits.min > limits.max { return failure("invalid table type"); } + boxed(wasm_externtype_t::Table { element, limits }) +}} +export! { pub unsafe extern "C" fn wasm_tabletype_element(ty: *const wasm_tabletype_t) -> *const wasm_valtype_t { + let wasm_externtype_t::Table { element, .. } = (unsafe { &*ty }) else { return ptr::null(); }; &**element +}} +export! { pub unsafe extern "C" fn wasm_tabletype_limits(ty: *const wasm_tabletype_t) -> *const wasm_limits_t { + let wasm_externtype_t::Table { limits, .. } = (unsafe { &*ty }) else { return ptr::null(); }; limits +}} +export! { pub unsafe extern "C" fn wasm_memorytype_new(limits: *const wasm_limits_t) -> *mut wasm_memorytype_t { + let limits = unsafe { *limits }; + if limits.min > 65536 || limits.min > limits.max || (limits.max != u32::MAX && limits.max > 65536) { return failure("invalid memory limits"); } + boxed(wasm_externtype_t::Memory(limits)) +}} +export! { pub unsafe extern "C" fn wasm_memorytype_limits(ty: *const wasm_memorytype_t) -> *const wasm_limits_t { + let wasm_externtype_t::Memory(limits) = (unsafe { &*ty }) else { return ptr::null(); }; limits +}} +export! { pub unsafe extern "C" fn wasm_tagtype_new(ty: *mut wasm_functype_t) -> *mut wasm_tagtype_t { + let ty = unsafe { Box::from_raw(ty) }; + if !ty.func().results().is_empty() { return failure("tag type must have no results"); } + boxed(wasm_externtype_t::Tag(ty)) +}} +export! { pub unsafe extern "C" fn wasm_tagtype_functype(ty: *const wasm_tagtype_t) -> *const wasm_functype_t { + let wasm_externtype_t::Tag(ty) = (unsafe { &*ty }) else { return ptr::null(); }; &**ty +}} +export! { pub unsafe extern "C" fn wasm_importtype_new(module: *mut wasm_byte_vec_t, name: *mut wasm_byte_vec_t, ty: *mut wasm_externtype_t) -> *mut wasm_importtype_t { + unsafe { boxed(wasm_importtype_t { module: ptr::replace(module, Default::default()), name: ptr::replace(name, Default::default()), ty: Box::from_raw(ty) }) } +}} +export! { pub unsafe extern "C" fn wasm_exporttype_new(name: *mut wasm_byte_vec_t, ty: *mut wasm_externtype_t) -> *mut wasm_exporttype_t { + unsafe { boxed(wasm_exporttype_t { name: ptr::replace(name, Default::default()), ty: Box::from_raw(ty) }) } +}} +export! { pub unsafe extern "C" fn wasm_importtype_module(ty: *const wasm_importtype_t) -> *const wasm_byte_vec_t { unsafe { &(*ty).module } }} +export! { pub unsafe extern "C" fn wasm_importtype_name(ty: *const wasm_importtype_t) -> *const wasm_byte_vec_t { unsafe { &(*ty).name } }} +export! { pub unsafe extern "C" fn wasm_importtype_type(ty: *const wasm_importtype_t) -> *const wasm_externtype_t { unsafe { &*(*ty).ty } }} +export! { pub unsafe extern "C" fn wasm_exporttype_name(ty: *const wasm_exporttype_t) -> *const wasm_byte_vec_t { unsafe { &(*ty).name } }} +export! { pub unsafe extern "C" fn wasm_exporttype_type(ty: *const wasm_exporttype_t) -> *const wasm_externtype_t { unsafe { &*(*ty).ty } }} diff --git a/crates/c-api/src/values.rs b/crates/c-api/src/values.rs new file mode 100644 index 00000000..7aa5091f --- /dev/null +++ b/crates/c-api/src/values.rs @@ -0,0 +1,133 @@ +use crate::{ + boxed, + objects::{ObjectKind, wasm_ref_t}, + runtime::StoreState, + vectors::Element, +}; +use std::{ptr, rc::Rc}; +use tinywasm::{ExternRef, Function, RefValue, Store, WasmValue}; + +#[repr(C)] +#[derive(Clone, Copy)] +pub union wasm_val_union { + pub i32: i32, + pub i64: i64, + pub f32: f32, + pub f64: f64, + pub reference: *mut wasm_ref_t, +} + +#[repr(C)] +pub struct wasm_val_t { + pub kind: u8, + pub of: wasm_val_union, +} + +impl Default for wasm_val_t { + fn default() -> Self { + Self { kind: 0, of: wasm_val_union { i64: 0 } } + } +} + +impl Element for wasm_val_t { + unsafe fn copy(&self) -> Self { + let of = + if self.kind >= 128 { wasm_val_union { reference: unsafe { self.of.reference.copy() } } } else { self.of }; + Self { kind: self.kind, of } + } + unsafe fn destroy(&mut self) { + if self.kind >= 128 { + unsafe { self.of.reference.destroy() }; + } + *self = Self::default(); + } +} + +impl wasm_val_t { + /// Reads a valid C value and roots any reference in the owning runtime store. + /// + /// # Safety + /// The value must be initialized, including the union field selected by `kind`. + pub(crate) unsafe fn to_runtime(&self, state: &Rc, store: &mut Store) -> tinywasm::Result { + Ok(unsafe { + match self.kind { + 0 => WasmValue::I32(self.of.i32), + 1 => WasmValue::I64(self.of.i64), + 2 => WasmValue::F32(self.of.f32), + 3 => WasmValue::F64(self.of.f64), + 128 | 129 => WasmValue::Ref(reference_to_runtime(self.of.reference, self.kind, state, store)?), + _ => return Err(tinywasm::Error::Other("invalid value kind".into())), + } + }) + } + + /// Allocates owned C references for a runtime result. + pub(crate) fn from_runtime( + value: &WasmValue, + kind: u8, + state: &Rc, + store: &Store, + ) -> tinywasm::Result { + let of = match value { + WasmValue::I32(value) => wasm_val_union { i32: *value }, + WasmValue::I64(value) => wasm_val_union { i64: *value }, + WasmValue::F32(value) => wasm_val_union { f32: *value }, + WasmValue::F64(value) => wasm_val_union { f64: *value }, + WasmValue::Ref(RefValue::Null) => wasm_val_union { reference: ptr::null_mut() }, + WasmValue::Ref(RefValue::Func(reference)) => { + let function = Function::from_func_ref(store, *reference)?; + wasm_val_union { reference: boxed(wasm_ref_t(state.intern(ObjectKind::Func(function, *reference)))) } + } + WasmValue::Ref(RefValue::Extern(reference)) => { + let key = reference.key(store)?; + let object = state + .objects + .borrow() + .get(key as usize) + .cloned() + .ok_or_else(|| tinywasm::Error::Other("unknown external reference".into()))?; + wasm_val_union { reference: boxed(wasm_ref_t(object)) } + } + _ => return Err(tinywasm::Error::Other("value is not representable by wasm.h".into())), + }; + Ok(Self { kind, of }) + } +} + +/// Converts a borrowed C reference, preserving store identity. +/// +/// # Safety +/// `value` must be null or a live C handle for the duration of this call. +pub(crate) unsafe fn reference_to_runtime( + value: *const wasm_ref_t, + kind: u8, + state: &Rc, + store: &mut Store, +) -> tinywasm::Result { + let Some(value) = (unsafe { value.as_ref() }) else { + return Ok(RefValue::Null); + }; + if !value.0.store.ptr_eq(&Rc::downgrade(state)) { + return Err(tinywasm::Trap::InvalidStore.into()); + } + if kind == 129 { + let ObjectKind::Func(_, reference) = &value.0.kind else { + return Err(tinywasm::Error::Other("expected function reference".into())); + }; + return Ok(RefValue::Func(*reference)); + } + let mut objects = state.objects.borrow_mut(); + let key = match objects.iter().position(|object| Rc::ptr_eq(object, &value.0)) { + Some(index) => index, + None => { + let index = objects.len(); + objects.push(value.0.clone()); + index + } + }; + let key = u32::try_from(key).map_err(|_| tinywasm::Error::Other("too many external references".into()))?; + Ok(RefValue::Extern(ExternRef::try_new(store, key)?)) +} + +export! { pub unsafe extern "C" fn wasm_val_copy(out: *mut wasm_val_t, value: *const wasm_val_t) { unsafe { out.write((*value).copy()) }; }} +export! { pub unsafe extern "C" fn wasm_val_delete(value: *mut wasm_val_t) { unsafe { (*value).destroy() }; }} diff --git a/crates/c-api/src/vectors.rs b/crates/c-api/src/vectors.rs new file mode 100644 index 00000000..13b5a59c --- /dev/null +++ b/crates/c-api/src/vectors.rs @@ -0,0 +1,238 @@ +use std::{ptr, slice}; + +use crate::{objects::*, types::*, values::wasm_val_t}; + +/// An element owned by a C API vector. +pub trait Element: Default { + /// Copies the element and any owned handle. + /// + /// # Safety + /// The element and any handle it contains must be initialized and live. + unsafe fn copy(&self) -> Self; + /// Releases any owned handle. + /// + /// # Safety + /// The element must own its handle and must not have been destroyed already. + unsafe fn destroy(&mut self); +} + +impl Element for u8 { + unsafe fn copy(&self) -> Self { + *self + } + unsafe fn destroy(&mut self) {} +} + +impl Element for *mut T { + unsafe fn copy(&self) -> Self { + unsafe { self.as_ref().map_or(ptr::null_mut(), |value| crate::boxed(value.clone())) } + } + unsafe fn destroy(&mut self) { + unsafe { crate::delete(*self) }; + *self = ptr::null_mut(); + } +} + +/// An owned, capacity-free C vector backed by a boxed slice. +#[repr(C)] +pub struct Vector { + pub size: usize, + pub data: *mut T, +} + +impl Default for Vector { + fn default() -> Self { + Self { size: 0, data: ptr::null_mut() } + } +} + +impl Vector { + /// Transfers a Rust vector to C ownership. + pub(crate) fn from_vec(values: Vec) -> Self { + if values.is_empty() { + return Self::default(); + } + let size = values.len(); + Self { size, data: Box::into_raw(values.into_boxed_slice()).cast() } + } + + /// Borrows initialized elements. Zero-length vectors may have null data. + /// + /// # Safety + /// For nonempty vectors, `data` must point to `size` live, initialized + /// elements that are not mutated for the duration of the borrow. + pub(crate) unsafe fn as_slice(&self) -> &[T] { + if self.size == 0 { &[] } else { unsafe { slice::from_raw_parts(self.data, self.size) } } + } + + /// Borrows initialized elements exclusively. + /// + /// # Safety + /// For nonempty vectors, `data` must point to `size` live, initialized + /// elements exclusively accessible for the duration of the borrow. + pub(crate) unsafe fn as_mut_slice(&mut self) -> &mut [T] { + if self.size == 0 { &mut [] } else { unsafe { slice::from_raw_parts_mut(self.data, self.size) } } + } +} + +impl Clone for Vector { + fn clone(&self) -> Self { + // SAFETY: the source vector must contain initialized, live elements. + unsafe { Self::from_vec(self.as_slice().iter().map(|value| value.copy()).collect()) } + } +} + +impl Drop for Vector { + fn drop(&mut self) { + if self.size == 0 { + return; + } + // SAFETY: owned vectors originate from `from_vec`, with exactly this length. + unsafe { + let mut values = Box::from_raw(ptr::slice_from_raw_parts_mut(self.data, self.size)); + for value in &mut values { + value.destroy(); + } + } + } +} + +macro_rules! vector_api { + ($ty:ident, $element:ty, $empty:ident, $uninit:ident, $new:ident, $copy:ident, $delete:ident) => { + pub type $ty = Vector<$element>; + export! { pub unsafe extern "C" fn $empty(out: *mut $ty) { + unsafe { out.write($ty::default()) }; + }} + export! { pub unsafe extern "C" fn $uninit(out: *mut $ty, size: usize) { + unsafe { out.write($ty::from_vec((0..size).map(|_| Default::default()).collect())) }; + }} + export! { pub unsafe extern "C" fn $new(out: *mut $ty, size: usize, data: *const $element) { + let values = (0..size).map(|i| unsafe { data.add(i).read() }).collect(); + unsafe { out.write($ty::from_vec(values)) }; + }} + export! { pub unsafe extern "C" fn $copy(out: *mut $ty, source: *const $ty) { + unsafe { out.write((*source).clone()) }; + }} + export! { pub unsafe extern "C" fn $delete(value: *mut $ty) { + if !value.is_null() { unsafe { drop(ptr::replace(value, $ty::default())) }; } + }} + }; +} + +vector_api!( + wasm_byte_vec_t, + u8, + wasm_byte_vec_new_empty, + wasm_byte_vec_new_uninitialized, + wasm_byte_vec_new, + wasm_byte_vec_copy, + wasm_byte_vec_delete +); +vector_api!( + wasm_val_vec_t, + wasm_val_t, + wasm_val_vec_new_empty, + wasm_val_vec_new_uninitialized, + wasm_val_vec_new, + wasm_val_vec_copy, + wasm_val_vec_delete +); +vector_api!( + wasm_valtype_vec_t, + *mut wasm_valtype_t, + wasm_valtype_vec_new_empty, + wasm_valtype_vec_new_uninitialized, + wasm_valtype_vec_new, + wasm_valtype_vec_copy, + wasm_valtype_vec_delete +); +vector_api!( + wasm_functype_vec_t, + *mut wasm_functype_t, + wasm_functype_vec_new_empty, + wasm_functype_vec_new_uninitialized, + wasm_functype_vec_new, + wasm_functype_vec_copy, + wasm_functype_vec_delete +); +vector_api!( + wasm_globaltype_vec_t, + *mut wasm_globaltype_t, + wasm_globaltype_vec_new_empty, + wasm_globaltype_vec_new_uninitialized, + wasm_globaltype_vec_new, + wasm_globaltype_vec_copy, + wasm_globaltype_vec_delete +); +vector_api!( + wasm_tabletype_vec_t, + *mut wasm_tabletype_t, + wasm_tabletype_vec_new_empty, + wasm_tabletype_vec_new_uninitialized, + wasm_tabletype_vec_new, + wasm_tabletype_vec_copy, + wasm_tabletype_vec_delete +); +vector_api!( + wasm_memorytype_vec_t, + *mut wasm_memorytype_t, + wasm_memorytype_vec_new_empty, + wasm_memorytype_vec_new_uninitialized, + wasm_memorytype_vec_new, + wasm_memorytype_vec_copy, + wasm_memorytype_vec_delete +); +vector_api!( + wasm_tagtype_vec_t, + *mut wasm_tagtype_t, + wasm_tagtype_vec_new_empty, + wasm_tagtype_vec_new_uninitialized, + wasm_tagtype_vec_new, + wasm_tagtype_vec_copy, + wasm_tagtype_vec_delete +); +vector_api!( + wasm_externtype_vec_t, + *mut wasm_externtype_t, + wasm_externtype_vec_new_empty, + wasm_externtype_vec_new_uninitialized, + wasm_externtype_vec_new, + wasm_externtype_vec_copy, + wasm_externtype_vec_delete +); +vector_api!( + wasm_importtype_vec_t, + *mut wasm_importtype_t, + wasm_importtype_vec_new_empty, + wasm_importtype_vec_new_uninitialized, + wasm_importtype_vec_new, + wasm_importtype_vec_copy, + wasm_importtype_vec_delete +); +vector_api!( + wasm_exporttype_vec_t, + *mut wasm_exporttype_t, + wasm_exporttype_vec_new_empty, + wasm_exporttype_vec_new_uninitialized, + wasm_exporttype_vec_new, + wasm_exporttype_vec_copy, + wasm_exporttype_vec_delete +); +vector_api!( + wasm_extern_vec_t, + *mut wasm_extern_t, + wasm_extern_vec_new_empty, + wasm_extern_vec_new_uninitialized, + wasm_extern_vec_new, + wasm_extern_vec_copy, + wasm_extern_vec_delete +); +vector_api!( + wasm_frame_vec_t, + *mut wasm_frame_t, + wasm_frame_vec_new_empty, + wasm_frame_vec_new_uninitialized, + wasm_frame_vec_new, + wasm_frame_vec_copy, + wasm_frame_vec_delete +); diff --git a/crates/c-api/tests/api.c b/crates/c-api/tests/api.c new file mode 100644 index 00000000..cc1c2325 --- /dev/null +++ b/crates/c-api/tests/api.c @@ -0,0 +1,220 @@ +#include +#include "tinywasm.h" + +static wasm_byte_vec_t read_binary(const char* directory, const char* name) { + char path[4096]; + snprintf(path, sizeof(path), "%s/%s.wasm", directory, name); + FILE* file = fopen(path, "rb"); + assert(file); + assert(fseek(file, 0, SEEK_END) == 0); + long size = ftell(file); + assert(size >= 0); + rewind(file); + wasm_byte_vec_t bytes; + wasm_byte_vec_new_uninitialized(&bytes, (size_t)size); + assert(fread(bytes.data, 1, bytes.size, file) == bytes.size); + fclose(file); + return bytes; +} + +struct callback_env { + wasm_func_t* nested; + wasm_memory_t* memory; + wasm_trap_t* trap; + unsigned depth; + unsigned calls; + unsigned finalized; +}; + +static void finalize(void* data) { ++*(unsigned*)data; } +static void finalize_env(void* data) { ++((struct callback_env*)data)->finalized; } + +static wasm_trap_t* callback_a(void* data, const wasm_val_vec_t* args, wasm_val_vec_t* results) { + struct callback_env* env = data; + ++env->calls; + ++env->depth; + if (env->depth == 3) { + results->data[0] = (wasm_val_t)WASM_I32_VAL(args->data[0].of.i32 + 1); + } else { + wasm_trap_t* trap = wasm_func_call(env->nested, args, results); + assert(!trap); + } + --env->depth; + wasm_memory_data(env->memory)[0] += 1; + assert(wasm_memory_size(env->memory) == 1); + return NULL; +} + +static wasm_trap_t* callback_b(void* data, const wasm_val_vec_t* args, wasm_val_vec_t* results) { + struct callback_env* env = data; + if (args->data[0].of.i32 < 0) return wasm_trap_copy(env->trap); + results->data[0] = (wasm_val_t)WASM_I32_VAL(args->data[0].of.i32 + 2); + return NULL; +} + +int main(int argc, char** argv) { + assert(argc == 2); + wasm_engine_t* engine = wasm_engine_new(); + wasm_store_t* store = wasm_store_new(engine); + wasm_byte_vec_t bytes = read_binary(argv[1], "api"); + assert(wasm_module_validate(store, &bytes)); + wasm_module_t* module = wasm_module_new(store, &bytes); + assert(module); + wasm_byte_vec_delete(&bytes); + + wasm_importtype_vec_t import_types; + wasm_module_imports(module, &import_types); + assert(import_types.size == 2); + const wasm_name_t* name = wasm_importtype_name(import_types.data[0]); + assert(name->size == 4 && memcmp(name->data, "same", 4) == 0); + wasm_importtype_vec_delete(&import_types); + + struct callback_env env = {0}; + wasm_functype_t* type = wasm_functype_new_1_1(wasm_valtype_new_i32(), wasm_valtype_new_i32()); + wasm_func_t* a = wasm_func_new_with_env(store, type, callback_a, &env, finalize_env); + wasm_func_t* b = wasm_func_new_with_env(store, type, callback_b, &env, NULL); + assert(a && b); + assert(wasm_func_param_arity(a) == 1 && wasm_func_result_arity(a) == 1); + assert(!wasm_extern_as_memory(wasm_func_as_extern(a))); + wasm_functype_delete(type); + + wasm_extern_t* bindings[] = { wasm_func_as_extern(a), wasm_func_as_extern(b) }; + wasm_extern_vec_t imports = WASM_ARRAY_VEC(bindings); + wasm_trap_t* trap = NULL; + wasm_instance_t* instance = wasm_instance_new(store, module, &imports, &trap); + assert(instance && !trap); + wasm_extern_vec_t exports; + wasm_instance_exports(instance, &exports); + assert(exports.size == 8); + env.memory = wasm_extern_as_memory(exports.data[0]); + wasm_global_t* global = wasm_extern_as_global(exports.data[1]); + wasm_table_t* table = wasm_extern_as_table(exports.data[2]); + wasm_func_t* inc = wasm_extern_as_func(exports.data[3]); + wasm_func_t* run = wasm_extern_as_func(exports.data[4]); + env.nested = wasm_extern_as_func(exports.data[5]); + + wasm_name_t message; + wasm_name_new_from_string_nt(&message, "callback failed"); + env.trap = wasm_trap_new(store, &message); + wasm_name_delete(&message); + unsigned trap_finalized = 0; + wasm_trap_set_host_info_with_finalizer(env.trap, &trap_finalized, finalize); + + wasm_val_t arg[] = { WASM_I32_VAL(40) }; + wasm_val_t output[1]; /* Deliberately uninitialized result storage. */ + wasm_val_vec_t args = WASM_ARRAY_VEC(arg); + wasm_val_vec_t results = WASM_ARRAY_VEC(output); + trap = wasm_func_call(run, &args, &results); + assert(!trap && output[0].kind == WASM_I32 && output[0].of.i32 == 83); + assert(env.calls == 3 && wasm_memory_data(env.memory)[0] == 3); + + arg[0].of.i32 = -1; + trap = wasm_func_call(wasm_extern_as_func(exports.data[6]), &args, &results); + assert(trap && wasm_trap_same(trap, env.trap)); + assert(wasm_trap_get_host_info(trap) == &trap_finalized); + wasm_trap_message(trap, &message); + assert(strcmp(message.data, "callback failed") == 0); + wasm_name_delete(&message); + wasm_trap_delete(trap); + assert(trap_finalized == 0); + wasm_trap_delete(env.trap); + assert(trap_finalized == 1); + + wasm_val_vec_t empty = WASM_EMPTY_VEC; + trap = wasm_func_call(wasm_extern_as_func(exports.data[7]), &empty, &empty); + assert(trap); + wasm_trap_delete(trap); + arg[0].of.i32 = 41; + assert(!wasm_func_call(inc, &args, &results) && output[0].of.i32 == 42); + + wasm_global_get(global, &output[0]); + assert(output[0].of.i32 == 7); + output[0] = (wasm_val_t)WASM_I32_VAL(12); + wasm_global_set(global, &output[0]); + wasm_global_get(global, &output[0]); + assert(output[0].of.i32 == 12); + assert(wasm_memory_grow(env.memory, 1)); + assert(wasm_memory_size(env.memory) == 2 && wasm_memory_data_size(env.memory) == 131072); + assert(wasm_memory_data(env.memory)[0] == 3); + assert(!wasm_memory_grow(env.memory, 1)); + + wasm_ref_t* reference = wasm_table_get(table, 0); + wasm_func_t* from_table = wasm_ref_as_func(reference); + assert(from_table && wasm_func_same(from_table, inc)); + assert(!wasm_func_call(from_table, &args, &results) && output[0].of.i32 == 42); + assert(wasm_table_set(table, 1, reference)); + assert(wasm_table_grow(table, 2, reference)); + assert(!wasm_table_grow(table, 1, reference)); + assert(wasm_table_size(table) == 4); + wasm_ref_delete(reference); + + unsigned host_finalized = 0; + wasm_func_set_host_info_with_finalizer(inc, &host_finalized, finalize); + wasm_extern_vec_t other_exports; + wasm_instance_exports(instance, &other_exports); + assert(wasm_func_get_host_info(wasm_extern_as_func(other_exports.data[3])) == &host_finalized); + wasm_extern_vec_delete(&other_exports); + assert(host_finalized == 0); + + unsigned foreign_finalized = 0; + wasm_foreign_t* foreign = wasm_foreign_new(store); + wasm_foreign_set_host_info_with_finalizer(foreign, &foreign_finalized, finalize); + wasm_val_t external = WASM_REF_VAL(wasm_foreign_as_ref(foreign)); + wasm_globaltype_t* external_type = wasm_globaltype_new(wasm_valtype_new_externref(), WASM_VAR); + wasm_global_t* external_global = wasm_global_new(store, external_type, &external); + wasm_globaltype_delete(external_type); + assert(external_global); + wasm_global_get(external_global, &output[0]); + assert(wasm_ref_same(output[0].of.ref, external.of.ref)); + wasm_val_t copy; + wasm_val_copy(©, &output[0]); + wasm_val_delete(&output[0]); + assert(wasm_ref_same(copy.of.ref, external.of.ref)); + wasm_val_delete(©); + wasm_foreign_delete(foreign); + wasm_global_delete(external_global); + + wasm_store_t* other_store = wasm_store_new(engine); + wasm_shared_module_t* shared = wasm_module_share(module); + wasm_module_t* obtained = wasm_module_obtain(other_store, shared); + assert(obtained); + wasm_shared_module_delete(shared); + assert(!wasm_instance_new(other_store, obtained, &imports, &trap)); + assert(trap); + wasm_trap_delete(trap); + wasm_module_delete(obtained); + wasm_store_delete(other_store); + + wasm_byte_vec_t archive; + wasm_module_serialize(module, &archive); + wasm_module_t* restored = wasm_module_deserialize(store, &archive); + assert(restored); + wasm_module_delete(restored); + wasm_byte_vec_delete(&archive); + + for (unsigned i = 0; i < 2; ++i) { + bytes = read_binary(argv[1], i == 0 ? "simd" : "memory64"); + assert(!wasm_module_validate(store, &bytes)); + assert(!wasm_module_new(store, &bytes)); + wasm_byte_vec_delete(&bytes); + } + bytes = read_binary(argv[1], "start"); + wasm_module_t* start_module = wasm_module_new(store, &bytes); + wasm_byte_vec_delete(&bytes); + wasm_extern_vec_t no_imports = WASM_EMPTY_VEC; + assert(!wasm_instance_new(store, start_module, &no_imports, &trap)); + assert(trap); + wasm_trap_delete(trap); + wasm_module_delete(start_module); + + wasm_extern_vec_delete(&exports); + wasm_instance_delete(instance); + wasm_func_delete(a); + wasm_func_delete(b); + wasm_module_delete(module); + wasm_store_delete(store); + wasm_engine_delete(engine); + assert(env.finalized == 1 && host_finalized == 1 && foreign_finalized == 1); + puts("C API integration tests passed"); + return 0; +} diff --git a/crates/c-api/tests/fixtures/api.wasm b/crates/c-api/tests/fixtures/api.wasm new file mode 100644 index 00000000..0aae8e90 Binary files /dev/null and b/crates/c-api/tests/fixtures/api.wasm differ diff --git a/crates/c-api/tests/fixtures/callback-reentry.wasm b/crates/c-api/tests/fixtures/callback-reentry.wasm new file mode 100644 index 00000000..5461921f Binary files /dev/null and b/crates/c-api/tests/fixtures/callback-reentry.wasm differ diff --git a/crates/c-api/tests/fixtures/memory-write.wasm b/crates/c-api/tests/fixtures/memory-write.wasm new file mode 100644 index 00000000..8144cc4a Binary files /dev/null and b/crates/c-api/tests/fixtures/memory-write.wasm differ diff --git a/crates/c-api/tests/fixtures/memory64.wasm b/crates/c-api/tests/fixtures/memory64.wasm new file mode 100644 index 00000000..26186d82 Binary files /dev/null and b/crates/c-api/tests/fixtures/memory64.wasm differ diff --git a/crates/c-api/tests/fixtures/noop.wasm b/crates/c-api/tests/fixtures/noop.wasm new file mode 100644 index 00000000..dfdf8f15 Binary files /dev/null and b/crates/c-api/tests/fixtures/noop.wasm differ diff --git a/crates/c-api/tests/fixtures/simd.wasm b/crates/c-api/tests/fixtures/simd.wasm new file mode 100644 index 00000000..886084ab Binary files /dev/null and b/crates/c-api/tests/fixtures/simd.wasm differ diff --git a/crates/c-api/tests/fixtures/start.wasm b/crates/c-api/tests/fixtures/start.wasm new file mode 100644 index 00000000..8ee0bcbf Binary files /dev/null and b/crates/c-api/tests/fixtures/start.wasm differ diff --git a/crates/c-api/tinywasm.pc.in b/crates/c-api/tinywasm.pc.in new file mode 100644 index 00000000..98a3b90e --- /dev/null +++ b/crates/c-api/tinywasm.pc.in @@ -0,0 +1,10 @@ +prefix=@PREFIX@ +libdir=@LIBDIR@ +includedir=${prefix}/include + +Name: TinyWasm +Description: TinyWasm WebAssembly C API +Version: @VERSION@ +Libs: -L${libdir} -ltinywasm +Libs.private: @NATIVE_LIBS@ +Cflags: -I${includedir} @PREFIX_FLAG@