From 17aa1125e8d2991266fe7aefc4a4ecaf72af11ca Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 23 Sep 2026 12:43:34 +0200 Subject: [PATCH 1/4] feat: initial wasm c-api impl Signed-off-by: Henry --- .github/workflows/release.yaml | 91 +++ .github/workflows/test.yaml | 16 + Cargo.lock | 8 + README.md | 12 + crates/c-api/Cargo.toml | 25 + crates/c-api/Makefile | 52 ++ crates/c-api/README.md | 32 + crates/c-api/build.rs | 8 + crates/c-api/examples/add.c | 48 ++ crates/c-api/examples/fixtures.rs | 32 + crates/c-api/include/LICENSE-wasm-c-api | 202 +++++++ crates/c-api/include/tinywasm-prefix.h | 308 ++++++++++ crates/c-api/include/tinywasm.h | 21 + crates/c-api/include/wasm.h | 737 ++++++++++++++++++++++++ crates/c-api/src/externals.rs | 131 +++++ crates/c-api/src/function.rs | 159 +++++ crates/c-api/src/lib.rs | 58 ++ crates/c-api/src/macros.rs | 8 + crates/c-api/src/module.rs | 138 +++++ crates/c-api/src/objects.rs | 330 +++++++++++ crates/c-api/src/runtime.rs | 135 +++++ crates/c-api/src/tests.rs | 152 +++++ crates/c-api/src/types.rs | 277 +++++++++ crates/c-api/src/values.rs | 127 ++++ crates/c-api/src/vectors.rs | 224 +++++++ crates/c-api/tests/api.c | 221 +++++++ crates/c-api/tests/symbols.py | 23 + crates/c-api/tinywasm.pc.in | 10 + 28 files changed, 3585 insertions(+) create mode 100644 crates/c-api/Cargo.toml create mode 100644 crates/c-api/Makefile create mode 100644 crates/c-api/README.md create mode 100644 crates/c-api/build.rs create mode 100644 crates/c-api/examples/add.c create mode 100644 crates/c-api/examples/fixtures.rs create mode 100644 crates/c-api/include/LICENSE-wasm-c-api create mode 100644 crates/c-api/include/tinywasm-prefix.h create mode 100644 crates/c-api/include/tinywasm.h create mode 100644 crates/c-api/include/wasm.h create mode 100644 crates/c-api/src/externals.rs create mode 100644 crates/c-api/src/function.rs create mode 100644 crates/c-api/src/lib.rs create mode 100644 crates/c-api/src/macros.rs create mode 100644 crates/c-api/src/module.rs create mode 100644 crates/c-api/src/objects.rs create mode 100644 crates/c-api/src/runtime.rs create mode 100644 crates/c-api/src/tests.rs create mode 100644 crates/c-api/src/types.rs create mode 100644 crates/c-api/src/values.rs create mode 100644 crates/c-api/src/vectors.rs create mode 100644 crates/c-api/tests/api.c create mode 100644 crates/c-api/tests/symbols.py create mode 100644 crates/c-api/tinywasm.pc.in diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index ae4bb2d1..38f4d234 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -3,7 +3,73 @@ on: push: tags: ["v*"] +permissions: + contents: read + jobs: + artifacts: + name: Build ${{ matrix.target }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-22.04 + target: x86_64-unknown-linux-gnu + shared-library: libtinywasm.so + - os: ubuntu-22.04-arm + target: aarch64-unknown-linux-gnu + shared-library: libtinywasm.so + - os: macos-15-intel + target: x86_64-apple-darwin + shared-library: libtinywasm.dylib + - os: macos-15 + target: aarch64-apple-darwin + shared-library: libtinywasm.dylib + 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: stable + target: ${{ matrix.target }} + rustflags: "" + - name: Build CLI and C API + env: + TARGET: ${{ matrix.target }} + run: | + cargo build --locked --release --target "$TARGET" -p tinywasm-cli + if [[ "$TARGET" == *-apple-darwin ]]; then + cargo rustc --locked --release --target "$TARGET" -p tinywasm-c-api -- \ + -C link-arg=-Wl,-install_name,@rpath/libtinywasm.dylib + else + cargo build --locked --release --target "$TARGET" -p tinywasm-c-api + fi + - name: Package and smoke-test + env: + TARGET: ${{ matrix.target }} + SHARED_LIBRARY: ${{ matrix.shared-library }} + run: | + name="tinywasm-${GITHUB_REF_NAME}-${TARGET}" + package="$RUNNER_TEMP/$name" + mkdir -p "$package/bin" "$package/lib" "$package/include" "$package/licenses" dist + cp "target/$TARGET/release/tinywasm" "$package/bin/" + cp "target/$TARGET/release/$SHARED_LIBRARY" "target/$TARGET/release/libtinywasm.a" "$package/lib/" + cp crates/c-api/include/*.h "$package/include/" + cp LICENSE-MIT LICENSE-APACHE crates/c-api/include/LICENSE-wasm-c-api "$package/licenses/" + cp crates/c-api/README.md "$package/README.md" + "$package/bin/tinywasm" --version + cc -std=c11 -I"$package/include" crates/c-api/examples/add.c \ + -L"$package/lib" -Wl,-rpath,"$package/lib" -ltinywasm -o "$RUNNER_TEMP/c-api-example" + "$RUNNER_TEMP/c-api-example" + tar -czf "dist/$name.tar.gz" -C "$RUNNER_TEMP" "$name" + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: tinywasm-${{ matrix.target }} + path: dist/*.tar.gz + if-no-files-found: error + publish: runs-on: ubuntu-latest environment: release @@ -19,3 +85,28 @@ jobs: - run: cargo publish --workspace env: CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }} + + release: + name: Attach release archives + needs: [publish, artifacts] + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + pattern: tinywasm-* + merge-multiple: true + path: dist + - name: Create checksums and upload + working-directory: dist + env: + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} + TAG: ${{ github.ref_name }} + run: | + sha256sum -- *.tar.gz > SHA256SUMS + if ! gh release view "$TAG" > /dev/null 2>&1; then + gh release create "$TAG" --verify-tag --generate-notes + fi + gh release upload "$TAG" ./*.tar.gz SHA256SUMS --clobber diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index ed441cd6..16030849 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -12,6 +12,22 @@ 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: "" + - 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 + build-wasm: name: Build wasm runs-on: ubuntu-latest diff --git a/Cargo.lock b/Cargo.lock index f49bf9d4..0359f994 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -991,6 +991,14 @@ dependencies = [ "wat", ] +[[package]] +name = "tinywasm-c-api" +version = "0.11.0" +dependencies = [ + "tinywasm", + "wat", +] + [[package]] name = "tinywasm-cli" version = "0.11.0" diff --git a/README.md b/README.md index 6d2ca695..10b8b259 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,18 @@ assert_eq!(result, 3); See the [examples](./examples) directory and [documentation](https://docs.rs/tinywasm) for more information. +### C and C++ + +The experimental [`tinywasm-c-api` crate](./crates/c-api) provides `wasm.h`, +TinyWasm extensions in `tinywasm.h`, and static and shared libraries. Build the +libraries with: + +```sh +make -C crates/c-api +``` + +See its [README](./crates/c-api/README.md) for installation and usage. + ## Cargo Features - **`full`:** Enables `archive`, `debug`, `parallel-parser`, `parser`, `state`, and `validate`. Enabled by default. diff --git a/crates/c-api/Cargo.toml b/crates/c-api/Cargo.toml new file mode 100644 index 00000000..9cd10e50 --- /dev/null +++ b/crates/c-api/Cargo.toml @@ -0,0 +1,25 @@ +[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"] } + +[dev-dependencies] +wat.workspace = true + +[features] +custom-prefix = [] + +[lints] +workspace = true diff --git a/crates/c-api/Makefile b/crates/c-api/Makefile new file mode 100644 index 00000000..ab6fcea7 --- /dev/null +++ b/crates/c-api/Makefile @@ -0,0 +1,52 @@ +# 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 + $(CARGO) run -p tinywasm-c-api --example fixtures --target-dir "$(abspath $(TARGET_DIR))" -- "$(BUILDDIR)" + $(CC) $(CPPFLAGS) $(CFLAGS) -std=c11 -Iinclude tests/api.c -L"$(BUILD_LIBDIR)" -Wl,-rpath,"$(BUILD_LIBDIR)" -ltinywasm $(LDFLAGS) -o "$(BUILDDIR)/api" + "$(BUILDDIR)/api" "$(BUILDDIR)" + $(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" "$(BUILDDIR)" + python3 tests/symbols.py "$(BUILD_LIBDIR)/libtinywasm.$(SHARED_EXT)" "$(CC)" "$(TINYWASM_C_API_PREFIX)" + +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 include/LICENSE-wasm-c-api ../../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..9755204b --- /dev/null +++ b/crates/c-api/README.md @@ -0,0 +1,32 @@ +# 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. + +## 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, 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 +[`include/LICENSE-wasm-c-api`](include/LICENSE-wasm-c-api). 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..47e94003 --- /dev/null +++ b/crates/c-api/examples/add.c @@ -0,0 +1,48 @@ +#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); + wasm_val_t arguments[] = { WASM_I32_VAL(20), WASM_I32_VAL(22) }; + wasm_val_t result[1]; + 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/examples/fixtures.rs b/crates/c-api/examples/fixtures.rs new file mode 100644 index 00000000..038979cc --- /dev/null +++ b/crates/c-api/examples/fixtures.rs @@ -0,0 +1,32 @@ +//! Produces binary fixtures for the native C integration test. +fn main() -> Result<(), Box> { + let directory = std::path::PathBuf::from(std::env::args_os().nth(1).expect("output directory")); + std::fs::create_dir_all(&directory)?; + let module = wat::parse_str( + r#" + (module + (import "host" "same" (func $a (param i32) (result i32))) + (import "host" "same" (func $b (param i32) (result i32))) + (memory (export "memory") 1 2) + (global (export "global") (mut i32) (i32.const 7)) + (table (export "table") 2 4 funcref) + (func $inc (export "inc") (param i32) (result i32) + local.get 0 i32.const 1 i32.add) + (elem (i32.const 0) func $inc) + (func (export "run") (param i32) (result i32) + local.get 0 call $a local.get 0 call $b i32.add) + (func (export "reenter") (param i32) (result i32) local.get 0 call $a) + (func (export "trap_host") (param i32) (result i32) local.get 0 call $b) + (func (export "trap") unreachable)) + "#, + )?; + std::fs::write(directory.join("api.wasm"), module)?; + for (name, source) in [ + ("simd", "(module (func (export \"v\") (result v128) v128.const i32x4 0 0 0 0))"), + ("memory64", "(module (memory (export \"m\") i64 1))"), + ("start", "(module (func $start unreachable) (start $start))"), + ] { + std::fs::write(directory.join(format!("{name}.wasm")), wat::parse_str(source)?)?; + } + Ok(()) +} diff --git a/crates/c-api/include/LICENSE-wasm-c-api b/crates/c-api/include/LICENSE-wasm-c-api new file mode 100644 index 00000000..8f71f43f --- /dev/null +++ b/crates/c-api/include/LICENSE-wasm-c-api @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/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..66d3f3b8 --- /dev/null +++ b/crates/c-api/src/externals.rs @@ -0,0 +1,131 @@ +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))) +}} + +/// Runs a checked store operation on an opaque 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..2c98b76f --- /dev/null +++ b/crates/c-api/src/function.rs @@ -0,0 +1,159 @@ +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 { + 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 = object.state().expect("function store must be alive"); + 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..80bbefe1 --- /dev/null +++ b/crates/c-api/src/lib.rs @@ -0,0 +1,58 @@ +//! Cargo-built implementation of the WebAssembly C API. +//! +//! The public contract is in `include/wasm.h` and `include/tinywasm.h`. +//! All pointer arguments follow those headers' ownership and lifetime rules. +//! Stores and their objects are confined to the creating thread. +#![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. +unsafe fn delete(value: *mut T) { + if !value.is_null() { + // SAFETY: the caller transfers a handle allocated by `boxed` exactly once. + 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..c2a7f6e3 --- /dev/null +++ b/crates/c-api/src/module.rs @@ -0,0 +1,138 @@ +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| { + // Build owned handles only after all fallible conversions complete. + let objects = instance.exports().map(|(_, item)| { + let kind = 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())), + }; + Ok(state.intern(kind)) + }).collect::>>()?; + Ok(Vector::from_vec(objects.into_iter().map(|object| boxed(wasm_ref_t(object))).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..fd7fcb2a --- /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}; + +/// A finalizer owns exactly one C environment or host-info value. +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..0f7ec36b --- /dev/null +++ b/crates/c-api/src/tests.rs @@ -0,0 +1,152 @@ +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( + wat::parse_str( + r#" + (module + (import "host" "call" (func $host (param i32) (result i32))) + (memory (export "memory") 1 2) + (func (export "run") (param i32) (result i32) + local.get 0 call $host)) + "#, + ) + .unwrap(), + ); + 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( + wat::parse_str( + r#" + (module + (memory (export "memory") 1 2) + (func (export "write") i32.const 0 i32.const 42 i32.store8)) + "#, + ) + .unwrap(), + ); + 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); + } +} diff --git a/crates/c-api/src/types.rs b/crates/c-api/src/types.rs new file mode 100644 index 00000000..15b80219 --- /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 { + 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..f4a861b8 --- /dev/null +++ b/crates/c-api/src/values.rs @@ -0,0 +1,127 @@ +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. + 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. +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..2a51d551 --- /dev/null +++ b/crates/c-api/src/vectors.rs @@ -0,0 +1,224 @@ +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. + unsafe fn copy(&self) -> Self; + /// Releases any owned handle. + 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. + 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. + 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: Rust-owned vectors always 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..3b0b3502 --- /dev/null +++ b/crates/c-api/tests/api.c @@ -0,0 +1,221 @@ +#include +#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/symbols.py b/crates/c-api/tests/symbols.py new file mode 100644 index 00000000..de63b345 --- /dev/null +++ b/crates/c-api/tests/symbols.py @@ -0,0 +1,23 @@ +"""Check every non-inline function declared in the vendored ABI is exported.""" +import ctypes +from pathlib import Path +import re +import shlex +import subprocess +import sys + +header = subprocess.check_output( + [*shlex.split(sys.argv[2]), "-E", "-P", "-DWASM_API_EXTERN=ABI_SYMBOL", "-x", "c", "include/tinywasm.h"], + text=True, +) +symbols = set(re.findall(r"ABI_SYMBOL\s+[^;{}]*?\b((?:wasm|tinywasm)_\w+)\s*\(", header)) +assert len(symbols) > 200, "header preprocessing did not find the API" +library = ctypes.CDLL(sys.argv[1]) +prefix = sys.argv[3] if len(sys.argv) > 3 else "" +missing = sorted(symbol for symbol in symbols if not hasattr(library, prefix + symbol)) +assert not missing, f"Missing symbols: {missing}" +aliases = set(re.findall(r"^#define ((?:wasm|tinywasm)_\w+) TINYWASM_SYMBOL", Path("include/tinywasm-prefix.h").read_text(), re.M)) +assert aliases == symbols, f"Prefix aliases differ: {aliases ^ symbols}" +if prefix: + assert not any(hasattr(library, symbol) for symbol in symbols), "unprefixed API symbols leaked" +print(f"Verified {len(symbols)} exported API symbols") 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@ From 21bc72afe4002a0c59dbeca1e574a968bf23a964 Mon Sep 17 00:00:00 2001 From: Henry Date: Fri, 25 Sep 2026 17:31:50 +0200 Subject: [PATCH 2/4] chore: cleanup Signed-off-by: Henry --- crates/c-api/README.md | 22 +++++++++++++++++++++- crates/c-api/examples/add.c | 3 +++ crates/c-api/src/externals.rs | 1 - crates/c-api/src/function.rs | 8 +++++++- crates/c-api/src/lib.rs | 32 +++++++++++++++++++++++++++----- crates/c-api/src/module.rs | 10 ++++------ crates/c-api/src/objects.rs | 2 +- crates/c-api/src/tests.rs | 29 +++++++++++++++++++++++++++++ crates/c-api/src/values.rs | 6 ++++++ crates/c-api/src/vectors.rs | 16 +++++++++++++++- 10 files changed, 113 insertions(+), 16 deletions(-) diff --git a/crates/c-api/README.md b/crates/c-api/README.md index 91ae3761..54cc4b13 100644 --- a/crates/c-api/README.md +++ b/crates/c-api/README.md @@ -15,6 +15,26 @@ This builds `libtinywasm.so` (or `.dylib` on macOS) and `libtinywasm.a` in `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 @@ -28,5 +48,5 @@ build and run the C example. the module boundary. `wasm.h` is vendored from WebAssembly/wasm-c-api commit -`9d6b93764ac96cdd9db51081c363e09d2d488b4d` under +`9d6b93764ac96cdd9db51081c363e09d2d488b4d`, under [`include/LICENSE-wasm-c-api`](include/LICENSE-wasm-c-api). diff --git a/crates/c-api/examples/add.c b/crates/c-api/examples/add.c index 47e94003..8ca41367 100644 --- a/crates/c-api/examples/add.c +++ b/crates/c-api/examples/add.c @@ -1,3 +1,4 @@ +#include #include #include "tinywasm.h" @@ -30,8 +31,10 @@ int main(void) { 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); diff --git a/crates/c-api/src/externals.rs b/crates/c-api/src/externals.rs index 66d3f3b8..71da5823 100644 --- a/crates/c-api/src/externals.rs +++ b/crates/c-api/src/externals.rs @@ -48,7 +48,6 @@ export! { pub unsafe extern "C" fn wasm_table_new(store: *mut wasm_store_t, ty: }).map_or_else(failure, |object| boxed(wasm_ref_t(object))) }} -/// Runs a checked store operation on an opaque object. fn with_object( object: &Object, action: impl FnOnce(&Rc, &mut Access<'_>) -> tinywasm::Result, diff --git a/crates/c-api/src/function.rs b/crates/c-api/src/function.rs index 2c98b76f..e6cd58a2 100644 --- a/crates/c-api/src/function.rs +++ b/crates/c-api/src/function.rs @@ -39,6 +39,9 @@ impl ThreadConfined { } 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 { @@ -123,7 +126,10 @@ export! { pub unsafe extern "C" fn wasm_func_result_arity(value: *const wasm_fun 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 = object.state().expect("function store must be alive"); + 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(); diff --git a/crates/c-api/src/lib.rs b/crates/c-api/src/lib.rs index 80bbefe1..484de1b5 100644 --- a/crates/c-api/src/lib.rs +++ b/crates/c-api/src/lib.rs @@ -1,8 +1,27 @@ -//! Cargo-built implementation of the WebAssembly C API. +//! C API implementation for TinyWasm. //! -//! The public contract is in `include/wasm.h` and `include/tinywasm.h`. -//! All pointer arguments follow those headers' ownership and lifetime rules. -//! Stores and their objects are confined to the creating thread. +//! 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)] @@ -38,9 +57,12 @@ fn boxed(value: T) -> *mut T { } /// 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() { - // SAFETY: the caller transfers a handle allocated by `boxed` exactly once. unsafe { drop(Box::from_raw(value)) }; } } diff --git a/crates/c-api/src/module.rs b/crates/c-api/src/module.rs index 76393588..02c346b0 100644 --- a/crates/c-api/src/module.rs +++ b/crates/c-api/src/module.rs @@ -122,17 +122,15 @@ export! { pub unsafe extern "C" fn wasm_instance_exports(value: *const wasm_inst let state = object.state()?; let ObjectKind::Instance(instance) = &object.kind else { return Err(tinywasm::Error::Other("expected instance".into())); }; state.access(|access| { - // Build owned handles only after all fallible conversions complete. - let objects = instance.exports().map(|(_, item)| { - let kind = match item { + 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())), - }; - Ok(state.intern(kind)) + }) }).collect::>>()?; - Ok(Vector::from_vec(objects.into_iter().map(|object| boxed(wasm_ref_t(object))).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 index fd7fcb2a..bb717997 100644 --- a/crates/c-api/src/objects.rs +++ b/crates/c-api/src/objects.rs @@ -11,7 +11,7 @@ use std::{ }; use tinywasm::{FuncRef, Function, Global, Memory, Module, ModuleInstance, Table}; -/// A finalizer owns exactly one C environment or host-info value. +/// Calls the optional finalizer when replaced or dropped. pub(crate) struct HostInfo { pub(crate) data: *mut c_void, pub(crate) finalizer: Option, diff --git a/crates/c-api/src/tests.rs b/crates/c-api/src/tests.rs index 0f7ec36b..3ac49d83 100644 --- a/crates/c-api/src/tests.rs +++ b/crates/c-api/src/tests.rs @@ -150,3 +150,32 @@ fn memory_pointer_lifetime() { wasm_engine_delete(engine); } } + +#[test] +fn function_call_after_store_delete() { + let binary = Vector::from_vec(wat::parse_str(r#"(module (func (export "noop")))"#).unwrap()); + 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/values.rs b/crates/c-api/src/values.rs index f4a861b8..7aa5091f 100644 --- a/crates/c-api/src/values.rs +++ b/crates/c-api/src/values.rs @@ -45,6 +45,9 @@ impl Element for wasm_val_t { 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 { @@ -92,6 +95,9 @@ impl wasm_val_t { } /// 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, diff --git a/crates/c-api/src/vectors.rs b/crates/c-api/src/vectors.rs index 2a51d551..13b5a59c 100644 --- a/crates/c-api/src/vectors.rs +++ b/crates/c-api/src/vectors.rs @@ -5,8 +5,14 @@ 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); } @@ -51,11 +57,19 @@ impl Vector { } /// 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) } } } @@ -63,7 +77,7 @@ impl Vector { impl Clone for Vector { fn clone(&self) -> Self { - // SAFETY: Rust-owned vectors always contain initialized, live elements. + // SAFETY: the source vector must contain initialized, live elements. unsafe { Self::from_vec(self.as_slice().iter().map(|value| value.copy()).collect()) } } } From fbf59747bf124bf63d3af2863cd6b24f800e7c8a Mon Sep 17 00:00:00 2001 From: Henry Date: Fri, 25 Sep 2026 17:36:49 +0200 Subject: [PATCH 3/4] chore: remove unnececary stuff for a first experimental version Signed-off-by: Henry --- .github/workflows/release.yaml | 91 ----------- .github/workflows/test.yaml | 1 + README.md | 12 -- crates/c-api/Makefile | 3 +- crates/c-api/README.md | 2 +- crates/c-api/include/LICENSE-wasm-c-api | 202 ------------------------ crates/c-api/tests/symbols.py | 23 --- 7 files changed, 3 insertions(+), 331 deletions(-) delete mode 100644 crates/c-api/include/LICENSE-wasm-c-api delete mode 100644 crates/c-api/tests/symbols.py diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 6ebe7790..757f31c8 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -3,73 +3,7 @@ on: push: tags: ["v*"] -permissions: - contents: read - jobs: - artifacts: - name: Build ${{ matrix.target }} - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - include: - - os: ubuntu-22.04 - target: x86_64-unknown-linux-gnu - shared-library: libtinywasm.so - - os: ubuntu-22.04-arm - target: aarch64-unknown-linux-gnu - shared-library: libtinywasm.so - - os: macos-15-intel - target: x86_64-apple-darwin - shared-library: libtinywasm.dylib - - os: macos-15 - target: aarch64-apple-darwin - shared-library: libtinywasm.dylib - 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: stable - target: ${{ matrix.target }} - rustflags: "" - - name: Build CLI and C API - env: - TARGET: ${{ matrix.target }} - run: | - cargo build --locked --release --target "$TARGET" -p tinywasm-cli - if [[ "$TARGET" == *-apple-darwin ]]; then - cargo rustc --locked --release --target "$TARGET" -p tinywasm-c-api -- \ - -C link-arg=-Wl,-install_name,@rpath/libtinywasm.dylib - else - cargo build --locked --release --target "$TARGET" -p tinywasm-c-api - fi - - name: Package and smoke-test - env: - TARGET: ${{ matrix.target }} - SHARED_LIBRARY: ${{ matrix.shared-library }} - run: | - name="tinywasm-${GITHUB_REF_NAME}-${TARGET}" - package="$RUNNER_TEMP/$name" - mkdir -p "$package/bin" "$package/lib" "$package/include" "$package/licenses" dist - cp "target/$TARGET/release/tinywasm" "$package/bin/" - cp "target/$TARGET/release/$SHARED_LIBRARY" "target/$TARGET/release/libtinywasm.a" "$package/lib/" - cp crates/c-api/include/*.h "$package/include/" - cp LICENSE-MIT LICENSE-APACHE crates/c-api/include/LICENSE-wasm-c-api "$package/licenses/" - cp crates/c-api/README.md "$package/README.md" - "$package/bin/tinywasm" --version - cc -std=c11 -I"$package/include" crates/c-api/examples/add.c \ - -L"$package/lib" -Wl,-rpath,"$package/lib" -ltinywasm -o "$RUNNER_TEMP/c-api-example" - "$RUNNER_TEMP/c-api-example" - tar -czf "dist/$name.tar.gz" -C "$RUNNER_TEMP" "$name" - - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: tinywasm-${{ matrix.target }} - path: dist/*.tar.gz - if-no-files-found: error - publish: runs-on: ubuntu-26.04 environment: release @@ -85,28 +19,3 @@ jobs: - run: cargo publish --workspace env: CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }} - - release: - name: Attach release archives - needs: [publish, artifacts] - runs-on: ubuntu-latest - permissions: - contents: write - steps: - - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - pattern: tinywasm-* - merge-multiple: true - path: dist - - name: Create checksums and upload - working-directory: dist - env: - GH_TOKEN: ${{ github.token }} - GH_REPO: ${{ github.repository }} - TAG: ${{ github.ref_name }} - run: | - sha256sum -- *.tar.gz > SHA256SUMS - if ! gh release view "$TAG" > /dev/null 2>&1; then - gh release create "$TAG" --verify-tag --generate-notes - fi - gh release upload "$TAG" ./*.tar.gz SHA256SUMS --clobber diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 5f497dfd..56256bd2 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -24,6 +24,7 @@ jobs: 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 diff --git a/README.md b/README.md index b1e786a3..5fb864c8 100644 --- a/README.md +++ b/README.md @@ -43,18 +43,6 @@ assert_eq!(result, 3); See the [examples](./examples) directory and [documentation](https://docs.rs/tinywasm) for more information. -### C and C++ - -The experimental [`tinywasm-c-api` crate](./crates/c-api) provides `wasm.h`, -TinyWasm extensions in `tinywasm.h`, and static and shared libraries. Build the -libraries with: - -```sh -make -C crates/c-api -``` - -See its [README](./crates/c-api/README.md) for installation and usage. - ## Cargo Features - **`full`:** Enables `archive`, `debug`, `parallel-parser`, `parser`, `state`, and `validate`. Enabled by default. diff --git a/crates/c-api/Makefile b/crates/c-api/Makefile index ab6fcea7..a4e2cea1 100644 --- a/crates/c-api/Makefile +++ b/crates/c-api/Makefile @@ -42,11 +42,10 @@ test: example "$(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" "$(BUILDDIR)" - python3 tests/symbols.py "$(BUILD_LIBDIR)/libtinywasm.$(SHARED_EXT)" "$(CC)" "$(TINYWASM_C_API_PREFIX)" 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 include/LICENSE-wasm-c-api ../../LICENSE-APACHE ../../LICENSE-MIT "$(DESTDIR)$(PREFIX)/share/licenses/tinywasm/" + 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 index 54cc4b13..c885ede7 100644 --- a/crates/c-api/README.md +++ b/crates/c-api/README.md @@ -49,4 +49,4 @@ When building with Cargo directly, enable `custom-prefix` and set the `wasm.h` is vendored from WebAssembly/wasm-c-api commit `9d6b93764ac96cdd9db51081c363e09d2d488b4d`, under -[`include/LICENSE-wasm-c-api`](include/LICENSE-wasm-c-api). +[Apache 2.0](../../LICENSE-APACHE). diff --git a/crates/c-api/include/LICENSE-wasm-c-api b/crates/c-api/include/LICENSE-wasm-c-api deleted file mode 100644 index 8f71f43f..00000000 --- a/crates/c-api/include/LICENSE-wasm-c-api +++ /dev/null @@ -1,202 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright {yyyy} {name of copyright owner} - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - diff --git a/crates/c-api/tests/symbols.py b/crates/c-api/tests/symbols.py deleted file mode 100644 index de63b345..00000000 --- a/crates/c-api/tests/symbols.py +++ /dev/null @@ -1,23 +0,0 @@ -"""Check every non-inline function declared in the vendored ABI is exported.""" -import ctypes -from pathlib import Path -import re -import shlex -import subprocess -import sys - -header = subprocess.check_output( - [*shlex.split(sys.argv[2]), "-E", "-P", "-DWASM_API_EXTERN=ABI_SYMBOL", "-x", "c", "include/tinywasm.h"], - text=True, -) -symbols = set(re.findall(r"ABI_SYMBOL\s+[^;{}]*?\b((?:wasm|tinywasm)_\w+)\s*\(", header)) -assert len(symbols) > 200, "header preprocessing did not find the API" -library = ctypes.CDLL(sys.argv[1]) -prefix = sys.argv[3] if len(sys.argv) > 3 else "" -missing = sorted(symbol for symbol in symbols if not hasattr(library, prefix + symbol)) -assert not missing, f"Missing symbols: {missing}" -aliases = set(re.findall(r"^#define ((?:wasm|tinywasm)_\w+) TINYWASM_SYMBOL", Path("include/tinywasm-prefix.h").read_text(), re.M)) -assert aliases == symbols, f"Prefix aliases differ: {aliases ^ symbols}" -if prefix: - assert not any(hasattr(library, symbol) for symbol in symbols), "unprefixed API symbols leaked" -print(f"Verified {len(symbols)} exported API symbols") From fa4fbcf8270e83557234ea9c4c041ca08adb96ab Mon Sep 17 00:00:00 2001 From: Henry Date: Fri, 25 Sep 2026 17:46:46 +0200 Subject: [PATCH 4/4] chore: simplify tests Signed-off-by: Henry --- .github/workflows/test.yaml | 2 +- Cargo.lock | 1 - crates/c-api/Cargo.toml | 3 -- crates/c-api/Makefile | 5 ++- crates/c-api/examples/fixtures.rs | 32 ------------------ crates/c-api/src/tests.rs | 26 ++------------ crates/c-api/tests/api.c | 1 - crates/c-api/tests/fixtures/api.wasm | Bin 0 -> 216 bytes .../tests/fixtures/callback-reentry.wasm | Bin 0 -> 69 bytes crates/c-api/tests/fixtures/memory-write.wasm | Bin 0 -> 57 bytes crates/c-api/tests/fixtures/memory64.wasm | Bin 0 -> 20 bytes crates/c-api/tests/fixtures/noop.wasm | Bin 0 -> 34 bytes crates/c-api/tests/fixtures/simd.wasm | Bin 0 -> 50 bytes crates/c-api/tests/fixtures/start.wasm | Bin 0 -> 45 bytes 14 files changed, 6 insertions(+), 64 deletions(-) delete mode 100644 crates/c-api/examples/fixtures.rs create mode 100644 crates/c-api/tests/fixtures/api.wasm create mode 100644 crates/c-api/tests/fixtures/callback-reentry.wasm create mode 100644 crates/c-api/tests/fixtures/memory-write.wasm create mode 100644 crates/c-api/tests/fixtures/memory64.wasm create mode 100644 crates/c-api/tests/fixtures/noop.wasm create mode 100644 crates/c-api/tests/fixtures/simd.wasm create mode 100644 crates/c-api/tests/fixtures/start.wasm diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 56256bd2..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: diff --git a/Cargo.lock b/Cargo.lock index 61586942..3b2eaf05 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1004,7 +1004,6 @@ name = "tinywasm-c-api" version = "0.11.0" dependencies = [ "tinywasm", - "wat", ] [[package]] diff --git a/crates/c-api/Cargo.toml b/crates/c-api/Cargo.toml index 9cd10e50..63a1c476 100644 --- a/crates/c-api/Cargo.toml +++ b/crates/c-api/Cargo.toml @@ -15,9 +15,6 @@ name = "tinywasm" [dependencies] tinywasm = { workspace = true, features = ["archive", "parser", "std", "validate"] } -[dev-dependencies] -wat.workspace = true - [features] custom-prefix = [] diff --git a/crates/c-api/Makefile b/crates/c-api/Makefile index a4e2cea1..dfe2b607 100644 --- a/crates/c-api/Makefile +++ b/crates/c-api/Makefile @@ -35,13 +35,12 @@ example: build test: example $(CXX) $(CPPFLAGS) -std=c++17 -Wall -Wextra -Werror -fsyntax-only -x c++ -Iinclude include/tinywasm.h - $(CARGO) run -p tinywasm-c-api --example fixtures --target-dir "$(abspath $(TARGET_DIR))" -- "$(BUILDDIR)" $(CC) $(CPPFLAGS) $(CFLAGS) -std=c11 -Iinclude tests/api.c -L"$(BUILD_LIBDIR)" -Wl,-rpath,"$(BUILD_LIBDIR)" -ltinywasm $(LDFLAGS) -o "$(BUILDDIR)/api" - "$(BUILDDIR)/api" "$(BUILDDIR)" + "$(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" "$(BUILDDIR)" + "$(BUILDDIR)/api-static" tests/fixtures install: build install -d "$(DESTDIR)$(PREFIX)/include" "$(DESTDIR)$(LIBDIR)/pkgconfig" "$(DESTDIR)$(PREFIX)/share/licenses/tinywasm" diff --git a/crates/c-api/examples/fixtures.rs b/crates/c-api/examples/fixtures.rs deleted file mode 100644 index 038979cc..00000000 --- a/crates/c-api/examples/fixtures.rs +++ /dev/null @@ -1,32 +0,0 @@ -//! Produces binary fixtures for the native C integration test. -fn main() -> Result<(), Box> { - let directory = std::path::PathBuf::from(std::env::args_os().nth(1).expect("output directory")); - std::fs::create_dir_all(&directory)?; - let module = wat::parse_str( - r#" - (module - (import "host" "same" (func $a (param i32) (result i32))) - (import "host" "same" (func $b (param i32) (result i32))) - (memory (export "memory") 1 2) - (global (export "global") (mut i32) (i32.const 7)) - (table (export "table") 2 4 funcref) - (func $inc (export "inc") (param i32) (result i32) - local.get 0 i32.const 1 i32.add) - (elem (i32.const 0) func $inc) - (func (export "run") (param i32) (result i32) - local.get 0 call $a local.get 0 call $b i32.add) - (func (export "reenter") (param i32) (result i32) local.get 0 call $a) - (func (export "trap_host") (param i32) (result i32) local.get 0 call $b) - (func (export "trap") unreachable)) - "#, - )?; - std::fs::write(directory.join("api.wasm"), module)?; - for (name, source) in [ - ("simd", "(module (func (export \"v\") (result v128) v128.const i32x4 0 0 0 0))"), - ("memory64", "(module (memory (export \"m\") i64 1))"), - ("start", "(module (func $start unreachable) (start $start))"), - ] { - std::fs::write(directory.join(format!("{name}.wasm")), wat::parse_str(source)?)?; - } - Ok(()) -} diff --git a/crates/c-api/src/tests.rs b/crates/c-api/src/tests.rs index 3ac49d83..c7d95159 100644 --- a/crates/c-api/src/tests.rs +++ b/crates/c-api/src/tests.rs @@ -45,18 +45,7 @@ fn callback_reentry() { calls: Cell::new(0), finalized: Cell::new(false), }); - let binary = Vector::from_vec( - wat::parse_str( - r#" - (module - (import "host" "call" (func $host (param i32) (result i32))) - (memory (export "memory") 1 2) - (func (export "run") (param i32) (result i32) - local.get 0 call $host)) - "#, - ) - .unwrap(), - ); + 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); @@ -113,16 +102,7 @@ fn vector_ownership() { #[test] fn memory_pointer_lifetime() { - let binary = Vector::from_vec( - wat::parse_str( - r#" - (module - (memory (export "memory") 1 2) - (func (export "write") i32.const 0 i32.const 42 i32.store8)) - "#, - ) - .unwrap(), - ); + 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); @@ -153,7 +133,7 @@ fn memory_pointer_lifetime() { #[test] fn function_call_after_store_delete() { - let binary = Vector::from_vec(wat::parse_str(r#"(module (func (export "noop")))"#).unwrap()); + 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); diff --git a/crates/c-api/tests/api.c b/crates/c-api/tests/api.c index 3b0b3502..cc1c2325 100644 --- a/crates/c-api/tests/api.c +++ b/crates/c-api/tests/api.c @@ -1,5 +1,4 @@ #include -#include #include "tinywasm.h" static wasm_byte_vec_t read_binary(const char* directory, const char* name) { diff --git a/crates/c-api/tests/fixtures/api.wasm b/crates/c-api/tests/fixtures/api.wasm new file mode 100644 index 0000000000000000000000000000000000000000..0aae8e90d59a768a86b52d581f02d62db9aa48a4 GIT binary patch literal 216 zcmYk0I}XAy7=!IUFG?7YnAq4jM%l{F;`YQuq|jV3A9mSXL^JFRRNyFR}K_SW`IXcf#9nl=*Zk**Mx zUkS?P7}FFpbT#ELoR={ey)bO$h#H=zJRGxC9z(6*# GY<&R5K_u<~ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..5461921fccfdf80f76076bb6eb0ed100a7cebf43 GIT binary patch literal 69 zcmV~$K?;B{3$rv7BVcJ2dYg$2g| literal 0 HcmV?d00001