Browser memory64: guest addresses to the global kernel are 64-bit (PAddr); the high arena

- gex_kernel::PAddr (and gex_kmem::PAddr): the address type of the process
  memory as it crosses between a process's shim and the global kernel module
  (the remote pipe/unix/pty/tty/vfs ops, the shim's k_* imports and rf_*/rp_*
  wrappers, the kernel's k_* exports). u64 in the wasm64 shim and in the
  kernel built with process64; u32 everywhere else. The 32-bit kernel's code
  is byte-identical (names aside, wasm-tools strip); the 32-bit shim's
  imports and exports keep their types.
- The wasm64 shim: its guest slice spans LIMIT64, and backing a high-arena
  mapping grows the memory without moving the low arena's cursor (which
  the shim's heap shares, and whose addresses cross to JS as u32).
- build.sh checks the wasm64 shim against gex_kernel.x86.m64.wasm.
- tests/x86-programs/mem64-io.c: a file, a pipe and a socketpair through
  buffers above 4 GiB. Under node on the wasm64 shim it and mem64-reserve
  match native; tests/nt browser 49/49 and gex-kernel's tests pass.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01PeRutwFgZ66XpxjasKrPn1
This commit is contained in:
velzieandClaude Opus 5.5 committed 2026-10-05 00:55:45 -04:00
1 parent be50f1cb9e
commit 06e6b95b21
15 files changed
+333 -215

No files matched your search

+3
View File
@@ -20,3 +20,6 @@ gex-gl.workspace = true
x86 = ["gex-abi/x86"]
# Serve a 32-bit big-endian PowerPC guest (docs/ppc.md).
ppc = ["gex-abi/ppc"]
# The global kernel module serving processes with a 64-bit memory (the
# wasm64 browser shim, docs/mem64.md step 6): `PAddr` is u64.
process64 = []
+10
View File
@@ -11,6 +11,16 @@
#![forbid(unsafe_code)]
/// An address in the calling process's memory as it crosses between a
/// process's shim and the global kernel module (the remote ops below and the
/// browser's `k_*` imports). 64-bit where that memory is a memory64: the
/// wasm64 browser shim and the kernel module built to serve it
/// (docs/mem64.md step 6). Everywhere else 32-bit, as it always was.
#[cfg(any(target_arch = "wasm64", feature = "process64"))]
pub type PAddr = u64;
#[cfg(not(any(target_arch = "wasm64", feature = "process64")))]
pub type PAddr = u32;
pub mod errno;
pub mod eventfd;
pub mod file;
+6 -6
View File
@@ -344,12 +344,12 @@ pub struct Remote {
/// How many writes have ever put bytes in. Negative when the pipe is gone.
pub writes: extern "C" fn(id: i32) -> i32,
/// The last message written, into the caller's memory. Returns its length.
pub last_message: extern "C" fn(id: i32, at: u32, cap: u32) -> i32,
pub last_message: extern "C" fn(id: i32, at: crate::PAddr, cap: u32) -> i32,
/// Drop a descriptor. The last one on an end is what a reader sees as EOF.
pub close_end: extern "C" fn(id: i32, write: i32),
/// Read into, or write out of, an address in the caller's memory.
pub read: extern "C" fn(id: i32, at: u32, len: u32) -> i32,
pub write: extern "C" fn(id: i32, at: u32, len: u32) -> i32,
pub read: extern "C" fn(id: i32, at: crate::PAddr, len: u32) -> i32,
pub write: extern "C" fn(id: i32, at: crate::PAddr, len: u32) -> i32,
pub readable: extern "C" fn(id: i32) -> i32,
pub writable: extern "C" fn(id: i32) -> i32,
pub buffered: extern "C" fn(id: i32) -> i32,
@@ -385,7 +385,7 @@ impl crate::vfs::FileHandle for RemotePipeEnd {
if self.write {
return Err(Errno::EBADF);
}
match (self.remote.read)(self.id, buf.as_mut_ptr() as u32, buf.len() as u32) {
match (self.remote.read)(self.id, buf.as_mut_ptr() as crate::PAddr, buf.len() as u32) {
n if n >= 0 => Ok(n as u64),
n => Err(Errno(-n)),
}
@@ -395,7 +395,7 @@ impl crate::vfs::FileHandle for RemotePipeEnd {
if !self.write {
return Err(Errno::EBADF);
}
match (self.remote.write)(self.id, buf.as_ptr() as u32, buf.len() as u32) {
match (self.remote.write)(self.id, buf.as_ptr() as crate::PAddr, buf.len() as u32) {
n if n >= 0 => Ok(n as u64),
n => Err(Errno(-n)),
}
@@ -463,7 +463,7 @@ impl crate::vfs::FileHandle for RemotePipeEnd {
};
let n = (self.remote.writes)(self.id);
let mut buf = [0u8; 180];
let got = (self.remote.last_message)(self.id, buf.as_mut_ptr() as u32, buf.len() as u32);
let got = (self.remote.last_message)(self.id, buf.as_mut_ptr() as crate::PAddr, buf.len() as u32);
let said = if got > 0 {
format!(", last {:?}", String::from_utf8_lossy(&buf[..got as usize]))
} else {
+10 -10
View File
@@ -227,16 +227,16 @@ pub struct Remote {
pub open_end: extern "C" fn(id: i32, master: i32),
/// Drop a descriptor. The last on an end is the other end's EOF/hangup.
pub close_end: extern "C" fn(id: i32, master: i32),
pub read: extern "C" fn(id: i32, master: i32, at: u32, len: u32) -> i32,
pub write: extern "C" fn(id: i32, master: i32, at: u32, len: u32) -> i32,
pub read: extern "C" fn(id: i32, master: i32, at: crate::PAddr, len: u32) -> i32,
pub write: extern "C" fn(id: i32, master: i32, at: crate::PAddr, len: u32) -> i32,
pub readable: extern "C" fn(id: i32, master: i32) -> i32,
pub available: extern "C" fn(id: i32, master: i32) -> i32,
pub ptn: extern "C" fn(id: i32) -> i32,
pub get_lock: extern "C" fn(id: i32) -> i32,
pub set_lock: extern "C" fn(id: i32, locked: i32),
/// The shared terminal state, in the byte shapes the ioctls exchange.
pub get_termios: extern "C" fn(id: i32, at: u32, len: u32) -> i32,
pub set_termios: extern "C" fn(id: i32, at: u32, len: u32, flush: i32) -> i32,
pub get_termios: extern "C" fn(id: i32, at: crate::PAddr, len: u32) -> i32,
pub set_termios: extern "C" fn(id: i32, at: crate::PAddr, len: u32, flush: i32) -> i32,
pub winsize: extern "C" fn(id: i32) -> u64,
/// Sets the window size and, when it changed, delivers `SIGWINCH` to the
/// foreground group itself (only the global kernel can, being next to the
@@ -285,14 +285,14 @@ impl RemotePty {
impl crate::vfs::FileHandle for RemotePty {
fn read_at(&self, _offset: u64, buf: &mut [u8]) -> SysResult {
match (self.remote.read)(self.id, self.m(), buf.as_mut_ptr() as u32, buf.len() as u32) {
match (self.remote.read)(self.id, self.m(), buf.as_mut_ptr() as crate::PAddr, buf.len() as u32) {
n if n >= 0 => Ok(n as u64),
n => Err(Errno(-n)),
}
}
fn write_at(&self, _offset: u64, buf: &[u8]) -> SysResult {
match (self.remote.write)(self.id, self.m(), buf.as_ptr() as u32, buf.len() as u32) {
match (self.remote.write)(self.id, self.m(), buf.as_ptr() as crate::PAddr, buf.len() as u32) {
n if n >= 0 => Ok(n as u64),
n => Err(Errno(-n)),
}
@@ -412,13 +412,13 @@ impl RemotePty {
match req {
ioc::TCGETS => {
let mut buf = [0u8; 60];
(r.get_termios)(id, buf.as_mut_ptr() as u32, buf.len() as u32);
(r.get_termios)(id, buf.as_mut_ptr() as crate::PAddr, buf.len() as u32);
m.write(arg, &buf[..KERNEL_TERMIOS])?;
Ok(0)
}
ioc::TCGETS2 => {
let mut buf = [0u8; 60];
(r.get_termios)(id, buf.as_mut_ptr() as u32, buf.len() as u32);
(r.get_termios)(id, buf.as_mut_ptr() as crate::PAddr, buf.len() as u32);
let mut out = [0u8; KERNEL_TERMIOS + 8];
out[..KERNEL_TERMIOS].copy_from_slice(&buf[..KERNEL_TERMIOS]);
out[KERNEL_TERMIOS..KERNEL_TERMIOS + 4].copy_from_slice(&38400u32.to_le_bytes());
@@ -429,13 +429,13 @@ impl RemotePty {
ioc::TCSETS | ioc::TCSETSW | ioc::TCSETSF => {
let src = m.read(arg, KERNEL_TERMIOS)?.to_vec();
let flush = (req == ioc::TCSETSF) as i32;
(r.set_termios)(id, src.as_ptr() as u32, src.len() as u32, flush);
(r.set_termios)(id, src.as_ptr() as crate::PAddr, src.len() as u32, flush);
Ok(0)
}
ioc::TCSETS2 | ioc::TCSETSW2 | ioc::TCSETSF2 => {
let src = m.read(arg, KERNEL_TERMIOS)?.to_vec();
let flush = (req == ioc::TCSETSF2) as i32;
(r.set_termios)(id, src.as_ptr() as u32, src.len() as u32, flush);
(r.set_termios)(id, src.as_ptr() as crate::PAddr, src.len() as u32, flush);
Ok(0)
}
ioc::TIOCGWINSZ => {
+2 -2
View File
@@ -5582,7 +5582,7 @@ impl Kernel {
// and `listen` is local to itself, and what the far end can see is
// only whether the name answers.
if let Some(r) = self.unix_remote {
let id = (r.bind)(path.as_ptr() as u32, path.len() as u32);
let id = (r.bind)(path.as_ptr() as crate::PAddr, path.len() as u32);
if id < 0 {
return Err(Errno(-id));
}
@@ -5624,7 +5624,7 @@ impl Kernel {
// is in *this* process's table and never in the kernel's, so the
// local path is still tried when the kernel does not know the name.
if let Some(r) = self.unix_remote {
let id = (r.connect)(path.as_ptr() as u32, path.len() as u32);
let id = (r.connect)(path.as_ptr() as crate::PAddr, path.len() as u32);
if id >= 0 {
let flags = self.fds.get(fd)?.flags();
let cloexec = self.fds.cloexec(fd).unwrap_or(false);
+10 -10
View File
@@ -645,10 +645,10 @@ impl Tty {
#[derive(Clone, Copy)]
pub struct Remote {
/// Write the 60-byte structure to `at`.
pub get_termios: extern "C" fn(at: u32, len: u32) -> i32,
pub set_termios: extern "C" fn(at: u32, len: u32) -> i32,
pub get_locked: extern "C" fn(at: u32, len: u32) -> i32,
pub set_locked: extern "C" fn(at: u32, len: u32) -> i32,
pub get_termios: extern "C" fn(at: crate::PAddr, len: u32) -> i32,
pub set_termios: extern "C" fn(at: crate::PAddr, len: u32) -> i32,
pub get_locked: extern "C" fn(at: crate::PAddr, len: u32) -> i32,
pub set_locked: extern "C" fn(at: crate::PAddr, len: u32) -> i32,
/// `rows << 48 | cols << 32 | xpixel << 16 | ypixel`.
pub winsize: extern "C" fn() -> u64,
/// Returns 1 if it changed, so the caller knows to raise `SIGWINCH`.
@@ -668,7 +668,7 @@ pub struct Remote {
/// Bytes waiting to be read, for `FIONREAD`.
pub queued: extern "C" fn() -> i32,
/// Push bytes into the input queue as though typed, for `TIOCSTI`.
pub push: extern "C" fn(at: u32, len: u32) -> i32,
pub push: extern "C" fn(at: crate::PAddr, len: u32) -> i32,
}
/// The terminal, wherever it is: a local `Tty` with no global kernel, or the
@@ -708,7 +708,7 @@ impl TtyRef {
match self.remote {
Some(r) => {
let mut t = [0u8; 60];
(r.get_termios)(t.as_mut_ptr() as u32, 60);
(r.get_termios)(t.as_mut_ptr() as crate::PAddr, 60);
t
}
None => self.local.termios(),
@@ -718,7 +718,7 @@ impl TtyRef {
pub fn set_termios(&self, src: &[u8]) {
match self.remote {
Some(r) => {
(r.set_termios)(src.as_ptr() as u32, src.len() as u32);
(r.set_termios)(src.as_ptr() as crate::PAddr, src.len() as u32);
}
None => self.local.set_termios(src),
}
@@ -728,7 +728,7 @@ impl TtyRef {
match self.remote {
Some(r) => {
let mut t = [0u8; 60];
(r.get_locked)(t.as_mut_ptr() as u32, 60);
(r.get_locked)(t.as_mut_ptr() as crate::PAddr, 60);
t
}
None => self.local.locked_termios(),
@@ -738,7 +738,7 @@ impl TtyRef {
pub fn set_locked_termios(&self, src: &[u8]) {
match self.remote {
Some(r) => {
(r.set_locked)(src.as_ptr() as u32, src.len() as u32);
(r.set_locked)(src.as_ptr() as crate::PAddr, src.len() as u32);
}
None => self.local.set_locked_termios(src),
}
@@ -856,7 +856,7 @@ impl TtyRef {
/// `TIOCSTI`: bytes into the input queue as though they had been typed.
pub fn push(&self, bytes: &[u8]) -> i32 {
match self.remote {
Some(r) => (r.push)(bytes.as_ptr() as u32, bytes.len() as u32),
Some(r) => (r.push)(bytes.as_ptr() as crate::PAddr, bytes.len() as u32),
None => 0,
}
}
+7 -7
View File
@@ -1072,20 +1072,20 @@ mod epoll_tests {
#[derive(Clone, Copy)]
pub struct Remote {
/// Bind and listen in one. Returns a listener id or a negated errno.
pub bind: extern "C" fn(path: u32, path_len: u32) -> i32,
pub bind: extern "C" fn(path: crate::PAddr, path_len: u32) -> i32,
/// Connect to a name. Returns this end's id, or a negated errno.
pub connect: extern "C" fn(path: u32, path_len: u32) -> i32,
pub connect: extern "C" fn(path: crate::PAddr, path_len: u32) -> i32,
/// Take a waiting connection. Returns the server end's id, or -EAGAIN.
pub accept: extern "C" fn(listener: i32) -> i32,
pub pending: extern "C" fn(listener: i32) -> i32,
pub read: extern "C" fn(id: i32, at: u32, len: u32) -> i32,
pub write: extern "C" fn(id: i32, at: u32, len: u32) -> i32,
pub read: extern "C" fn(id: i32, at: crate::PAddr, len: u32) -> i32,
pub write: extern "C" fn(id: i32, at: crate::PAddr, len: u32) -> i32,
pub readable: extern "C" fn(id: i32) -> i32,
pub writable: extern "C" fn(id: i32) -> i32,
pub available: extern "C" fn(id: i32) -> i32,
pub close: extern "C" fn(id: i32),
pub shutdown: extern "C" fn(id: i32),
pub exists: extern "C" fn(path: u32, path_len: u32) -> i32,
pub exists: extern "C" fn(path: crate::PAddr, path_len: u32) -> i32,
}
/// One end of a socket in the global kernel.
@@ -1166,7 +1166,7 @@ impl crate::vfs::FileHandle for RemoteUnix {
if self.listening {
return Err(Errno::ENOTCONN);
}
match (self.remote.read)(self.id, buf.as_mut_ptr() as u32, buf.len() as u32) {
match (self.remote.read)(self.id, buf.as_mut_ptr() as crate::PAddr, buf.len() as u32) {
n if n >= 0 => Ok(n as u64),
n => Err(Errno(-n)),
}
@@ -1176,7 +1176,7 @@ impl crate::vfs::FileHandle for RemoteUnix {
if self.listening {
return Err(Errno::ENOTCONN);
}
match (self.remote.write)(self.id, buf.as_ptr() as u32, buf.len() as u32) {
match (self.remote.write)(self.id, buf.as_ptr() as crate::PAddr, buf.len() as u32) {
n if n >= 0 => Ok(n as u64),
n => Err(Errno(-n)),
}
+24 -24
View File
@@ -25,34 +25,34 @@ use crate::vfs::{DirEntry, FileHandle, FileSystem, FileType, Metadata, SysResult
#[derive(Clone, Copy)]
pub struct Remote {
/// Open, returning a handle id or a negated errno.
pub open: extern "C" fn(path: u32, path_len: u32, flags: u64, mode: u32) -> i32,
pub open: extern "C" fn(path: crate::PAddr, path_len: u32, flags: u64, mode: u32) -> i32,
pub close: extern "C" fn(handle: i32),
/// A second id for one open file, for a child to inherit. Returns the
/// new id or a negated errno.
pub dup: extern "C" fn(handle: i32) -> i32,
pub read: extern "C" fn(handle: i32, offset: u64, at: u32, len: u32) -> i32,
pub write: extern "C" fn(handle: i32, offset: u64, at: u32, len: u32) -> i32,
pub read: extern "C" fn(handle: i32, offset: u64, at: crate::PAddr, len: u32) -> i32,
pub write: extern "C" fn(handle: i32, offset: u64, at: crate::PAddr, len: u32) -> i32,
/// Write this handle's metadata as [`META_BYTES`] bytes at `at`.
pub meta: extern "C" fn(handle: i32, at: u32) -> i32,
pub meta: extern "C" fn(handle: i32, at: crate::PAddr) -> i32,
pub truncate: extern "C" fn(handle: i32, len: u64) -> i32,
/// Write the directory listing at `at`, or `-ERANGE` if `cap` is short.
pub read_dir: extern "C" fn(handle: i32, at: u32, cap: u32) -> i32,
pub path_meta: extern "C" fn(path: u32, path_len: u32, at: u32) -> i32,
pub read_link: extern "C" fn(path: u32, path_len: u32, at: u32, cap: u32) -> i32,
pub unlink: extern "C" fn(path: u32, path_len: u32) -> i32,
pub mkdir: extern "C" fn(path: u32, path_len: u32, mode: u32) -> i32,
pub rmdir: extern "C" fn(path: u32, path_len: u32) -> i32,
pub rename: extern "C" fn(from: u32, from_len: u32, to: u32, to_len: u32) -> i32,
pub chmod: extern "C" fn(path: u32, path_len: u32, mode: u32) -> i32,
pub symlink: extern "C" fn(target: u32, target_len: u32, path: u32, path_len: u32) -> i32,
pub link: extern "C" fn(from: u32, from_len: u32, to: u32, to_len: u32) -> i32,
pub read_dir: extern "C" fn(handle: i32, at: crate::PAddr, cap: u32) -> i32,
pub path_meta: extern "C" fn(path: crate::PAddr, path_len: u32, at: crate::PAddr) -> i32,
pub read_link: extern "C" fn(path: crate::PAddr, path_len: u32, at: crate::PAddr, cap: u32) -> i32,
pub unlink: extern "C" fn(path: crate::PAddr, path_len: u32) -> i32,
pub mkdir: extern "C" fn(path: crate::PAddr, path_len: u32, mode: u32) -> i32,
pub rmdir: extern "C" fn(path: crate::PAddr, path_len: u32) -> i32,
pub rename: extern "C" fn(from: crate::PAddr, from_len: u32, to: crate::PAddr, to_len: u32) -> i32,
pub chmod: extern "C" fn(path: crate::PAddr, path_len: u32, mode: u32) -> i32,
pub symlink: extern "C" fn(target: crate::PAddr, target_len: u32, path: crate::PAddr, path_len: u32) -> i32,
pub link: extern "C" fn(from: crate::PAddr, from_len: u32, to: crate::PAddr, to_len: u32) -> i32,
/// Put a socket node at a path.
///
/// Not read -- only stat'd: Xvfb binds `/tmp/.X11-unix/X0` and its startup
/// script waits for that path to exist before launching a client.
pub mksocket: extern "C" fn(path: u32, path_len: u32) -> i32,
pub mksocket: extern "C" fn(path: crate::PAddr, path_len: u32) -> i32,
/// Set a path's modification time (`utimensat`, a bundle's file times).
pub set_mtime: extern "C" fn(path: u32, path_len: u32, secs: i64) -> i32,
pub set_mtime: extern "C" fn(path: crate::PAddr, path_len: u32, secs: i64) -> i32,
/// Set an open file's modification time. The global kernel has no clock
/// of its own, so a write's time is the writing process's: it stamps the
/// file after each write.
@@ -163,8 +163,8 @@ fn ok(v: i32) -> Result<i32, Errno> {
/// Where a path is staged for the other side to copy out. The address must be
/// real in this process's memory and stay valid across the call.
fn at(b: &[u8]) -> (u32, u32) {
(b.as_ptr() as u32, b.len() as u32)
fn at(b: &[u8]) -> (crate::PAddr, u32) {
(b.as_ptr() as crate::PAddr, b.len() as u32)
}
/// A filesystem served by the global kernel.
@@ -209,7 +209,7 @@ impl FileSystem for RemoteFs {
let p = path.as_bytes();
let (a, n) = at(p);
let mut buf = [0u8; META_BYTES];
ok((self.remote.path_meta)(a, n, buf.as_mut_ptr() as u32))?;
ok((self.remote.path_meta)(a, n, buf.as_mut_ptr() as crate::PAddr))?;
Ok(decode_meta(&buf))
}
@@ -222,7 +222,7 @@ impl FileSystem for RemoteFs {
let got = ok((self.remote.read_link)(
a,
n,
buf.as_mut_ptr() as u32,
buf.as_mut_ptr() as crate::PAddr,
buf.len() as u32,
))? as usize;
Ok(String::from_utf8_lossy(&buf[..got]).into_owned())
@@ -311,7 +311,7 @@ impl FileHandle for RemoteHandle {
let n = ok((self.remote.read)(
self.id,
offset,
buf.as_mut_ptr() as u32,
buf.as_mut_ptr() as crate::PAddr,
buf.len() as u32,
))?;
Ok(n as u64)
@@ -321,7 +321,7 @@ impl FileHandle for RemoteHandle {
let n = ok((self.remote.write)(
self.id,
offset,
buf.as_ptr() as u32,
buf.as_ptr() as crate::PAddr,
buf.len() as u32,
))?;
if n > 0 {
@@ -336,7 +336,7 @@ impl FileHandle for RemoteHandle {
fn metadata(&self) -> Result<Metadata, Errno> {
let mut buf = [0u8; META_BYTES];
ok((self.remote.meta)(self.id, buf.as_mut_ptr() as u32))?;
ok((self.remote.meta)(self.id, buf.as_mut_ptr() as crate::PAddr))?;
Ok(decode_meta(&buf))
}
@@ -346,7 +346,7 @@ impl FileHandle for RemoteHandle {
let mut cap = 4096;
loop {
let mut buf = vec![0u8; cap];
match (self.remote.read_dir)(self.id, buf.as_mut_ptr() as u32, cap as u32) {
match (self.remote.read_dir)(self.id, buf.as_mut_ptr() as crate::PAddr, cap as u32) {
n if n >= 0 => {
buf.truncate(n as usize);
return Ok(decode_dir(&buf));
+17 -14
View File
@@ -18,13 +18,20 @@
/// 0; the process memory is appended to the import section, landing at 1.
pub const PROCESS_MEMORY: u32 = 1;
/// An address in the process memory: 64-bit when it is a memory64
/// (`process64`, docs/mem64.md step 6), as `gex_kernel::PAddr`.
#[cfg(feature = "process64")]
pub type PAddr = u64;
#[cfg(not(feature = "process64"))]
pub type PAddr = u32;
/// How many 64 KiB pages the calling process's memory has.
///
/// `memory.size` can name a memory index (unlike a load), so this is how the
/// kernel tells a valid guest pointer from one off the end and answers `EFAULT`
/// rather than trapping the Worker mid-syscall with the kernel lock held.
#[inline]
pub fn process_pages() -> u32 {
pub fn process_pages() -> PAddr {
#[cfg(not(feature = "process64"))]
let n: u32;
#[cfg(feature = "process64")]
@@ -36,15 +43,15 @@ pub fn process_pages() -> u32 {
n = out(local) n,
);
}
n as u32
n
}
/// Whether `at..at+len` is inside the calling process's memory. Every `push`
/// and `pull` should be guarded by this: `memory.copy` traps rather than
/// returning, and a trap mid-syscall kills the Worker holding the kernel lock.
#[inline]
pub fn in_process(at: u32, len: u32) -> bool {
let end = match at.checked_add(len) {
pub fn in_process(at: PAddr, len: u32) -> bool {
let end = match at.checked_add(len as PAddr) {
Some(e) => e as u64,
None => return false,
};
@@ -59,11 +66,9 @@ pub fn in_process(at: u32, len: u32) -> bool {
/// rather than corrupting anything if it is not, but a trap here unwinds a
/// syscall that has already taken the kernel lock.
#[inline]
pub unsafe fn pull(dst: *mut u8, src: u32, len: u32) {
pub unsafe fn pull(dst: *mut u8, src: PAddr, len: u32) {
// A memory64 process memory takes an i64 address; the kernel's own (and
// the length, the smaller of the two index types) stay i32.
#[cfg(feature = "process64")]
let src = src as u64;
unsafe {
core::arch::asm!(
"local.get {dst}",
@@ -82,9 +87,7 @@ pub unsafe fn pull(dst: *mut u8, src: u32, len: u32) {
/// # Safety
/// As [`pull`], with the roles reversed.
#[inline]
pub unsafe fn push(dst: u32, src: *const u8, len: u32) {
#[cfg(feature = "process64")]
let dst = dst as u64;
pub unsafe fn push(dst: PAddr, src: *const u8, len: u32) {
unsafe {
core::arch::asm!(
"local.get {dst}",
@@ -104,7 +107,7 @@ pub unsafe fn push(dst: u32, src: *const u8, len: u32) {
/// # Safety
/// `at..at+8` must be in bounds of the process memory.
#[inline]
pub unsafe fn read_u64(at: u32) -> u64 {
pub unsafe fn read_u64(at: PAddr) -> u64 {
let mut buf = [0u8; 8];
unsafe { pull(buf.as_mut_ptr(), at, 8) };
u64::from_le_bytes(buf)
@@ -115,7 +118,7 @@ pub unsafe fn read_u64(at: u32) -> u64 {
/// # Safety
/// `at..at+8` must be in bounds of the process memory.
#[inline]
pub unsafe fn write_u64(at: u32, v: u64) {
pub unsafe fn write_u64(at: PAddr, v: u64) {
let buf = v.to_le_bytes();
unsafe { push(at, buf.as_ptr(), 8) };
}
@@ -129,7 +132,7 @@ pub unsafe fn write_u64(at: u32, v: u64) {
/// `dst` must be valid for `len` bytes in the kernel's own memory. The process
/// side is checked here.
#[inline]
pub unsafe fn pull_checked(dst: *mut u8, src: u32, len: u32) -> bool {
pub unsafe fn pull_checked(dst: *mut u8, src: PAddr, len: u32) -> bool {
if !in_process(src, len) {
return false;
}
@@ -142,7 +145,7 @@ pub unsafe fn pull_checked(dst: *mut u8, src: u32, len: u32) -> bool {
/// # Safety
/// `src` must be valid for `len` bytes in the kernel's own memory.
#[inline]
pub unsafe fn push_checked(dst: u32, src: *const u8, len: u32) -> bool {
pub unsafe fn push_checked(dst: PAddr, src: *const u8, len: u32) -> bool {
if !in_process(dst, len) {
return false;
}
+4
View File
@@ -237,6 +237,10 @@ if [ -n "${GEX_MEM64:-}" ] && [ -n "$ARCH_FEAT" ]; then
echo "shims/browser: the wasm64 build FAILED"; exit 1
fi
cp "$M64OUT" "$PUB/gex_browser$INFIX.m64.shared.wasm"
if [ -f "$PUB/gex_kernel$INFIX.m64.wasm" ] \
&& ! node shims/browser/js/check-kernel.mjs "$M64OUT" "$PUB/gex_kernel$INFIX.m64.wasm"; then
echo "shims/browser: the wasm64 shim and gex_kernel$INFIX.m64.wasm disagree (GEX_MEM64=1 shims/kernel/build.sh)"; exit 1
fi
$GEXC seed --mem64 -o web/public/gex/seed$INFIX.m64.shared.wasm >/dev/null
echo "shims/browser: $(stat -c%s "$M64OUT") bytes (wasm64, memory64), seed$INFIX.m64.shared.wasm"
fi
+115 -93
View File
@@ -255,27 +255,27 @@ extern "C" {
// other side copies across with `memory.copy`, which is the only way it
// can reach a memory it does not own. See `gex_kernel::vfs::remote` for
// the two layouts that cross.
fn k_fs_open(path: u32, path_len: u32, flags: u64, mode: u32) -> i32;
fn k_fs_open(path: gex_kernel::PAddr, path_len: u32, flags: u64, mode: u32) -> i32;
fn k_fs_close(handle: i32);
fn k_fs_dup(handle: i32) -> i32;
fn k_fs_read(handle: i32, offset: u64, at: u32, len: u32) -> i32;
fn k_fs_write(handle: i32, offset: u64, at: u32, len: u32) -> i32;
fn k_fs_meta(handle: i32, at: u32) -> i32;
fn k_fs_read(handle: i32, offset: u64, at: gex_kernel::PAddr, len: u32) -> i32;
fn k_fs_write(handle: i32, offset: u64, at: gex_kernel::PAddr, len: u32) -> i32;
fn k_fs_meta(handle: i32, at: gex_kernel::PAddr) -> i32;
fn k_fs_truncate(handle: i32, len: u64) -> i32;
fn k_fs_read_dir(handle: i32, at: u32, cap: u32) -> i32;
fn k_fs_path_meta(path: u32, path_len: u32, at: u32) -> i32;
fn k_fs_read_link(path: u32, path_len: u32, at: u32, cap: u32) -> i32;
fn k_fs_unlink(path: u32, path_len: u32) -> i32;
fn k_fs_mkdir(path: u32, path_len: u32, mode: u32) -> i32;
fn k_fs_rmdir(path: u32, path_len: u32) -> i32;
fn k_fs_rename(from: u32, from_len: u32, to: u32, to_len: u32) -> i32;
fn k_fs_chmod(path: u32, path_len: u32, mode: u32) -> i32;
fn k_fs_set_mtime(path: u32, path_len: u32, secs: i64) -> i32;
fn k_fs_read_dir(handle: i32, at: gex_kernel::PAddr, cap: u32) -> i32;
fn k_fs_path_meta(path: gex_kernel::PAddr, path_len: u32, at: gex_kernel::PAddr) -> i32;
fn k_fs_read_link(path: gex_kernel::PAddr, path_len: u32, at: gex_kernel::PAddr, cap: u32) -> i32;
fn k_fs_unlink(path: gex_kernel::PAddr, path_len: u32) -> i32;
fn k_fs_mkdir(path: gex_kernel::PAddr, path_len: u32, mode: u32) -> i32;
fn k_fs_rmdir(path: gex_kernel::PAddr, path_len: u32) -> i32;
fn k_fs_rename(from: gex_kernel::PAddr, from_len: u32, to: gex_kernel::PAddr, to_len: u32) -> i32;
fn k_fs_chmod(path: gex_kernel::PAddr, path_len: u32, mode: u32) -> i32;
fn k_fs_set_mtime(path: gex_kernel::PAddr, path_len: u32, secs: i64) -> i32;
fn k_fs_touch(handle: i32, secs: i64) -> i32;
fn k_fs_symlink(target: u32, target_len: u32, path: u32, path_len: u32) -> i32;
fn k_fs_link(from: u32, from_len: u32, to: u32, to_len: u32) -> i32;
fn k_fs_mksocket(path: u32, path_len: u32) -> i32;
fn k_fs_add(path: u32, path_len: u32, at: u32, len: u32) -> i32;
fn k_fs_symlink(target: gex_kernel::PAddr, target_len: u32, path: gex_kernel::PAddr, path_len: u32) -> i32;
fn k_fs_link(from: gex_kernel::PAddr, from_len: u32, to: gex_kernel::PAddr, to_len: u32) -> i32;
fn k_fs_mksocket(path: gex_kernel::PAddr, path_len: u32) -> i32;
fn k_fs_add(path: gex_kernel::PAddr, path_len: u32, at: gex_kernel::PAddr, len: u32) -> i32;
/// The system-wide map from a page of a mapped object to the dispatch slots
/// its code was translated into; zero means nobody has. In the kernel so
/// the second process to run `ls` finds what the first one translated.
@@ -285,17 +285,17 @@ extern "C" {
/// Standard input, shared. Whoever has a keystroke writes it here and
/// wakes a parked reader, rather than posting a message to a Worker that
/// is asleep inside wasm and cannot receive one.
fn k_stdin_write(at: u32, len: u32) -> i32;
fn k_stdin_read(at: u32, len: u32) -> i32;
fn k_stdin_write(at: gex_kernel::PAddr, len: u32) -> i32;
fn k_stdin_read(at: gex_kernel::PAddr, len: u32) -> i32;
fn k_stdin_ready() -> i32;
fn k_stdin_eof();
/// The terminal: one `struct termios`, one size, one foreground process
/// group and one line discipline, for every process at once. The page
/// pushes keystrokes straight into it; these are the guest's half.
fn k_tty_get_termios(at: u32, len: u32) -> i32;
fn k_tty_set_termios(at: u32, len: u32) -> i32;
fn k_tty_get_locked(at: u32, len: u32) -> i32;
fn k_tty_set_locked(at: u32, len: u32) -> i32;
fn k_tty_get_termios(at: gex_kernel::PAddr, len: u32) -> i32;
fn k_tty_set_termios(at: gex_kernel::PAddr, len: u32) -> i32;
fn k_tty_get_locked(at: gex_kernel::PAddr, len: u32) -> i32;
fn k_tty_set_locked(at: gex_kernel::PAddr, len: u32) -> i32;
fn k_tty_winsize() -> u64;
fn k_tty_set_winsize(packed: u64) -> i32;
fn k_tty_fg() -> i32;
@@ -308,7 +308,7 @@ extern "C" {
fn k_tty_set_bits(which: i32, on: i32);
fn k_tty_flush(which: i32) -> i32;
fn k_tty_queued() -> i32;
fn k_tty_push(at: u32, len: u32) -> i32;
fn k_tty_push(at: gex_kernel::PAddr, len: u32) -> i32;
/// Slots nobody else will be given, and the mark that keeps runtime
/// allocation clear of what static recompilation already assigned.
fn k_slot_alloc(count: u64) -> u64;
@@ -318,7 +318,7 @@ extern "C" {
fn k_slot_left() -> u64;
/// Translated modules, once for the whole system: a dispatch table belongs
/// to a module instance, so the module bytes themselves must be shareable.
fn k_mod_store(ptr: u32, len: u32, slots: u64, dirs_ptr: u32, dirs_len: u32) -> u32;
fn k_mod_store(ptr: gex_kernel::PAddr, len: u32, slots: u64, dirs_ptr: gex_kernel::PAddr, dirs_len: u32) -> u32;
fn k_mod_claim(page: u64, key: u64, id: u32);
fn k_mod_for(page: u64, key: u64, n: u32) -> u32;
fn k_mod_claim_object(obj: u64, id: u32);
@@ -329,17 +329,17 @@ extern "C" {
fn k_mod_slots(id: u32) -> u64;
fn k_mod_len(id: u32) -> u32;
fn k_mod_dirs_len(id: u32) -> u32;
fn k_mod_read(id: u32, out: u32, cap: u32) -> u32;
fn k_mod_dirs_read(id: u32, out: u32, cap: u32) -> u32;
fn k_mod_read(id: u32, out: gex_kernel::PAddr, cap: u32) -> u32;
fn k_mod_dirs_read(id: u32, out: gex_kernel::PAddr, cap: u32) -> u32;
/// Has some process already filled the shared root? Only the first
/// should: the rest would unpack the same rootfs over the top of it.
fn k_place(key: u64, len: u64) -> u64;
fn k_place_set(key: u64, base: u64, len: u64);
/// The linked address space of a program, kept so the next process of it
/// does not have to run the dynamic linker again.
fn k_snap_store(key: u64, ptr: u32, len: u32);
fn k_snap_store(key: u64, ptr: gex_kernel::PAddr, len: u32);
fn k_snap_len(key: u64) -> u32;
fn k_snap_get(key: u64, at: u32, cap: u32) -> u32;
fn k_snap_get(key: u64, at: gex_kernel::PAddr, cap: u32) -> u32;
fn k_fs_populated() -> i32;
fn k_fs_populate_done();
@@ -351,11 +351,11 @@ extern "C" {
fn k_pipe_ends(id: i32) -> i32;
fn k_pipe_write_stats(id: i32) -> i32;
fn k_pipe_writes(id: i32) -> i32;
fn k_pipe_last_message(id: i32, at: u32, cap: u32) -> i32;
fn k_pipe_last_message(id: i32, at: gex_kernel::PAddr, cap: u32) -> i32;
fn k_pipe_close_end(id: i32, write: i32, pid: i32);
fn k_pipe_forget(pid: i32) -> u32;
fn k_pipe_read(id: i32, at: u32, len: u32) -> i32;
fn k_pipe_write(id: i32, at: u32, len: u32) -> i32;
fn k_pipe_read(id: i32, at: gex_kernel::PAddr, len: u32) -> i32;
fn k_pipe_write(id: i32, at: gex_kernel::PAddr, len: u32) -> i32;
fn k_pipe_readable(id: i32) -> i32;
fn k_pipe_writable(id: i32) -> i32;
fn k_pipe_buffered(id: i32) -> i32;
@@ -368,8 +368,8 @@ extern "C" {
fn k_pty_open_end(id: i32, master: i32, pid: i32);
fn k_pty_close_end(id: i32, master: i32, pid: i32);
fn k_pty_forget(pid: i32) -> u32;
fn k_pty_read(id: i32, master: i32, at: u32, len: u32) -> i32;
fn k_pty_write(id: i32, master: i32, at: u32, len: u32) -> i32;
fn k_pty_read(id: i32, master: i32, at: gex_kernel::PAddr, len: u32) -> i32;
fn k_pty_write(id: i32, master: i32, at: gex_kernel::PAddr, len: u32) -> i32;
fn k_pty_readable(id: i32, master: i32) -> i32;
fn k_pty_counts(id: i32) -> u32;
fn k_pty_for_sid(sid: i32) -> i32;
@@ -379,8 +379,8 @@ extern "C" {
fn k_pty_ptn(id: i32) -> i32;
fn k_pty_get_lock(id: i32) -> i32;
fn k_pty_set_lock(id: i32, locked: i32);
fn k_pty_get_termios(id: i32, at: u32, len: u32) -> i32;
fn k_pty_set_termios(id: i32, at: u32, len: u32, flush: i32) -> i32;
fn k_pty_get_termios(id: i32, at: gex_kernel::PAddr, len: u32) -> i32;
fn k_pty_set_termios(id: i32, at: gex_kernel::PAddr, len: u32, flush: i32) -> i32;
fn k_pty_winsize(id: i32) -> u64;
fn k_pty_set_winsize(id: i32, packed: u64) -> i32;
fn k_pty_fg(id: i32) -> i32;
@@ -392,18 +392,18 @@ extern "C" {
// Unix sockets. An X server binds a name and its client connects to it,
// and with a Worker per process those are two memories -- so the name has
// to be somewhere neither of them owns.
fn k_sock_bind(path: u32, path_len: u32) -> i32;
fn k_sock_connect(path: u32, path_len: u32) -> i32;
fn k_sock_bind(path: gex_kernel::PAddr, path_len: u32) -> i32;
fn k_sock_connect(path: gex_kernel::PAddr, path_len: u32) -> i32;
fn k_sock_accept(listener: i32) -> i32;
fn k_sock_pending(listener: i32) -> i32;
fn k_sock_read(id: i32, at: u32, len: u32) -> i32;
fn k_sock_write(id: i32, at: u32, len: u32) -> i32;
fn k_sock_read(id: i32, at: gex_kernel::PAddr, len: u32) -> i32;
fn k_sock_write(id: i32, at: gex_kernel::PAddr, len: u32) -> i32;
fn k_sock_readable(id: i32) -> i32;
fn k_sock_writable(id: i32) -> i32;
fn k_sock_available(id: i32) -> i32;
fn k_sock_close(id: i32);
fn k_sock_shutdown(id: i32);
fn k_sock_exists(path: u32, path_len: u32) -> i32;
fn k_sock_exists(path: gex_kernel::PAddr, path_len: u32) -> i32;
}
@@ -646,7 +646,7 @@ fn global_procs() -> Option<Arc<gex_kernel::proc::ProcTable>> {
)))
}
extern "C" fn rf_open(path: u32, path_len: u32, flags: u64, mode: u32) -> i32 {
extern "C" fn rf_open(path: gex_kernel::PAddr, path_len: u32, flags: u64, mode: u32) -> i32 {
unsafe { k_fs_open(path, path_len, flags, mode) }
}
extern "C" fn rf_close(handle: i32) {
@@ -655,55 +655,55 @@ extern "C" fn rf_close(handle: i32) {
extern "C" fn rf_dup(handle: i32) -> i32 {
unsafe { k_fs_dup(handle) }
}
extern "C" fn rf_read(handle: i32, offset: u64, at: u32, len: u32) -> i32 {
extern "C" fn rf_read(handle: i32, offset: u64, at: gex_kernel::PAddr, len: u32) -> i32 {
unsafe { k_fs_read(handle, offset, at, len) }
}
extern "C" fn rf_write(handle: i32, offset: u64, at: u32, len: u32) -> i32 {
extern "C" fn rf_write(handle: i32, offset: u64, at: gex_kernel::PAddr, len: u32) -> i32 {
unsafe { k_fs_write(handle, offset, at, len) }
}
extern "C" fn rf_meta(handle: i32, at: u32) -> i32 {
extern "C" fn rf_meta(handle: i32, at: gex_kernel::PAddr) -> i32 {
unsafe { k_fs_meta(handle, at) }
}
extern "C" fn rf_truncate(handle: i32, len: u64) -> i32 {
unsafe { k_fs_truncate(handle, len) }
}
extern "C" fn rf_read_dir(handle: i32, at: u32, cap: u32) -> i32 {
extern "C" fn rf_read_dir(handle: i32, at: gex_kernel::PAddr, cap: u32) -> i32 {
unsafe { k_fs_read_dir(handle, at, cap) }
}
extern "C" fn rf_path_meta(path: u32, path_len: u32, at: u32) -> i32 {
extern "C" fn rf_path_meta(path: gex_kernel::PAddr, path_len: u32, at: gex_kernel::PAddr) -> i32 {
unsafe { k_fs_path_meta(path, path_len, at) }
}
extern "C" fn rf_read_link(path: u32, path_len: u32, at: u32, cap: u32) -> i32 {
extern "C" fn rf_read_link(path: gex_kernel::PAddr, path_len: u32, at: gex_kernel::PAddr, cap: u32) -> i32 {
unsafe { k_fs_read_link(path, path_len, at, cap) }
}
extern "C" fn rf_unlink(path: u32, path_len: u32) -> i32 {
extern "C" fn rf_unlink(path: gex_kernel::PAddr, path_len: u32) -> i32 {
unsafe { k_fs_unlink(path, path_len) }
}
extern "C" fn rf_mkdir(path: u32, path_len: u32, mode: u32) -> i32 {
extern "C" fn rf_mkdir(path: gex_kernel::PAddr, path_len: u32, mode: u32) -> i32 {
unsafe { k_fs_mkdir(path, path_len, mode) }
}
extern "C" fn rf_rmdir(path: u32, path_len: u32) -> i32 {
extern "C" fn rf_rmdir(path: gex_kernel::PAddr, path_len: u32) -> i32 {
unsafe { k_fs_rmdir(path, path_len) }
}
extern "C" fn rf_rename(from: u32, from_len: u32, to: u32, to_len: u32) -> i32 {
extern "C" fn rf_rename(from: gex_kernel::PAddr, from_len: u32, to: gex_kernel::PAddr, to_len: u32) -> i32 {
unsafe { k_fs_rename(from, from_len, to, to_len) }
}
extern "C" fn rf_chmod(path: u32, path_len: u32, mode: u32) -> i32 {
unsafe { k_fs_chmod(path, path_len, mode) }
extern "C" fn rf_chmod(path: gex_kernel::PAddr, path_len: u32, mode: u32) -> i32 {
unsafe { k_fs_chmod(gex_kernel::PAddr::from(path), path_len, mode) }
}
extern "C" fn rf_set_mtime(path: u32, path_len: u32, secs: i64) -> i32 {
unsafe { k_fs_set_mtime(path, path_len, secs) }
extern "C" fn rf_set_mtime(path: gex_kernel::PAddr, path_len: u32, secs: i64) -> i32 {
unsafe { k_fs_set_mtime(gex_kernel::PAddr::from(path), path_len, secs) }
}
extern "C" fn rf_touch(handle: i32, secs: i64) -> i32 {
unsafe { k_fs_touch(handle, secs) }
}
extern "C" fn rf_symlink(target: u32, target_len: u32, path: u32, path_len: u32) -> i32 {
extern "C" fn rf_symlink(target: gex_kernel::PAddr, target_len: u32, path: gex_kernel::PAddr, path_len: u32) -> i32 {
unsafe { k_fs_symlink(target, target_len, path, path_len) }
}
extern "C" fn rf_link(from: u32, from_len: u32, to: u32, to_len: u32) -> i32 {
extern "C" fn rf_link(from: gex_kernel::PAddr, from_len: u32, to: gex_kernel::PAddr, to_len: u32) -> i32 {
unsafe { k_fs_link(from, from_len, to, to_len) }
}
extern "C" fn rf_mksocket(path: u32, path_len: u32) -> i32 {
extern "C" fn rf_mksocket(path: gex_kernel::PAddr, path_len: u32) -> i32 {
unsafe { k_fs_mksocket(path, path_len) }
}
@@ -755,17 +755,17 @@ extern "C" fn rp_write_stats(id: i32) -> i32 {
extern "C" fn rp_writes(id: i32) -> i32 {
unsafe { k_pipe_writes(id) }
}
extern "C" fn rp_last_message(id: i32, at: u32, cap: u32) -> i32 {
extern "C" fn rp_last_message(id: i32, at: gex_kernel::PAddr, cap: u32) -> i32 {
unsafe { k_pipe_last_message(id, at, cap) }
}
extern "C" fn rp_close_end(id: i32, write: i32) {
unsafe { k_pipe_close_end(id, write, my_pid()) }
pipe_note("close", id, write);
}
extern "C" fn rp_read(id: i32, at: u32, len: u32) -> i32 {
extern "C" fn rp_read(id: i32, at: gex_kernel::PAddr, len: u32) -> i32 {
unsafe { k_pipe_read(id, at, len) }
}
extern "C" fn rp_write(id: i32, at: u32, len: u32) -> i32 {
extern "C" fn rp_write(id: i32, at: gex_kernel::PAddr, len: u32) -> i32 {
unsafe { k_pipe_write(id, at, len) }
}
extern "C" fn rp_readable(id: i32) -> i32 {
@@ -780,16 +780,16 @@ extern "C" fn rp_buffered(id: i32) -> i32 {
// The terminal, which is one object for the whole system and not one per
// process. See `gex_kernel::tty`.
extern "C" fn rt_get_termios(at: u32, len: u32) -> i32 {
extern "C" fn rt_get_termios(at: gex_kernel::PAddr, len: u32) -> i32 {
unsafe { k_tty_get_termios(at, len) }
}
extern "C" fn rt_set_termios(at: u32, len: u32) -> i32 {
extern "C" fn rt_set_termios(at: gex_kernel::PAddr, len: u32) -> i32 {
unsafe { k_tty_set_termios(at, len) }
}
extern "C" fn rt_get_locked(at: u32, len: u32) -> i32 {
extern "C" fn rt_get_locked(at: gex_kernel::PAddr, len: u32) -> i32 {
unsafe { k_tty_get_locked(at, len) }
}
extern "C" fn rt_set_locked(at: u32, len: u32) -> i32 {
extern "C" fn rt_set_locked(at: gex_kernel::PAddr, len: u32) -> i32 {
unsafe { k_tty_set_locked(at, len) }
}
extern "C" fn rt_winsize() -> u64 {
@@ -828,7 +828,7 @@ extern "C" fn rt_flush(which: i32) -> i32 {
extern "C" fn rt_queued() -> i32 {
unsafe { k_tty_queued() }
}
extern "C" fn rt_push(at: u32, len: u32) -> i32 {
extern "C" fn rt_push(at: gex_kernel::PAddr, len: u32) -> i32 {
unsafe { k_tty_push(at, len) }
}
@@ -946,7 +946,7 @@ extern "C" fn rpty_close_end(id: i32, master: i32) {
unsafe { k_pty_close_end(id, master, my_pid()) };
pty_note("close", id, master, my_pid());
}
extern "C" fn rpty_read(id: i32, master: i32, at: u32, len: u32) -> i32 {
extern "C" fn rpty_read(id: i32, master: i32, at: gex_kernel::PAddr, len: u32) -> i32 {
let r = unsafe { k_pty_read(id, master, at, len) };
// End of file on the master is how a terminal emulator learns its shell is
// gone -- and if it is wrong, the emulator kills the session. Worth saying.
@@ -955,7 +955,7 @@ extern "C" fn rpty_read(id: i32, master: i32, at: u32, len: u32) -> i32 {
}
r
}
extern "C" fn rpty_write(id: i32, master: i32, at: u32, len: u32) -> i32 {
extern "C" fn rpty_write(id: i32, master: i32, at: gex_kernel::PAddr, len: u32) -> i32 {
unsafe { k_pty_write(id, master, at, len) }
}
extern "C" fn rpty_readable(id: i32, master: i32) -> i32 {
@@ -973,10 +973,10 @@ extern "C" fn rpty_get_lock(id: i32) -> i32 {
extern "C" fn rpty_set_lock(id: i32, locked: i32) {
unsafe { k_pty_set_lock(id, locked) }
}
extern "C" fn rpty_get_termios(id: i32, at: u32, len: u32) -> i32 {
extern "C" fn rpty_get_termios(id: i32, at: gex_kernel::PAddr, len: u32) -> i32 {
unsafe { k_pty_get_termios(id, at, len) }
}
extern "C" fn rpty_set_termios(id: i32, at: u32, len: u32, flush: i32) -> i32 {
extern "C" fn rpty_set_termios(id: i32, at: gex_kernel::PAddr, len: u32, flush: i32) -> i32 {
unsafe { k_pty_set_termios(id, at, len, flush) }
}
extern "C" fn rpty_winsize(id: i32) -> u64 {
@@ -1038,10 +1038,10 @@ fn global_ptys() -> Option<gex_kernel::pty::Remote> {
})
}
extern "C" fn rs_bind(path: u32, path_len: u32) -> i32 {
extern "C" fn rs_bind(path: gex_kernel::PAddr, path_len: u32) -> i32 {
unsafe { k_sock_bind(path, path_len) }
}
extern "C" fn rs_connect(path: u32, path_len: u32) -> i32 {
extern "C" fn rs_connect(path: gex_kernel::PAddr, path_len: u32) -> i32 {
unsafe { k_sock_connect(path, path_len) }
}
extern "C" fn rs_accept(listener: i32) -> i32 {
@@ -1050,10 +1050,10 @@ extern "C" fn rs_accept(listener: i32) -> i32 {
extern "C" fn rs_pending(listener: i32) -> i32 {
unsafe { k_sock_pending(listener) }
}
extern "C" fn rs_read(id: i32, at: u32, len: u32) -> i32 {
extern "C" fn rs_read(id: i32, at: gex_kernel::PAddr, len: u32) -> i32 {
unsafe { k_sock_read(id, at, len) }
}
extern "C" fn rs_write(id: i32, at: u32, len: u32) -> i32 {
extern "C" fn rs_write(id: i32, at: gex_kernel::PAddr, len: u32) -> i32 {
unsafe { k_sock_write(id, at, len) }
}
extern "C" fn rs_readable(id: i32) -> i32 {
@@ -1071,7 +1071,7 @@ extern "C" fn rs_close(id: i32) {
extern "C" fn rs_shutdown(id: i32) {
unsafe { k_sock_shutdown(id) }
}
extern "C" fn rs_exists(path: u32, path_len: u32) -> i32 {
extern "C" fn rs_exists(path: gex_kernel::PAddr, path_len: u32) -> i32 {
unsafe { k_sock_exists(path, path_len) }
}
@@ -1204,7 +1204,7 @@ impl FileHandle for StdinQueue {
// SAFETY: scalars, and the kernel refuses a range outside this
// process's memory rather than writing it.
if unsafe { k_present() } != 0 {
let n = unsafe { k_stdin_read(buf.as_mut_ptr() as u32, buf.len() as u32) };
let n = unsafe { k_stdin_read(buf.as_mut_ptr() as gex_kernel::PAddr, buf.len() as u32) };
if n > 0 {
return Ok(n as u64);
}
@@ -2191,6 +2191,20 @@ fn arena_take(len: u64) -> Option<u64> {
/// stepped over as arena of its own (see `MemoryMap::grows_on_demand`), or
/// `None` when the memory would not grow.
fn arena_back(end: u64) -> Option<u64> {
// The high arena of a 64-bit memory (docs/mem64.md §3): the memory grows
// to cover it (V8 commits lazily), but the low arena's cursor -- which
// this module's heap also takes from, and whose addresses cross to JS as
// u32 -- stays below 4 GiB.
if end > gex_abi::HIGH_ARENA_BASE && gex_abi::mem64() {
if !grow_memory_to(end) {
return None;
}
let prev = HIGH_NEXT.fetch_max(end, std::sync::atomic::Ordering::Relaxed).max(gex_abi::HIGH_ARENA_BASE);
if end > prev {
memmap::claim(memmap::Kind::Guest, 0, prev, end, "guest mapping (high arena)");
}
return Some(prev);
}
if !grow_memory_to(end) {
return None;
}
@@ -2201,6 +2215,9 @@ fn arena_back(end: u64) -> Option<u64> {
Some(prev)
}
/// How far the high arena has been backed (claimed in `memmap`).
static HIGH_NEXT: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
/// What the kernel's memory map calls to claim a span of arena.
///
/// The same cursor this module's heap takes from, so the two can never hand
@@ -2614,7 +2631,12 @@ static mut HOST: Option<Host> = None;
/// than failing cleanly. The guest gets everything below this line; the shim
/// takes what is above through ordinary pointers. The guest's stack, its
/// highest thing, is placed here rather than at `STACK_TOP`.
#[cfg(target_arch = "wasm32")]
const GUEST_LIMIT: u64 = 0x7fff_0000;
/// A wasm64 shim's slice may span the whole memory64, high arena included
/// (docs/mem64.md step 6).
#[cfg(target_arch = "wasm64")]
const GUEST_LIMIT: u64 = gex_abi::LIMIT64;
/// The guest's memory, as a slice.
///
@@ -4050,14 +4072,14 @@ pub extern "C" fn gex_add_file_part(path: u32, path_len: u32, offset: u64, data:
// SAFETY: scalars and pointers into this module's memory, which the
// kernel copies from.
unsafe {
let h = k_fs_open(path, path_len, 1 /* O_WRONLY */, 0o644);
let h = k_fs_open(gex_kernel::PAddr::from(path), path_len, 1 /* O_WRONLY */, 0o644);
if h < 0 {
warn_line(&format!("gex_add_file_part: cannot reopen a staged file ({h})\n"));
return;
}
let mut done = 0u32;
while done < data_len {
let n = k_fs_write(h, offset + done as u64, data + done, data_len - done);
let n = k_fs_write(h, offset + done as u64, gex_kernel::PAddr::from(data + done), data_len - done);
if n <= 0 {
warn_line("gex_add_file_part: a staged write failed\n");
break;
@@ -4095,7 +4117,7 @@ pub extern "C" fn gex_add_file(path: u32, path_len: u32, data: u32, data_len: u3
// cost the rootfs's size in every process that mounted it.
if unsafe { k_present() } != 0 {
if unsafe { k_fs_populated() } == 0 {
unsafe { k_fs_add(path, path_len, data, data_len) };
unsafe { k_fs_add(gex_kernel::PAddr::from(path), path_len, gex_kernel::PAddr::from(data), data_len) };
}
return;
}
@@ -4117,7 +4139,7 @@ pub extern "C" fn gex_add_hardlink(target: u32, target_len: u32, path: u32, path
// SAFETY: scalars naming ranges of this module's memory, no borrow held.
if unsafe { k_present() } != 0 {
if unsafe { k_fs_populated() } == 0 {
return (unsafe { k_fs_link(target, target_len, path, path_len) } == 0) as i32;
return (unsafe { k_fs_link(gex_kernel::PAddr::from(target), target_len, gex_kernel::PAddr::from(path), path_len) } == 0) as i32;
}
return 1;
}
@@ -4135,7 +4157,7 @@ pub extern "C" fn gex_set_mtime(path: u32, path_len: u32, secs: i64) {
// SAFETY: scalars naming a range of this module's memory, no borrow held.
if unsafe { k_present() } != 0 {
if unsafe { k_fs_populated() } == 0 {
unsafe { k_fs_set_mtime(path, path_len, secs) };
unsafe { k_fs_set_mtime(gex_kernel::PAddr::from(path), path_len, secs) };
}
return;
}
@@ -4153,7 +4175,7 @@ pub extern "C" fn gex_chmod(path: u32, path_len: u32, mode: u32) {
// SAFETY: scalars naming a range of this module's memory, no borrow held.
if unsafe { k_present() } != 0 {
if unsafe { k_fs_populated() } == 0 {
unsafe { k_fs_chmod(path, path_len, mode) };
unsafe { k_fs_chmod(gex_kernel::PAddr::from(path), path_len, mode) };
}
return;
}
@@ -4171,7 +4193,7 @@ pub extern "C" fn gex_add_dir(path: u32, path_len: u32) {
// SAFETY: as `gex_add_file` -- scalars, no borrow held.
if unsafe { k_present() } != 0 {
if unsafe { k_fs_populated() } == 0 {
unsafe { k_fs_mkdir(path, path_len, 0o755) };
unsafe { k_fs_mkdir(gex_kernel::PAddr::from(path), path_len, 0o755) };
}
return;
}
@@ -6054,7 +6076,7 @@ pub extern "C" fn gex_syscall(ctx: i32, pc: i64) {
// the call.
if snapshots_shared() {
let n = blob.len().min(snapshot_share_cap()) as u32;
unsafe { k_snap_store(key, blob.as_ptr() as u32, n) };
unsafe { k_snap_store(key, blob.as_ptr() as gex_kernel::PAddr, n) };
}
link_state(3);
// Under tracing only: the per-process line already says
@@ -8343,7 +8365,7 @@ fn snap_lookup(key: u64) -> Option<Vec<u8>> {
return None;
}
let mut blob = vec![0u8; len as usize];
let got = k_snap_get(key, blob.as_mut_ptr() as u32, len);
let got = k_snap_get(key, blob.as_mut_ptr() as gex_kernel::PAddr, len);
(got == len).then_some(blob)
}
}
@@ -8398,10 +8420,10 @@ fn share_module(wasm: &[u8], slots: u64, dirs: &[(u64, u32)]) -> u32 {
raw.extend_from_slice(&base.to_le_bytes());
}
k_mod_store(
wasm.as_ptr() as u32,
wasm.as_ptr() as gex_kernel::PAddr,
wasm.len() as u32,
slots,
raw.as_ptr() as u32,
raw.as_ptr() as gex_kernel::PAddr,
raw.len() as u32,
)
}
@@ -8509,12 +8531,12 @@ fn shared_modules_of_object(
}
took += len as u64;
let mut wasm = vec![0u8; len];
if k_mod_read(id, wasm.as_mut_ptr() as u32, len as u32) as usize != len {
if k_mod_read(id, wasm.as_mut_ptr() as gex_kernel::PAddr, len as u32) as usize != len {
continue;
}
let dlen = k_mod_dirs_len(id) as usize;
let mut raw = vec![0u8; dlen];
if dlen > 0 && k_mod_dirs_read(id, raw.as_mut_ptr() as u32, dlen as u32) as usize != dlen
if dlen > 0 && k_mod_dirs_read(id, raw.as_mut_ptr() as gex_kernel::PAddr, dlen as u32) as usize != dlen
{
continue;
}
@@ -8579,7 +8601,7 @@ fn backfill_registry(h: &mut Host) {
continue;
}
let mut wasm = vec![0u8; len];
if unsafe { k_mod_read(id, wasm.as_mut_ptr() as u32, len as u32) } as usize != len {
if unsafe { k_mod_read(id, wasm.as_mut_ptr() as gex_kernel::PAddr, len as u32) } as usize != len {
continue;
}
// As this process installed it: at its dense base, or not at all.
@@ -8681,12 +8703,12 @@ fn shared_module_at(
return None;
}
let mut wasm = vec![0u8; len];
if k_mod_read(id, wasm.as_mut_ptr() as u32, len as u32) as usize != len {
if k_mod_read(id, wasm.as_mut_ptr() as gex_kernel::PAddr, len as u32) as usize != len {
return None;
}
let dlen = k_mod_dirs_len(id) as usize;
let mut raw = vec![0u8; dlen];
if dlen > 0 && k_mod_dirs_read(id, raw.as_mut_ptr() as u32, dlen as u32) as usize != dlen {
if dlen > 0 && k_mod_dirs_read(id, raw.as_mut_ptr() as gex_kernel::PAddr, dlen as u32) as usize != dlen {
return None;
}
let dirs = raw
+1 -1
View File
@@ -27,7 +27,7 @@ pub fn connect(kernel: &Kernel) -> Option<Xwm> {
fn open(kernel: &Kernel) -> Option<Arc<dyn FileHandle>> {
for name in X_SOCKETS {
if let Some(r) = kernel.unix_remote {
let id = (r.connect)(name.as_ptr() as u32, name.len() as u32);
let id = (r.connect)(name.as_ptr() as gex_kernel::PAddr, name.len() as u32);
if id >= 0 {
unsafe {
crate::write_stderr(&format!(
+1 -1
View File
@@ -15,6 +15,6 @@ gex-kernel.workspace = true
# Serve an x86-64 guest. See `gex_abi::X86`.
x86 = ["gex-abi/x86", "gex-kernel/x86"]
# Processes with a 64-bit memory (the wasm64 browser shim, docs/mem64.md step 6).
process64 = ["gex-kmem/process64"]
process64 = ["gex-kmem/process64", "gex-kernel/process64"]
# Host a 32-bit big-endian PowerPC guest (docs/ppc.md).
ppc = ["gex-abi/ppc", "gex-kernel/ppc"]
+47 -47
View File
@@ -261,7 +261,7 @@ fn stdin() -> &'static Mutex<(std::collections::VecDeque<u8>, bool)> {
/// Append what was typed, from the caller's process memory.
#[no_mangle]
pub extern "C" fn k_stdin_write(at: u32, len: u32) -> i32 {
pub extern "C" fn k_stdin_write(at: gex_kmem::PAddr, len: u32) -> i32 {
let mut buf = vec![0u8; len as usize];
// SAFETY: the same copy every pipe write makes, and `pull_checked`
// refuses a range outside the process memory rather than reading it.
@@ -289,7 +289,7 @@ pub extern "C" fn k_stdin_ready() -> i32 {
/// Take up to `len` bytes into the caller's process memory.
#[no_mangle]
pub extern "C" fn k_stdin_read(at: u32, len: u32) -> i32 {
pub extern "C" fn k_stdin_read(at: gex_kmem::PAddr, len: u32) -> i32 {
let mut s = stdin().lock().unwrap_or_else(|e| e.into_inner());
let n = (len as usize).min(s.0.len());
if n == 0 {
@@ -334,7 +334,7 @@ fn fg_group() -> i32 {
/// group is what could only be done here, next to the process table. Returns
/// how many bytes of echo are queued, so the caller knows whether to drain.
#[no_mangle]
pub extern "C" fn k_tty_input(at: u32, len: u32) -> i32 {
pub extern "C" fn k_tty_input(at: gex_kmem::PAddr, len: u32) -> i32 {
let mut buf = vec![0u8; len as usize];
// SAFETY: the same copy `k_stdin_write` makes, and `pull_checked` refuses
// a range outside the caller's memory rather than reading it.
@@ -369,7 +369,7 @@ pub extern "C" fn k_tty_input(at: u32, len: u32) -> i32 {
/// Take what the terminal should display, into the caller's memory.
#[no_mangle]
pub extern "C" fn k_tty_echo(at: u32, cap: u32) -> i32 {
pub extern "C" fn k_tty_echo(at: gex_kmem::PAddr, cap: u32) -> i32 {
let mut q = echoed().lock().unwrap_or_else(|e| e.into_inner());
let n = (cap as usize).min(q.len());
if n == 0 {
@@ -382,7 +382,7 @@ pub extern "C" fn k_tty_echo(at: u32, cap: u32) -> i32 {
}
#[no_mangle]
pub extern "C" fn k_tty_get_termios(at: u32, len: u32) -> i32 {
pub extern "C" fn k_tty_get_termios(at: gex_kmem::PAddr, len: u32) -> i32 {
let t = tty().termios();
let n = (len as usize).min(t.len());
unsafe { gex_kmem::push_checked(at, t.as_ptr(), n as u32) };
@@ -390,7 +390,7 @@ pub extern "C" fn k_tty_get_termios(at: u32, len: u32) -> i32 {
}
#[no_mangle]
pub extern "C" fn k_tty_set_termios(at: u32, len: u32) -> i32 {
pub extern "C" fn k_tty_set_termios(at: gex_kmem::PAddr, len: u32) -> i32 {
let mut buf = vec![0u8; (len as usize).min(60)];
let n = buf.len() as u32;
unsafe { gex_kmem::pull_checked(buf.as_mut_ptr(), at, n) };
@@ -399,7 +399,7 @@ pub extern "C" fn k_tty_set_termios(at: u32, len: u32) -> i32 {
}
#[no_mangle]
pub extern "C" fn k_tty_get_locked(at: u32, len: u32) -> i32 {
pub extern "C" fn k_tty_get_locked(at: gex_kmem::PAddr, len: u32) -> i32 {
let t = tty().locked_termios();
let n = (len as usize).min(t.len());
unsafe { gex_kmem::push_checked(at, t.as_ptr(), n as u32) };
@@ -407,7 +407,7 @@ pub extern "C" fn k_tty_get_locked(at: u32, len: u32) -> i32 {
}
#[no_mangle]
pub extern "C" fn k_tty_set_locked(at: u32, len: u32) -> i32 {
pub extern "C" fn k_tty_set_locked(at: gex_kmem::PAddr, len: u32) -> i32 {
let mut buf = vec![0u8; (len as usize).min(60)];
let n = buf.len() as u32;
unsafe { gex_kmem::pull_checked(buf.as_mut_ptr(), at, n) };
@@ -532,7 +532,7 @@ pub extern "C" fn k_tty_queued() -> i32 {
/// go through the discipline, because that is what "as though typed" means --
/// a `\n` pushed this way completes a line.
#[no_mangle]
pub extern "C" fn k_tty_push(at: u32, len: u32) -> i32 {
pub extern "C" fn k_tty_push(at: gex_kmem::PAddr, len: u32) -> i32 {
k_tty_input(at, len)
}
@@ -689,10 +689,10 @@ fn modules() -> &'static Mutex<ModStore> {
/// memory, so the bytes must be pulled across rather than read directly.
#[no_mangle]
pub unsafe extern "C" fn k_mod_store(
ptr: u32,
ptr: gex_kmem::PAddr,
len: u32,
slots: u64,
dirs_ptr: u32,
dirs_ptr: gex_kmem::PAddr,
dirs_len: u32,
) -> u32 {
let mut m = modules().lock().unwrap_or_else(|e| e.into_inner());
@@ -855,7 +855,7 @@ pub extern "C" fn k_mod_dirs_len(id: u32) -> u32 {
///
/// SAFETY: `out` and `cap` describe a range of the calling process's memory.
#[no_mangle]
pub unsafe extern "C" fn k_mod_read(id: u32, out: u32, cap: u32) -> u32 {
pub unsafe extern "C" fn k_mod_read(id: u32, out: gex_kmem::PAddr, cap: u32) -> u32 {
let m = modules().lock().unwrap_or_else(|e| e.into_inner());
let Some(sm) = m.mods.get((id as usize).wrapping_sub(1)) else {
return 0;
@@ -871,7 +871,7 @@ pub unsafe extern "C" fn k_mod_read(id: u32, out: u32, cap: u32) -> u32 {
///
/// SAFETY: as above.
#[no_mangle]
pub unsafe extern "C" fn k_mod_dirs_read(id: u32, out: u32, cap: u32) -> u32 {
pub unsafe extern "C" fn k_mod_dirs_read(id: u32, out: gex_kmem::PAddr, cap: u32) -> u32 {
let m = modules().lock().unwrap_or_else(|e| e.into_inner());
let Some(sm) = m.mods.get((id as usize).wrapping_sub(1)) else {
return 0;
@@ -1004,7 +1004,7 @@ fn snaps() -> &'static Mutex<Snaps> {
/// SAFETY: `ptr` names the calling process's memory, pulled across not read. See
/// `k_mod_store`.
#[no_mangle]
pub unsafe extern "C" fn k_snap_store(key: u64, ptr: u32, len: u32) {
pub unsafe extern "C" fn k_snap_store(key: u64, ptr: gex_kmem::PAddr, len: u32) {
let mut s = snaps().lock().unwrap_or_else(|e| e.into_inner());
if key == 0 || len == 0 || s.n == SNAP_MAX {
return;
@@ -1043,7 +1043,7 @@ pub extern "C" fn k_snap_len(key: u64) -> u32 {
/// Copy the linked image of `key` into the caller's memory. Returns how many
/// bytes were written.
#[no_mangle]
pub unsafe extern "C" fn k_snap_get(key: u64, at: u32, cap: u32) -> u32 {
pub unsafe extern "C" fn k_snap_get(key: u64, at: gex_kmem::PAddr, cap: u32) -> u32 {
let s = snaps().lock().unwrap_or_else(|e| e.into_inner());
let Some(&(_, off, len)) = s.at[..s.n].iter().find(|(k, _, _)| *k == key) else {
return 0;
@@ -1370,7 +1370,7 @@ pub extern "C" fn k_pipe_new(pid: i32) -> i32 {
/// The last message written to a pipe, into the caller's memory. A diagnostic;
/// see `Pipe::last_message`.
#[no_mangle]
pub extern "C" fn k_pipe_last_message(id: i32, at: u32, cap: u32) -> i32 {
pub extern "C" fn k_pipe_last_message(id: i32, at: gex_kmem::PAddr, cap: u32) -> i32 {
let Some(p) = pipe_at(id) else { return -1 };
let text = p.last_message();
let n = text.len().min(cap as usize);
@@ -1428,7 +1428,7 @@ pub extern "C" fn k_pipe_close_end(id: i32, write: i32, pid: i32) {
/// out first (this module cannot address the process's memory) -- which is what
/// lets the two ends live in different address spaces. Returns bytes or -errno.
#[no_mangle]
pub extern "C" fn k_pipe_write(id: i32, at: u32, len: u32) -> i32 {
pub extern "C" fn k_pipe_write(id: i32, at: gex_kmem::PAddr, len: u32) -> i32 {
let Some(p) = pipe_at(id) else { return -9 }; // EBADF
let mut buf = vec![0u8; len as usize];
unsafe { gex_kmem::pull_checked(buf.as_mut_ptr(), at, len) };
@@ -1440,7 +1440,7 @@ pub extern "C" fn k_pipe_write(id: i32, at: u32, len: u32) -> i32 {
/// Read up to `len` bytes into the caller's memory. See [`k_pipe_write`].
#[no_mangle]
pub extern "C" fn k_pipe_read(id: i32, at: u32, len: u32) -> i32 {
pub extern "C" fn k_pipe_read(id: i32, at: gex_kmem::PAddr, len: u32) -> i32 {
let Some(p) = pipe_at(id) else { return -9 };
let mut buf = vec![0u8; len as usize];
match p.read(&mut buf) {
@@ -1696,7 +1696,7 @@ fn last_slave_closer() -> &'static Mutex<BTreeMap<i32, i32>> {
/// terminal input: the line discipline runs and `^C`-style signals reach the
/// foreground group. On the slave it is program output. Returns bytes or -errno.
#[no_mangle]
pub extern "C" fn k_pty_write(id: i32, master: i32, at: u32, len: u32) -> i32 {
pub extern "C" fn k_pty_write(id: i32, master: i32, at: gex_kmem::PAddr, len: u32) -> i32 {
let Some(p) = pty_at(id) else { return -9 }; // EBADF
let mut buf = vec![0u8; len as usize];
unsafe { gex_kmem::pull_checked(buf.as_mut_ptr(), at, len) };
@@ -1715,7 +1715,7 @@ pub extern "C" fn k_pty_write(id: i32, master: i32, at: u32, len: u32) -> i32 {
/// Read up to `len` bytes from an end into the caller's memory.
#[no_mangle]
pub extern "C" fn k_pty_read(id: i32, master: i32, at: u32, len: u32) -> i32 {
pub extern "C" fn k_pty_read(id: i32, master: i32, at: gex_kmem::PAddr, len: u32) -> i32 {
let Some(p) = pty_at(id) else { return -9 };
let mut buf = vec![0u8; len as usize];
let r = if master != 0 {
@@ -1786,7 +1786,7 @@ pub extern "C" fn k_pty_set_lock(id: i32, locked: i32) {
}
#[no_mangle]
pub extern "C" fn k_pty_get_termios(id: i32, at: u32, len: u32) -> i32 {
pub extern "C" fn k_pty_get_termios(id: i32, at: gex_kmem::PAddr, len: u32) -> i32 {
let Some(p) = pty_at(id) else { return -1 };
let t = p.tty.termios();
let n = (len as usize).min(t.len());
@@ -1795,7 +1795,7 @@ pub extern "C" fn k_pty_get_termios(id: i32, at: u32, len: u32) -> i32 {
}
#[no_mangle]
pub extern "C" fn k_pty_set_termios(id: i32, at: u32, len: u32, flush: i32) -> i32 {
pub extern "C" fn k_pty_set_termios(id: i32, at: gex_kmem::PAddr, len: u32, flush: i32) -> i32 {
let Some(p) = pty_at(id) else { return -1 };
let mut buf = vec![0u8; len as usize];
unsafe { gex_kmem::pull_checked(buf.as_mut_ptr(), at, len) };
@@ -1877,7 +1877,7 @@ pub extern "C" fn k_pty_flush(id: i32, which: i32) {
/// Copy `len` bytes of the calling process's memory in, and report their sum. A
/// live check that the two-memory pull path works.
#[no_mangle]
pub extern "C" fn k_checksum(at: u32, len: u32) -> u32 {
pub extern "C" fn k_checksum(at: gex_kmem::PAddr, len: u32) -> u32 {
let n = (len as usize).min(4096);
let buf = unsafe { &mut *core::ptr::addr_of_mut!(SCRATCH) };
unsafe { gex_kmem::pull_checked(buf.as_mut_ptr(), at, n as u32) };
@@ -1933,7 +1933,7 @@ fn handle_at(id: i32) -> Option<std::sync::Arc<dyn FileHandle>> {
/// A path argument, copied out of the caller's memory (this module cannot
/// dereference an address in it).
fn path_of(at: u32, len: u32) -> String {
fn path_of(at: gex_kmem::PAddr, len: u32) -> String {
let mut buf = vec![0u8; len as usize];
if len > 0 {
unsafe { gex_kmem::pull_checked(buf.as_mut_ptr(), at, len) };
@@ -1962,7 +1962,7 @@ fn as_unit(r: Result<(), gex_kernel::errno::Errno>) -> i32 {
/// Open a path, returning a handle id or a negated errno.
#[no_mangle]
pub extern "C" fn k_fs_open(path: u32, path_len: u32, flags: u64, mode: u32) -> i32 {
pub extern "C" fn k_fs_open(path: gex_kmem::PAddr, path_len: u32, flags: u64, mode: u32) -> i32 {
let p = path_of(path, path_len);
match rootfs().open(&p, flags, mode) {
Ok(h) => {
@@ -2019,7 +2019,7 @@ pub extern "C" fn k_fs_close(handle: i32) {
/// Read into the caller's memory. Returns bytes read, or a negated errno.
#[no_mangle]
pub extern "C" fn k_fs_read(handle: i32, offset: u64, at: u32, len: u32) -> i32 {
pub extern "C" fn k_fs_read(handle: i32, offset: u64, at: gex_kmem::PAddr, len: u32) -> i32 {
let Some(h) = handle_at(handle) else { return -9 };
let mut buf = vec![0u8; len as usize];
match h.read_at(offset, &mut buf) {
@@ -2035,7 +2035,7 @@ pub extern "C" fn k_fs_read(handle: i32, offset: u64, at: u32, len: u32) -> i32
/// Write from the caller's memory.
#[no_mangle]
pub extern "C" fn k_fs_write(handle: i32, offset: u64, at: u32, len: u32) -> i32 {
pub extern "C" fn k_fs_write(handle: i32, offset: u64, at: gex_kmem::PAddr, len: u32) -> i32 {
let Some(h) = handle_at(handle) else { return -9 };
let mut buf = vec![0u8; len as usize];
if len > 0 {
@@ -2046,7 +2046,7 @@ pub extern "C" fn k_fs_write(handle: i32, offset: u64, at: u32, len: u32) -> i32
/// This handle's metadata, as `remote::META_BYTES` bytes at `at`.
#[no_mangle]
pub extern "C" fn k_fs_meta(handle: i32, at: u32) -> i32 {
pub extern "C" fn k_fs_meta(handle: i32, at: gex_kmem::PAddr) -> i32 {
let Some(h) = handle_at(handle) else { return -9 };
match h.metadata() {
Ok(m) => {
@@ -2066,7 +2066,7 @@ pub extern "C" fn k_fs_truncate(handle: i32, len: u64) -> i32 {
/// A directory listing, or `-ERANGE` when `cap` will not hold it.
#[no_mangle]
pub extern "C" fn k_fs_read_dir(handle: i32, at: u32, cap: u32) -> i32 {
pub extern "C" fn k_fs_read_dir(handle: i32, at: gex_kmem::PAddr, cap: u32) -> i32 {
let Some(h) = handle_at(handle) else { return -9 };
match h.read_dir() {
Ok(entries) => {
@@ -2086,7 +2086,7 @@ pub extern "C" fn k_fs_read_dir(handle: i32, at: u32, cap: u32) -> i32 {
/// A path's metadata, without opening it -- which matters for a symlink and
/// for anything an open would refuse.
#[no_mangle]
pub extern "C" fn k_fs_path_meta(path: u32, path_len: u32, at: u32) -> i32 {
pub extern "C" fn k_fs_path_meta(path: gex_kmem::PAddr, path_len: u32, at: gex_kmem::PAddr) -> i32 {
let p = path_of(path, path_len);
match rootfs().metadata(&p) {
Ok(m) => {
@@ -2099,7 +2099,7 @@ pub extern "C" fn k_fs_path_meta(path: u32, path_len: u32, at: u32) -> i32 {
}
#[no_mangle]
pub extern "C" fn k_fs_read_link(path: u32, path_len: u32, at: u32, cap: u32) -> i32 {
pub extern "C" fn k_fs_read_link(path: gex_kmem::PAddr, path_len: u32, at: gex_kmem::PAddr, cap: u32) -> i32 {
let p = path_of(path, path_len);
match rootfs().read_link(&p) {
Ok(t) => {
@@ -2117,33 +2117,33 @@ pub extern "C" fn k_fs_read_link(path: u32, path_len: u32, at: u32, cap: u32) ->
}
#[no_mangle]
pub extern "C" fn k_fs_unlink(path: u32, path_len: u32) -> i32 {
pub extern "C" fn k_fs_unlink(path: gex_kmem::PAddr, path_len: u32) -> i32 {
as_unit(rootfs().unlink(&path_of(path, path_len)))
}
#[no_mangle]
pub extern "C" fn k_fs_mkdir(path: u32, path_len: u32, mode: u32) -> i32 {
pub extern "C" fn k_fs_mkdir(path: gex_kmem::PAddr, path_len: u32, mode: u32) -> i32 {
as_unit(rootfs().mkdir(&path_of(path, path_len), mode))
}
#[no_mangle]
pub extern "C" fn k_fs_rmdir(path: u32, path_len: u32) -> i32 {
pub extern "C" fn k_fs_rmdir(path: gex_kmem::PAddr, path_len: u32) -> i32 {
as_unit(rootfs().rmdir(&path_of(path, path_len)))
}
#[no_mangle]
pub extern "C" fn k_fs_rename(from: u32, from_len: u32, to: u32, to_len: u32) -> i32 {
pub extern "C" fn k_fs_rename(from: gex_kmem::PAddr, from_len: u32, to: gex_kmem::PAddr, to_len: u32) -> i32 {
as_unit(rootfs().rename(&path_of(from, from_len), &path_of(to, to_len)))
}
#[no_mangle]
pub extern "C" fn k_fs_chmod(path: u32, path_len: u32, mode: u32) -> i32 {
pub extern "C" fn k_fs_chmod(path: gex_kmem::PAddr, path_len: u32, mode: u32) -> i32 {
as_unit(rootfs().chmod(&path_of(path, path_len), mode))
}
/// A path's modification time: `utimensat`, or a bundle's file times.
#[no_mangle]
pub extern "C" fn k_fs_set_mtime(path: u32, path_len: u32, secs: i64) -> i32 {
pub extern "C" fn k_fs_set_mtime(path: gex_kmem::PAddr, path_len: u32, secs: i64) -> i32 {
as_unit(rootfs().set_mtime(&path_of(path, path_len), secs))
}
@@ -2160,12 +2160,12 @@ pub extern "C" fn k_fs_touch(handle: i32, secs: i64) -> i32 {
/// absolute-path target says, which would then resolve against the link's own
/// directory and reach nothing).
#[no_mangle]
pub extern "C" fn k_fs_symlink(target: u32, target_len: u32, path: u32, path_len: u32) -> i32 {
pub extern "C" fn k_fs_symlink(target: gex_kmem::PAddr, target_len: u32, path: gex_kmem::PAddr, path_len: u32) -> i32 {
as_unit(rootfs().symlink(&path_of_raw(target, target_len), &path_of(path, path_len)))
}
#[no_mangle]
pub extern "C" fn k_fs_link(from: u32, from_len: u32, to: u32, to_len: u32) -> i32 {
pub extern "C" fn k_fs_link(from: gex_kmem::PAddr, from_len: u32, to: gex_kmem::PAddr, to_len: u32) -> i32 {
as_unit(rootfs().link(&path_of(from, from_len), &path_of(to, to_len)))
}
@@ -2173,7 +2173,7 @@ pub extern "C" fn k_fs_link(from: u32, from_len: u32, to: u32, to_len: u32) -> i
/// to: Xvfb binds `/tmp/.X11-unix/X0` and its start script waits for that path
/// to exist before running a client.
#[no_mangle]
pub extern "C" fn k_fs_mksocket(path: u32, path_len: u32) -> i32 {
pub extern "C" fn k_fs_mksocket(path: gex_kmem::PAddr, path_len: u32) -> i32 {
rootfs().add_socket(&path_of(path, path_len));
0
}
@@ -2181,7 +2181,7 @@ pub extern "C" fn k_fs_mksocket(path: u32, path_len: u32) -> i32 {
/// Put a file into the shared root before anything runs -- how a page's bundle
/// gets in, once, rather than through one process only it could then see.
#[no_mangle]
pub extern "C" fn k_fs_add(path: u32, path_len: u32, at: u32, len: u32) -> i32 {
pub extern "C" fn k_fs_add(path: gex_kmem::PAddr, path_len: u32, at: gex_kmem::PAddr, len: u32) -> i32 {
let p = path_of(path, path_len);
let mut buf = vec![0u8; len as usize];
if len > 0 {
@@ -2256,7 +2256,7 @@ fn keep(s: std::sync::Arc<UnixSocket>) -> i32 {
/// Bind and listen in one: a remote socket has no state between `bind` and
/// `listen` worth modelling. Returns a listener id, or a negated errno.
#[no_mangle]
pub extern "C" fn k_sock_bind(path: u32, path_len: u32) -> i32 {
pub extern "C" fn k_sock_bind(path: gex_kmem::PAddr, path_len: u32) -> i32 {
let p = path_of_raw(path, path_len);
let s = std::sync::Arc::new(UnixSocket::new(true));
if let Err(e) = s.bind(&p) {
@@ -2273,7 +2273,7 @@ pub extern "C" fn k_sock_bind(path: u32, path_len: u32) -> i32 {
/// Connect to a name. Returns this end's id, or a negated errno.
#[no_mangle]
pub extern "C" fn k_sock_connect(path: u32, path_len: u32) -> i32 {
pub extern "C" fn k_sock_connect(path: gex_kmem::PAddr, path_len: u32) -> i32 {
let p = path_of_raw(path, path_len);
let s = std::sync::Arc::new(UnixSocket::new(true));
match names().connect(&s, &p) {
@@ -2315,7 +2315,7 @@ pub extern "C" fn k_sock_pending(listener: i32) -> i32 {
/// Read into the caller's memory. The bytes cross by `memory.copy`, which is
/// what makes two ends in two address spaces possible at all.
#[no_mangle]
pub extern "C" fn k_sock_read(id: i32, at: u32, len: u32) -> i32 {
pub extern "C" fn k_sock_read(id: i32, at: gex_kmem::PAddr, len: u32) -> i32 {
let Some(s) = sock_at(id) else { return -9 };
let mut buf = vec![0u8; len as usize];
match s.recv(&mut buf, 0, false) {
@@ -2330,7 +2330,7 @@ pub extern "C" fn k_sock_read(id: i32, at: u32, len: u32) -> i32 {
}
#[no_mangle]
pub extern "C" fn k_sock_write(id: i32, at: u32, len: u32) -> i32 {
pub extern "C" fn k_sock_write(id: i32, at: gex_kmem::PAddr, len: u32) -> i32 {
let Some(s) = sock_at(id) else { return -9 };
let mut buf = vec![0u8; len as usize];
if len > 0 {
@@ -2385,14 +2385,14 @@ pub extern "C" fn k_sock_shutdown(id: i32) {
/// Whether anything is listening on a name -- which is what a client that
/// looks before it leaps is asking. Xlib does; Mesa does not.
#[no_mangle]
pub extern "C" fn k_sock_exists(path: u32, path_len: u32) -> i32 {
pub extern "C" fn k_sock_exists(path: gex_kmem::PAddr, path_len: u32) -> i32 {
names().exists(&path_of_raw(path, path_len)) as i32
}
/// A path exactly as the caller wrote it. A socket name is compared for
/// equality, never looked up, so the leading slash must survive -- trimming one
/// end's would make two different names.
fn path_of_raw(at: u32, len: u32) -> String {
fn path_of_raw(at: gex_kmem::PAddr, len: u32) -> String {
let mut buf = vec![0u8; len as usize];
if len > 0 {
unsafe { gex_kmem::pull_checked(buf.as_mut_ptr(), at, len) };
+76
View File
@@ -0,0 +1,76 @@
/* I/O into buffers above 4 GiB (docs/mem64.md step 6): a file, a pipe and
* a socketpair read into and written from anonymous memory in the high arena.
* In the browser these bytes cross between the process's shim and the global
* kernel module by address, which was 32-bit before the wasm64 shim. Under
* `gexc run --mem64` (run.sh passes it for mem64-*); with a wasm32 memory, or
* natively, the buffers are low and the output is the same. x86-64 only. */
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <unistd.h>
#define LEN 100000
static unsigned sum(const unsigned char *p, size_t n) {
unsigned h = 2166136261u;
for (size_t i = 0; i < n; i++) h = (h ^ p[i]) * 16777619u;
return h;
}
static char *high(size_t n) {
char *p = mmap(NULL, n, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
return p == MAP_FAILED ? NULL : p;
}
int main(void) {
char *a = high(1 << 20), *b = high(1 << 20);
if (!a || !b) { puts("mmap failed"); return 1; }
for (int i = 0; i < LEN; i++) a[i] = (char)(i * 7 + i / 251);
unsigned want = sum((unsigned char *)a, LEN);
/* A file: write from a, read back into b. */
const char *path = "/tmp/mem64-io.bin";
int fd = open(path, O_CREAT | O_TRUNC | O_RDWR, 0644);
if (fd < 0) { puts("open failed"); return 1; }
if (write(fd, a, LEN) != LEN) { puts("file write short"); return 1; }
memset(b, 0, LEN);
if (pread(fd, b, LEN, 0) != LEN) { puts("file read short"); return 1; }
printf("file %s\n", sum((unsigned char *)b, LEN) == want ? "ok" : "MISMATCH");
close(fd);
unlink(path);
/* A pipe, in chunks the pipe buffer takes. */
int p[2];
if (pipe(p) != 0) { puts("pipe failed"); return 1; }
memset(b, 0, LEN);
size_t done = 0;
while (done < LEN) {
size_t n = LEN - done < 4096 ? LEN - done : 4096;
if (write(p[1], a + done, n) != (ssize_t)n) { puts("pipe write short"); return 1; }
size_t got = 0;
while (got < n) {
ssize_t r = read(p[0], b + done + got, n - got);
if (r <= 0) { puts("pipe read failed"); return 1; }
got += r;
}
done += n;
}
printf("pipe %s\n", sum((unsigned char *)b, LEN) == want ? "ok" : "MISMATCH");
/* A socketpair. */
int s[2];
if (socketpair(AF_UNIX, SOCK_STREAM, 0, s) != 0) { puts("socketpair failed"); return 1; }
memset(b, 0, 4096);
if (write(s[0], a, 4096) != 4096) { puts("socket write short"); return 1; }
size_t got = 0;
while (got < 4096) {
ssize_t r = read(s[1], b + got, 4096 - got);
if (r <= 0) { puts("socket read failed"); return 1; }
got += r;
}
printf("socket %s\n", sum((unsigned char *)b, 4096) == sum((unsigned char *)a, 4096) ? "ok" : "MISMATCH");
return 0;
}