mirror of
https://github.com/RustPython/RustPython.git
synced 2026-06-02 19:39:49 +09:00
Merge pull request #6283 from youknowone/test-wasm32-without-js
Test wasm32 without js
This commit is contained in:
@@ -82,6 +82,7 @@ unsync
|
||||
wasip1
|
||||
wasip2
|
||||
wasmbind
|
||||
wasmer
|
||||
wasmtime
|
||||
widestring
|
||||
winapi
|
||||
|
||||
10
.github/workflows/ci.yaml
vendored
10
.github/workflows/ci.yaml
vendored
@@ -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: |
|
||||
|
||||
2
example_projects/wasm32_without_js/.gitignore
vendored
Normal file
2
example_projects/wasm32_without_js/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
*/target/
|
||||
*/Cargo.lock
|
||||
18
example_projects/wasm32_without_js/README.md
Normal file
18
example_projects/wasm32_without_js/README.md
Normal 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
|
||||
```
|
||||
|
||||
@@ -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]
|
||||
@@ -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
example_projects/wasm32_without_js/wasm-runtime/.gitignore
vendored
Normal file
4
example_projects/wasm32_without_js/wasm-runtime/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
*.wasm
|
||||
target
|
||||
Cargo.lock
|
||||
!wasm/rustpython.wasm
|
||||
@@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "wasm-runtime"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
wasmer = "6.1.0"
|
||||
|
||||
[workspace]
|
||||
19
example_projects/wasm32_without_js/wasm-runtime/README.md
Normal file
19
example_projects/wasm32_without_js/wasm-runtime/README.md
Normal 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`
|
||||
133
example_projects/wasm32_without_js/wasm-runtime/src/main.rs
Normal file
133
example_projects/wasm32_without_js/wasm-runtime/src/main.rs
Normal 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,
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -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]
|
||||
@@ -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)
|
||||
}
|
||||
Reference in New Issue
Block a user