Skip to content

Commit d79e85d

Browse files
nickpdemarcoHKalbasi
authored andcommitted
Add wasm32 example
1 parent efcd428 commit d79e85d

File tree

11 files changed

+347
-0
lines changed

11 files changed

+347
-0
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: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ members = [
1010
"xtask",
1111
"benchmark",
1212
]
13+
# Exclude tutorial-wasm32 from default workspace builds since it requires extra dependencies
14+
exclude = [
15+
"examples/tutorial-wasm32",
16+
]
1317
resolver = "2"
1418

1519
[workspace.package]
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
generated.h
2+
generated.rs
3+
generated.cpp
4+
generated.o
5+
main.js
6+
main.wasm
7+
target/
8+
a.out
9+
example_tutorial_wasm32.wasm
10+
11+
# WASI SDK and dependencies
12+
wasi-sdk-*
13+
wasi-sdk-latest.tar.gz
14+
wasi-sdk-installed
15+
wasm32-wasip1-target
16+
17+
# Build artifacts
18+
actual_output.txt

examples/tutorial-wasm32/Cargo.lock

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[package]
2+
name = "example-tutorial-wasm32"
3+
version = "0.6.0"
4+
edition = "2024"
5+
rust-version = "1.85"
6+
license = "MIT OR Apache-2.0"
7+
publish = false
8+
9+
[lib]
10+
crate-type = ["staticlib"]
11+
12+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
13+
14+
[dependencies]

examples/tutorial-wasm32/Makefile

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# Detect platform for WASI SDK
2+
UNAME_S := $(shell uname -s)
3+
ifeq ($(UNAME_S),Linux)
4+
WASI_SDK_PLATFORM = x86_64-linux
5+
else ifeq ($(UNAME_S),Darwin)
6+
WASI_SDK_PLATFORM = x86_64-macos
7+
else
8+
$(error Unsupported platform: $(UNAME_S))
9+
endif
10+
11+
# WASI SDK paths and URLs
12+
WASI_SDK_VERSION = 25.0
13+
WASI_SDK_DIR = wasi-sdk-$(WASI_SDK_VERSION)-$(WASI_SDK_PLATFORM)
14+
WASI_SDK_PATH = $(shell pwd)/$(WASI_SDK_DIR)
15+
WASI_SDK_URL = $(or $(WASI_SDK_DOWNLOAD_URL),https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-25/$(WASI_SDK_DIR).tar.gz)
16+
17+
# WASI build flags for C++ compilation with wasmtime
18+
WASIFLAGS = -std=c++14 \
19+
--target=wasm32-wasi \
20+
--sysroot=$(WASI_SDK_PATH)/share/wasi-sysroot \
21+
-D_WASI_EMULATED_SIGNAL \
22+
-lwasi-emulated-signal \
23+
-fno-exceptions
24+
25+
# Use the WASI SDK C++ compiler
26+
CXX = $(WASI_SDK_PATH)/bin/clang++
27+
28+
# Detect wasmtime location (system PATH or home directory)
29+
WASMTIME := $(shell which wasmtime 2>/dev/null || echo "$$HOME/.wasmtime/bin/wasmtime")
30+
31+
# Default target
32+
all: run a.out
33+
34+
# Run the example with wasmtime (installs all deps automatically)
35+
run: main.wasm
36+
$(WASMTIME) run --allow-precompiled main.wasm
37+
38+
# CI expects a.out; we create it as a bash script that simply execs wasmtime
39+
a.out: main.wasm
40+
@printf '%s\n' '#!/usr/bin/env bash' 'exec $(WASMTIME) run --allow-precompiled "$$(dirname "$$0")/main.wasm" "$$@"' > $@
41+
@chmod +x $@
42+
43+
main.wasm: wasi-sdk-installed main.cpp generated.h generated.cpp \
44+
target/wasm32-wasip1/release/libexample_tutorial_wasm32.a
45+
$(CXX) $(WASIFLAGS) \
46+
main.cpp generated.cpp \
47+
target/wasm32-wasip1/release/libexample_tutorial_wasm32.a \
48+
-o $@
49+
50+
target/wasm32-wasip1/release/libexample_tutorial_wasm32.a: wasm32-wasip1-target generated.h ./src/generated.rs ./src/lib.rs
51+
cargo build --target=wasm32-wasip1 --release
52+
53+
generated.h ./src/generated.rs generated.cpp: main32.zng
54+
cargo run --release --manifest-path ../../zngur-cli/Cargo.toml g main32.zng
55+
56+
# Install wasmtime and WASI SDK automatically
57+
wasi-sdk-installed:
58+
@echo "Checking dependencies..."
59+
@if ! command -v wasmtime >/dev/null 2>&1 && [ ! -f "$$HOME/.wasmtime/bin/wasmtime" ]; then \
60+
echo "Installing wasmtime..."; \
61+
curl https://wasmtime.dev/install.sh -sSf | bash; \
62+
fi
63+
@if [ ! -d "$(WASI_SDK_DIR)" ]; then \
64+
echo "Installing WASI SDK for $(WASI_SDK_PLATFORM)..."; \
65+
curl -L -o wasi-sdk-latest.tar.gz "$(WASI_SDK_URL)" && \
66+
tar -xzf wasi-sdk-latest.tar.gz; \
67+
fi
68+
@touch wasi-sdk-installed
69+
70+
# Ensure wasm32-wasip1 target is installed
71+
wasm32-wasip1-target:
72+
@rustup target list --installed | grep -q wasm32-wasip1 || { \
73+
echo "Installing wasm32-wasip1 Rust target..."; \
74+
rustup target add wasm32-wasip1; \
75+
}
76+
@touch wasm32-wasip1-target
77+
78+
clean:
79+
cargo clean
80+
rm -f generated.h ./src/generated.rs generated.cpp generated.o
81+
rm -f main.wasm example_tutorial_wasm32.wasm a.out wasi-sdk-latest.tar.gz
82+
rm -f wasi-sdk-installed wasm32-wasip1-target
83+
rm -rf wasi-sdk-*
84+
85+
# Legacy target for manual dependency installation (now automatic)
86+
install-deps: wasi-sdk-installed wasm32-wasip1-target
87+
88+
FORCE: ;

