Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: Rust CI

on:
push:
branches: ["**"]
branches: ["next", "main"]
pull_request:
branches: ["next", "main"]
schedule:
Expand All @@ -12,6 +12,23 @@ permissions:
contents: read

jobs:
c-api:
name: C API
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions-rust-lang/setup-rust-toolchain@ecabd13d1c56bd1345c230e542e9144811ad706f # v2.0.0
with:
toolchain: nightly
components: miri
rustflags: ""
build-warnings: warn
- run: cargo miri test -p tinywasm-c-api --lib
- run: make -C crates/c-api test
- run: make -C crates/c-api test TINYWASM_C_API_PREFIX=test_ TARGET_DIR=../../target/c-api-prefixed

run-tests:
strategy:
matrix:
Expand Down
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 22 additions & 0 deletions crates/c-api/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[package]
name = "tinywasm-c-api"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "WebAssembly C API for TinyWasm"
repository.workspace = true
license.workspace = true
publish = false

[lib]
crate-type = ["cdylib", "staticlib"]
name = "tinywasm"

[dependencies]
tinywasm = { workspace = true, features = ["archive", "parser", "std", "validate"] }

[features]
custom-prefix = []

[lints]
workspace = true
50 changes: 50 additions & 0 deletions crates/c-api/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Optional convenience targets. Cargo remains the library build system.
CARGO ?= cargo
CC ?= cc
CXX ?= c++
PREFIX ?= /usr/local
DESTDIR ?=
LIBDIR ?= $(PREFIX)/lib
PROFILE ?= release
TARGET_DIR ?= $(if $(CARGO_TARGET_DIR),$(CARGO_TARGET_DIR),../../target)
BUILD_LIBDIR := $(abspath $(TARGET_DIR)/$(if $(filter dev,$(PROFILE)),debug,$(PROFILE)))
BUILDDIR := $(abspath $(TARGET_DIR)/c-api)
CARGO_FLAGS := --profile $(PROFILE)
ifneq ($(TINYWASM_C_API_PREFIX),)
export TINYWASM_C_API_PREFIX
CARGO_FLAGS += --features custom-prefix
CPPFLAGS += -DTINYWASM_C_API_PREFIX=$(TINYWASM_C_API_PREFIX)
endif
CFLAGS ?= -O2 -Wall -Wextra -Werror
UNAME := $(shell uname -s)
SHARED_EXT := $(if $(filter Darwin,$(UNAME)),dylib,so)
# Native libraries reported by `cargo rustc -- --print native-static-libs`.
NATIVE_LIBS ?= $(if $(filter Darwin,$(UNAME)),-framework Security -framework CoreFoundation -liconv -lSystem,-lgcc_s -lutil -lrt -lpthread -lm -ldl -lc)
VERSION := $(shell sed -n '/^\[workspace.package\]/,/^\[/s/^version = "\(.*\)"/\1/p' ../../Cargo.toml)

.PHONY: all build example test install
all: build

build:
$(CARGO) build -p tinywasm-c-api $(CARGO_FLAGS) --target-dir "$(abspath $(TARGET_DIR))"

example: build
mkdir -p "$(BUILDDIR)"
$(CC) $(CPPFLAGS) $(CFLAGS) -std=c11 -Iinclude examples/add.c -L"$(BUILD_LIBDIR)" -Wl,-rpath,"$(BUILD_LIBDIR)" -ltinywasm $(LDFLAGS) -o "$(BUILDDIR)/add"
"$(BUILDDIR)/add"

test: example
$(CXX) $(CPPFLAGS) -std=c++17 -Wall -Wextra -Werror -fsyntax-only -x c++ -Iinclude include/tinywasm.h
$(CC) $(CPPFLAGS) $(CFLAGS) -std=c11 -Iinclude tests/api.c -L"$(BUILD_LIBDIR)" -Wl,-rpath,"$(BUILD_LIBDIR)" -ltinywasm $(LDFLAGS) -o "$(BUILDDIR)/api"
"$(BUILDDIR)/api" tests/fixtures
$(CC) $(CPPFLAGS) $(CFLAGS) -std=c11 -Iinclude examples/add.c "$(BUILD_LIBDIR)/libtinywasm.a" $(NATIVE_LIBS) $(LDFLAGS) -o "$(BUILDDIR)/add-static"
"$(BUILDDIR)/add-static"
$(CC) $(CPPFLAGS) $(CFLAGS) -std=c11 -Iinclude tests/api.c "$(BUILD_LIBDIR)/libtinywasm.a" $(NATIVE_LIBS) $(LDFLAGS) -o "$(BUILDDIR)/api-static"
"$(BUILDDIR)/api-static" tests/fixtures

