Compare commits

...

5 Commits

Author SHA1 Message Date
yuroitaki
d5cfbae2cc Add readme. 2025-10-08 18:58:05 +08:00
yuroitaki
bb4868aeb3 Init. 2025-08-20 16:01:03 +08:00
yuroitaki
f5ecf1ee79 Add android bindgen. 2025-08-15 18:37:41 +08:00
yuroitaki
317f9a7c9b Add wasmtime host and async. 2025-08-05 19:48:58 +08:00
yuroitaki
9ecf34a8a4 Init. 2025-08-01 18:35:03 +08:00
13 changed files with 1469 additions and 12 deletions

1234
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -27,6 +27,8 @@ members = [
"crates/harness/runner",
"crates/harness/plot",
"crates/tlsn",
"crates/wasmtime-plugin",
"crates/wasmtime-host",
]
resolver = "2"

View File

@@ -0,0 +1,25 @@
[package]
name = "wasmtime-host"
version = "0.1.0"
edition = "2024"
[lints]
workspace = true
[lib]
crate-type = ["staticlib"]
[dependencies]
anyhow = { workspace = true }
async-std = { version = "1.13.2" }
log = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
oslog = { version = "0.2.0" }
tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
uniffi = { version = "0.29", features = [ "cli" ] }
wasmtime = { git = "https://github.com/bytecodealliance/wasmtime", branch = "main", features = ["component-model-async"] }
[[bin]]
name = "uniffi-bindgen"
path = "uniffi_bindgen.rs"

View File

@@ -0,0 +1,9 @@
# Info
This crate builds a wasmtime binary to be run in iOS. The binary loads and run a wasm component model plugin. UniFFI is used to generate Swift bindings and allow the binary's future to be driven by iOS' async executor.
# Step
1. Build this crate by running [build.sh](build.sh). The build script assumes the following to be available.
- `aarch64-apple-ios-sim`, `aarch64-apple-ios` rust targets
- `xcodebuild` CLI tool
2. Drag and drop the `wasmtime_host.xcframework/`, `uniffi/` built into Xcode.

40
crates/wasmtime-host/build.sh Executable file
View File

@@ -0,0 +1,40 @@
#!/bin/bash
set -e
# Add ios targets (skip if already installed)
if ! rustup target list --installed | grep -q "aarch64-apple-ios-sim"; then
rustup target add aarch64-apple-ios-sim
fi
if ! rustup target list --installed | grep -q "aarch64-apple-ios"; then
rustup target add aarch64-apple-ios
fi
# Build the uniffi-bindgen binary and initial library file
cargo build
# Build FFI swift bindings
cargo run --bin uniffi-bindgen generate --library ../../target/debug/libwasmtime_host.a --language swift --out-dir ../../target/uniffi
# Rename binding for Xcode
mv ../../target/uniffi/wasmtime_hostFFI.modulemap ../../target/uniffi/module.modulemap
# Build ios binaries
cargo build --target aarch64-apple-ios-sim --release
cargo build --target aarch64-apple-ios --release
# Remove old XCFramework if it exists
if [ -d "../../target/ios/wasmtime_host.xcframework" ]; then
rm -rf "../../target/ios/wasmtime_host.xcframework"
fi
# Build XCFramework required by Xcode
xcodebuild -create-xcframework \
-library ../../target/aarch64-apple-ios-sim/release/libwasmtime_host.a -headers ../../target/uniffi \
-library ../../target/aarch64-apple-ios/release/libwasmtime_host.a -headers ../../target/uniffi \
-output "../../target/ios/wasmtime_host.xcframework"
echo -e "\033[32mBuild completed."
echo -e "\033[38;5;208mNow drag and drop 'wasmtime_host.xcframework/', 'uniffi/' into Xcode."

View File

