diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index bec7eafa32..0f39ed8343 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -216,13 +216,6 @@ jobs: - name: Check compilation for freebsd run: cargo check --target x86_64-unknown-freebsd - - uses: dtolnay/rust-toolchain@stable - with: - target: wasm32-unknown-unknown - - - name: Check compilation for wasm32 - run: cargo check --target wasm32-unknown-unknown --no-default-features - - uses: dtolnay/rust-toolchain@stable with: target: x86_64-unknown-freebsd @@ -380,6 +373,14 @@ jobs: env: NODE_OPTIONS: "--openssl-legacy-provider" working-directory: ./wasm/demo + - uses: mwilliamson/setup-wabt-action@v1 + with: { wabt-version: "1.0.30" } + - name: check wasm32-unknown without js + run: | + cargo build --release --manifest-path wasm/wasm-unknown-test/Cargo.toml --target wasm32-unknown-unknown --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 + fi - name: build notebook demo if: github.ref == 'refs/heads/release' run: | diff --git a/Cargo.toml b/Cargo.toml index eec5d84a8f..283698f52c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -96,7 +96,8 @@ x86_64-pc-windows-msvc = { triplet = "x64-windows-static-md", dev-dependencies = resolver = "2" members = [ "compiler", "compiler/core", "compiler/codegen", - ".", "common", "derive", "jit", "vm", "vm/sre_engine", "pylib", "stdlib", "wasm/lib", "derive-impl", + ".", "common", "derive", "jit", "vm", "vm/sre_engine", "pylib", "stdlib", "derive-impl", + "wasm/lib", ] [workspace.package] @@ -146,6 +147,7 @@ cfg-if = "1.0" chrono = "0.4.37" crossbeam-utils = "0.8.19" flame = "0.2.2" +getrandom = "0.2.12" glob = "0.3" hex = "0.4.3" indexmap = { version = "2.2.6", features = ["std"] } @@ -178,6 +180,7 @@ thread_local = "1.1.4" unicode_names2 = "1.2.0" widestring = "1.1.0" windows-sys = "0.52.0" +wasm-bindgen = "0.2.92" # Lints diff --git a/stdlib/Cargo.toml b/stdlib/Cargo.toml index 485855d646..1e327c4e9d 100644 --- a/stdlib/Cargo.toml +++ b/stdlib/Cargo.toml @@ -22,7 +22,7 @@ ssl-vendor = ["ssl", "openssl/vendored", "openssl-probe"] [dependencies] # rustpython crates rustpython-derive = { workspace = true } -rustpython-vm = { workspace = true } +rustpython-vm = { workspace = true, default-features = false } rustpython-common = { workspace = true } ahash = { workspace = true } diff --git a/stdlib/src/dis.rs b/stdlib/src/dis.rs index d9322d4871..12c2ea75df 100644 --- a/stdlib/src/dis.rs +++ b/stdlib/src/dis.rs @@ -13,18 +13,23 @@ mod decl { let co = if let Ok(co) = obj.get_attr("__code__", vm) { // Method or function: PyRef::try_from_object(vm, co)? - } else if let Ok(_co_str) = PyStrRef::try_from_object(vm, obj.clone()) { + } else if let Ok(co_str) = PyStrRef::try_from_object(vm, obj.clone()) { #[cfg(not(feature = "compiler"))] - return Err(vm.new_runtime_error( - "dis.dis() with str argument requires `compiler` feature".to_owned(), - )); + { + let _ = co_str; + return Err(vm.new_runtime_error( + "dis.dis() with str argument requires `compiler` feature".to_owned(), + )); + } #[cfg(feature = "compiler")] - vm.compile( - _co_str.as_str(), - crate::vm::compiler::Mode::Exec, - "".to_owned(), - ) - .map_err(|err| vm.new_syntax_error(&err, Some(_co_str.as_str())))? + { + vm.compile( + co_str.as_str(), + crate::vm::compiler::Mode::Exec, + "".to_owned(), + ) + .map_err(|err| vm.new_syntax_error(&err, Some(co_str.as_str())))? + } } else { PyRef::try_from_object(vm, obj)? }; diff --git a/vm/Cargo.toml b/vm/Cargo.toml index 251d482ebe..78702bc8fd 100644 --- a/vm/Cargo.toml +++ b/vm/Cargo.toml @@ -10,7 +10,7 @@ repository.workspace = true license.workspace = true [features] -default = ["compiler"] +default = ["compiler", "wasmbind"] importlib = [] encodings = ["importlib"] vm-tracing-logging = [] @@ -23,6 +23,7 @@ ast = ["rustpython-ast"] codegen = ["rustpython-codegen", "ast"] parser = ["rustpython-parser", "ast"] serde = ["dep:serde"] +wasmbind = ["chrono/wasmbind", "getrandom/js", "wasm-bindgen"] [dependencies] rustpython-compiler = { workspace = true, optional = true } @@ -48,6 +49,7 @@ cfg-if = { workspace = true } crossbeam-utils = { workspace = true } chrono = { workspace = true, features = ["wasmbind"] } flame = { workspace = true, optional = true } +getrandom = { workspace = true } hex = { workspace = true } indexmap = { workspace = true } itertools = { workspace = true } @@ -71,7 +73,6 @@ thread_local = { workspace = true } memchr = { workspace = true } caseless = "0.2.1" -getrandom = { version = "0.2.12", features = ["js"] } flamer = { version = "0.4", optional = true } half = "1.8.2" memoffset = "0.9.1" @@ -140,8 +141,9 @@ features = [ "Win32_UI_WindowsAndMessaging", ] -[target.'cfg(target_arch = "wasm32")'.dependencies] -wasm-bindgen = "0.2.92" +[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies] +wasm-bindgen = { workspace = true, optional = true } +getrandom = { workspace = true, features = ["custom"] } [build-dependencies] glob = { workspace = true } diff --git a/vm/src/stdlib/time.rs b/vm/src/stdlib/time.rs index d026f2953e..8ef67bacec 100644 --- a/vm/src/stdlib/time.rs +++ b/vm/src/stdlib/time.rs @@ -97,12 +97,19 @@ mod decl { _time(vm) } - #[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))] + #[cfg(not(all( + target_arch = "wasm32", + not(any(target_os = "emscripten", target_os = "wasi")), + )))] fn _time(vm: &VirtualMachine) -> PyResult { Ok(duration_since_system_now(vm)?.as_secs_f64()) } - #[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] + #[cfg(all( + target_arch = "wasm32", + feature = "wasmbind", + not(any(target_os = "emscripten", target_os = "wasi")) + ))] fn _time(_vm: &VirtualMachine) -> PyResult { use wasm_bindgen::prelude::*; #[wasm_bindgen] @@ -115,6 +122,15 @@ mod decl { Ok(Date::now() / 1000.0) } + #[cfg(all( + target_arch = "wasm32", + not(feature = "wasmbind"), + not(any(target_os = "emscripten", target_os = "wasi")) + ))] + fn _time(vm: &VirtualMachine) -> PyResult { + Err(vm.new_not_implemented_error("time.time".to_owned())) + } + #[pyfunction] fn monotonic(vm: &VirtualMachine) -> PyResult { Ok(get_monotonic_time(vm)?.as_secs_f64()) diff --git a/vm/src/vm/vm_object.rs b/vm/src/vm/vm_object.rs index e3d1436882..b687eea34c 100644 --- a/vm/src/vm/vm_object.rs +++ b/vm/src/vm/vm_object.rs @@ -13,13 +13,21 @@ impl VirtualMachine { #[track_caller] #[cold] fn _py_panic_failed(&self, exc: PyBaseExceptionRef, msg: &str) -> ! { - #[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"))))] + #[cfg(not(all( + target_arch = "wasm32", + not(any(target_os = "emscripten", target_os = "wasi")), + )))] { self.print_exception(exc); self.flush_std(); panic!("{msg}") } - #[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] + #[cfg(all( + target_arch = "wasm32", + feature = "wasmbind", + not(any(target_os = "emscripten", target_os = "wasi")), + ))] + #[cfg(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind"))] { use wasm_bindgen::prelude::*; #[wasm_bindgen] @@ -32,6 +40,17 @@ impl VirtualMachine { error(&s); panic!("{}; exception backtrace above", msg) } + #[cfg(all( + target_arch = "wasm32", + not(feature = "wasmbind"), + not(any(target_os = "emscripten", target_os = "wasi")), + ))] + { + use crate::convert::ToPyObject; + let err_string: String = exc.to_pyobject(self).repr(self).unwrap().to_string(); + eprintln!("{err_string}"); + panic!("{}; python exception not available", msg) + } } pub(crate) fn flush_std(&self) { diff --git a/wasm/lib/Cargo.toml b/wasm/lib/Cargo.toml index 9b71bbce25..1e5c37f4ef 100644 --- a/wasm/lib/Cargo.toml +++ b/wasm/lib/Cargo.toml @@ -21,16 +21,16 @@ rustpython-common = { workspace = true } rustpython-pylib = { workspace = true, optional = true } rustpython-stdlib = { workspace = true, default-features = false, optional = true } # make sure no threading! otherwise wasm build will fail -rustpython-vm = { workspace = true, features = ["compiler", "encodings", "serde"] } +rustpython-vm = { workspace = true, features = ["compiler", "encodings", "serde", "wasmbind"] } rustpython-parser = { workspace = true } serde = { workspace = true } +wasm-bindgen = { workspace = true } console_error_panic_hook = "0.1" js-sys = "0.3" serde-wasm-bindgen = "0.3.1" -wasm-bindgen = "0.2.80" wasm-bindgen-futures = "0.4" web-sys = { version = "0.3", features = [ "console", diff --git a/wasm/wasm-unknown-test/Cargo.toml b/wasm/wasm-unknown-test/Cargo.toml new file mode 100644 index 0000000000..f5e0b55786 --- /dev/null +++ b/wasm/wasm-unknown-test/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "wasm-unknown-test" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +getrandom = { version = "0.2.12", features = ["custom"] } +rustpython-vm = { path = "../../vm", default-features = false, features = ["compiler"] } + +[workspace] diff --git a/wasm/wasm-unknown-test/README.md b/wasm/wasm-unknown-test/README.md new file mode 100644 index 0000000000..3c4abd59cf --- /dev/null +++ b/wasm/wasm-unknown-test/README.md @@ -0,0 +1 @@ +A test crate to ensure that `rustpython-vm` compiles on `wasm32-unknown-unknown` without a JS host. diff --git a/wasm/wasm-unknown-test/src/lib.rs b/wasm/wasm-unknown-test/src/lib.rs new file mode 100644 index 0000000000..fd043aea3a --- /dev/null +++ b/wasm/wasm-unknown-test/src/lib.rs @@ -0,0 +1,16 @@ +use rustpython_vm::{eval, Interpreter}; + +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() + }) +} + +fn getrandom_always_fail(_buf: &mut [u8]) -> Result<(), getrandom::Error> { + Err(getrandom::Error::UNSUPPORTED) +} + +getrandom::register_custom_getrandom!(getrandom_always_fail);