install: build
install -d "$(DESTDIR)$(PREFIX)/include" "$(DESTDIR)$(LIBDIR)/pkgconfig" "$(DESTDIR)$(PREFIX)/share/licenses/tinywasm"
install -m644 include/wasm.h include/tinywasm.h include/tinywasm-prefix.h "$(DESTDIR)$(PREFIX)/include/"
install -m644 "$(BUILD_LIBDIR)/libtinywasm.a" "$(BUILD_LIBDIR)/libtinywasm.$(SHARED_EXT)" "$(DESTDIR)$(LIBDIR)/"
install -m644 ../../LICENSE-APACHE ../../LICENSE-MIT "$(DESTDIR)$(PREFIX)/share/licenses/tinywasm/"
sed -e 's|@PREFIX@|$(PREFIX)|g' -e 's|@LIBDIR@|$(LIBDIR)|g' -e 's|@VERSION@|$(VERSION)|g' -e 's|@NATIVE_LIBS@|$(NATIVE_LIBS)|g' -e 's|@PREFIX_FLAG@|$(if $(TINYWASM_C_API_PREFIX),-DTINYWASM_C_API_PREFIX=$(TINYWASM_C_API_PREFIX))|g' tinywasm.pc.in > "$(DESTDIR)$(LIBDIR)/pkgconfig/tinywasm.pc"
52 changes: 52 additions & 0 deletions crates/c-api/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# TinyWasm C API

Experimental [WebAssembly C API](https://github.com/WebAssembly/wasm-c-api) support for TinyWasm.
Use `wasm.h` to embed it in C or C++. See [Wasmtime's C API documentation](https://docs.wasmtime.dev/c-api/wasm_8h.html)
for the standard interface and [`examples/add.c`](examples/add.c) for an example.

## Build

```sh
make -C crates/c-api
```

This builds `libtinywasm.so` (or `.dylib` on macOS) and `libtinywasm.a` in
`target/release`. Headers are in `crates/c-api/include`. To install them and a
`pkg-config` file, run `make -C crates/c-api install`. Run `make -C crates/c-api example` to
build and run the C example.

## Symbol prefix

By default, the library exports the names in `wasm.h`. To avoid symbol
collisions with another WebAssembly C API implementation, build with a prefix:

```sh
make -C crates/c-api TINYWASM_C_API_PREFIX=my_
```

Define the same prefix before including `tinywasm.h` in C or C++:

```c
#define TINYWASM_C_API_PREFIX my_
#include "tinywasm.h"
```

When building with Cargo directly, enable `custom-prefix` and set the
`TINYWASM_C_API_PREFIX` environment variable. Include `tinywasm.h` before
`wasm.h` so the symbol aliases take effect.

## Notes

- Follow the ownership annotations in `wasm.h` and use the matching delete
functions. Keep a store and its objects on the same thread.
- Load `.wasm` bytes with `wasm_module_new`. Imports are positional, in module
import order.
- Include `tinywasm.h` for `tinywasm_last_error_message`, which copies the
calling thread's last error into a vector you delete with `wasm_byte_vec_delete`.
- Module imports and exports must have types representable in `wasm.h`. SIMD
signatures, shared memory, memory64/table64, GC references, and tags are not supported at
the module boundary.

`wasm.h` is vendored from WebAssembly/wasm-c-api commit
`9d6b93764ac96cdd9db51081c363e09d2d488b4d`, under
[Apache 2.0](../../LICENSE-APACHE).
8 changes: 8 additions & 0 deletions crates/c-api/build.rs
Original file line number Diff line number Diff line change
@@ -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_");
}
}
}
51 changes: 51 additions & 0 deletions crates/c-api/examples/add.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#include <assert.h>
#include <stdio.h>
#include "tinywasm.h"

int main(void) {
/* (module (func (export "add") (param i32 i32) (result i32)
* local.get 0 local.get 1 i32.add)) */
const unsigned char binary[] = {
0,97,115,109,1,0,0,0,1,7,1,96,2,127,127,1,127,
3,2,1,0,7,7,1,3,97,100,100,0,0,10,9,1,7,0,32,0,32,1,106,11
};
wasm_engine_t* engine = wasm_engine_new();
wasm_store_t* store = wasm_store_new(engine);
wasm_byte_vec_t bytes;
wasm_byte_vec_new(&bytes, sizeof(binary), (const char*)binary);
wasm_module_t* module = wasm_module_new(store, &bytes);
wasm_byte_vec_delete(&bytes);
if (!module) {
wasm_message_t error;
tinywasm_last_error_message(&error);
fprintf(stderr, "%s\n", error.data);
wasm_byte_vec_delete(&error);
wasm_store_delete(store);
wasm_engine_delete(engine);
return 1;
}

wasm_extern_vec_t imports = WASM_EMPTY_VEC;
wasm_trap_t* trap = NULL;
wasm_instance_t* instance = wasm_instance_new(store, module, &imports, &trap);
assert(instance && !trap);
wasm_extern_vec_t exports;
wasm_instance_exports(instance, &exports);
/* The export vector owns its handles. wasm_extern_as_func borrows one. */
wasm_val_t arguments[] = { WASM_I32_VAL(20), WASM_I32_VAL(22) };
wasm_val_t result[1];
/* Stack-backed vectors need no vector delete. The call writes the result slot. */
wasm_val_vec_t args = WASM_ARRAY_VEC(arguments);
wasm_val_vec_t results = WASM_ARRAY_VEC(result);
trap = wasm_func_call(wasm_extern_as_func(exports.data[0]), &args, &results);
assert(!trap);
printf("20 + 22 = %d\n", result[0].of.i32);
assert(result[0].of.i32 == 42);

wasm_extern_vec_delete(&exports);
wasm_instance_delete(instance);
wasm_module_delete(module);
wasm_store_delete(store);
wasm_engine_delete(engine);
return 0;
}
Loading
Loading