Skip to content

Provide os module for wasm32-unknown-unknown target #7363

@youknowone

Description

@youknowone

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

  • Used in cpu_count()

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

  1. cargo check --target wasm32-unknown-unknown -p rustpython-vm --features "compiler,host_env" — must compile
  2. cargo test -p rustpython-vm on host — no regressions
  3. Update example_projects/wasm32_without_js/ to optionally enable host_env
  4. Runtime: build wasm32 binary with import os; print(os.name) — should print "posix" without error

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions