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:
@@ -2,48 +2,69 @@
|
|||||||
|
|
||||||
## Project Overview
|
## Project Overview
|
||||||
|
|
||||||
This is a minimal Rust experiment that builds one executable without Cargo
|
This project is a small Rust common library and runtime built without Cargo.
|
||||||
and without linking Rust `core`, `alloc`, or `std`. CMake invokes `rustc`
|
CMake invokes `rustc` directly. The library provides a low-footprint subset
|
||||||
directly. The executable writes `Hello` to stdout and exits.
|
of standard-library-like functionality:
|
||||||
|
|
||||||
The project currently contains:
|
- `Result` and formatting from `core`
|
||||||
|
- `Vec` and `vec![]` from `alloc`
|
||||||
|
- project-local `print!` and `println!` macros
|
||||||
|
- direct platform stdout and heap calls
|
||||||
|
|
||||||
- `main.rs`: the `#![no_core]` Rust program
|
The project contains:
|
||||||
- `CMakeLists.txt`: the direct `rustc` custom command
|
|
||||||
- `.gitignore`: ignores generated build output
|
|
||||||
|
|
||||||
## Non-Negotiable No-Core Design
|
- `src/lib.rs`: the `#![no_std]` common library API
|
||||||
|
- `src/io.rs`: the `core::fmt::Write` stdout backend
|
||||||
|
- `src/runtime.rs`: allocator, async polling, and platform entry helpers
|
||||||
|
- `macros/src/lib.rs`: host-side `#[rslibc_macros::main]` attribute macro
|
||||||
|
- `runtime/main.rs`: minimal example entry point
|
||||||
|
- `CMakeLists.txt`: direct `rustc` library, macro, and executable commands
|
||||||
|
|
||||||
- Keep `#![no_core]` in `main.rs`.
|
## No-Std Design
|
||||||
- Do not replace `#![no_core]` with `#![no_std]`; `no_std` still links
|
|
||||||
Rust `core`.
|
- Keep `#![no_std]` in both the library and runtime.
|
||||||
- Do not add Cargo files or invoke Cargo.
|
- Do not add Rust `std` to the target library or runtime, and do not add
|
||||||
- Do not import or link `core`, `alloc`, or `std`.
|
Cargo unless explicitly requested.
|
||||||
- Keep the required language-item and marker-trait definitions local to
|
- The host-side procedural macro may use `proc_macro` and host libraries;
|
||||||
`main.rs` unless the user explicitly asks for a different design.
|
it is not linked into the target executable.
|
||||||
- Use primitive Rust types and explicit platform FFI instead of library
|
- `core` is allowed and is the foundation for `Result`, formatting, slices,
|
||||||
helpers.
|
and other basic language-library functionality.
|
||||||
- Keep unsafe code inside small safe wrappers. The public call site for
|
- `alloc` is allowed through `extern crate alloc`; it supplies `Vec` and
|
||||||
stdout should remain a normal string call such as:
|
related heap-backed types.
|
||||||
|
- The final runtime must provide its own global allocator and panic handler;
|
||||||
|
`rslibc::entry!` supplies them to an executable.
|
||||||
|
- Keep OS integration in small FFI modules. Do not pull in a general-purpose
|
||||||
|
runtime or C I/O layer for basic output.
|
||||||
|
- `rslibc_macros::main` can wrap `async fn main()` with the small `block_on`
|
||||||
|
executor. It is only a polling loop and does not provide Tokio-style I/O,
|
||||||
|
timers, or a reactor.
|
||||||
|
- Keep unsafe code inside small safe wrappers. Application code should be
|
||||||
|
able to use calls such as:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
write_stdout("Hello\\n");
|
let values = rslibc::vec![1, 2, 3];
|
||||||
|
rslibc::println!("values = {:?}", values);
|
||||||
```
|
```
|
||||||
|
|
||||||
## Output Implementation
|
## Platform Runtime
|
||||||
|
|
||||||
- Unix builds call the libc `write` function with file descriptor `1`.
|
- Unix stdout calls libc `write` with file descriptor `1`.
|
||||||
- Windows builds call `GetStdHandle`, `WriteFile`, and `ExitProcess`
|
- Windows stdout calls `GetStdHandle` and `WriteFile` through `kernel32`.
|
||||||
directly through `kernel32`.
|
- Windows allocation calls `HeapAlloc` and `HeapFree` through the system
|
||||||
- The Windows build must not link the C runtime. This keeps the executable
|
heap.
|
||||||
small and uses `/NODEFAULTLIB` with the `kernel32` import library.
|
- The Windows runtime dynamically links only the platform support needed by
|
||||||
- Preserve the safe `write_stdout(&str)` interface when changing output.
|
`alloc` and the compiler runtime. Do not statically link the full C
|
||||||
|
runtime.
|
||||||
|
- Keep `panic=abort` enabled.
|
||||||
|
|
||||||
## Build System
|
## Build System
|
||||||
|
|
||||||
CMake is configured with `LANGUAGES NONE` and calls `rustc` through an
|
CMake is configured with `LANGUAGES NONE` and invokes `rustc` through three
|
||||||
`add_custom_command`. `find_program(RUSTC rustc REQUIRED)` locates the
|
custom commands:
|
||||||
compiler.
|
|
||||||
|
1. Compile `macros/src/lib.rs` as a host procedural macro.
|
||||||
|
2. Compile `src/lib.rs` and its modules to `librslibc.rlib`.
|
||||||
|
3. Compile the minimal `runtime/main.rs` and link it against that rlib.
|
||||||
|
|
||||||
Configure and build a release executable with Ninja:
|
Configure and build a release executable with Ninja:
|
||||||
|
|
||||||
@@ -52,16 +73,28 @@ cmake -S . -B build -GNinja -DCMAKE_BUILD_TYPE=Release
|
|||||||
cmake --build build
|
cmake --build build
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Build only the host macro with:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cmake --build build --target rslibc_macros
|
||||||
|
```
|
||||||
|
|
||||||
|
Build only the common library with:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cmake --build build --target rslibc_library
|
||||||
|
```
|
||||||
|
|
||||||
Run it on Unix:
|
Run it on Unix:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
./build/no_core_write
|
./build/rslibc_example
|
||||||
```
|
```
|
||||||
|
|
||||||
Run it on Windows:
|
Run it on Windows:
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
.\build\no_core_write.exe
|
.\build\rslibc_example.exe
|
||||||
```
|
```
|
||||||
|
|
||||||
`CMAKE_BUILD_TYPE` is translated explicitly to rustc flags:
|
`CMAKE_BUILD_TYPE` is translated explicitly to rustc flags:
|
||||||
@@ -70,28 +103,24 @@ Run it on Windows:
|
|||||||
- `RelWithDebInfo`: `-C opt-level=3 -C debuginfo=2`
|
- `RelWithDebInfo`: `-C opt-level=3 -C debuginfo=2`
|
||||||
- Other or unset configurations: `-C opt-level=0 -C debuginfo=2`
|
- Other or unset configurations: `-C opt-level=0 -C debuginfo=2`
|
||||||
|
|
||||||
`no_core` is unstable, so CMake passes `RUSTC_BOOTSTRAP=1` to the direct
|
|
||||||
rustc invocation. A nightly compiler may be used instead, but Cargo is
|
|
||||||
still not permitted.
|
|
||||||
|
|
||||||
## Verification
|
## Verification
|
||||||
|
|
||||||
After changing the source or build configuration, run a clean release build
|
After changing the source or build configuration, run a clean release build
|
||||||
and execute the program:
|
and execute the example:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
rm -rf build
|
rm -rf build
|
||||||
cmake -S . -B build -GNinja -DCMAKE_BUILD_TYPE=Release
|
cmake -S . -B build -GNinja -DCMAKE_BUILD_TYPE=Release
|
||||||
cmake --build build
|
cmake --build build
|
||||||
./build/no_core_write
|
./build/rslibc_example
|
||||||
```
|
```
|
||||||
|
|
||||||
On Windows, use `Remove-Item -Recurse -Force build` and run
|
On Windows, use `Remove-Item -Recurse -Force build` and run
|
||||||
`build\\no_core_write.exe` instead. Confirm that stdout contains `Hello`.
|
`build\\rslibc_example.exe` instead. Confirm that stdout contains `Hello`.
|
||||||
|
|
||||||
When size matters, inspect the Windows executable size after a release
|
When size matters, inspect the Windows executable after a release build.
|
||||||
build. Avoid adding runtime libraries, formatting code, or Rust library
|
Avoid adding formatting, allocation, or OS dependencies unless they are
|
||||||
dependencies.
|
needed by the library API.
|
||||||
|
|
||||||
## Commit Messages
|
## Commit Messages
|
||||||
|
|
||||||
@@ -106,7 +135,7 @@ dependencies.
|
|||||||
|
|
||||||
```text
|
```text
|
||||||
Co-Authored-By: qwen (lmstudio/qwen3.6-27b-mtp): wrote tests + build
|
Co-Authored-By: qwen (lmstudio/qwen3.6-27b-mtp): wrote tests + build
|
||||||
Co-Authored-By: luna (openai/gpt-5.6-luna): reviewed edge cases
|
Co-Authored-By: luna (openrouter/openai/gpt-5.6-luna): reviewed edge cases
|
||||||
```
|
```
|
||||||
|
|
||||||
## Editing Guidelines
|
## Editing Guidelines
|
||||||
@@ -115,5 +144,5 @@ dependencies.
|
|||||||
- Use four-space indentation in Rust and CMake.
|
- Use four-space indentation in Rust and CMake.
|
||||||
- Keep normal text in Markdown within 80 columns where practical.
|
- Keep normal text in Markdown within 80 columns where practical.
|
||||||
- Do not add speculative abstractions or dependencies.
|
- Do not add speculative abstractions or dependencies.
|
||||||
- Update this file when the build workflow, no-core constraints, supported
|
- Update this file when the build workflow, library API, supported
|
||||||
platforms, or source layout changes.
|
platforms, or source layout changes.
|
||||||
|
|||||||
+55
-22
@@ -1,16 +1,17 @@
|
|||||||
cmake_minimum_required(VERSION 3.20)
|
cmake_minimum_required(VERSION 3.20)
|
||||||
project(no_core_write LANGUAGES NONE)
|
project(rslibc LANGUAGES NONE)
|
||||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||||
|
|
||||||
find_program(RUSTC rustc REQUIRED)
|
find_program(RUSTC rustc REQUIRED)
|
||||||
|
|
||||||
set(RUST_SOURCE "${CMAKE_CURRENT_SOURCE_DIR}/main.rs")
|
set(RUST_MACRO_SOURCE "${CMAKE_CURRENT_SOURCE_DIR}/macros/src/lib.rs")
|
||||||
set(RUST_BINARY "${CMAKE_CURRENT_BINARY_DIR}/no_core_write${CMAKE_EXECUTABLE_SUFFIX}")
|
set(RUST_MACRO "${CMAKE_CURRENT_BINARY_DIR}/rslibc_macros${CMAKE_SHARED_LIBRARY_SUFFIX}")
|
||||||
|
set(RUST_LIBRARY_SOURCE "${CMAKE_CURRENT_SOURCE_DIR}/src/lib.rs")
|
||||||
|
set(RUST_RUNTIME_SOURCE "${CMAKE_CURRENT_SOURCE_DIR}/runtime/main.rs")
|
||||||
|
set(RUST_LIBRARY "${CMAKE_CURRENT_BINARY_DIR}/librslibc.rlib")
|
||||||
|
set(RUST_BINARY "${CMAKE_CURRENT_BINARY_DIR}/rslibc_example${CMAKE_EXECUTABLE_SUFFIX}")
|
||||||
|
|
||||||
# no_core is an unstable rustc feature. RUSTC_BOOTSTRAP lets this deliberately
|
set(RUSTC_FLAGS --edition=2021 -C panic=abort)
|
||||||
# minimal example be built with the installed compiler without Cargo or core.
|
|
||||||
set(RUSTC_ENV "RUSTC_BOOTSTRAP=1")
|
|
||||||
set(RUSTC_FLAGS -C panic=abort)
|
|
||||||
|
|
||||||
# CMAKE_BUILD_TYPE does not automatically affect a custom rustc command.
|
# CMAKE_BUILD_TYPE does not automatically affect a custom rustc command.
|
||||||
# Pass the corresponding optimization/debug-info settings explicitly.
|
# Pass the corresponding optimization/debug-info settings explicitly.
|
||||||
@@ -22,31 +23,63 @@ else()
|
|||||||
list(APPEND RUSTC_FLAGS -C opt-level=0 -C debuginfo=2)
|
list(APPEND RUSTC_FLAGS -C opt-level=0 -C debuginfo=2)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# A no_core binary does not get Rust's normal startup/linker setup. Link the
|
# A no_std binary does not get Rust's normal startup or allocator setup.
|
||||||
# small platform system/runtime library needed by the stdout implementation.
|
# Use the OS heap and dynamically link only the small platform libraries
|
||||||
|
# needed by this runtime.
|
||||||
if (WIN32)
|
if (WIN32)
|
||||||
# Use Win32 directly instead of the C runtime; this keeps the executable
|
|
||||||
# small while still writing to the console's stdout handle.
|
|
||||||
set(RUSTC_LINK_ARGS
|
set(RUSTC_LINK_ARGS
|
||||||
-l kernel32
|
-l kernel32
|
||||||
|
-l vcruntime
|
||||||
-C
|
-C
|
||||||
"link-args=/NODEFAULTLIB /ENTRY:mainCRTStartup /SUBSYSTEM:CONSOLE /OPT:REF /OPT:ICF"
|
"link-args=/ENTRY:mainCRTStartup /SUBSYSTEM:CONSOLE /OPT:REF /OPT:ICF"
|
||||||
)
|
)
|
||||||
elseif (UNIX)
|
elseif (UNIX)
|
||||||
set(RUSTC_LINK_ARGS -C "link-args=-lc")
|
set(RUSTC_LINK_ARGS -C "link-args=-lc")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_custom_command(
|
add_custom_command(
|
||||||
OUTPUT "${RUST_BINARY}"
|
OUTPUT "${RUST_MACRO}"
|
||||||
COMMAND "${CMAKE_COMMAND}" -E env "${RUSTC_ENV}" "${RUSTC}"
|
COMMAND "${RUSTC}"
|
||||||
"${RUST_SOURCE}"
|
"${RUST_MACRO_SOURCE}"
|
||||||
--crate-name no_core_write
|
--crate-name rslibc_macros
|
||||||
--crate-type bin
|
--crate-type proc-macro
|
||||||
-o "${RUST_BINARY}"
|
-o "${RUST_MACRO}"
|
||||||
${RUSTC_FLAGS}
|
--edition=2021
|
||||||
${RUSTC_LINK_ARGS}
|
-C opt-level=3
|
||||||
DEPENDS "${RUST_SOURCE}"
|
DEPENDS "${RUST_MACRO_SOURCE}"
|
||||||
VERBATIM
|
VERBATIM
|
||||||
)
|
)
|
||||||
|
|
||||||
add_custom_target(no_core_write ALL DEPENDS "${RUST_BINARY}")
|
add_custom_command(
|
||||||
|
OUTPUT "${RUST_LIBRARY}"
|
||||||
|
COMMAND "${RUSTC}"
|
||||||
|
"${RUST_LIBRARY_SOURCE}"
|
||||||
|
--crate-name rslibc
|
||||||
|
--crate-type rlib
|
||||||
|
-o "${RUST_LIBRARY}"
|
||||||
|
${RUSTC_FLAGS}
|
||||||
|
DEPENDS
|
||||||
|
"${RUST_LIBRARY_SOURCE}"
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/src/io.rs"
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/src/runtime.rs"
|
||||||
|
VERBATIM
|
||||||
|
)
|
||||||
|
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT "${RUST_BINARY}"
|
||||||
|
COMMAND "${RUSTC}"
|
||||||
|
"${RUST_RUNTIME_SOURCE}"
|
||||||
|
--crate-name rslibc_example
|
||||||
|
--crate-type bin
|
||||||
|
--extern "rslibc=${RUST_LIBRARY}"
|
||||||
|
--extern "rslibc_macros=${RUST_MACRO}"
|
||||||
|
-o "${RUST_BINARY}"
|
||||||
|
${RUSTC_FLAGS}
|
||||||
|
${RUSTC_LINK_ARGS}
|
||||||
|
DEPENDS "${RUST_RUNTIME_SOURCE}" "${RUST_LIBRARY}" "${RUST_MACRO}"
|
||||||
|
VERBATIM
|
||||||
|
)
|
||||||
|
|
||||||
|
add_custom_target(rslibc_macros DEPENDS "${RUST_MACRO}")
|
||||||
|
add_custom_target(rslibc_library DEPENDS "${RUST_LIBRARY}")
|
||||||
|
add_custom_target(rslibc_example ALL DEPENDS "${RUST_BINARY}")
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
extern crate proc_macro;
|
||||||
|
|
||||||
|
use proc_macro::{Ident, TokenStream, TokenTree};
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
#[proc_macro_attribute]
|
||||||
|
pub fn main(_: TokenStream, item: TokenStream) -> TokenStream {
|
||||||
|
let mut output = TokenStream::new();
|
||||||
|
let mut saw_fn = false;
|
||||||
|
let mut renamed = false;
|
||||||
|
let mut is_async = false;
|
||||||
|
|
||||||
|
for token in item {
|
||||||
|
match &token {
|
||||||
|
TokenTree::Ident(identifier)
|
||||||
|
if identifier.to_string() == "async" && !saw_fn =>
|
||||||
|
{
|
||||||
|
is_async = true;
|
||||||
|
}
|
||||||
|
TokenTree::Ident(identifier) if identifier.to_string() == "fn" => {
|
||||||
|
saw_fn = true;
|
||||||
|
}
|
||||||
|
TokenTree::Ident(identifier)
|
||||||
|
if saw_fn && !renamed && identifier.to_string() == "main" =>
|
||||||
|
{
|
||||||
|
output.extend([TokenTree::Ident(Ident::new(
|
||||||
|
"__rslibc_user_main",
|
||||||
|
identifier.span(),
|
||||||
|
))]);
|
||||||
|
renamed = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
output.extend([token]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !renamed {
|
||||||
|
return "compile_error!(\"rslibc::main expects fn main()\");"
|
||||||
|
.parse()
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
let wrapper = if is_async {
|
||||||
|
"fn main() { rslibc::runtime::block_on(__rslibc_user_main()); } rslibc::entry!(main);"
|
||||||
|
} else {
|
||||||
|
"fn main() { __rslibc_user_main(); } rslibc::entry!(main);"
|
||||||
|
};
|
||||||
|
|
||||||
|
output.extend(TokenStream::from_str(wrapper).unwrap());
|
||||||
|
output
|
||||||
|
}
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
#![no_core]
|
|
||||||
#![no_main]
|
|
||||||
#![feature(no_core, lang_items, auto_traits)]
|
|
||||||
#![allow(internal_features)]
|
|
||||||
|
|
||||||
// These three marker traits are the minimum language items needed when core is
|
|
||||||
// not linked. They are compiler contracts, not a replacement for core.
|
|
||||||
#[lang = "pointee_sized"]
|
|
||||||
trait PointeeSized {}
|
|
||||||
|
|
||||||
#[lang = "meta_sized"]
|
|
||||||
trait MetaSized: PointeeSized {}
|
|
||||||
|
|
||||||
#[lang = "sized"]
|
|
||||||
trait Sized: MetaSized {}
|
|
||||||
|
|
||||||
#[lang = "copy"]
|
|
||||||
trait Copy {}
|
|
||||||
|
|
||||||
#[lang = "freeze"]
|
|
||||||
unsafe auto trait Freeze {}
|
|
||||||
|
|
||||||
#[lang = "eh_personality"]
|
|
||||||
extern "C" fn eh_personality() {}
|
|
||||||
|
|
||||||
#[lang = "panic_info"]
|
|
||||||
struct PanicInfo;
|
|
||||||
|
|
||||||
#[lang = "panic_impl"]
|
|
||||||
fn panic_impl(_: &PanicInfo) -> ! {
|
|
||||||
loop {}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[lang = "panic_cannot_unwind"]
|
|
||||||
fn panic_cannot_unwind() -> ! {
|
|
||||||
loop {}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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;
|
|
||||||
fn ExitProcess(status: u32) -> !;
|
|
||||||
}
|
|
||||||
|
|
||||||
// A string slice is a (pointer, length) pair. This keeps the public helper
|
|
||||||
// ergonomic without importing core just to call str::as_ptr and str::len.
|
|
||||||
#[repr(C)]
|
|
||||||
union StringParts<'a> {
|
|
||||||
string: &'a str,
|
|
||||||
bytes: (*const u8, usize),
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_stdout(message: &str) {
|
|
||||||
let (buffer, length) = unsafe { StringParts { string: message }.bytes };
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
#[cfg(unix)]
|
|
||||||
{
|
|
||||||
write(1, buffer, length);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(windows)]
|
|
||||||
{
|
|
||||||
let handle = GetStdHandle(0xffff_fff5); // STD_OUTPUT_HANDLE
|
|
||||||
let mut written: u32 = 0;
|
|
||||||
WriteFile(handle, buffer, length as u32, &mut written, 0 as *mut u8);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(unix)]
|
|
||||||
#[no_mangle]
|
|
||||||
pub extern "C" fn main() -> i32 {
|
|
||||||
write_stdout("Hello\n");
|
|
||||||
0
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(windows)]
|
|
||||||
#[no_mangle]
|
|
||||||
pub extern "system" fn mainCRTStartup() -> ! {
|
|
||||||
write_stdout("Hello\n");
|
|
||||||
unsafe { ExitProcess(0) }
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
#![no_std]
|
||||||
|
#![no_main]
|
||||||
|
|
||||||
|
fn print(message: &str) {
|
||||||
|
let _ = rslibc::io::write_str(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rslibc_macros::main]
|
||||||
|
async fn main() {
|
||||||
|
print("Hello\n");
|
||||||
|
}
|
||||||
@@ -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
@@ -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
@@ -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(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user