Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ members = [
"userspace/pjdfstest_runner",
"userspace/repro_fork",
"userspace/shell_pipeline",
"userspace/vibix_abi",
]
# tests/pjdfstest is a vendored copy of saidsay-so/pjdfstest (2-clause BSD). It
# is intentionally kept outside the workspace until #581 wires it into xtask —
Expand Down
10 changes: 10 additions & 0 deletions userspace/vibix_abi/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[package]
name = "vibix_abi"
version.workspace = true
edition.workspace = true
authors.workspace = true
license.workspace = true

[lib]
name = "vibix_abi"
path = "src/lib.rs"
114 changes: 114 additions & 0 deletions userspace/vibix_abi/src/alloc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
//! Global allocator for vibix userspace.
//!
//! Uses `brk` for small allocations (< 128 KiB) and `mmap` for large ones.
//! This is the allocator that std's `System` allocator delegates to on vibix.

use core::alloc::{GlobalAlloc, Layout};
use core::ptr;
use core::sync::atomic::{AtomicUsize, Ordering};

use crate::syscall;

/// Syscall numbers (Linux x86_64 convention).
const SYS_BRK: u64 = 12;
const SYS_MMAP: u64 = 9;
const SYS_MUNMAP: u64 = 11;

/// Allocations at or above this size use mmap instead of brk.
const MMAP_THRESHOLD: usize = 128 * 1024;

/// mmap protection and flag constants.
const PROT_READ: u64 = 0x1;
const PROT_WRITE: u64 = 0x2;
const MAP_PRIVATE: u64 = 0x02;
const MAP_ANONYMOUS: u64 = 0x20;

/// A simple bump allocator backed by `brk` for small allocations.
///
/// Large allocations (>= MMAP_THRESHOLD) go directly to `mmap` so they can be
/// individually `munmap`'d without fragmenting the brk region.
pub struct VibixAllocator;

/// Current brk pointer. Initialized lazily on first allocation.
static BRK_CURRENT: AtomicUsize = AtomicUsize::new(0);

/// Initialize the brk region by querying the current program break.
fn brk_init() -> usize {
let current = unsafe { syscall::syscall1(SYS_BRK, 0) } as usize;
BRK_CURRENT.store(current, Ordering::Relaxed);
current
}

unsafe impl GlobalAlloc for VibixAllocator {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
let size = layout.size();
let align = layout.align();

if size >= MMAP_THRESHOLD {
return mmap_alloc(size);
}

// Bump-allocate from the brk region.
loop {
let mut current = BRK_CURRENT.load(Ordering::Relaxed);
if current == 0 {
current = brk_init();
}

// Align up.
let aligned = (current + align - 1) & !(align - 1);
let new_brk = aligned + size;

// Extend the program break.
let result = unsafe { syscall::syscall1(SYS_BRK, new_brk as u64) } as usize;
if result < new_brk {
// brk failed -- cannot grow the heap. Return null (OOM).
// We intentionally do NOT fall back to mmap here because
// dealloc() uses the size threshold to decide whether to
// munmap(); a small mmap'd block would never be freed.
return ptr::null_mut();
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

// Try to commit our bump. If another thread raced us, retry.
match BRK_CURRENT.compare_exchange(
current,
new_brk,
Ordering::AcqRel,
Ordering::Relaxed,
) {
Ok(_) => return aligned as *mut u8,
Err(_) => continue,
}
}
}

unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
let size = layout.size();
if size >= MMAP_THRESHOLD {
unsafe {
syscall::syscall2(SYS_MUNMAP, ptr as u64, size as u64);
}
}
// Small allocations from brk are not individually freed (bump allocator).
}
}

/// Allocate via anonymous mmap.
fn mmap_alloc(size: usize) -> *mut u8 {
let ret = unsafe {
syscall::syscall6(
SYS_MMAP,
0, // addr (kernel chooses)
size as u64, // length
PROT_READ | PROT_WRITE, // prot
MAP_PRIVATE | MAP_ANONYMOUS, // flags
u64::MAX, // fd (-1)
0, // offset
)
};
// mmap returns MAP_FAILED (typically -1..-4095) on error.
if ret < 0 && ret > -4096 {
return ptr::null_mut();
}
ret as *mut u8
}
19 changes: 19 additions & 0 deletions userspace/vibix_abi/src/errno.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//! Thread-local errno storage.
//!
//! Each task gets its own `ERRNO` cell via TLS (epic #827). The kernel
//! allocates a fresh TLS block per task and sets `MSR_FS_BASE` to the TCB
//! pointer, so the compiler's `%fs:`-relative accesses work out of the box.

