Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .cspell.dict/rust-more.txt
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ unsync
wasip1
wasip2
wasmbind
wasmer
wasmtime
widestring
winapi
Expand Down
10 changes: 6 additions & 4 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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: |
Expand Down
2 changes: 2 additions & 0 deletions example_projects/wasm32_without_js/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*/target/
*/Cargo.lock
18 changes: 18 additions & 0 deletions example_projects/wasm32_without_js/README.md
Original file line number Diff line number Diff line change
@@ -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
```

Original file line number Diff line number Diff line change
@@ -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]
Original file line number Diff line number Diff line change
@@ -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(())
}
4 changes: 4 additions & 0 deletions example_projects/wasm32_without_js/wasm-runtime/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
*.wasm
target
Cargo.lock
!wasm/rustpython.wasm
9 changes: 9 additions & 0 deletions example_projects/wasm32_without_js/wasm-runtime/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[package]
name = "wasm-runtime"
version = "0.1.0"
edition = "2024"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix invalid Rust edition.

Edition 2024 does not exist. The latest stable Rust edition is 2021. This will cause a compilation failure.

Apply this diff to fix:

-edition = "2024"
+edition = "2021"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
edition = "2024"
edition = "2021"
🤖 Prompt for AI Agents
In example_projects/wasm32_without_js/wasm-runtime/Cargo.toml around line 4, the
Cargo.toml incorrectly sets edition = "2024"; change it to the latest valid Rust
edition by replacing the value with "2021" so the crate compiles successfully.


[dependencies]
wasmer = "6.1.0"

[workspace]
19 changes: 19 additions & 0 deletions example_projects/wasm32_without_js/wasm-runtime/README.md
Original file line number Diff line number Diff line change
@@ -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`
133 changes: 133 additions & 0 deletions example_projects/wasm32_without_js/wasm-runtime/src/main.rs
Original file line number Diff line number Diff line change
@@ -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,
}
}
Comment on lines +13 to +41
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Replace unwrap() with proper error handling.

Lines 18 and 30 call unwrap() on c.mem.as_ref(), which will panic if the memory reference is None. While this should never happen if the module is correctly initialized, it creates a potential panic point.

Consider using expect() with a descriptive message or returning an error code:

 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)
+    let mem = c.mem.as_ref().expect("memory not initialized");
+    if mem.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)
+            if mem.view(&s)
                 .write(vp as u64, &val[..len])
                 .is_err()
             {
                 return -1;
             }
             len as i32
         }
         None => 0,
     }
 }
🤖 Prompt for AI Agents
In example_projects/wasm32_without_js/wasm-runtime/src/main.rs around lines 13
to 41, replace the two uses of c.mem.as_ref().unwrap() with safe error handling:
check whether c.mem.as_ref() is Some and if not return an appropriate error code
(e.g. -1) or use expect() with a clear message if you prefer a hard failure;
then use the unwrapped reference afterward (or bind it to a variable) so the
read/write calls operate on a guaranteed memory reference and avoid potential
panics.


/// 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
}
Comment on lines +44 to +54
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Replace unwrap() with proper error handling.

Line 48 calls unwrap() on c.mem.as_ref(), which creates a potential panic point if memory is not initialized.

Apply similar fix as suggested for kv_get:

 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);
+    let m = c.mem.as_ref().expect("memory not initialized").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
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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
}
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().expect("memory not initialized").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
}
🤖 Prompt for AI Agents
In example_projects/wasm32_without_js/wasm-runtime/src/main.rs around lines 44
to 54, replace the direct unwrap() on c.mem.as_ref() with safe handling: check
whether c.mem is Some and if not return an error code (e.g., -1) like kv_get
does; if Some(mem) obtain the memory view (let m = mem.view(&s)) and continue
with the existing reads and inserts, preserving the current read error checks
and returning -1 on failure and 0 on success.


// // 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
}
Comment on lines +75 to +85
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Improve error handling for memory access and UTF-8 validation.

Two issues:

  1. Line 78: unwrap() on c.mem.as_ref() creates a panic point
  2. Line 82: expect() will panic if the WASM module passes non-UTF8 data

Consider this more robust approach:

 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);
+    let m = c.mem.as_ref().expect("memory not initialized").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
+    match std::str::from_utf8(&msg) {
+        Ok(s) => {
+            println!("{s}");
+            0
+        }
+        Err(_) => {
+            eprintln!("print received non-UTF8 data");
+            -1
+        }
+    }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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 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().expect("memory not initialized").view(&s);
if m.read(p as u64, &mut msg).is_err() {
return -1;
}
match std::str::from_utf8(&msg) {
Ok(s) => {
println!("{s}");
0
}
Err(_) => {
eprintln!("print received non-UTF8 data");
-1
}
}
}


fn main() {
let mut store = Store::default();
let module = Module::new(
&store,
&std::fs::read(&std::env::args().nth(1).unwrap()).unwrap(),
)
.unwrap();
Comment on lines +87 to +93
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add command-line argument validation.

Line 91 calls nth(1).unwrap() without checking if the argument exists, which will panic if no WASM file path is provided. Consider adding proper error handling.

Apply this diff:

 fn main() {
+    let wasm_path = std::env::args()
+        .nth(1)
+        .expect("Usage: wasm-runtime <wasm_file_path>");
+    
     let mut store = Store::default();
-    let module = Module::new(
-        &store,
-        &std::fs::read(&std::env::args().nth(1).unwrap()).unwrap(),
-    )
-    .unwrap();
+    let wasm_bytes = std::fs::read(&wasm_path)
+        .expect("Failed to read WASM file");
+    let module = Module::new(&store, &wasm_bytes)
+        .expect("Failed to create WASM module");
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fn main() {
let mut store = Store::default();
let module = Module::new(
&store,
&std::fs::read(&std::env::args().nth(1).unwrap()).unwrap(),
)
.unwrap();
fn main() {
let wasm_path = std::env::args()
.nth(1)
.expect("Usage: wasm-runtime <wasm_file_path>");
let mut store = Store::default();
let wasm_bytes = std::fs::read(&wasm_path)
.expect("Failed to read WASM file");
let module = Module::new(&store, &wasm_bytes)
.expect("Failed to create WASM module");
🤖 Prompt for AI Agents
In example_projects/wasm32_without_js/wasm-runtime/src/main.rs around lines 87
to 93, the code calls std::env::args().nth(1).unwrap() which will panic if no
argument is provided; update the main function to validate the presence of the
first command-line argument, return a clear error or print a usage message and
exit when it is missing, and then use the validated path when reading the file
(e.g., check args().nth(1).ok_or_else(|| ...) or pattern-match and handle the
None case before calling std::fs::read and Module::new).


// 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,
}
);
}
15 changes: 0 additions & 15 deletions wasm/wasm-unknown-test/Cargo.toml

This file was deleted.

19 changes: 0 additions & 19 deletions wasm/wasm-unknown-test/src/lib.rs

This file was deleted.

Loading