Summary
Currently, RustPython does not provide the os module for wasm32-unknown-unknown. The module is gated behind the host_env feature, and the wasm32 example project disables it (default-features = false, features = ["compiler"]). However, CPython's Emscripten build provides an os module with a reduced function set, and many Python packages do import os at the top level. Without the os module, even os.path.join() (pure Python) fails with ImportError.
Motivation
- Many Python packages unconditionally
import os at the top level — they currently fail entirely on wasm32-unknown-unknown
os.path functions (join, dirname, basename, etc.) are pure Python and would work fine if the module existed
- Constants like
os.sep, os.name, os.O_RDONLY are useful even without real I/O
- Functions like
os.fspath(), os.getpid(), os.strerror() can return sensible values
- CPython's Emscripten build takes this approach — provide the module, let unsupported operations raise
OSError at runtime
Problem Analysis
The os module (specifically _os in os.rs) fails to compile on wasm32-unknown-unknown due to several dependencies:
1. crt_fd.rs depends on libc
mod c { pub(super) use libc::*; } — libc exports nothing for wasm32-unknown-unknown
- Functions like
c::open(), c::close(), c::read(), c::write(), c::fsync(), c::ftruncate() don't exist
- Constants like
c::EBADF, c::off_t don't exist
- Currently gated:
#[cfg(all(feature = "std", any(unix, windows, target_os = "wasi")))] in lib.rs
2. fileutils.rs depends on libc::stat
pub use libc::stat as StatStruct on non-windows — no libc::stat on wasm32
fstat() uses libc::fstat() — doesn't exist
- Currently gated:
#[cfg(all(feature = "std", any(not(target_arch = "wasm32"), target_os = "wasi")))]
3. os.rs uses libc directly
#[pyattr] use libc::{O_APPEND, O_CREAT, O_EXCL, O_RDONLY, O_RDWR, O_TRUNC, O_WRONLY} — empty on wasm32
libc::EINTR in read/write retry loops
libc::isatty() in isatty()
libc::strerror() in strerror()
libc::lseek() in lseek()
libc::nl_langinfo() in device_encoding()
libc::stat/lstat/fstat/fstatat in stat_inner()
4. num_cpus crate — not available for wasm32-unknown-unknown
5. os_open() is gated behind cfg(any(unix, windows, target_os = "wasi"))
Proposed Solution
Approach: Compile-only support — the os module compiles and is importable on wasm32-unknown-unknown. Functions that can't work on wasm32 will raise OSError at runtime (since std::fs returns errors). Constants and path functions work normally.
Files to Modify
crates/common/src/lib.rs
Expand cfg gates to include wasm32-unknown-unknown:
// crt_fd: include wasm32
#[cfg(all(feature = "std", any(unix, windows, target_os = "wasi", target_arch = "wasm32")))]
pub mod crt_fd;
// fileutils: remove wasm32 exclusion
#[cfg(feature = "std")]
pub mod fileutils;
crates/common/src/crt_fd.rs
Add a wasm32-specific mod c block (similar to the existing Windows-specific sections):
#[cfg(all(target_arch = "wasm32", not(any(unix, windows, target_os = "wasi"))))]
mod c {
pub type off_t = i64;
pub type c_int = i32;
pub type c_char = i8;
pub const EBADF: c_int = 9;
// Stub functions that always return -1
pub unsafe fn open(_: *const c_char, _: c_int, _: c_int) -> c_int { -1 }
pub unsafe fn close(_: c_int) -> c_int { -1 }
pub unsafe fn read(_: c_int, _: *mut u8, _: usize) -> isize { -1 }
pub unsafe fn write(_: c_int, _: *const u8, _: usize) -> isize { -1 }
pub unsafe fn fsync(_: c_int) -> c_int { -1 }
pub unsafe fn ftruncate(_: c_int, _: off_t) -> c_int { -1 }
}
std::os::fd types (OwnedFd, BorrowedFd, RawFd) are available on wasm32 since Rust 1.84, so the existing #[cfg(not(windows))] imports work.
crates/common/src/fileutils.rs
Add wasm32-specific StatStruct and fstat stub:
#[cfg(not(any(unix, windows, target_os = "wasi")))]
pub struct StatStruct { /* minimal fields matching libc::stat layout */ }
#[cfg(not(any(unix, windows, target_os = "wasi")))]
pub fn fstat(_: crate::crt_fd::Borrowed<'_>) -> std::io::Result<StatStruct> {
Err(std::io::Error::new(std::io::ErrorKind::Unsupported, "fstat not supported"))
}
crates/vm/src/stdlib/os.rs
~10 changes needed:
- O_* constants: Define manually for wasm32 (
O_RDONLY=0, O_WRONLY=1, O_RDWR=2, etc.)
open/os_open: Add wasm32 path returning error
read/write: Replace libc::EINTR with cfg-gated constant
isatty: Return false on wasm32
strerror: Basic error-number-to-string mapping
lseek: Return error on wasm32
stat_inner: Use std::fs::metadata or return error
StatResultData::from_stat: Handle wasm32 StatStruct fields
cpu_count: Return 1 on wasm32
device_encoding: Return "UTF-8" on wasm32
utime_impl: Return "not supported" error on wasm32
crates/vm/src/stdlib/posix_compat.rs
Add environ for wasm32 (currently only defined for target_os = "wasi"):
#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
#[pyattr]
fn environ(vm: &VirtualMachine) -> crate::builtins::PyDictRef {
vm.ctx.new_dict() // Empty dict — no env vars on bare wasm32
}
Cfg Pattern
The recurring condition is:
#[cfg(all(target_arch = "wasm32", not(any(unix, windows, target_os = "wasi"))))]
This matches wasm32-unknown-unknown specifically (not emscripten, not wasi, not windows).
Verification Plan
cargo check --target wasm32-unknown-unknown -p rustpython-vm --features "compiler,host_env" — must compile
cargo test -p rustpython-vm on host — no regressions
- Update
example_projects/wasm32_without_js/ to optionally enable host_env
- Runtime: build wasm32 binary with
import os; print(os.name) — should print "posix" without error
References
Summary
Currently, RustPython does not provide the
osmodule forwasm32-unknown-unknown. The module is gated behind thehost_envfeature, and the wasm32 example project disables it (default-features = false, features = ["compiler"]). However, CPython's Emscripten build provides anosmodule with a reduced function set, and many Python packages doimport osat the top level. Without theosmodule, evenos.path.join()(pure Python) fails withImportError.Motivation
import osat the top level — they currently fail entirely on wasm32-unknown-unknownos.pathfunctions (join,dirname,basename, etc.) are pure Python and would work fine if the module existedos.sep,os.name,os.O_RDONLYare useful even without real I/Oos.fspath(),os.getpid(),os.strerror()can return sensible valuesOSErrorat runtimeProblem Analysis
The
osmodule (specifically_osinos.rs) fails to compile onwasm32-unknown-unknowndue to several dependencies:1.
crt_fd.rsdepends onlibcmod c { pub(super) use libc::*; }—libcexports nothing forwasm32-unknown-unknownc::open(),c::close(),c::read(),c::write(),c::fsync(),c::ftruncate()don't existc::EBADF,c::off_tdon't exist#[cfg(all(feature = "std", any(unix, windows, target_os = "wasi")))]inlib.rs2.
fileutils.rsdepends onlibc::statpub use libc::stat as StatStructon non-windows — nolibc::staton wasm32fstat()useslibc::fstat()— doesn't exist#[cfg(all(feature = "std", any(not(target_arch = "wasm32"), target_os = "wasi")))]3.
os.rsuseslibcdirectly#[pyattr] use libc::{O_APPEND, O_CREAT, O_EXCL, O_RDONLY, O_RDWR, O_TRUNC, O_WRONLY}— empty on wasm32libc::EINTRin read/write retry loopslibc::isatty()inisatty()libc::strerror()instrerror()libc::lseek()inlseek()libc::nl_langinfo()indevice_encoding()libc::stat/lstat/fstat/fstatatinstat_inner()4.
num_cpuscrate — not available forwasm32-unknown-unknowncpu_count()5.
os_open()is gated behindcfg(any(unix, windows, target_os = "wasi"))Proposed Solution
Approach: Compile-only support — the
osmodule compiles and is importable on wasm32-unknown-unknown. Functions that can't work on wasm32 will raiseOSErrorat runtime (sincestd::fsreturns errors). Constants and path functions work normally.Files to Modify
crates/common/src/lib.rsExpand cfg gates to include wasm32-unknown-unknown:
crates/common/src/crt_fd.rsAdd a wasm32-specific
mod cblock (similar to the existing Windows-specific sections):std::os::fdtypes (OwnedFd,BorrowedFd,RawFd) are available on wasm32 since Rust 1.84, so the existing#[cfg(not(windows))]imports work.crates/common/src/fileutils.rsAdd wasm32-specific
StatStructandfstatstub:crates/vm/src/stdlib/os.rs~10 changes needed:
O_RDONLY=0,O_WRONLY=1,O_RDWR=2, etc.)open/os_open: Add wasm32 path returning errorread/write: Replacelibc::EINTRwith cfg-gated constantisatty: Returnfalseon wasm32strerror: Basic error-number-to-string mappinglseek: Return error on wasm32stat_inner: Usestd::fs::metadataor return errorStatResultData::from_stat: Handle wasm32 StatStruct fieldscpu_count: Return 1 on wasm32device_encoding: Return "UTF-8" on wasm32utime_impl: Return "not supported" error on wasm32crates/vm/src/stdlib/posix_compat.rsAdd
environfor wasm32 (currently only defined fortarget_os = "wasi"):Cfg Pattern
The recurring condition is:
#[cfg(all(target_arch = "wasm32", not(any(unix, windows, target_os = "wasi"))))]This matches
wasm32-unknown-unknownspecifically (not emscripten, not wasi, not windows).Verification Plan
cargo check --target wasm32-unknown-unknown -p rustpython-vm --features "compiler,host_env"— must compilecargo test -p rustpython-vmon host — no regressionsexample_projects/wasm32_without_js/to optionally enablehost_envimport os; print(os.name)— should print"posix"without errorReferences
std::os::fdportable stabilization (Rust 1.84): std::os::fd module missing on Hermit rust-lang/rust#126198libccrate wasm32-unknown-unknown: exports nothing — https://docs.rs/libc/latest/libc/