use core::cell::Cell;

/// Per-thread errno value. Syscall wrappers store the positive error code
/// here when a raw syscall returns a negative value.
#[thread_local]
pub static ERRNO: Cell<i32> = Cell::new(0);

/// C-ABI-compatible accessor for errno's address. This is what the libc
/// crate's `__errno_location` resolves to.
#[no_mangle]
pub extern "C" fn __errno_location() -> *mut i32 {
ERRNO.as_ptr()
}
13 changes: 13 additions & 0 deletions userspace/vibix_abi/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//! `vibix_abi` -- the Rust ABI bridge between std's platform abstraction layer
//! and vibix syscalls.
//!
//! This crate provides the syscall macro, memory allocator, errno TLS, and
//! stdio wrappers that the std PAL calls into.

#![no_std]
#![feature(thread_local)]

pub mod alloc;
pub mod errno;
pub mod stdio;
pub mod syscall;
42 changes: 42 additions & 0 deletions userspace/vibix_abi/src/stdio.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//! Standard I/O helpers for vibix userspace.
//!
//! Provides `write_stdout` and `write_stderr` using the `writev` syscall
//! (nr 20), which is the vectored-write primitive that std's `Stdout`/`Stderr`
//! implementations call through.

use crate::syscall;

/// `writev` syscall number (Linux x86_64).
const SYS_WRITEV: u64 = 20;

/// Standard file descriptors.
const STDOUT_FD: u64 = 1;
const STDERR_FD: u64 = 2;

/// An iovec for vectored I/O, matching the Linux `struct iovec` layout.
#[repr(C)]
struct IoVec {
iov_base: *const u8,
iov_len: usize,
}

/// Write `buf` to stdout. Returns the number of bytes written, or a negative
/// errno on failure.
pub fn write_stdout(buf: &[u8]) -> i64 {
writev(STDOUT_FD, buf)
}

/// Write `buf` to stderr. Returns the number of bytes written, or a negative
/// errno on failure.
pub fn write_stderr(buf: &[u8]) -> i64 {
writev(STDERR_FD, buf)
}

/// Issue a `writev` syscall with a single iovec entry.
fn writev(fd: u64, buf: &[u8]) -> i64 {
let iov = IoVec {
iov_base: buf.as_ptr(),
iov_len: buf.len(),
};
unsafe { syscall::syscall3(SYS_WRITEV, fd, &iov as *const IoVec as u64, 1) }
}
175 changes: 175 additions & 0 deletions userspace/vibix_abi/src/syscall.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
//! Raw syscall interface for vibix.
//!
//! The `syscall!()` macro wraps inline x86_64 `syscall` assembly with the
//! correct clobber registers per the Linux x86_64 syscall convention (and
//! issue #531). The kernel does not preserve rdi/rsi/rdx/r8/r9/r10 across
//! a syscall, so every argument register is declared `inlateout` and rcx/r11
//! are clobbered by the CPU itself.

/// Issue a raw syscall. Returns the value left in `rax` (negative values
/// encode `-errno`).
///
/// # Safety
///
/// The caller must ensure the syscall number and arguments are valid.
#[macro_export]
macro_rules! syscall {
($nr:expr) => {
$crate::syscall::syscall0($nr as u64)
};
($nr:expr, $a0:expr) => {
$crate::syscall::syscall1($nr as u64, $a0 as u64)
};
($nr:expr, $a0:expr, $a1:expr) => {
$crate::syscall::syscall2($nr as u64, $a0 as u64, $a1 as u64)
};
($nr:expr, $a0:expr, $a1:expr, $a2:expr) => {
$crate::syscall::syscall3($nr as u64, $a0 as u64, $a1 as u64, $a2 as u64)
};
($nr:expr, $a0:expr, $a1:expr, $a2:expr, $a3:expr) => {
$crate::syscall::syscall4($nr as u64, $a0 as u64, $a1 as u64, $a2 as u64, $a3 as u64)
};
($nr:expr, $a0:expr, $a1:expr, $a2:expr, $a3:expr, $a4:expr) => {
$crate::syscall::syscall5(
$nr as u64, $a0 as u64, $a1 as u64, $a2 as u64, $a3 as u64, $a4 as u64,
)
};
($nr:expr, $a0:expr, $a1:expr, $a2:expr, $a3:expr, $a4:expr, $a5:expr) => {
$crate::syscall::syscall6(
$nr as u64, $a0 as u64, $a1 as u64, $a2 as u64, $a3 as u64, $a4 as u64, $a5 as u64,
)
};
}

