Merge pull request #6283 from youknowone/test-wasm32-without-js

Test wasm32 without js
This commit is contained in:
Jeong, YunWon
2025-11-17 22:10:53 +09:00
committed by GitHub
14 changed files with 266 additions and 38 deletions

View File

@@ -82,6 +82,7 @@ unsync
wasip1
wasip2
wasmbind
wasmer
wasmtime
widestring
winapi

View File

@@ -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: |

View File

@@ -0,0 +1,2 @@
*/target/
*/Cargo.lock

View File

@@ -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
```

View File

@@ -0,0 +1,15 @@
[package]
name = "rustpython-without-js"
version = "0.1.0"
edition = "2024"
[lib]
crate-type = ["cdylib"]
[dependencies]
getrandom = "0.3"
rustpython-vm = { path = "../../../crates/vm", default-features = false, features = ["compiler"] }
[workspace]
[patch.crates-io]

View File

@@ -0,0 +1,59 @@
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) -> 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)]
unsafe extern "Rust" fn __getrandom_v03_custom(
_dest: *mut u8,
_len: usize,
) -> Result<(), getrandom::Error> {
// Err(getrandom::Error::UNSUPPORTED)
// WARNING: This function **MUST** perform proper getrandom
Ok(())
}

View File

@@ -0,0 +1,4 @@
*.wasm
target
Cargo.lock
!wasm/rustpython.wasm

View File

@@ -0,0 +1,9 @@
[package]
name = "wasm-runtime"
version = "0.1.0"
edition = "2024"
[dependencies]
wasmer = "6.1.0"
[workspace]

View File

@@ -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>
```
## 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`

View File

@@ -0,0 +1,133 @@
use std::collections::HashMap;
use wasmer::{
Function, FunctionEnv, FunctionEnvMut, Instance, Memory, Module, Store, Value, imports,
};
struct Ctx {
kv: HashMap<Vec<u8>, Vec<u8>>,
mem: Option<Memory>,
}
/// 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<Ctx>, 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<Ctx>, 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<Ctx>, 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<Ctx>, 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,
}
);
}

View File

@@ -1,15 +0,0 @@
[package]
name = "wasm-unknown-test"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
getrandom = "0.3"
rustpython-vm = { path = "../../crates/vm", default-features = false, features = ["compiler"] }
[workspace]
[patch.crates-io]

View File

@@ -1,19 +0,0 @@
use rustpython_vm::{Interpreter, eval};
#[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(), "<string>").unwrap();
res.try_into_value(vm).unwrap()
})
}
#[unsafe(no_mangle)]
unsafe extern "Rust" fn __getrandom_v03_custom(
_dest: *mut u8,
_len: usize,
) -> Result<(), getrandom::Error> {
Err(getrandom::Error::UNSUPPORTED)
}