Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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"
107 changes: 107 additions & 0 deletions userspace/vibix_abi/src/alloc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
//! 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 -- fall back to mmap.
return mmap_alloc(size);
}
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