-
Notifications
You must be signed in to change notification settings - Fork 0
Add vibix_abi crate with syscall macro, GlobalAlloc, errno, and stdio #862
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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(); | ||
| } | ||
|
|
||
| // 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 | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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() | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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) } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.