#[inline(always)]
pub unsafe fn syscall0(nr: u64) -> i64 {
let ret: i64;
core::arch::asm!(
"syscall",
inlateout("rax") nr as i64 => ret,
lateout("rcx") _,
lateout("r11") _,
lateout("rdx") _,
lateout("rsi") _,
lateout("rdi") _,
lateout("r8") _,
lateout("r9") _,
lateout("r10") _,
options(nostack, preserves_flags),
);
ret
}

#[inline(always)]
pub unsafe fn syscall1(nr: u64, a0: u64) -> i64 {
let ret: i64;
core::arch::asm!(
"syscall",
inlateout("rax") nr as i64 => ret,
inlateout("rdi") a0 => _,
lateout("rcx") _,
lateout("r11") _,
lateout("rdx") _,
lateout("rsi") _,
lateout("r8") _,
lateout("r9") _,
lateout("r10") _,
options(nostack, preserves_flags),
);
ret
}

#[inline(always)]
pub unsafe fn syscall2(nr: u64, a0: u64, a1: u64) -> i64 {
let ret: i64;
core::arch::asm!(
"syscall",
inlateout("rax") nr as i64 => ret,
inlateout("rdi") a0 => _,
inlateout("rsi") a1 => _,
lateout("rcx") _,
lateout("r11") _,
lateout("rdx") _,
lateout("r8") _,
lateout("r9") _,
lateout("r10") _,
options(nostack, preserves_flags),
);
ret
}

#[inline(always)]
pub unsafe fn syscall3(nr: u64, a0: u64, a1: u64, a2: u64) -> i64 {
let ret: i64;
core::arch::asm!(
"syscall",
inlateout("rax") nr as i64 => ret,
inlateout("rdi") a0 => _,
inlateout("rsi") a1 => _,
inlateout("rdx") a2 => _,
lateout("rcx") _,
lateout("r11") _,
lateout("r8") _,
lateout("r9") _,
lateout("r10") _,
options(nostack, preserves_flags),
);
ret
}

#[inline(always)]
pub unsafe fn syscall4(nr: u64, a0: u64, a1: u64, a2: u64, a3: u64) -> i64 {
let ret: i64;
core::arch::asm!(
"syscall",
inlateout("rax") nr as i64 => ret,
inlateout("rdi") a0 => _,
inlateout("rsi") a1 => _,
inlateout("rdx") a2 => _,
inlateout("r10") a3 => _,
lateout("rcx") _,
lateout("r11") _,
lateout("r8") _,
lateout("r9") _,
options(nostack, preserves_flags),
);
ret
}

#[inline(always)]
pub unsafe fn syscall5(nr: u64, a0: u64, a1: u64, a2: u64, a3: u64, a4: u64) -> i64 {
let ret: i64;
core::arch::asm!(
"syscall",
inlateout("rax") nr as i64 => ret,
inlateout("rdi") a0 => _,
inlateout("rsi") a1 => _,
inlateout("rdx") a2 => _,
inlateout("r10") a3 => _,
inlateout("r8") a4 => _,
lateout("rcx") _,
lateout("r11") _,
lateout("r9") _,
options(nostack, preserves_flags),
);
ret
}

#[inline(always)]
pub unsafe fn syscall6(nr: u64, a0: u64, a1: u64, a2: u64, a3: u64, a4: u64, a5: u64) -> i64 {
let ret: i64;
core::arch::asm!(
"syscall",
inlateout("rax") nr as i64 => ret,
inlateout("rdi") a0 => _,
inlateout("rsi") a1 => _,
inlateout("rdx") a2 => _,
inlateout("r10") a3 => _,
inlateout("r8") a4 => _,
inlateout("r9") a5 => _,
lateout("rcx") _,
lateout("r11") _,
options(nostack, preserves_flags),
);
ret
}
Loading