Skip to content

Commit eac8968

Browse files
youknowonevalentynfaychukmoreal
committed
Add wasm runtime and fix the example code to actually run
Co-Authored-By: Valentyn Faychuk <[email protected]> Co-Authored-By: Lee Dogeon <[email protected]>
1 parent 5fb5db9 commit eac8968

File tree

9 files changed

+237
-11
lines changed

9 files changed

+237
-11
lines changed

.cspell.dict/rust-more.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ unsync
8282
wasip1
8383
wasip2
8484
wasmbind
85+
wasmer
8586
wasmtime
8687
widestring
8788
winapi
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
*/target/
2+
*/Cargo.lock
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# RustPython wasm32 build without JS
2+
3+
To test, build rustpython to wasm32-unknown-unknown target first.
4+
5+
```shell
6+
cd rustpython-without-js # due to `.cargo/config.toml`
7+
cargo build
8+
cd ..
9+
```
10+
11+
Then there will be `rustpython-without-js/target/wasm32-unknown-unknown/debug/rustpython_without_js.wasm` file.
12+
13+
Now we can run the wasm file with wasm runtime:
14+
15+
```shell
16+
cargo run --release --manifest-path wasm-runtime/Cargo.toml rustpython-without-js/target/wasm32-unknown-unknown/debug/rustpython_without_js.wasm
17+
```
18+

example_projects/wasm32_without_js/rustpython-without-js/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
[package]
22
name = "rustpython-without-js"
33
version = "0.1.0"
4-
edition = "2021"
4+
edition = "2024"
55

66
[lib]
77
crate-type = ["cdylib"]
88

99
[dependencies]
1010
getrandom = "0.3"
11-
rustpython-vm = { path = "../../crates/vm", default-features = false, features = ["compiler"] }
11+
rustpython-vm = { path = "../../../crates/vm", default-features = false, features = ["compiler"] }
1212

1313
[workspace]
1414

Lines changed: 49 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,59 @@
1-
use rustpython_vm::{Interpreter, eval};
1+
use rustpython_vm::{Interpreter};
2+
3+
unsafe extern "C" {
4+
fn kv_get(kp: i32, kl: i32, vp: i32, vl: i32) -> i32;
5+
6+
/// kp and kl are the key pointer and length in wasm memory, vp and vl are for the value
7+
fn kv_put(kp: i32, kl: i32, vp: i32, vl: i32) -> i32;
8+
9+
fn print(p: i32, l: i32) -> i32;
10+
}
211

