examples: adds rust build and WASI example (#676)
This fixes where our pull requests didn't check the rust source in examples were valid. It also adds an example of wasi with rust. This uses cargo-wasi because the default target creates almost 2MB of wasm for a simple cat program. Signed-off-by: Adrian Cole <adrian@tetrate.io>
This commit is contained in:
44
.github/workflows/examples.yaml
vendored
44
.github/workflows/examples.yaml
vendored
@@ -4,27 +4,27 @@ on:
|
|||||||
branches: [main]
|
branches: [main]
|
||||||
paths:
|
paths:
|
||||||
- '.github/workflows/examples.yaml'
|
- '.github/workflows/examples.yaml'
|
||||||
- 'examples/*/testdata/*.go'
|
- 'examples/**'
|
||||||
- 'examples/*/*/testdata/*.go'
|
|
||||||
- 'examples/*/testdata/*/*.go'
|
|
||||||
- 'examples/*/testdata/*/*.c'
|
|
||||||
- 'examples/*/testdata/*.ts'
|
|
||||||
- 'Makefile'
|
- 'Makefile'
|
||||||
push:
|
push:
|
||||||
branches: [main]
|
branches: [main]
|
||||||
paths:
|
paths:
|
||||||
- '.github/workflows/examples.yaml'
|
- '.github/workflows/examples.yaml'
|
||||||
- 'examples/*/testdata/*.go'
|
- 'examples/**'
|
||||||
- 'examples/*/*/testdata/*.go'
|
|
||||||
- 'examples/*/testdata/*/*.go'
|
|
||||||
- 'examples/*/testdata/*/*.c'
|
|
||||||
- 'examples/*/testdata/*.ts'
|
|
||||||
- 'Makefile'
|
- 'Makefile'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
# Not all toolchains are idempotent when generating wasm, so we don't check
|
||||||
|
# in %.wasm as a part of this job.
|
||||||
examples:
|
examples:
|
||||||
name: Build examples
|
name: Build examples
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
|
strategy:
|
||||||
|
matrix: # use latest available versions and be consistent on all workflows!
|
||||||
|
go-version:
|
||||||
|
- "1.17" # == ${{ env.GO_VERSION }} because matrix cannot expand env variables
|
||||||
|
- "1.18"
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Install latest TinyGo
|
- name: Install latest TinyGo
|
||||||
run: | # installing via curl so commands are similar on OS/x
|
run: | # installing via curl so commands are similar on OS/x
|
||||||
@@ -38,21 +38,37 @@ jobs:
|
|||||||
with: # on laptop, use `brew install zig`
|
with: # on laptop, use `brew install zig`
|
||||||
version: 0.9.1
|
version: 0.9.1
|
||||||
|
|
||||||
|
- name: Install wasm32-wasi target
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
toolchain: stable
|
||||||
|
target: wasm32-wasi
|
||||||
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
# TinyGo -> Wasm is not idempotent, so we only check things build.
|
- uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.cache/go-build
|
||||||
|
~/go/pkg/mod
|
||||||
|
~/go/bin
|
||||||
|
key: check-${{ runner.os }}-go-${{ matrix.go-version }}-${{ hashFiles('**/go.sum', 'Makefile') }}
|
||||||
|
|
||||||
- name: Build TinyGO examples
|
- name: Build TinyGO examples
|
||||||
run: make build.examples.tinygo
|
run: make build.examples.tinygo
|
||||||
|
|
||||||
# AssemblyScript -> Wasm is not idempotent, so we only check things build.
|
|
||||||
- name: Build AssemblyScript examples
|
- name: Build AssemblyScript examples
|
||||||
run: make build.examples.as
|
run: make build.examples.as
|
||||||
|
|
||||||
# zig-cc -> Wasm is not idempotent, so we only check things build.
|
|
||||||
- name: Build zig-cc examples
|
- name: Build zig-cc examples
|
||||||
run: make build.examples.zig-cc
|
run: make build.examples.zig-cc
|
||||||
|
|
||||||
# TinyGo -> Wasm is not idempotent, so we only check things build.
|
- name: Build Rust examples
|
||||||
|
run: make build.examples.rust
|
||||||
|
|
||||||
- name: Build bench cases
|
- name: Build bench cases
|
||||||
run: make build.bench
|
run: make build.bench
|
||||||
|
|
||||||
|
- name: Run example tests
|
||||||
|
run: go test ./examples/...
|
||||||
|
|||||||
11
Makefile
11
Makefile
@@ -61,6 +61,17 @@ build.examples.zig-cc: $(c_sources)
|
|||||||
zig cc $$f -o $$(echo $$f | sed -e 's/\.c/\.wasm/') --target=wasm32-wasi -O3; \
|
zig cc $$f -o $$(echo $$f | sed -e 's/\.c/\.wasm/') --target=wasm32-wasi -O3; \
|
||||||
done
|
done
|
||||||
|
|
||||||
|
%/greet.wasm : cargo_target := wasm32-unknown-unknown
|
||||||
|
%/cat.wasm : cargo_target := wasm32-wasi
|
||||||
|
|
||||||
|
.PHONY: build.examples.rust
|
||||||
|
build.examples.rust: examples/allocation/rust/testdata/greet.wasm examples/wasi/testdata/cargo-wasi/cat.wasm
|
||||||
|
|
||||||
|
# Builds rust using cargo normally, or cargo-wasi.
|
||||||
|
%.wasm: %.rs
|
||||||
|
@(cd $(@D); cargo $(if $(findstring wasi,$(cargo_target)),wasi build,build --target $(cargo_target)) --release)
|
||||||
|
@mv $(@D)/target/$(cargo_target)/release/$(@F) $(@D)
|
||||||
|
|
||||||
spectest_base_dir := internal/integration_test/spectest
|
spectest_base_dir := internal/integration_test/spectest
|
||||||
spectest_v1_dir := $(spectest_base_dir)/v1
|
spectest_v1_dir := $(spectest_base_dir)/v1
|
||||||
spectest_v1_testdata_dir := $(spectest_v1_dir)/testdata
|
spectest_v1_testdata_dir := $(spectest_v1_dir)/testdata
|
||||||
|
|||||||
2
examples/allocation/rust/testdata/Cargo.toml
vendored
2
examples/allocation/rust/testdata/Cargo.toml
vendored
@@ -6,6 +6,8 @@ edition = "2021"
|
|||||||
[lib]
|
[lib]
|
||||||
# cdylib builds a a %.wasm file with `cargo build --release --target wasm32-unknown-unknown`
|
# cdylib builds a a %.wasm file with `cargo build --release --target wasm32-unknown-unknown`
|
||||||
crate-type = ["cdylib"]
|
crate-type = ["cdylib"]
|
||||||
|
name = "greet"
|
||||||
|
path = "greet.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# wee_aloc is a WebAssembly optimized allocator, which is needed to use non-numeric types like strings.
|
# wee_aloc is a WebAssembly optimized allocator, which is needed to use non-numeric types like strings.
|
||||||
|
|||||||
@@ -8,20 +8,53 @@ $ go run cat.go /test.txt
|
|||||||
greet filesystem
|
greet filesystem
|
||||||
```
|
```
|
||||||
|
|
||||||
If you do not set the environment variable `WASM_COMPILER`, main defaults
|
If you do not set the environment variable `TOOLCHAIN`, main defaults
|
||||||
to use Wasm built with "tinygo". Here are the included examples:
|
to use Wasm built with "tinygo". Here are the included examples:
|
||||||
|
|
||||||
* [tjnygo](testdata/tinygo) - Built via `tinygo build -o cat.wasm -scheduler=none --no-debug -target=wasi cat.go`
|
* [cargo-wasi](testdata/cargo-wasi) - Built via `cargo wasi build --release; mv ./target/wasm32-wasi/release/cat.wasm .`
|
||||||
|
* [tinygo](testdata/tinygo) - Built via `tinygo build -o cat.wasm -scheduler=none --no-debug -target=wasi cat.go`
|
||||||
* [zig-cc](testdata/zig-cc) - Built via `zig cc cat.c -o cat.wasm --target=wasm32-wasi -O3`
|
* [zig-cc](testdata/zig-cc) - Built via `zig cc cat.c -o cat.wasm --target=wasm32-wasi -O3`
|
||||||
|
|
||||||
Ex. To run the same example with zig-cc:
|
Ex. To run the same example with zig-cc:
|
||||||
```bash
|
```bash
|
||||||
$ WASM_COMPILER=zig-cc go run cat.go /test.txt
|
$ TOOLCHAIN=zig-cc go run cat.go /test.txt
|
||||||
greet filesystem
|
greet filesystem
|
||||||
```
|
```
|
||||||
|
|
||||||
Note: While WASI attempts to be portable, there are no specification tests and
|
### Toolchain notes
|
||||||
some compilers only partially implement features.
|
|
||||||
|
|
||||||
For example, Emscripten only supports WASI I/O on stdin/stdout/stderr, so
|
Examples here check in the resulting binary, as doing so removes a potentially
|
||||||
cannot be used for this example. See emscripten-core/emscripten#17167
|
expensive toolchain setup. This means we have to be careful how wasm is built,
|
||||||
|
so that the repository doesn't bloat (ex more bandwidth for `git clone`).
|
||||||
|
|
||||||
|
While WASI attempts to be portable, there are no specification tests and
|
||||||
|
some compilers partially implement features. Notes about portability follow.
|
||||||
|
|
||||||
|
### cargo-wasi (Rustlang)
|
||||||
|
|
||||||
|
The [Rustlang source][testdata/cargo-wasi] uses [cargo-wasi][1] because the
|
||||||
|
normal release target `wasm32-wasi` results in almost 2MB, which is too large
|
||||||
|
to check into our source tree.
|
||||||
|
|
||||||
|
Concretely, if you use `cargo build --target wasm32-wasi --release`, the binary
|
||||||
|
`./target/wasm32-wasi/release/cat.wasm` is over 1.9MB. One explanation for this
|
||||||
|
bloat is [`wasm32-wasi` target is not pure rust][2]. As that issue is over two
|
||||||
|
years old, it is unlikely to change. This means the only way to create smaller
|
||||||
|
wasm is via optimization.
|
||||||
|
|
||||||
|
The [cargo-wasi][3] crate includes many optimizations in its release target,
|
||||||
|
including `wasm-opt`, a part of [binaryen][3]. `cargo wasi build --release`
|
||||||
|
generates 82KB of wasm, which is small enough to check in.
|
||||||
|
|
||||||
|
### emscripten
|
||||||
|
|
||||||
|
Emscripten is not included as we cannot create a cat program without using
|
||||||
|
custom filesystem code. Emscripten only supports WASI I/O for
|
||||||
|
[stdin/stdout/stderr][4] and suggest using wasi-libc instead. This is used in
|
||||||
|
the [zig-cc](testdata/zig-cc) example.
|
||||||
|
|
||||||
|
[1]: https://github.com/bytecodealliance/cargo-wasi
|
||||||
|
[2]: https://github.com/rust-lang/rust/issues/73432
|
||||||
|
[3]: https://github.com/bytecodealliance/cargo-wasi
|
||||||
|
[4]: https://github.com/WebAssembly/binaryen
|
||||||
|
[5]: https://github.com/emscripten-core/emscripten/issues/17167#issuecomment-1150252755
|
||||||
|
|||||||
@@ -18,6 +18,10 @@ import (
|
|||||||
//go:embed testdata/test.txt
|
//go:embed testdata/test.txt
|
||||||
var catFS embed.FS
|
var catFS embed.FS
|
||||||
|
|
||||||
|
// catWasmCargoWasi was compiled from testdata/cargo-wasi/cat.rs
|
||||||
|
//go:embed testdata/cargo-wasi/cat.wasm
|
||||||
|
var catWasmCargoWasi []byte
|
||||||
|
|
||||||
// catWasmTinyGo was compiled from testdata/tinygo/cat.go
|
// catWasmTinyGo was compiled from testdata/tinygo/cat.go
|
||||||
//go:embed testdata/tinygo/cat.wasm
|
//go:embed testdata/tinygo/cat.wasm
|
||||||
var catWasmTinyGo []byte
|
var catWasmTinyGo []byte
|
||||||
@@ -59,16 +63,18 @@ func main() {
|
|||||||
// Choose the binary we want to test. Most compilers that implement WASI
|
// Choose the binary we want to test. Most compilers that implement WASI
|
||||||
// are portable enough to use binaries interchangeably.
|
// are portable enough to use binaries interchangeably.
|
||||||
var catWasm []byte
|
var catWasm []byte
|
||||||
compiler := os.Getenv("WASM_COMPILER")
|
toolchain := os.Getenv("TOOLCHAIN")
|
||||||
switch compiler {
|
switch toolchain {
|
||||||
case "":
|
case "":
|
||||||
fallthrough // default to TinyGo
|
fallthrough // default to TinyGo
|
||||||
|
case "cargo-wasi":
|
||||||
|
catWasm = catWasmCargoWasi
|
||||||
case "tinygo":
|
case "tinygo":
|
||||||
catWasm = catWasmTinyGo
|
catWasm = catWasmTinyGo
|
||||||
case "zig-cc":
|
case "zig-cc":
|
||||||
catWasm = catWasmZigCc
|
catWasm = catWasmZigCc
|
||||||
default:
|
default:
|
||||||
log.Panicln("unknown compiler", compiler)
|
log.Panicln("unknown toolchain", toolchain)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compile the WebAssembly module using the default configuration.
|
// Compile the WebAssembly module using the default configuration.
|
||||||
|
|||||||
@@ -11,10 +11,10 @@ import (
|
|||||||
//
|
//
|
||||||
// go run cat.go /test.txt
|
// go run cat.go /test.txt
|
||||||
func Test_main(t *testing.T) {
|
func Test_main(t *testing.T) {
|
||||||
for _, compiler := range []string{"tinygo", "zig-cc"} {
|
for _, toolchain := range []string{"cargo-wasi", "tinygo", "zig-cc"} {
|
||||||
compiler := compiler
|
toolchain := toolchain
|
||||||
t.Run(compiler, func(t *testing.T) {
|
t.Run(toolchain, func(t *testing.T) {
|
||||||
t.Setenv("WASM_COMPILER", compiler)
|
t.Setenv("TOOLCHAIN", toolchain)
|
||||||
stdout, stderr := maintester.TestMain(t, main, "cat", "/test.txt")
|
stdout, stderr := maintester.TestMain(t, main, "cat", "/test.txt")
|
||||||
require.Equal(t, "", stderr)
|
require.Equal(t, "", stderr)
|
||||||
require.Equal(t, "greet filesystem\n", stdout)
|
require.Equal(t, "greet filesystem\n", stdout)
|
||||||
|
|||||||
2
examples/wasi/testdata/cargo-wasi/.gitignore
vendored
Normal file
2
examples/wasi/testdata/cargo-wasi/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
/target
|
||||||
|
Cargo.lock
|
||||||
8
examples/wasi/testdata/cargo-wasi/Cargo.toml
vendored
Normal file
8
examples/wasi/testdata/cargo-wasi/Cargo.toml
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
[package]
|
||||||
|
name = "cat"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "cat"
|
||||||
|
path = "cat.rs"
|
||||||
17
examples/wasi/testdata/cargo-wasi/cat.rs
vendored
Normal file
17
examples/wasi/testdata/cargo-wasi/cat.rs
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
use std::env;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io;
|
||||||
|
use std::io::Write;
|
||||||
|
use std::process::exit;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// Start at arg[1] because args[0] is the program name.
|
||||||
|
for path in env::args().skip(1) {
|
||||||
|
if let Ok(mut file) = File::open(&path) {
|
||||||
|
io::copy(&mut file, &mut io::stdout()).unwrap();
|
||||||
|
} else {
|
||||||
|
writeln!(io::stderr(), "error opening: {}: No such file or directory", path).unwrap();
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
examples/wasi/testdata/cargo-wasi/cat.wasm
vendored
Normal file
BIN
examples/wasi/testdata/cargo-wasi/cat.wasm
vendored
Normal file
Binary file not shown.
Reference in New Issue
Block a user