@@ -0,0 +1,87 @@
uniffi::setup_scaffolding!();
use std::time::Duration;
use async_std::future::{pending, timeout};
use log::{info, LevelFilter};
use oslog::OsLogger;
use serde::{Deserialize, Serialize};
use wasmtime::{component::{bindgen, Component, HasSelf, Linker}, Config, Engine, Store};
bindgen!({
path: "../wasmtime-plugin/wit/world.wit",
imports: {
"read": async,
"write": async,
},
exports: {
"main": async,
}
});
#[derive(Serialize, Debug)]
struct Input {
id: u8
}
#[derive(Serialize, Deserialize, Debug)]
struct Output {
result: bool
}
struct HostState {}
impl PluginImports for HostState {
async fn read(&mut self, id: u8) -> Vec<u8> {
info!("Id received from plugin: {id}");
serde_json::to_vec(&format!("Hello from host {}", id)).unwrap()
}
async fn write(&mut self, payload: Vec<u8>) -> Vec<u8> {
let payload: String = serde_json::from_slice(&payload).unwrap();
info!("Payload received from plugin: {payload}");
let success = true;
serde_json::to_vec(&success).unwrap()
}
}
#[uniffi::export]
async fn main(wasm_path: &str) {
OsLogger::new("RustWasmtime")
.level_filter(LevelFilter::Info)
.category_level_filter("Settings", LevelFilter::Trace)
.init()
.unwrap();
info!("Starting wasmtime");
let mut config = Config::new();
config
.async_support(true)
.wasm_component_model_async(true);
let engine = Engine::new(&config).unwrap();
let component = Component::from_file(&engine, wasm_path).unwrap();
let mut linker = Linker::new(&engine);
Plugin::add_to_linker::<_, HasSelf<_>>(&mut linker, |state| state).unwrap();
let mut store = Store::new(&engine, HostState {});
let plugin = Plugin::instantiate_async(&mut store, &component, &linker).await.unwrap();
let input = Input { id: 0 };
info!("Input: {:.?}", input);
// Sleep for 10s to test async capability on android host.
info!("Sleeping for 10s...");
let never = pending::<()>();
timeout(Duration::from_millis(10000), never).await.unwrap_err();
let input_bytes = serde_json::to_vec(&input).unwrap();
let res_byte = plugin.call_main(&mut store, &input_bytes).await.unwrap();
let res: Output = serde_json::from_slice(&res_byte).unwrap();
info!("Output: {:?}", res);
}

View File

@@ -0,0 +1,3 @@
fn main() {
uniffi::uniffi_bindgen_main()
}

View File

@@ -0,0 +1,5 @@
[build]
target = "wasm32-unknown-unknown"
[unstable]
build-std = ["panic_abort", "std"]

View File

@@ -0,0 +1,15 @@
[package]
name = "wasmtime-plugin"
version = "0.1.0"
edition = "2024"
[lints]
workspace = true
[lib]
crate-type = ["cdylib"]
[dependencies]
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
wit-bindgen = { version = "0.43.0" }

View File

@@ -0,0 +1,8 @@
# Info
This crate builds a wasm component model plugin. It has an async main function that calls some host functions.
# Step
1. Build this crate by running [build.sh](build.sh). The build script assumes the following to be available.
- `wasm32-unknown-unknown` rust target
- [`wasm-tools`](https://github.com/bytecodealliance/wasm-tools)
2. Drag and drop the `wasmtime_plugin_component.wasm` built into Xcode.

15
crates/wasmtime-plugin/build.sh Executable file
View File

@@ -0,0 +1,15 @@
#!/bin/bash
PACKAGE_NAME="wasmtime_plugin"
set -e
# Build the project
cargo build --release
# Convert the wasm binary to a component
wasm-tools component new ../../target/wasm32-unknown-unknown/release/${PACKAGE_NAME}.wasm -o ../../target/wasm32-unknown-unknown/release/${PACKAGE_NAME}_component.wasm
echo -e "\033[32mComponent created: ${PACKAGE_NAME}_component.wasm.\033[0m"
echo -e "\033[38;5;208mNow drag and drop it into Xcode.\033[0m"

View File

@@ -0,0 +1,31 @@
use serde::{Deserialize, Serialize};
wit_bindgen::generate!({
path: "wit/world.wit",
async: true
});
struct Component;
#[derive(Deserialize)]
struct Input {
id: u8
}
#[derive(Serialize)]
struct Output {
result: bool
}
impl Guest for Component {
async fn main(input: Vec<u8>) -> Vec<u8> {
let input: Input = serde_json::from_slice(&input).unwrap();
let payload_bytes = read(input.id).await;
let result_bytes = write(payload_bytes).await;
let result: bool = serde_json::from_slice(&result_bytes).unwrap();
let output = Output { result };
serde_json::to_vec(&output).unwrap()
}
}
export!(Component);

View File

@@ -0,0 +1,7 @@
package component:wasmtime-plugin;
world plugin {
export main: func(input: list<u8>) -> list<u8>;
import read: func(id: u8) -> list<u8>;
import write: func(payload: list<u8>) -> list<u8>;
}