Skip to content

Commit 55b6820

Browse files
committed
Add examples of linking and WASI
This commit adds two example programs, one for linking two modules together and one for instantiating WASI. The linkage example additionally uses WASI to get some meaningful output at this time. cc #1272
1 parent adff432 commit 55b6820

File tree

11 files changed

+524
-3
lines changed

11 files changed

+524
-3
lines changed

Cargo.lock

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ members = [
6767
"crates/misc/rust",
6868
"crates/wiggle",
6969
"examples/fib-debug/wasm",
70+
"examples/wasi/wasm",
7071
"fuzz",
7172
]
7273

crates/misc/run-examples/src/main.rs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,18 @@ use std::collections::BTreeSet;
22
use std::process::Command;
33

44
fn main() {
5+
let example_to_run = std::env::args().nth(1);
56
let examples = std::fs::read_dir("examples").unwrap();
67
let examples = examples
7-
.map(|e| {
8+
.filter_map(|e| {
89
let e = e.unwrap();
910
let path = e.path();
1011
let dir = e.metadata().unwrap().is_dir();
11-
(path.file_stem().unwrap().to_str().unwrap().to_owned(), dir)
12+
if let Some("wat") = path.extension().and_then(|s| s.to_str()) {
13+
return None;
14+
}
15+
16+
Some((path.file_stem().unwrap().to_str().unwrap().to_owned(), dir))
1217
})
1318
.collect::<BTreeSet<_>>();
1419

@@ -21,14 +26,24 @@ fn main() {
2126
if example == "README" {
2227
continue;
2328
}
29+
if let Some(example_to_run) = &example_to_run {
30+
if !example.contains(&example_to_run[..]) {
31+
continue;
32+
}
33+
}
2434
if is_dir {
2535
println!("======== Rust wasm file `{}` ============", example);
36+
let target = if example == "fib-debug" {
37+
"wasm32-unknown-unknown"
38+
} else {
39+
"wasm32-wasi"
40+
};
2641
run(Command::new("cargo")
2742
.arg("build")
2843
.arg("-p")
2944
.arg(format!("example-{}-wasm", example))
3045
.arg("--target")
31-
.arg("wasm32-unknown-unknown"));
46+
.arg(target));
3247
}
3348
println!("======== Rust example `{}` ============", example);
3449
run(Command::new("cargo")

examples/linking.c

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
/*
2+
Example of compiling, instantiating, and linking two WebAssembly modules
3+
together.
4+
5+
You can compile and run this example on Linux with:
6+
7+
cargo build --release -p wasmtime
8+
cc examples/linking.c \
9+
-I crates/c-api/include \
10+
-I crates/c-api/wasm-c-api/include \
11+
target/release/libwasmtime.a \
12+
-lpthread -ldl -lm \
13+
-o linking
14+
./linking
15+
16+
Note that on Windows and macOS the command will be similar, but you'll need
17+
to tweak the `-lpthread` and such annotations.
18+
*/
19+
20+
#include <assert.h>
21+
#include <stdio.h>
22+
#include <stdlib.h>
23+
#include <wasm.h>
24+
#include <wasi.h>
25+
#include <wasmtime.h>
26+
27+
#define MIN(a, b) ((a) < (b) ? (a) : (b))
28+
29+
static void print_trap(wasm_trap_t *trap);
30+
static void read_wat_file(wasm_engine_t *engine, wasm_byte_vec_t *bytes, const char *file);
31+
32+
int main() {
33+
int ret = 0;
34+
// Set up our context
35+
wasm_engine_t *engine = wasm_engine_new();
36+
assert(engine != NULL);
37+
wasm_store_t *store = wasm_store_new(engine);
38+
assert(store != NULL);
39+
40+
wasm_byte_vec_t linking1_wasm, linking2_wasm;
41+
read_wat_file(engine, &linking1_wasm, "examples/linking1.wat");
42+
read_wat_file(engine, &linking2_wasm, "examples/linking2.wat");
43+
44+
// Compile our two modules
45+
wasm_module_t *linking1_module = wasm_module_new(store, &linking1_wasm);
46+
assert(linking1_module != NULL);
47+
wasm_module_t *linking2_module = wasm_module_new(store, &linking2_wasm);
48+
assert(linking2_module != NULL);
49+
wasm_byte_vec_delete(&linking1_wasm);
50+
wasm_byte_vec_delete(&linking2_wasm);
51+
52+
// Instantiate wasi
53+
wasi_config_t *wasi_config = wasi_config_new();
54+
assert(wasi_config);
55+
wasi_config_inherit_argv(wasi_config);
56+
wasi_config_inherit_env(wasi_config);
57+
wasi_config_inherit_stdin(wasi_config);
58+
wasi_config_inherit_stdout(wasi_config);
59+
wasi_config_inherit_stderr(wasi_config);
60+
wasm_trap_t *trap = NULL;
61+
wasi_instance_t *wasi = wasi_instance_new(store, wasi_config, &trap);
62+
if (wasi == NULL) {
63+
print_trap(trap);
64+
exit(1);
65+
}
66+
67+
// Create imports for `linking2`
68+
wasm_importtype_vec_t linking2_import_types;
69+
wasm_module_imports(linking2_module, &linking2_import_types);
70+
const wasm_extern_t **linking2_imports = calloc(linking2_import_types.size, sizeof(void*));
71+
assert(linking2_imports);
72+
for (int i = 0; i < linking2_import_types.size; i++) {
73+
const wasm_extern_t *binding = wasi_instance_bind_import(wasi, linking2_import_types.data[i]);
74+
if (binding != NULL) {
75+
linking2_imports[i] = binding;
76+
} else {
77+
printf("> Failed to satisfy import\n");
78+
exit(1);
79+
}
80+
}
81+
wasm_importtype_vec_delete(&linking2_import_types);
82+
83+
// Instantiate `linking2`
84+
wasm_instance_t *linking2 = wasm_instance_new(store, linking2_module, linking2_imports, &trap);
85+
if (linking2 == NULL) {
86+
print_trap(trap);
87+
exit(1);
88+
}
89+
free(linking2_imports);
90+
wasm_extern_vec_t linking2_externs;
91+
wasm_instance_exports(linking2, &linking2_externs);
92+
wasm_exporttype_vec_t linking2_exports;
93+
wasm_module_exports(linking2_module, &linking2_exports);
94+
95+
// Create imports for `linking1`
96+
wasm_importtype_vec_t linking1_import_types;
97+
wasm_module_imports(linking1_module, &linking1_import_types);
98+
const wasm_extern_t **linking1_imports = calloc(linking1_import_types.size, sizeof(void*));
99+
assert(linking1_imports);
100+
for (int i = 0; i < linking1_import_types.size; i++) {
101+
const wasm_importtype_t *import = linking1_import_types.data[i];
102+
const wasm_name_t *module = wasm_importtype_module(import);
103+
const wasm_name_t *name = wasm_importtype_name(import);
104+
if (strncmp(module->data, "linking2", module->size) == 0) {
105+
const wasm_extern_t *e = NULL;
106+
for (int j = 0; j < linking2_exports.size; j++) {
107+
const wasm_name_t *export_name = wasm_exporttype_name(linking2_exports.data[j]);
108+
if (name->size == export_name->size &&
109+
strncmp(name->data, export_name->data, name->size) == 0) {
110+
e = linking2_externs.data[j];
111+
break;
112+
}
113+
}
114+
if (e) {
115+
linking1_imports[i] = e;
116+
continue;
117+
}
118+
}
119+
120+
printf("> Failed to satisfy import\n");
121+
exit(1);
122+
}
123+
wasm_importtype_vec_delete(&linking1_import_types);
124+
125+
// Instantiate `linking1`
126+
wasm_instance_t *linking1 = wasm_instance_new(store, linking1_module, linking1_imports, &trap);
127+
if (linking1 == NULL) {
128+
print_trap(trap);
129+
exit(1);
130+
}
131+
132+
// Lookup our `run` export function
133+
wasm_extern_vec_t linking1_externs;
134+
wasm_instance_exports(linking1, &linking1_externs);
135+
assert(linking1_externs.size == 1);
136+
wasm_func_t *run = wasm_extern_as_func(linking1_externs.data[0]);
137+
assert(run != NULL);
138+
trap = wasm_func_call(run, NULL, NULL);
139+
if (trap != NULL) {
140+
print_trap(trap);
141+
exit(1);
142+
}
143+
144+
// Clean up after ourselves at this point
145+
wasm_extern_vec_delete(&linking1_externs);
146+
wasm_extern_vec_delete(&linking2_externs);
147+
wasm_instance_delete(linking1);
148+
wasm_instance_delete(linking2);
149+
wasm_module_delete(linking1_module);
150+
wasm_module_delete(linking2_module);
151+
wasm_store_delete(store);
152+
wasm_engine_delete(engine);
153+
return 0;
154+
}
155+
156+
static void read_wat_file(
157+
wasm_engine_t *engine,
158+
wasm_byte_vec_t *bytes,
159+
const char *filename
160+
) {
161+
wasm_byte_vec_t wat;
162+
// Load our input file to parse it next
163+
FILE* file = fopen(filename, "r");
164+
if (!file) {
165+
printf("> Error loading file!\n");
166+
exit(1);
167+
}
168+
fseek(file, 0L, SEEK_END);
169+
size_t file_size = ftell(file);
170+
wasm_byte_vec_new_uninitialized(&wat, file_size);
171+
fseek(file, 0L, SEEK_SET);
172+
if (fread(wat.data, file_size, 1, file) != 1) {
173+
printf("> Error loading module!\n");
174+
exit(1);
175+
}
176+
fclose(file);
177+
178+
// Parse the wat into the binary wasm format
179+
wasm_byte_vec_t error;
180+
if (wasmtime_wat2wasm(engine, &wat, bytes, &error) == 0) {
181+
fprintf(stderr, "failed to parse wat %.*s\n", (int) error.size, error.data);
182+
exit(0);
183+
}
184+
wasm_byte_vec_delete(&wat);
185+
}
186+
187+
static void print_trap(wasm_trap_t *trap) {
188+
assert(trap != NULL);
189+
wasm_message_t message;
190+
wasm_trap_message(trap, &message);
191+
fprintf(stderr, "failed to instantiate module %.*s\n", (int) message.size, message.data);
192+
wasm_byte_vec_delete(&message);
193+
wasm_trap_delete(trap);
194+
}

examples/linking.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
//! Example of instantiating two modules which link to each other.
2+
3+
// You can execute this example with `cargo run --example linking`
4+
5+
use anyhow::Result;
6+
use wasmtime::*;
7+
use wasmtime_wasi::{Wasi, WasiCtx};
8+
9+
fn main() -> Result<()> {
10+
let store = Store::default();
11+
12+
// Load and compile our two modules
13+
let linking1 = Module::from_file(&store, "examples/linking1.wat")?;
14+
let linking2 = Module::from_file(&store, "examples/linking2.wat")?;
15+
16+
// Instantiate the first, `linking2`, which uses WASI imports
17+
let wasi = Wasi::new(&store, WasiCtx::new(std::env::args())?);
18+
let mut imports = Vec::new();
19+
for import in linking2.imports() {
20+
if import.module() == "wasi_snapshot_preview1" {
21+
if let Some(export) = wasi.get_export(import.name()) {
22+
imports.push(Extern::from(export.clone()));
23+
continue;
24+
}
25+
}
26+
panic!(
27+
"couldn't find import for `{}::{}`",
28+
import.module(),
29+
import.name()
30+
);
31+
}
32+
let linking2 = Instance::new(&linking2, &imports)?;
33+
34+
// And using the previous instance we can create the imports for `linking1`,
35+
// using the previous exports.
36+
let mut imports = Vec::new();
37+
for import in linking1.imports() {
38+
if import.module() == "linking2" {
39+
if let Some(export) = linking2.get_export(import.name()) {
40+
imports.push(export.clone());
41+
continue;
42+
}
43+
}
44+
panic!(
45+
"couldn't find import for `{}::{}`",
46+
import.module(),
47+
import.name()
48+
);
49+
}
50+
let linking1 = Instance::new(&linking1, &imports)?;
51+
52+
// And once everything is instantiated we can run!
53+
let run = linking1.get_export("run").and_then(|e| e.func()).unwrap();
54+
let run = run.get0::<()>()?;
55+
run()?;
56+
Ok(())
57+
}

examples/linking1.wat

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
(module
2+
(import "linking2" "double" (func $double (param i32) (result i32)))
3+
(import "linking2" "log" (func $log (param i32 i32)))
4+
(import "linking2" "memory" (memory 1))
5+
(import "linking2" "memory_offset" (global $offset i32))
6+
7+
(func (export "run")
8+
;; Call into the other module to double our number, and we could print it
9+
;; here but for now we just drop it
10+
i32.const 2
11+
call $double
12+
drop
13+
14+
;; Our `data` segment initialized our imported memory, so let's print the
15+
;; string there now.
16+
global.get $offset
17+
i32.const 14
18+
call $log
19+
)
20+
21+
(data (global.get $offset) "Hello, world!\n")
22+
)
23+

examples/linking2.wat

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
(module
2+
(type $fd_write_ty (func (param i32 i32 i32 i32) (result i32)))
3+
(import "wasi_snapshot_preview1" "fd_write" (func $fd_write (type $fd_write_ty)))
4+
5+
(func (export "double") (param i32) (result i32)
6+
local.get 0
7+
i32.const 2
8+
i32.mul
9+
)
10+
11+
(func (export "log") (param i32 i32)
12+
;; store the pointer in the first iovec field
13+
i32.const 4
14+
local.get 0
15+
i32.store
16+
17+
;; store the length in the first iovec field
18+
i32.const 4
19+
local.get 1
20+
i32.store offset=4
21+
22+
;; call the `fd_write` import
23+
i32.const 1 ;; stdout fd
24+
i32.const 4 ;; iovs start
25+
i32.const 1 ;; number of iovs
26+
i32.const 0 ;; where to write nwritten bytes
27+
call $fd_write
28+
drop
29+
)
30+
31+
(memory (export "memory") 2)
32+
(global (export "memory_offset") i32 (i32.const 65536))
33+
)

0 commit comments

Comments
 (0)