diff --git a/README.md b/README.md index df1afbf..6c14f5f 100644 --- a/README.md +++ b/README.md @@ -90,8 +90,8 @@ See the "Known Issues" section for details. | Program | w2c2
-O0 | w2c2
-O3 | wasmtime | wasmer
(cranelift) | wasmer
(llvm) | WAMR
-O0 | WAMR
-O3 | directly | |---|---|---|---|---|---|---|---|---| -| `reva-client-eth` (Rust) | 4,543,397,058 | 1,337,696,305 | 1,074,488,397 | doesn't work | ? | didn't check | ? | 388,564,723 | -| `stateless` (Go) | 6,039,298,186 | 2,154,036,727 | 874,758,419 | 953,874,491 | ? | 5,427,433,654 | ? | 236,265,327 | +| `reva-client-eth` (Rust) | 4,543,397,058 | 1,337,696,305 | 1,074,488,397 | doesn't work | ? | 2,472,559,016 | ? | 388,564,723 | +| `stateless` (Go) | 6,039,298,186 | 2,154,036,727 | 874,758,419 | 953,874,491 | ? | 3,526,934,350 | ? | 236,265,327 | ## Analysis diff --git a/docker/Dockerfile b/docker/Dockerfile index c26f545..0942eb4 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -163,7 +163,7 @@ WORKDIR /tmp # https://github.com/llvm/llvm-project/issues/81440 RUN git clone --branch zkvm https://github.com/psilva261/wasm-micro-runtime-zkvm.git wamr-riscv && \ cd wamr-riscv && \ - git checkout e9df4f8169eaafa387b001c0352aec0ea2080ed3 && \ + git checkout a93ff4cbbf99d35869fac3e03a012e58fc6cfb9c && \ cmake . \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_INSTALL_PREFIX=/opt/wamr-riscv \ diff --git a/examples/build-wasm/go/stateless-by-wasmer/src/main.rs b/examples/build-wasm/go/stateless-by-wasmer/src/main.rs index f5c75ac..a7873ea 100644 --- a/examples/build-wasm/go/stateless-by-wasmer/src/main.rs +++ b/examples/build-wasm/go/stateless-by-wasmer/src/main.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use anyhow::Result; -use wasmer::{sys::NativeEngineExt, Engine, Instance, Module, Store}; +use wasmer::{sys::NativeEngineExt, Engine, Function, Instance, Module, Store}; use wasmer_wasix::{runtime::task_manager::tokio::TokioTaskManager, PluggableRuntime, WasiEnv}; fn main() -> Result<()> { @@ -41,7 +41,13 @@ fn main() -> Result<()> { .finalize(&mut store)?; // Create imports from WASI - let import_object = wasi_env.import_object(&mut store, &module)?; + let mut import_object = wasi_env.import_object(&mut store, &module)?; + + let shutdown = Function::new_typed(&mut store, || { + println!("shutdown called"); + }); + + import_object.define("testmodule", "shutdown", shutdown); // Instantiate the module let instance = Instance::new(&mut store, &module, &import_object)?; diff --git a/examples/build-wasm/go/stateless-by-wasmtime/src/main.rs b/examples/build-wasm/go/stateless-by-wasmtime/src/main.rs index 0cd75ca..d232337 100644 --- a/examples/build-wasm/go/stateless-by-wasmtime/src/main.rs +++ b/examples/build-wasm/go/stateless-by-wasmtime/src/main.rs @@ -5,12 +5,17 @@ fn main() -> anyhow::Result<()> { let wasm_bytes = include_bytes!("stateless.cwasm"); let engine = Engine::default(); + let module = unsafe { Module::deserialize(&engine, wasm_bytes)? }; // Create linker first let mut linker = Linker::new(&engine); wasmtime_wasi::p1::add_to_linker_sync(&mut linker, |s| s)?; + linker.func_wrap("testmodule", "shutdown", || { + println!("shutdown called"); + })?; + // Create WASI context with build_p1() let wasi = WasiCtx::builder().inherit_stdio().inherit_args().build_p1(); diff --git a/examples/go/empty/example.go b/examples/go/empty/example.go index da29a2c..5bbaae7 100644 --- a/examples/go/empty/example.go +++ b/examples/go/empty/example.go @@ -1,4 +1,8 @@ package main func main() { + // During exit a fatal runtime error occurs in WAMR when + // compiled with --bounds-check=0. An early shutdown provides + // a reliable workaround. + shutdown() } diff --git a/examples/go/empty/utils_other.go b/examples/go/empty/utils_other.go new file mode 100644 index 0000000..575b719 --- /dev/null +++ b/examples/go/empty/utils_other.go @@ -0,0 +1,5 @@ +//go:build !wasm + +package main + +func shutdown() {} diff --git a/examples/go/empty/utils_wasm.go b/examples/go/empty/utils_wasm.go new file mode 100644 index 0000000..3bb805e --- /dev/null +++ b/examples/go/empty/utils_wasm.go @@ -0,0 +1,5 @@ +package main + +//go:wasmimport testmodule shutdown +//go:noescape +func shutdown() diff --git a/examples/go/fibonacci/example.go b/examples/go/fibonacci/example.go index 8e0aad1..c2cf7c2 100644 --- a/examples/go/fibonacci/example.go +++ b/examples/go/fibonacci/example.go @@ -13,4 +13,9 @@ func main() { n := 20 result := fibonacci(n) fmt.Printf("Fibonacci(%d) = %d\n", n, result) + + // During exit a fatal runtime error occurs in WAMR when + // compiled with --bounds-check=0. An early shutdown provides + // a reliable workaround. + shutdown() } diff --git a/examples/go/fibonacci/utils_other.go b/examples/go/fibonacci/utils_other.go new file mode 100644 index 0000000..575b719 --- /dev/null +++ b/examples/go/fibonacci/utils_other.go @@ -0,0 +1,5 @@ +//go:build !wasm + +package main + +func shutdown() {} diff --git a/examples/go/fibonacci/utils_wasm.go b/examples/go/fibonacci/utils_wasm.go new file mode 100644 index 0000000..3bb805e --- /dev/null +++ b/examples/go/fibonacci/utils_wasm.go @@ -0,0 +1,5 @@ +package main + +//go:wasmimport testmodule shutdown +//go:noescape +func shutdown() diff --git a/examples/go/println/example.go b/examples/go/println/example.go index b692102..d5a5d19 100644 --- a/examples/go/println/example.go +++ b/examples/go/println/example.go @@ -14,5 +14,12 @@ func main() { debug.SetMemoryLimit(400 * (1 << 20)) fmt.Println("Hello world from golang") + + // During exit a fatal runtime error occurs in WAMR when + // compiled with --bounds-check=0. An early shutdown provides + // a reliable workaround. + shutdown() + panic("foo") + } diff --git a/examples/go/println/utils_other.go b/examples/go/println/utils_other.go new file mode 100644 index 0000000..575b719 --- /dev/null +++ b/examples/go/println/utils_other.go @@ -0,0 +1,5 @@ +//go:build !wasm + +package main + +func shutdown() {} diff --git a/examples/go/println/utils_wasm.go b/examples/go/println/utils_wasm.go new file mode 100644 index 0000000..3bb805e --- /dev/null +++ b/examples/go/println/utils_wasm.go @@ -0,0 +1,5 @@ +package main + +//go:wasmimport testmodule shutdown +//go:noescape +func shutdown() diff --git a/examples/go/stateless/main.go b/examples/go/stateless/main.go index fd5f177..36fdd06 100644 --- a/examples/go/stateless/main.go +++ b/examples/go/stateless/main.go @@ -110,4 +110,9 @@ func main() { if receiptRoot == (common.Hash{}) { panic("Receipt root is empty") } + + // During exit a fatal runtime error occurs in WAMR when + // compiled with --bounds-check=0. An early shutdown provides + // a reliable workaround. + shutdown() } diff --git a/examples/go/stateless/utils_other.go b/examples/go/stateless/utils_other.go new file mode 100644 index 0000000..575b719 --- /dev/null +++ b/examples/go/stateless/utils_other.go @@ -0,0 +1,5 @@ +//go:build !wasm + +package main + +func shutdown() {} diff --git a/examples/go/stateless/utils_wasm.go b/examples/go/stateless/utils_wasm.go new file mode 100644 index 0000000..3bb805e --- /dev/null +++ b/examples/go/stateless/utils_wasm.go @@ -0,0 +1,5 @@ +package main + +//go:wasmimport testmodule shutdown +//go:noescape +func shutdown() diff --git a/platform/riscv-qemu-user/custom_imports.c b/platform/riscv-qemu-user/custom_imports.c new file mode 100644 index 0000000..f22e93b --- /dev/null +++ b/platform/riscv-qemu-user/custom_imports.c @@ -0,0 +1,47 @@ +/* Custom WASM imports for zkvm target + * + * This file implements custom functions for Go programs using //go:wasmimport. + * + * This will be used to implement zkvm precompiles and zkvm specific functions that the guest + * program needs. + * + */ + +#include "w2c2_base.h" +#include + +U32 testmodule__testfunc(void* instance, U32 a, U32 b) { + return 1000*a + b; +} + +U32 testmodule__testfunc2(void* instance, U32 a, U32 b) { + return a * b; +} + +int testmodule__printk(void* instance, U32 val) { + char buf[12]; + static const char hex[] = "0123456789abcdef"; + + buf[0] = '0'; + buf[1] = 'x'; + for (int i = 7; i >= 0; --i) { + buf[i+2] = hex[val & 0xF]; + val >>= 4; + } + buf[10] = '\n'; + buf[11] = '\0'; + + puts(buf); +} + +void testmodule__shutdown(void* instance) { + exit(0); +} + +U32 testmodule__input_data_len(void* instance) { + return 0; +} + +U32 testmodule__input_data(void* instance, U32 index) { + return 0; +} \ No newline at end of file diff --git a/platform/riscv-qemu-user/scripts/c2riscv-qemu-user.sh b/platform/riscv-qemu-user/scripts/c2riscv-qemu-user.sh index 428cf06..222c8dc 100755 --- a/platform/riscv-qemu-user/scripts/c2riscv-qemu-user.sh +++ b/platform/riscv-qemu-user/scripts/c2riscv-qemu-user.sh @@ -93,6 +93,7 @@ INCLUDES=( # Source files SOURCES=( platform/riscv-qemu-user/main.c + platform/riscv-qemu-user/custom_imports.c "$GUEST_DIR/guest.c" $GUEST_DIR/s0*.c w2c2/embedded/wasi.c diff --git a/platform/riscv-wamr-qemu/main.c b/platform/riscv-wamr-qemu/main.c index 65391fd..3f4e074 100644 --- a/platform/riscv-wamr-qemu/main.c +++ b/platform/riscv-wamr-qemu/main.c @@ -2,10 +2,13 @@ #include #include #include +#include extern const char wasmModuleBuffer[]; extern int wasmModuleBuffer_length; +void shutdown(); + int main(void) { int argc = 0; char *argv[0]; @@ -18,10 +21,30 @@ int main(void) { wasm_exec_env_t exec_env; uint32_t size, stack_size = 16*1024*1024; + RuntimeInitArgs init_args; + memset(&init_args, 0, sizeof(RuntimeInitArgs)); + wasm_runtime_set_log_level(WASM_LOG_LEVEL_VERBOSE); - /* initialize the wasm runtime by default configurations */ - if (!wasm_runtime_init()) { + /* Define an array of NativeSymbol for the APIs to be exported. */ + static NativeSymbol native_symbols[] = { + { + "shutdown", // the name of WASM function name + shutdown, // the native function pointer + "()", // the function prototype signature, avoid to use i32 + NULL // attachment is NULL + }, + }; + + init_args.mem_alloc_type = Alloc_With_System_Allocator; + + /* Native symbols need below registration phase */ + init_args.n_native_symbols = sizeof(native_symbols) / sizeof(NativeSymbol); + init_args.native_module_name = "testmodule"; + init_args.native_symbols = native_symbols; + + /* initialize the wasm runtime with init_args */ + if (!wasm_runtime_full_init(&init_args)) { printf("runtime init failed\n"); exit(1); } diff --git a/platform/riscv-wamr-qemu/scripts/wasm2wamr-qemu.sh b/platform/riscv-wamr-qemu/scripts/wasm2wamr-qemu.sh index 795ee68..2d1b754 100755 --- a/platform/riscv-wamr-qemu/scripts/wasm2wamr-qemu.sh +++ b/platform/riscv-wamr-qemu/scripts/wasm2wamr-qemu.sh @@ -24,12 +24,12 @@ if [ $# -lt 2 ]; then echo " output-elf Output RISC-V ELF binary path" echo "" echo "Example:" - echo " $0 build/.c-packages/println build/bin/println.riscv.elf" + echo " $0 examples/build-wasm/go/println.wasm build/bin/println.wamr.elf" echo "" echo "To run in QEMU:" echo " ./docker/docker-shell.sh qemu-system-riscv64 -machine virt \\" echo " -m 1024M -d plugin -plugin /libinsn.so \\" - echo " -kernel build/bin/println.riscv.elf -nographic \\" + echo " -kernel build/bin/println.wamr.elf -nographic \\" echo " -semihosting-config enable=on,target=native" echo "" exit 1 @@ -90,7 +90,7 @@ wamrc \ --cpu-features='+i,+m,+a' \ --opt-level=0 \ --size-level=1 \ - --bounds-checks=1 \ + --bounds-checks=0 \ -o $OUTPUT.riscv64.wamr $1 gcc platform/riscv-wamr-qemu/file2c/file2c.c \ @@ -182,7 +182,7 @@ if [ $? -eq 0 ] && [ -f "$OUTPUT" ]; then echo "Size: $SIZE" echo "" echo "To run in QEMU:" - echo " ./docker/docker-shell.sh qemu-system-riscv64 -machine virt \\" + echo " ./docker-shell.sh qemu-system-riscv64 -machine virt \\" echo " -m 1024M -d plugin -plugin /libinsn.so \\" echo " -kernel $OUTPUT -nographic \\" echo " -semihosting-config enable=on,target=native" diff --git a/platform/riscv-wamr-qemu/startup.S b/platform/riscv-wamr-qemu/startup.S index 0fb4e34..f384bb0 100644 --- a/platform/riscv-wamr-qemu/startup.S +++ b/platform/riscv-wamr-qemu/startup.S @@ -1,5 +1,6 @@ .section .text.init .global _start +.global shutdown _start: la sp, _stack_top @@ -17,6 +18,7 @@ bss_done: /* Call main */ call main +shutdown: /* https://github.com/xypron/riscv_test_payload (MIT-licensed) */ li a7, 0x53525354 # SBI extension: SRST (System Reset) li a6, 0 # Function: system reset diff --git a/platform/riscv-wamr-qemu/syscalls.c b/platform/riscv-wamr-qemu/syscalls.c index 593ab63..1b9cf19 100644 --- a/platform/riscv-wamr-qemu/syscalls.c +++ b/platform/riscv-wamr-qemu/syscalls.c @@ -1,7 +1,8 @@ -#include -#include #include +#include #include +#include +#include #include "uart.h" diff --git a/rust_benchmark.sh b/rust_benchmark.sh index bca8b8a..92df715 100755 --- a/rust_benchmark.sh +++ b/rust_benchmark.sh @@ -19,6 +19,10 @@ echo "Transpiling WASM to WASMU with wasmer..." # TODO: Cranelift is used for now because LLVM support is buggy. Once LLVM is fixed in wasmer use `--llvm` flag instead of `cranelift`; https://github.com/wasmerio/wasmer/issues/5951#issuecomment-3632904384 /root/.wasmer/bin/wasmer compile --cranelift --target riscv64gc-unknown-linux-gnu examples/build-wasm/rust/reva-client-eth.wasm -o examples/build-wasm/rust/reva-client-eth-by-wasmer/src/reva-client-eth.wasmu +echo "Transpiling WASM to WAMR AOT with wamrc..." + +./platform/riscv-wamr-qemu/scripts/wasm2wamr-qemu.sh examples/build-wasm/rust/reva-client-eth.wasm build/bin/reva-client-eth.wamr.elf + echo "Compiling C to RISCV..." OPT_LEVEL="-O0" ./platform/riscv-qemu/scripts/c2riscv-qemu.sh build/c-packages/reva-client-eth/ build/bin/reva-client-eth.riscv.O0.elf @@ -38,6 +42,7 @@ run_qemu "$success_string" "false" "native" "examples/rust/reva-client-e run_qemu "$success_string" "true" "w2c2-O0" "build/bin/reva-client-eth.riscv.O0.elf" run_qemu "$success_string" "true" "w2c2-O3" "build/bin/reva-client-eth.riscv.O3.elf" run_qemu "$success_string" "false" "wasmtime" "examples/build-wasm/rust/reva-client-eth-by-wasmtime/target/riscv64gc-unknown-linux-gnu/release/standalone" +run_qemu "$success_string" "true" "wamr" "build/bin/reva-client-eth.wamr.elf" # `reva-client-eth` via `wasmer` does not work for some reason. The root cause is not yet known. The error is: # Error: RuntimeError: out of bounds memory access