examples/tutorial-wasm32/README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Wasm32 Example
2+
3+
This example builds a sample application for wasmtime to test zngur's support for basic WASM applications.
4+
5+
## To build and run
6+
7+
```
8+
$ make run
9+
```
10+
11+
This automatically installs all dependencies (wasmtime, WASI SDK, Rust targets) and runs the example.
12+
13+
## Alternative targets
14+
15+
```
16+
$ make # Same as 'make run'
17+
$ make main.wasm # Build without running
18+
$ make a.out # Create executable wrapper script
19+
$ make clean # Clean all build artifacts
20+
```
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
17
2+
s[2] = 7
3+
Rust panic would happen if we accessed invalid index, but we avoid it
4+
hello 2 2
5+
hello 5 7
6+
hello 7 14
7+
hello 3 17
8+
34 17
9+
vector iterator has been destructed
10+
[main.cpp:70] t = [
11+
10,
12+
20,
13+
60,
14+
]

examples/tutorial-wasm32/main.cpp

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
#include <iostream>
2+
#include <vector>
3+
4+
#include "./generated.h"
5+
6+
// Rust values are available in the `::rust` namespace from their absolute path
7+
// in Rust
8+
template <typename T> using Vec = rust::std::vec::Vec<T>;
9+
template <typename T> using Option = rust::std::option::Option<T>;
10+
template <typename T> using BoxDyn = rust::Box<rust::Dyn<T>>;
11+
12+
// You can implement Rust traits for your classes
13+
template <typename T>
14+
class VectorIterator : public rust::std::iter::Iterator<T> {
15+
std::vector<T> vec;
16+
size_t pos;
17+
18+
public:
19+
VectorIterator(std::vector<T> &&v) : vec(v), pos(0) {}
20+
~VectorIterator() {
21+
std::cout << "vector iterator has been destructed" << std::endl;
22+
}
23+
24+
Option<T> next() override {
25+
if (pos >= vec.size()) {
26+
return Option<T>::None();
27+
}
28+
T value = vec[pos++];
29+
// You can construct Rust enum with fields in C++
30+
return Option<T>::Some(value);
31+
}
32+
};
33+
34+
int main() {
35+
// You can call Rust functions that return things by value, and store that
36+
// value in your stack.
37+
auto s = Vec<int32_t>::new_();
38+
s.push(2);
39+
Vec<int32_t>::push(s, 5);
40+
s.push(7);
41+
Vec<int32_t>::push(s, 3);
42+
// You can call Rust functions just like normal Rust.
43+
std::cout << s.clone().into_iter().sum() << std::endl;
44+
// Access valid indices
45+
std::cout << "s[2] = " << *s.get(2).unwrap() << std::endl;
46+
// TODO: Uncomment this line after enabling wasmtime exceptions.
47+
// std::cout << "s[4] = " << *s.get(4).unwrap() << std::endl;
48+
std::cout << "Rust panic would happen if we accessed invalid index, but we avoid it" << std::endl;
49+
50+
int state = 0;
51+
// You can convert a C++ lambda into a `Box<dyn Fn>` and friends.
52+
auto f = BoxDyn<rust::Fn<int32_t, int32_t>>::make_box([&](int32_t x) {
53+
state += x;
54+
std::cout << "hello " << x << " " << state << "\n";
55+
return x * 2;
56+
});
57+
// And pass it to Rust functions that accept closures.
58+
auto x = s.into_iter().map(std::move(f)).sum();
59+
std::cout << x << " " << state << "\n";
60+
std::vector<int32_t> vec{10, 20, 60};
61+
// You can convert a C++ type that implements `Trait` to a `Box<dyn Trait>`.
62+
// `make_box` is similar to the `make_unique`, it takes constructor arguments
63+
// and construct it inside the `Box` (instead of `unique_ptr`).
64+
auto vec_as_iter = BoxDyn<rust::std::iter::Iterator<int32_t>>::make_box<
65+
VectorIterator<int32_t>>(std::move(vec));
66+
// Then use it like a normal Rust value.
67+
auto t = vec_as_iter.collect();
68+
// Some utilities are also provided. For example, `zngur_dbg` is the
69+
// equivalent of `dbg!` macro.
70+
zngur_dbg(t);
71+
return 0;
72+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
type Box<dyn Fn(i32) -> i32> {
2+
#layout(size = 8, align = 4);
3+
}
4+
5+
mod ::std {
6+
type option::Option<i32> {
7+
#layout(size = 8, align = 4);
8+
wellknown_traits(Copy);
9+
10+
constructor None;
11+
constructor Some(i32);
12+
13+
fn unwrap(self) -> i32;
14+
}
15+
16+
type option::Option<&i32> {
17+
#layout(size = 4, align = 4);
18+
wellknown_traits(Copy);
19+
20+
fn unwrap(self) -> &i32;
21+
}
22+
23+
type iter::Map<::std::vec::IntoIter<i32>, Box<dyn Fn(i32) -> i32>> {
24+
#layout(size = 24, align = 4);
25+
26+
fn sum<i32>(self) -> i32;
27+
}
28+
29+
mod vec {
30+
type IntoIter<i32> {
31+
#layout(size = 16, align = 4);
32+
33+
fn sum<i32>(self) -> i32;
34+
fn map<i32, Box<dyn Fn(i32) -> i32>>(self, Box<dyn Fn(i32) -> i32>)
35+
-> ::std::iter::Map<::std::vec::IntoIter<i32>, Box<dyn Fn(i32) -> i32>>;
36+
}
37+
38+
type Vec<i32> {
39+
#layout(size = 12, align = 4);
40+
wellknown_traits(Debug);
41+
42+
fn new() -> Vec<i32>;
43+
fn push(&mut self, i32);
44+
fn clone(&self) -> Vec<i32>;
45+
fn get(&self, usize) -> ::std::option::Option<&i32> deref [i32];
46+
fn into_iter(self) -> ::std::vec::IntoIter<i32>;
47+
}
48+
}
49+
50+
trait iter::Iterator::<Item = i32> {
51+
fn next(&mut self) -> ::std::option::Option<i32>;
52+
}
53+
}
54+
55+
type Box<dyn ::std::iter::Iterator<Item = i32>> {
56+
#layout(size = 8, align = 4);
57+
58+
fn collect<::std::vec::Vec<i32>>(self) -> ::std::vec::Vec<i32>;
59+
}

0 commit comments

Comments
 (0)