312
#[unsafe(no_mangle)]
4-
pub unsafe extern "C" fn eval(s: *const u8, l: usize) -> u32 {
5-
let src = std::slice::from_raw_parts(s, l);
6-
let src = std::str::from_utf8(src).unwrap();
7-
Interpreter::without_stdlib(Default::default()).enter(|vm| {
8-
let res = eval::eval(vm, src, vm.new_scope_with_builtins(), "<string>").unwrap();
9-
res.try_into_value(vm).unwrap()
10-
})
13+
pub unsafe extern "C" fn eval(s: *const u8, l: usize) -> i32 {
14+
// let src = unsafe { std::slice::from_raw_parts(s, l) };
15+
// let src = std::str::from_utf8(src).unwrap();
16+
// TODO: use src
17+
let src = "1 + 3";
18+
19+
// 2. Execute Python code
20+
let interpreter = Interpreter::without_stdlib(Default::default());
21+
let result = interpreter.enter(|vm| {
22+
let scope = vm.new_scope_with_builtins();
23+
let res = match vm.run_block_expr(scope, src) {
24+
Ok(val) => val,
25+
Err(_) => return Err(-1), // Python execution error
26+
};
27+
let repr_str = match res.repr(vm) {
28+
Ok(repr) => repr.as_str().to_string(),
29+
Err(_) => return Err(-1), // Failed to get string representation
30+
};
31+
Ok(repr_str)
32+
});
33+
let result = match result {
34+
Ok(r) => r,
35+
Err(code) => return code,
36+
};
37+
38+
let msg = format!("eval result: {result}");
39+
40+
unsafe {
41+
print(
42+
msg.as_str().as_ptr() as usize as i32,
43+
msg.len() as i32,
44+
)
45+
};
46+
47+
0
1148
}
1249

1350
#[unsafe(no_mangle)]
1451
unsafe extern "Rust" fn __getrandom_v03_custom(
1552
_dest: *mut u8,
1653
_len: usize,
1754
) -> Result<(), getrandom::Error> {
18-
Err(getrandom::Error::UNSUPPORTED)
55+
// Err(getrandom::Error::UNSUPPORTED)
56+
57+
// WARNING: This function **MUST** perform proper getrandom
58+
Ok(())
1959
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
*.wasm
2+
target
3+
Cargo.lock
4+
!wasm/rustpython.wasm
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[package]
2+
name = "wasm-runtime"
3+
version = "0.1.0"
4+
edition = "2024"
5+
6+
[dependencies]
7+
wasmer = "6.1.0"
8+
9+
[workspace]
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Simple WASM Runtime
2+
3+
WebAssembly runtime POC with wasmer with HashMap-based KV store.
4+
First make sure to install wat2wasm and rust.
5+
6+
```bash
7+
# following command installs rust
8+
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
9+
10+
cargo run --release <wasm binary>
11+
```
12+
13+
## WASM binary requirements
14+
15+
Entry point is `eval(code_ptr: i32, code_len: i32) -> i32`, following are exported functions, on error return -1:
16+
17+
- `kv_put(key_ptr: i32, key_len: i32, val_ptr: i32, val_len: i32) -> i32`
18+
- `kv_get(key_ptr: i32, key_len: i32, val_ptr: i32, val_len: i32) -> i32`
19+
- `print(msg_ptr: i32, msg_len: i32) -> i32`
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
use std::collections::HashMap;
2+
use wasmer::{
3+
Function, FunctionEnv, FunctionEnvMut, Instance, Memory, Module, Store, Value, imports,
4+
};
5+
6+
struct Ctx {
7+
kv: HashMap<Vec<u8>, Vec<u8>>,
8+
mem: Option<Memory>,
9+
}
10+
11+
/// kp and kl are the key pointer and length in wasm memory, vp and vl are for the return value
12+
/// if read value is bigger than vl then it will be truncated to vl, returns read bytes
13+
fn kv_get(mut ctx: FunctionEnvMut<Ctx>, kp: i32, kl: i32, vp: i32, vl: i32) -> i32 {
14+
let (c, s) = ctx.data_and_store_mut();
15+
let mut key = vec![0u8; kl as usize];
16+
if c.mem
17+
.as_ref()
18+
.unwrap()
19+
.view(&s)
20+
.read(kp as u64, &mut key)
21+
.is_err()
22+
{
23+
return -1;
24+
}
25+
match c.kv.get(&key) {
26+
Some(val) => {
27+
let len = val.len().min(vl as usize);
28+
if c.mem
29+
.as_ref()
30+
.unwrap()
31+
.view(&s)
32+
.write(vp as u64, &val[..len])
33+
.is_err()
34+
{
35+
return -1;
36+
}
37+
len as i32
38+
}
39+
None => 0,
40+
}
41+
}
42+
43+
/// kp and kl are the key pointer and length in wasm memory, vp and vl are for the value
44+
fn kv_put(mut ctx: FunctionEnvMut<Ctx>, kp: i32, kl: i32, vp: i32, vl: i32) -> i32 {
45+
let (c, s) = ctx.data_and_store_mut();
46+
let mut key = vec![0u8; kl as usize];
47+
let mut val = vec![0u8; vl as usize];
48+
let m = c.mem.as_ref().unwrap().view(&s);
49+
if m.read(kp as u64, &mut key).is_err() || m.read(vp as u64, &mut val).is_err() {
50+
return -1;
51+
}
52+
c.kv.insert(key, val);
53+
0
54+
}
55+
56+
// // p and l are the buffer pointer and length in wasm memory.
57+
// fn get_code(mut ctx:FunctionEnvMut<Ctx>, p: i32, l: i32) -> i32 {
58+
// let file_name = std::env::args().nth(2).expect("file_name is not given");
59+
// let code : String = std::fs::read_to_string(file_name).expect("file read failed");
60+
// if code.len() > l as usize {
61+
// eprintln!("code is too long");
62+
// return -1;
63+
// }
64+
65+
// let (c, s) = ctx.data_and_store_mut();
66+
// let m = c.mem.as_ref().unwrap().view(&s);
67+
// if m.write(p as u64, code.as_bytes()).is_err() {
68+
// return -2;
69+
// }
70+
71+
// 0
72+
// }
73+
74+
// p and l are the message pointer and length in wasm memory.
75+
fn print(mut ctx: FunctionEnvMut<Ctx>, p: i32, l: i32) -> i32 {
76+
let (c, s) = ctx.data_and_store_mut();
77+
let mut msg = vec![0u8; l as usize];
78+
let m = c.mem.as_ref().unwrap().view(&s);
79+
if m.read(p as u64, &mut msg).is_err() {
80+
return -1;
81+
}
82+
let s = std::str::from_utf8(&msg).expect("print got non-utf8 str");
83+
println!("{s}");
84+
0
85+
}
86+
87+
fn main() {
88+
let mut store = Store::default();
89+
let module = Module::new(
90+
&store,
91+
&std::fs::read(&std::env::args().nth(1).unwrap()).unwrap(),
92+
)
93+
.unwrap();
94+
95+
// Prepare initial KV store with Python code
96+
let mut initial_kv = HashMap::new();
97+
initial_kv.insert(
98+
b"code".to_vec(),
99+
b"a=10;b='str';f'{a}{b}'".to_vec(), // Python code to execute
100+
);
101+
102+
let env = FunctionEnv::new(
103+
&mut store,
104+
Ctx {
105+
kv: initial_kv,
106+
mem: None,
107+
},
108+
);
109+
let imports = imports! {
110+
"env" => {
111+
"kv_get" => Function::new_typed_with_env(&mut store, &env, kv_get),
112+
"kv_put" => Function::new_typed_with_env(&mut store, &env, kv_put),
113+
// "get_code" => Function::new_typed_with_env(&mut store, &env, get_code),
114+
"print" => Function::new_typed_with_env(&mut store, &env, print),
115+
}
116+
};
117+
let inst = Instance::new(&mut store, &module, &imports).unwrap();
118+
env.as_mut(&mut store).mem = inst.exports.get_memory("memory").ok().cloned();
119+
let res = inst
120+
.exports
121+
.get_function("eval")
122+
.unwrap()
123+
// TODO: actually pass source code
124+
.call(&mut store, &[wasmer::Value::I32(0), wasmer::Value::I32(0)])
125+
.unwrap();
126+
println!(
127+
"Result: {}",
128+
match res[0] {
129+
Value::I32(v) => v,
130+
_ => -1,
131+
}
132+
);
133+
}

0 commit comments

Comments
 (0)