From 5fb5db961764b2043cb6fcd77feebe4ebcf42c61 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Mon, 17 Nov 2025 18:58:07 +0900 Subject: [PATCH 1/3] relocate wasm test crate under example_projects --- .../wasm32_without_js/rustpython-without-js}/.cargo/config.toml | 0 .../wasm32_without_js/rustpython-without-js}/Cargo.toml | 2 +- .../wasm32_without_js/rustpython-without-js}/README.md | 0 .../wasm32_without_js/rustpython-without-js}/src/lib.rs | 0 4 files changed, 1 insertion(+), 1 deletion(-) rename {wasm/wasm-unknown-test => example_projects/wasm32_without_js/rustpython-without-js}/.cargo/config.toml (100%) rename {wasm/wasm-unknown-test => example_projects/wasm32_without_js/rustpython-without-js}/Cargo.toml (88%) rename {wasm/wasm-unknown-test => example_projects/wasm32_without_js/rustpython-without-js}/README.md (100%) rename {wasm/wasm-unknown-test => example_projects/wasm32_without_js/rustpython-without-js}/src/lib.rs (100%) diff --git a/wasm/wasm-unknown-test/.cargo/config.toml b/example_projects/wasm32_without_js/rustpython-without-js/.cargo/config.toml similarity index 100% rename from wasm/wasm-unknown-test/.cargo/config.toml rename to example_projects/wasm32_without_js/rustpython-without-js/.cargo/config.toml diff --git a/wasm/wasm-unknown-test/Cargo.toml b/example_projects/wasm32_without_js/rustpython-without-js/Cargo.toml similarity index 88% rename from wasm/wasm-unknown-test/Cargo.toml rename to example_projects/wasm32_without_js/rustpython-without-js/Cargo.toml index 277a9810b99..78c64eabe63 100644 --- a/wasm/wasm-unknown-test/Cargo.toml +++ b/example_projects/wasm32_without_js/rustpython-without-js/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "wasm-unknown-test" +name = "rustpython-without-js" version = "0.1.0" edition = "2021" diff --git a/wasm/wasm-unknown-test/README.md b/example_projects/wasm32_without_js/rustpython-without-js/README.md similarity index 100% rename from wasm/wasm-unknown-test/README.md rename to example_projects/wasm32_without_js/rustpython-without-js/README.md diff --git a/wasm/wasm-unknown-test/src/lib.rs b/example_projects/wasm32_without_js/rustpython-without-js/src/lib.rs similarity index 100% rename from wasm/wasm-unknown-test/src/lib.rs rename to example_projects/wasm32_without_js/rustpython-without-js/src/lib.rs From eac8968f84c5709f92fd9057b559241f7f79a330 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Mon, 17 Nov 2025 20:30:43 +0900 Subject: [PATCH 2/3] Add wasm runtime and fix the example code to actually run Co-Authored-By: Valentyn Faychuk Co-Authored-By: Lee Dogeon --- .cspell.dict/rust-more.txt | 1 + example_projects/wasm32_without_js/.gitignore | 2 + example_projects/wasm32_without_js/README.md | 18 +++ .../rustpython-without-js/Cargo.toml | 4 +- .../rustpython-without-js/src/lib.rs | 58 ++++++-- .../wasm32_without_js/wasm-runtime/.gitignore | 4 + .../wasm32_without_js/wasm-runtime/Cargo.toml | 9 ++ .../wasm32_without_js/wasm-runtime/README.md | 19 +++ .../wasm-runtime/src/main.rs | 133 ++++++++++++++++++ 9 files changed, 237 insertions(+), 11 deletions(-) create mode 100644 example_projects/wasm32_without_js/.gitignore create mode 100644 example_projects/wasm32_without_js/README.md create mode 100644 example_projects/wasm32_without_js/wasm-runtime/.gitignore create mode 100644 example_projects/wasm32_without_js/wasm-runtime/Cargo.toml create mode 100644 example_projects/wasm32_without_js/wasm-runtime/README.md create mode 100644 example_projects/wasm32_without_js/wasm-runtime/src/main.rs diff --git a/.cspell.dict/rust-more.txt b/.cspell.dict/rust-more.txt index f27e53bd6ed..ff2013e81a7 100644 --- a/.cspell.dict/rust-more.txt +++ b/.cspell.dict/rust-more.txt @@ -82,6 +82,7 @@ unsync wasip1 wasip2 wasmbind +wasmer wasmtime widestring winapi diff --git a/example_projects/wasm32_without_js/.gitignore b/example_projects/wasm32_without_js/.gitignore new file mode 100644 index 00000000000..50a623b5daa --- /dev/null +++ b/example_projects/wasm32_without_js/.gitignore @@ -0,0 +1,2 @@ +*/target/ +*/Cargo.lock diff --git a/example_projects/wasm32_without_js/README.md b/example_projects/wasm32_without_js/README.md new file mode 100644 index 00000000000..67fef3fba47 --- /dev/null +++ b/example_projects/wasm32_without_js/README.md @@ -0,0 +1,18 @@ +# RustPython wasm32 build without JS + +To test, build rustpython to wasm32-unknown-unknown target first. + +```shell +cd rustpython-without-js # due to `.cargo/config.toml` +cargo build +cd .. +``` + +Then there will be `rustpython-without-js/target/wasm32-unknown-unknown/debug/rustpython_without_js.wasm` file. + +Now we can run the wasm file with wasm runtime: + +```shell +cargo run --release --manifest-path wasm-runtime/Cargo.toml rustpython-without-js/target/wasm32-unknown-unknown/debug/rustpython_without_js.wasm +``` + diff --git a/example_projects/wasm32_without_js/rustpython-without-js/Cargo.toml b/example_projects/wasm32_without_js/rustpython-without-js/Cargo.toml index 78c64eabe63..f987fe94a24 100644 --- a/example_projects/wasm32_without_js/rustpython-without-js/Cargo.toml +++ b/example_projects/wasm32_without_js/rustpython-without-js/Cargo.toml @@ -1,14 +1,14 @@ [package] name = "rustpython-without-js" version = "0.1.0" -edition = "2021" +edition = "2024" [lib] crate-type = ["cdylib"] [dependencies] getrandom = "0.3" -rustpython-vm = { path = "../../crates/vm", default-features = false, features = ["compiler"] } +rustpython-vm = { path = "../../../crates/vm", default-features = false, features = ["compiler"] } [workspace] diff --git a/example_projects/wasm32_without_js/rustpython-without-js/src/lib.rs b/example_projects/wasm32_without_js/rustpython-without-js/src/lib.rs index aae922864dd..0a8695fd7fb 100644 --- a/example_projects/wasm32_without_js/rustpython-without-js/src/lib.rs +++ b/example_projects/wasm32_without_js/rustpython-without-js/src/lib.rs @@ -1,13 +1,50 @@ -use rustpython_vm::{Interpreter, eval}; +use rustpython_vm::{Interpreter}; + +unsafe extern "C" { + fn kv_get(kp: i32, kl: i32, vp: i32, vl: i32) -> i32; + + /// kp and kl are the key pointer and length in wasm memory, vp and vl are for the value + fn kv_put(kp: i32, kl: i32, vp: i32, vl: i32) -> i32; + + fn print(p: i32, l: i32) -> i32; +} #[unsafe(no_mangle)] -pub unsafe extern "C" fn eval(s: *const u8, l: usize) -> u32 { - let src = std::slice::from_raw_parts(s, l); - let src = std::str::from_utf8(src).unwrap(); - Interpreter::without_stdlib(Default::default()).enter(|vm| { - let res = eval::eval(vm, src, vm.new_scope_with_builtins(), "").unwrap(); - res.try_into_value(vm).unwrap() - }) +pub unsafe extern "C" fn eval(s: *const u8, l: usize) -> i32 { + // let src = unsafe { std::slice::from_raw_parts(s, l) }; + // let src = std::str::from_utf8(src).unwrap(); + // TODO: use src + let src = "1 + 3"; + + // 2. Execute Python code + let interpreter = Interpreter::without_stdlib(Default::default()); + let result = interpreter.enter(|vm| { + let scope = vm.new_scope_with_builtins(); + let res = match vm.run_block_expr(scope, src) { + Ok(val) => val, + Err(_) => return Err(-1), // Python execution error + }; + let repr_str = match res.repr(vm) { + Ok(repr) => repr.as_str().to_string(), + Err(_) => return Err(-1), // Failed to get string representation + }; + Ok(repr_str) + }); + let result = match result { + Ok(r) => r, + Err(code) => return code, + }; + + let msg = format!("eval result: {result}"); + + unsafe { + print( + msg.as_str().as_ptr() as usize as i32, + msg.len() as i32, + ) + }; + + 0 } #[unsafe(no_mangle)] @@ -15,5 +52,8 @@ unsafe extern "Rust" fn __getrandom_v03_custom( _dest: *mut u8, _len: usize, ) -> Result<(), getrandom::Error> { - Err(getrandom::Error::UNSUPPORTED) + // Err(getrandom::Error::UNSUPPORTED) + + // WARNING: This function **MUST** perform proper getrandom + Ok(()) } diff --git a/example_projects/wasm32_without_js/wasm-runtime/.gitignore b/example_projects/wasm32_without_js/wasm-runtime/.gitignore new file mode 100644 index 00000000000..2e2101b5066 --- /dev/null +++ b/example_projects/wasm32_without_js/wasm-runtime/.gitignore @@ -0,0 +1,4 @@ +*.wasm +target +Cargo.lock +!wasm/rustpython.wasm diff --git a/example_projects/wasm32_without_js/wasm-runtime/Cargo.toml b/example_projects/wasm32_without_js/wasm-runtime/Cargo.toml new file mode 100644 index 00000000000..a1d0de51719 --- /dev/null +++ b/example_projects/wasm32_without_js/wasm-runtime/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "wasm-runtime" +version = "0.1.0" +edition = "2024" + +[dependencies] +wasmer = "6.1.0" + +[workspace] \ No newline at end of file diff --git a/example_projects/wasm32_without_js/wasm-runtime/README.md b/example_projects/wasm32_without_js/wasm-runtime/README.md new file mode 100644 index 00000000000..2fa2f9e119a --- /dev/null +++ b/example_projects/wasm32_without_js/wasm-runtime/README.md @@ -0,0 +1,19 @@ +# Simple WASM Runtime + +WebAssembly runtime POC with wasmer with HashMap-based KV store. +First make sure to install wat2wasm and rust. + +```bash +# following command installs rust +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh + +cargo run --release +``` + +## WASM binary requirements + +Entry point is `eval(code_ptr: i32, code_len: i32) -> i32`, following are exported functions, on error return -1: + +- `kv_put(key_ptr: i32, key_len: i32, val_ptr: i32, val_len: i32) -> i32` +- `kv_get(key_ptr: i32, key_len: i32, val_ptr: i32, val_len: i32) -> i32` +- `print(msg_ptr: i32, msg_len: i32) -> i32` diff --git a/example_projects/wasm32_without_js/wasm-runtime/src/main.rs b/example_projects/wasm32_without_js/wasm-runtime/src/main.rs new file mode 100644 index 00000000000..8ca7d581ce4 --- /dev/null +++ b/example_projects/wasm32_without_js/wasm-runtime/src/main.rs @@ -0,0 +1,133 @@ +use std::collections::HashMap; +use wasmer::{ + Function, FunctionEnv, FunctionEnvMut, Instance, Memory, Module, Store, Value, imports, +}; + +struct Ctx { + kv: HashMap, Vec>, + mem: Option, +} + +/// kp and kl are the key pointer and length in wasm memory, vp and vl are for the return value +/// if read value is bigger than vl then it will be truncated to vl, returns read bytes +fn kv_get(mut ctx: FunctionEnvMut, kp: i32, kl: i32, vp: i32, vl: i32) -> i32 { + let (c, s) = ctx.data_and_store_mut(); + let mut key = vec![0u8; kl as usize]; + if c.mem + .as_ref() + .unwrap() + .view(&s) + .read(kp as u64, &mut key) + .is_err() + { + return -1; + } + match c.kv.get(&key) { + Some(val) => { + let len = val.len().min(vl as usize); + if c.mem + .as_ref() + .unwrap() + .view(&s) + .write(vp as u64, &val[..len]) + .is_err() + { + return -1; + } + len as i32 + } + None => 0, + } +} + +/// kp and kl are the key pointer and length in wasm memory, vp and vl are for the value +fn kv_put(mut ctx: FunctionEnvMut, kp: i32, kl: i32, vp: i32, vl: i32) -> i32 { + let (c, s) = ctx.data_and_store_mut(); + let mut key = vec![0u8; kl as usize]; + let mut val = vec![0u8; vl as usize]; + let m = c.mem.as_ref().unwrap().view(&s); + if m.read(kp as u64, &mut key).is_err() || m.read(vp as u64, &mut val).is_err() { + return -1; + } + c.kv.insert(key, val); + 0 +} + +// // p and l are the buffer pointer and length in wasm memory. +// fn get_code(mut ctx:FunctionEnvMut, p: i32, l: i32) -> i32 { +// let file_name = std::env::args().nth(2).expect("file_name is not given"); +// let code : String = std::fs::read_to_string(file_name).expect("file read failed"); +// if code.len() > l as usize { +// eprintln!("code is too long"); +// return -1; +// } + +// let (c, s) = ctx.data_and_store_mut(); +// let m = c.mem.as_ref().unwrap().view(&s); +// if m.write(p as u64, code.as_bytes()).is_err() { +// return -2; +// } + +// 0 +// } + +// p and l are the message pointer and length in wasm memory. +fn print(mut ctx: FunctionEnvMut, p: i32, l: i32) -> i32 { + let (c, s) = ctx.data_and_store_mut(); + let mut msg = vec![0u8; l as usize]; + let m = c.mem.as_ref().unwrap().view(&s); + if m.read(p as u64, &mut msg).is_err() { + return -1; + } + let s = std::str::from_utf8(&msg).expect("print got non-utf8 str"); + println!("{s}"); + 0 +} + +fn main() { + let mut store = Store::default(); + let module = Module::new( + &store, + &std::fs::read(&std::env::args().nth(1).unwrap()).unwrap(), + ) + .unwrap(); + + // Prepare initial KV store with Python code + let mut initial_kv = HashMap::new(); + initial_kv.insert( + b"code".to_vec(), + b"a=10;b='str';f'{a}{b}'".to_vec(), // Python code to execute + ); + + let env = FunctionEnv::new( + &mut store, + Ctx { + kv: initial_kv, + mem: None, + }, + ); + let imports = imports! { + "env" => { + "kv_get" => Function::new_typed_with_env(&mut store, &env, kv_get), + "kv_put" => Function::new_typed_with_env(&mut store, &env, kv_put), + // "get_code" => Function::new_typed_with_env(&mut store, &env, get_code), + "print" => Function::new_typed_with_env(&mut store, &env, print), + } + }; + let inst = Instance::new(&mut store, &module, &imports).unwrap(); + env.as_mut(&mut store).mem = inst.exports.get_memory("memory").ok().cloned(); + let res = inst + .exports + .get_function("eval") + .unwrap() + // TODO: actually pass source code + .call(&mut store, &[wasmer::Value::I32(0), wasmer::Value::I32(0)]) + .unwrap(); + println!( + "Result: {}", + match res[0] { + Value::I32(v) => v, + _ => -1, + } + ); +} From 9134cca17b8f8059bcbd7de6cbed93544146060c Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Mon, 17 Nov 2025 20:38:19 +0900 Subject: [PATCH 3/3] Make CI to run rustpython-without-js test --- .github/workflows/ci.yaml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index fe45a1d71bb..95a1ac98d62 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -404,11 +404,13 @@ jobs: with: { wabt-version: "1.0.36" } - name: check wasm32-unknown without js run: | - cd wasm/wasm-unknown-test - cargo build --release --verbose - if wasm-objdump -xj Import target/wasm32-unknown-unknown/release/wasm_unknown_test.wasm; then - echo "ERROR: wasm32-unknown module expects imports from the host environment" >2 + cd example_projects/wasm32_without_js/rustpython-without-js + cargo build + cd .. + if wasm-objdump -xj Import rustpython-without-js/target/wasm32-unknown-unknown/debug/rustpython_without_js.wasm; then + echo "ERROR: wasm32-unknown module expects imports from the host environment" >&2 fi + cargo run --release --manifest-path wasm-runtime/Cargo.toml rustpython-without-js/target/wasm32-unknown-unknown/debug/rustpython_without_js.wasm - name: build notebook demo if: github.ref == 'refs/heads/release' run: |