feat: add no-std common runtime

Add an alloc-backed common library with direct platform I/O.

Build a host attribute macro for async entry points and keep runtime
boilerplate outside application code. Compile the library, macro, and
example directly with rustc through CMake.

Co-Authored-By: luna (openrouter/openai/gpt-5.6-luna): runtime work
This commit is contained in:
2026-09-05 04:48:21 +02:00
parent 54051d4068
commit 812278a22f
8 changed files with 412 additions and 161 deletions
+61
View File
@@ -0,0 +1,61 @@
use core::fmt;
#[cfg(unix)]
unsafe extern "C" {
fn write(fd: i32, buffer: *const u8, count: usize) -> isize;
}
#[cfg(windows)]
unsafe extern "system" {
fn GetStdHandle(which: u32) -> *mut u8;
fn WriteFile(
handle: *mut u8,
buffer: *const u8,
count: u32,
written: *mut u32,
overlapped: *mut u8,
) -> i32;
}
pub struct Stdout;
impl fmt::Write for Stdout {
fn write_str(&mut self, message: &str) -> fmt::Result {
unsafe {
#[cfg(unix)]
{
if write(1, message.as_ptr(), message.len()) < 0 {
return Err(fmt::Error);
}
}
#[cfg(windows)]
{
let handle = GetStdHandle(0xffff_fff5); // STD_OUTPUT_HANDLE
let mut written: u32 = 0;
if WriteFile(
handle,
message.as_ptr(),
message.len() as u32,
&mut written,
0 as *mut u8,
) == 0
{
return Err(fmt::Error);
}
}
}
Ok(())
}
}
pub fn write_str(message: &str) -> fmt::Result {
let mut stdout = Stdout;
fmt::Write::write_str(&mut stdout, message)
}
pub fn print(arguments: fmt::Arguments<'_>) -> fmt::Result {
let mut stdout = Stdout;
fmt::Write::write_fmt(&mut stdout, arguments)
}
+56
View File
@@ -0,0 +1,56 @@
#![no_std]
extern crate alloc;
pub mod io;
pub mod runtime;
pub use alloc::vec;
pub use alloc::vec::Vec;
pub use core::result::Result;
#[macro_export]
macro_rules! print {
($($arg:tt)*) => {{
let _ = $crate::io::print(core::format_args!($($arg)*));
}};
}
#[macro_export]
macro_rules! println {
() => {
$crate::print!("\n")
};
($($arg:tt)*) => {{
$crate::print!($($arg)*);
$crate::print!("\n");
}};
}
#[macro_export]
macro_rules! entry {
($main:path) => {
#[global_allocator]
static RSLIBC_ALLOCATOR: $crate::runtime::SystemAllocator =
$crate::runtime::SystemAllocator;
#[panic_handler]
fn rslibc_panic(_: &core::panic::PanicInfo<'_>) -> ! {
loop {}
}
#[cfg(unix)]
#[no_mangle]
pub extern "C" fn main() -> i32 {
$main();
0
}
#[cfg(windows)]
#[no_mangle]
pub extern "system" fn mainCRTStartup() -> ! {
$main();
$crate::runtime::exit(0)
}
};
}
+103
View File
@@ -0,0 +1,103 @@
use core::alloc::{GlobalAlloc, Layout};
use core::future::Future;
use core::pin::Pin;
use core::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
#[cfg(unix)]
unsafe extern "C" {
fn malloc(size: usize) -> *mut u8;
fn free(pointer: *mut u8);
}
#[cfg(windows)]
unsafe extern "system" {
fn GetProcessHeap() -> *mut u8;
fn HeapAlloc(heap: *mut u8, flags: u32, bytes: usize) -> *mut u8;
fn HeapFree(heap: *mut u8, flags: u32, pointer: *mut u8) -> i32;
fn ExitProcess(status: u32) -> !;
}
pub struct SystemAllocator;
// alloc's CString support references the C ABI strlen symbol. Keep this
// compatibility shim local so the runtime only needs VCRUNTIME's low-level
// allocation helpers instead of the full UCRT.
#[no_mangle]
unsafe extern "C" fn strlen(mut pointer: *const u8) -> usize {
let mut length = 0;
while *pointer != 0 {
pointer = pointer.add(1);
length += 1;
}
length
}
unsafe impl GlobalAlloc for SystemAllocator {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
if layout.size() == 0 {
return layout.align() as *mut u8;
}
#[cfg(unix)]
{
malloc(layout.size())
}
#[cfg(windows)]
{
HeapAlloc(GetProcessHeap(), 0, layout.size())
}
}
unsafe fn dealloc(&self, pointer: *mut u8, layout: Layout) {
if layout.size() == 0 {
return;
}
#[cfg(unix)]
{
free(pointer);
}
#[cfg(windows)]
{
HeapFree(GetProcessHeap(), 0, pointer);
}
}
}
#[cfg(windows)]
pub fn exit(status: u32) -> ! {
unsafe { ExitProcess(status) }
}
fn clone_waker(_: *const ()) -> RawWaker {
RawWaker::new(core::ptr::null(), &WAKER_VTABLE)
}
unsafe fn wake_waker(_: *const ()) {}
unsafe fn wake_by_ref_waker(_: *const ()) {}
unsafe fn drop_waker(_: *const ()) {}
static WAKER_VTABLE: RawWakerVTable = RawWakerVTable::new(
clone_waker,
wake_waker,
wake_by_ref_waker,
drop_waker,
);
pub fn block_on<F>(mut future: F)
where
F: Future<Output = ()>,
{
let waker = unsafe { Waker::from_raw(clone_waker(core::ptr::null())) };
let mut context = Context::from_waker(&waker);
let mut future = unsafe { Pin::new_unchecked(&mut future) };
loop {
match future.as_mut().poll(&mut context) {
Poll::Ready(()) => return,
Poll::Pending => core::hint::spin_loop(),
}
}
}