feat: add Rust userspace support; fix TLS layout for p_align < 16; kill whole process on sibling-thread faults
This commit is contained in:
1 parent
a81298fb24
commit
f8b8f9610f
31 files changed
+1076
-38
No files matched your search
+23
-3
@@ -96,9 +96,9 @@ WPDIR := data/wallpapers
|
||||
WPSRC := $(wildcard $(WPDIR)/*.jpg)
|
||||
WPDST := $(patsubst $(WPDIR)/%,$(BINDIR)/os/wallpapers/%,$(WPSRC))
|
||||
|
||||
.PHONY: all clean 2048 fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer audio music video bluetooth network display capabilities keyboard sshserver sshd terminal syslog procmgr powermgr calculator charmap login desktop shell paint tcc lua screenshot texteditor mandelbrot printers timezone printd printctl dialogs icons fonts configs osdata bearssl libc tls libjpeg libjpegwrite install-apps libloader crashpad rtlsdr sdr check-syscalls gen-syscalls
|
||||
.PHONY: all clean 2048 fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer audio music video bluetooth network display capabilities keyboard sshserver sshd terminal syslog procmgr powermgr calculator charmap login desktop shell paint tcc lua screenshot texteditor mandelbrot printers timezone printd printctl dialogs icons fonts configs osdata bearssl libc tls libjpeg libjpegwrite install-apps libloader crashpad rtlsdr sdr check-syscalls gen-syscalls rust
|
||||
|
||||
all: bearssl libc libjpeg libjpegwrite tls libloader rtlsdr sdr devkit $(TARGETS) fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer audio music video bluetooth network display capabilities sshserver terminal syslog procmgr powermgr calculator charmap 2048 paint tcc lua screenshot texteditor mandelbrot printers timezone printd printctl dialogs login desktop shell sshd icons fonts install-apps crashpad $(MANDST) $(WWWDST) $(CA_CERTS) $(CONFIGDST) $(OSDATADST) $(FWDST) $(LICDST) $(WPDST)
|
||||
all: bearssl libc libjpeg libjpegwrite tls libloader rtlsdr sdr devkit $(TARGETS) fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer audio music video bluetooth network display capabilities sshserver terminal syslog procmgr powermgr calculator charmap 2048 paint tcc lua screenshot texteditor mandelbrot printers timezone printd printctl dialogs login desktop shell sshd icons fonts install-apps crashpad rust $(MANDST) $(WWWDST) $(CA_CERTS) $(CONFIGDST) $(OSDATADST) $(FWDST) $(LICDST) $(WPDST)
|
||||
|
||||
# Build BearSSL static library (cross-compiled for freestanding x86_64).
|
||||
BEARSSL_INCLUDES := -isystem $(shell cd .. && pwd)/kernel/freestnd-c-hdrs/x86_64/include -isystem $(abspath include/libc)
|
||||
@@ -320,6 +320,26 @@ mandelbrot: libc
|
||||
screenshot: libc libjpegwrite libloader dialogs
|
||||
$(MAKE) -C src/screenshot
|
||||
|
||||
# Rust programs (programs/rust, Phase 1: no_std + alloc via -Zbuild-std).
|
||||
# Optional: skipped with a notice when no rustup-managed cargo with the
|
||||
# pinned nightly is installed, so the default build never requires Rust.
|
||||
# RUSTUP_AUTO_INSTALL=0 keeps 'make' from downloading a toolchain itself.
|
||||
# Rust links the toolchain sysroot libc (like ports), not lib/libc directly:
|
||||
# re-run build-montauk-toolchain.sh after libc changes.
|
||||
CARGO ?= $(firstword $(wildcard $(HOME)/.cargo/bin/cargo) $(shell command -v cargo 2>/dev/null))
|
||||
RUST_DIR := rust
|
||||
RUST_OUT := $(RUST_DIR)/target/x86_64-unknown-montauk/release
|
||||
RUST_PROGRAMS := rusthello
|
||||
|
||||
rust:
|
||||
@if [ -z "$(CARGO)" ] || ! (cd $(RUST_DIR) && RUSTUP_AUTO_INSTALL=0 $(CARGO) --version >/dev/null 2>&1); then \
|
||||
echo "rust: no nightly cargo found - skipping Rust programs (see programs/rust/README.md)"; \
|
||||
else \
|
||||
(cd $(RUST_DIR) && RUSTUP_AUTO_INSTALL=0 $(CARGO) build --release) || exit 1; \
|
||||
mkdir -p $(BINDIR)/os; \
|
||||
for p in $(RUST_PROGRAMS); do cp $(RUST_OUT)/$$p $(BINDIR)/os/$$p.elf || exit 1; done; \
|
||||
fi
|
||||
|
||||
# Verify the TCC C API header (libc/montauk.h) exposes every kernel syscall with
|
||||
# matching numbers. Fails the build on drift; 'make gen-syscalls' refreshes them.
|
||||
check-syscalls:
|
||||
@@ -486,7 +506,7 @@ else
|
||||
endif
|
||||
|
||||
clean:
|
||||
rm -rf $(BINDIR) obj
|
||||
rm -rf $(BINDIR) obj $(RUST_DIR)/target
|
||||
$(MAKE) -C lib/bearssl clean
|
||||
$(MAKE) -C lib/libc clean
|
||||
$(MAKE) -C lib/libjpeg clean
|
||||
|
||||
@@ -32,6 +32,9 @@ typedef enum {
|
||||
WouldBlock = 16
|
||||
} StatusCode;
|
||||
|
||||
// Highest defined StatusCode; update when adding a code above.
|
||||
enum { MaxStatusCode = WouldBlock };
|
||||
|
||||
inline const char* GetStatusText(StatusCode statusCode) {
|
||||
const char* statusText[] = {
|
||||
"Success", // 0
|
||||
@@ -53,7 +56,7 @@ inline const char* GetStatusText(StatusCode statusCode) {
|
||||
"Operation would block", // 16
|
||||
};
|
||||
|
||||
if (statusCode > 0 && statusCode <= 16) {
|
||||
if (statusCode > 0 && statusCode <= MaxStatusCode) {
|
||||
return statusText[statusCode];
|
||||
} else {
|
||||
return "Unknown error";
|
||||
@@ -63,7 +66,7 @@ inline const char* GetStatusText(StatusCode statusCode) {
|
||||
// Map a raw syscall result to a StatusCode.
|
||||
inline StatusCode StatusFromResult(long long rc) {
|
||||
if (rc >= 0) return Success;
|
||||
if (rc < -(long long)WouldBlock) return GenericFailure;
|
||||
if (rc < -(long long)MaxStatusCode) return GenericFailure;
|
||||
return (StatusCode)(-rc);
|
||||
}
|
||||
|
||||
@@ -89,7 +92,7 @@ inline const char* GetStatusExplanationText(StatusCode statusCode) {
|
||||
0, // 16
|
||||
};
|
||||
|
||||
if (statusCode > 0 && statusCode <= 16) {
|
||||
if (statusCode > 0 && statusCode <= MaxStatusCode) {
|
||||
if (explanationText[statusCode] != 0) {
|
||||
return explanationText[statusCode];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
# Phase 1 Rust support: no_std + alloc on a stock nightly, built with
|
||||
# -Zbuild-std against the custom target spec next to this directory.
|
||||
# Paths below are relative to programs/rust/ (the parent of .cargo/).
|
||||
|
||||
[build]
|
||||
target = "x86_64-unknown-montauk.json"
|
||||
|
||||
[unstable]
|
||||
build-std = ["core", "alloc"]
|
||||
json-target-spec = true
|
||||
|
||||
[target.x86_64-unknown-montauk]
|
||||
# The montauk GCC driver supplies crt1/crti/crtbegin, -lc and libgcc, and
|
||||
# links the static non-PIE ET_EXEC layout the kernel ELF loader expects.
|
||||
linker = "../../toolchain/local/bin/x86_64-montauk-gcc"
|
||||
@@ -0,0 +1 @@
|
||||
target/
|
||||
Generated
+14
@@ -0,0 +1,14 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "montauk-rt"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "rusthello"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"montauk-rt",
|
||||
]
|
||||
@@ -0,0 +1,13 @@
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = ["montauk-rt", "rusthello"]
|
||||
|
||||
[profile.dev]
|
||||
panic = "abort"
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
opt-level = "s"
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
debug = false
|
||||
@@ -0,0 +1,70 @@
|
||||
# Rust on MontaukOS
|
||||
|
||||
Phase 1: `no_std` + `alloc` programs, cross-compiled from Linux with a
|
||||
stock nightly toolchain and a custom target spec. No compiler fork.
|
||||
|
||||
## Setup (once)
|
||||
|
||||
```bash
|
||||
curl --proto '=https' -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain none
|
||||
~/.cargo/bin/rustup toolchain install nightly --component rust-src
|
||||
```
|
||||
|
||||
`rust-toolchain.toml` selects nightly + `rust-src` for this directory.
|
||||
`make programs` builds the Rust programs when that toolchain is present
|
||||
and prints a notice and skips them otherwise. It never downloads a toolchain
|
||||
(`RUSTUP_AUTO_INSTALL=0`).
|
||||
|
||||
## Building
|
||||
|
||||
```bash
|
||||
make -C programs rust # cargo build --release, copies to bin/os/*.elf
|
||||
cd programs/rust && cargo build --release
|
||||
```
|
||||
|
||||
Output: `target/x86_64-unknown-montauk/release/<name>`, installed as
|
||||
`programs/bin/os/<name>.elf`. Add new binaries to `RUST_PROGRAMS` in
|
||||
`programs/GNUmakefile` and to the workspace `members`.
|
||||
|
||||
## Layout
|
||||
|
||||
| Path | Purpose |
|
||||
|------|---------|
|
||||
| `x86_64-unknown-montauk.json` | Target spec: static non-PIE ET_EXEC, no red zone, local-exec TLS, panic=abort |
|
||||
| `.cargo/config.toml` | `-Zbuild-std=core,alloc`, linker = `x86_64-montauk-gcc` |
|
||||
| `montauk-rt/` | Runtime: syscalls, allocator, `print!`, argv, threads, futex `Mutex`, panic handler |
|
||||
| `rusthello/` | Smoke test (`rusthello`, `rusthello panic`, `rusthello panic-thread`) |
|
||||
|
||||
The GCC driver links crt1/crti/crtbegin, the libc and libgcc from
|
||||
`toolchain/sysroot`, the same as ports. After changing `programs/lib/libc`,
|
||||
re-run `toolchain/build-montauk-toolchain.sh` so Rust programs pick it up.
|
||||
|
||||
Syscall numbers and status codes are generated at build time by
|
||||
`montauk-rt/build.rs` from `programs/include/Api/Syscall.hpp` and
|
||||
`kernel/src/Common/StatusCodes.hpp`, so there is nothing to keep in sync by hand.
|
||||
|
||||
## Writing a program
|
||||
|
||||
```rust
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
extern crate alloc;
|
||||
|
||||
montauk_rt::entry!(main);
|
||||
|
||||
fn main() -> i32 {
|
||||
montauk_rt::println!("hello from Rust");
|
||||
0
|
||||
}
|
||||
```
|
||||
|
||||
## Limits (Phase 1)
|
||||
|
||||
- No `std`. No `std::fs`, `std::net`, `std::process` or `std::thread`; use
|
||||
`montauk_rt` equivalents where they exist.
|
||||
- `panic = "abort"`. A panic on any thread prints the message and kills the
|
||||
whole process.
|
||||
- Thread stacks are owned by the `JoinHandle`. An unjoined thread leaks its stack.
|
||||
|
||||
Phase 2 (a forked rustc at `~/dev/mtk/rust` with a native `target_os =
|
||||
"montauk"` std) replaces the JSON spec with a built-in target.
|
||||
@@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "montauk-rt"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
description = "Minimal no_std runtime for Rust programs on MontaukOS"
|
||||
build = "build.rs"
|
||||
|
||||
[lib]
|
||||
path = "src/lib.rs"
|
||||
@@ -0,0 +1,65 @@
|
||||
// Generate syscall numbers and status codes from the C++ headers so Rust
|
||||
// never becomes another hand-maintained copy of the syscall table.
|
||||
//
|
||||
// programs/include/Api/Syscall.hpp -> pub const SYS_*: u64
|
||||
// kernel/src/Common/StatusCodes.hpp -> pub enum Status (positive values,
|
||||
// matching montauk/statuscodes.h)
|
||||
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn main() {
|
||||
let manifest = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
|
||||
let repo = manifest.join("../../..");
|
||||
let syscall_hpp = repo.join("programs/include/Api/Syscall.hpp");
|
||||
let status_hpp = repo.join("kernel/src/Common/StatusCodes.hpp");
|
||||
println!("cargo:rerun-if-changed={}", syscall_hpp.display());
|
||||
println!("cargo:rerun-if-changed={}", status_hpp.display());
|
||||
|
||||
let mut out = String::from("// @generated by montauk-rt/build.rs - do not edit.\n\n");
|
||||
|
||||
// static constexpr uint64_t SYS_NAME = 123; (decimal literals only)
|
||||
let syscalls = fs::read_to_string(&syscall_hpp).expect("read Syscall.hpp");
|
||||
let mut count = 0;
|
||||
for line in syscalls.lines() {
|
||||
let line = line.trim();
|
||||
let Some(rest) = line.strip_prefix("static constexpr uint64_t SYS_") else { continue };
|
||||
let Some((name, value)) = rest.split_once('=') else { continue };
|
||||
let value = value.split(';').next().unwrap_or("").trim();
|
||||
if value.is_empty() || !value.bytes().all(|b| b.is_ascii_digit()) {
|
||||
continue;
|
||||
}
|
||||
out.push_str(&format!("pub const SYS_{}: u64 = {};\n", name.trim(), value));
|
||||
count += 1;
|
||||
}
|
||||
assert!(count > 100, "parsed only {count} syscalls from {}", syscall_hpp.display());
|
||||
|
||||
// enum StatusCodes { Success = 0, GenericFailure = -1, ... };
|
||||
let status = fs::read_to_string(&status_hpp).expect("read StatusCodes.hpp");
|
||||
let body = status
|
||||
.split_once("enum StatusCodes")
|
||||
.and_then(|(_, rest)| rest.split_once('{'))
|
||||
.and_then(|(_, rest)| rest.split_once('}'))
|
||||
.map(|(body, _)| body)
|
||||
.expect("enum StatusCodes not found");
|
||||
out.push_str("\n/// Kernel status codes, stored positive like montauk/statuscodes.h.\n");
|
||||
out.push_str("/// The kernel returns them negated; see [`Status::from_result`].\n");
|
||||
out.push_str("#[derive(Clone, Copy, Debug, PartialEq, Eq)]\n#[repr(i32)]\npub enum Status {\n");
|
||||
let mut variants = Vec::new();
|
||||
for entry in body.split(',') {
|
||||
let entry = entry.split("//").next().unwrap_or("").trim();
|
||||
let Some((name, value)) = entry.split_once('=') else { continue };
|
||||
let value: i32 = value.trim().parse().expect("status value");
|
||||
out.push_str(&format!(" {} = {},\n", name.trim(), -value));
|
||||
variants.push((name.trim().to_string(), -value));
|
||||
}
|
||||
out.push_str("}\n\nimpl Status {\n pub const fn from_code(code: i32) -> Status {\n match code {\n");
|
||||
for (name, value) in &variants {
|
||||
out.push_str(&format!(" {} => Status::{},\n", value, name));
|
||||
}
|
||||
out.push_str(" _ => Status::GenericFailure,\n }\n }\n}\n");
|
||||
|
||||
let dest = PathBuf::from(env::var("OUT_DIR").unwrap()).join("abi.rs");
|
||||
fs::write(dest, out).expect("write abi.rs");
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
//! Command-line arguments, captured from crt1.o's argv by [`crate::entry!`].
|
||||
|
||||
use core::ffi::CStr;
|
||||
use core::sync::atomic::{AtomicPtr, AtomicUsize, Ordering};
|
||||
|
||||
static ARGC: AtomicUsize = AtomicUsize::new(0);
|
||||
static ARGV: AtomicPtr<*const u8> = AtomicPtr::new(core::ptr::null_mut());
|
||||
|
||||
/// # Safety
|
||||
/// `argv` must point to `argc` NUL-terminated strings that live forever.
|
||||
#[doc(hidden)]
|
||||
pub unsafe fn init(argc: i32, argv: *const *const u8) {
|
||||
ARGV.store(argv as *mut *const u8, Ordering::Relaxed);
|
||||
ARGC.store(argc.max(0) as usize, Ordering::Release);
|
||||
}
|
||||
|
||||
/// Iterator over the program arguments; argv[0] is the executable path.
|
||||
/// Arguments that are not valid UTF-8 are yielded lossily as `"?"`.
|
||||
pub fn args() -> Args {
|
||||
Args { next: 0 }
|
||||
}
|
||||
|
||||
pub struct Args {
|
||||
next: usize,
|
||||
}
|
||||
|
||||
impl Iterator for Args {
|
||||
type Item = &'static str;
|
||||
|
||||
fn next(&mut self) -> Option<&'static str> {
|
||||
let argc = ARGC.load(Ordering::Acquire);
|
||||
if self.next >= argc {
|
||||
return None;
|
||||
}
|
||||
let argv = ARGV.load(Ordering::Relaxed);
|
||||
// SAFETY: init() guarantees argc valid, immortal C strings.
|
||||
let s = unsafe { CStr::from_ptr(*argv.add(self.next) as *const core::ffi::c_char) };
|
||||
self.next += 1;
|
||||
Some(s.to_str().unwrap_or("?"))
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
let left = ARGC.load(Ordering::Acquire).saturating_sub(self.next);
|
||||
(left, Some(left))
|
||||
}
|
||||
}
|
||||
|
||||
impl ExactSizeIterator for Args {}
|
||||
@@ -0,0 +1,66 @@
|
||||
//! Global allocator over the Montauk libc heap (process-wide locked, so
|
||||
//! safe across threads). libc malloc returns 16-byte aligned blocks; larger
|
||||
//! alignments over-allocate and stash the original pointer just below the
|
||||
//! aligned block.
|
||||
|
||||
use core::alloc::{GlobalAlloc, Layout};
|
||||
use core::ptr;
|
||||
|
||||
unsafe extern "C" {
|
||||
fn malloc(size: usize) -> *mut u8;
|
||||
fn free(ptr: *mut u8);
|
||||
fn realloc(ptr: *mut u8, size: usize) -> *mut u8;
|
||||
}
|
||||
|
||||
const MALLOC_ALIGN: usize = 16;
|
||||
|
||||
pub struct LibcAllocator;
|
||||
|
||||
unsafe impl GlobalAlloc for LibcAllocator {
|
||||
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
|
||||
unsafe {
|
||||
if layout.align() <= MALLOC_ALIGN {
|
||||
return malloc(layout.size().max(1));
|
||||
}
|
||||
let Some(total) = layout.size().checked_add(layout.align() + size_of::<usize>()) else {
|
||||
return ptr::null_mut();
|
||||
};
|
||||
let raw = malloc(total);
|
||||
if raw.is_null() {
|
||||
return raw;
|
||||
}
|
||||
let start = raw as usize + size_of::<usize>();
|
||||
let aligned = (start + layout.align() - 1) & !(layout.align() - 1);
|
||||
*((aligned - size_of::<usize>()) as *mut usize) = raw as usize;
|
||||
aligned as *mut u8
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn dealloc(&self, p: *mut u8, layout: Layout) {
|
||||
unsafe {
|
||||
if layout.align() <= MALLOC_ALIGN {
|
||||
free(p);
|
||||
} else {
|
||||
free(*((p as usize - size_of::<usize>()) as *const usize) as *mut u8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn realloc(&self, p: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
|
||||
unsafe {
|
||||
if layout.align() <= MALLOC_ALIGN {
|
||||
return realloc(p, new_size.max(1));
|
||||
}
|
||||
let new_layout = Layout::from_size_align_unchecked(new_size, layout.align());
|
||||
let q = self.alloc(new_layout);
|
||||
if !q.is_null() {
|
||||
ptr::copy_nonoverlapping(p, q, layout.size().min(new_size));
|
||||
self.dealloc(p, layout);
|
||||
}
|
||||
q
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[global_allocator]
|
||||
static ALLOCATOR: LibcAllocator = LibcAllocator;
|
||||
@@ -0,0 +1,82 @@
|
||||
//! Terminal output. `print!`/`println!` format into a stack buffer and hand
|
||||
//! it to SYS_PRINT in NUL-terminated chunks, so one call is one (or a few)
|
||||
//! syscalls rather than libc write()'s one SYS_PUTCHAR per byte.
|
||||
|
||||
use core::fmt::{self, Write};
|
||||
|
||||
use crate::sys;
|
||||
|
||||
const CHUNK: usize = 256;
|
||||
|
||||
/// Buffered writer to the process's terminal (stdout).
|
||||
pub struct Stdout {
|
||||
buf: [u8; CHUNK],
|
||||
len: usize,
|
||||
}
|
||||
|
||||
impl Stdout {
|
||||
pub const fn new() -> Self {
|
||||
Stdout { buf: [0; CHUNK], len: 0 }
|
||||
}
|
||||
|
||||
pub fn write_bytes(&mut self, bytes: &[u8]) {
|
||||
for &b in bytes {
|
||||
if b == 0 {
|
||||
// SYS_PRINT stops at NUL; send it on its own.
|
||||
self.flush();
|
||||
unsafe { sys::syscall1(sys::SYS_PUTCHAR, 0) };
|
||||
continue;
|
||||
}
|
||||
if self.len == CHUNK - 1 {
|
||||
self.flush();
|
||||
}
|
||||
self.buf[self.len] = b;
|
||||
self.len += 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn flush(&mut self) {
|
||||
if self.len == 0 {
|
||||
return;
|
||||
}
|
||||
self.buf[self.len] = 0;
|
||||
unsafe { sys::syscall1(sys::SYS_PRINT, self.buf.as_ptr() as u64) };
|
||||
self.len = 0;
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Stdout {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Write for Stdout {
|
||||
fn write_str(&mut self, s: &str) -> fmt::Result {
|
||||
self.write_bytes(s.as_bytes());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Stdout {
|
||||
fn drop(&mut self) {
|
||||
self.flush();
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn _print(args: fmt::Arguments) {
|
||||
let mut out = Stdout::new();
|
||||
let _ = out.write_fmt(args);
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! print {
|
||||
($($arg:tt)*) => { $crate::io::_print(format_args!($($arg)*)) };
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! println {
|
||||
() => { $crate::print!("\n") };
|
||||
($($arg:tt)*) => { $crate::io::_print(format_args!("{}\n", format_args!($($arg)*))) };
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
//! Minimal `no_std` runtime for Rust programs on MontaukOS.
|
||||
//!
|
||||
//! Phase 1 of Rust support: `core` + `alloc` on a stock nightly with a
|
||||
//! custom target spec. Programs link against the Montauk libc through the
|
||||
//! x86_64-montauk GCC driver, so crt1.o owns `_start` (argv, environment,
|
||||
//! .init_array) and calls the `main` that [`entry!`] defines.
|
||||
//!
|
||||
//! ```ignore
|
||||
//! #![no_std]
|
||||
//! #![no_main]
|
||||
//! montauk_rt::entry!(main);
|
||||
//! fn main() -> i32 { montauk_rt::println!("hi"); 0 }
|
||||
//! ```
|
||||
|
||||
#![no_std]
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
pub mod sys;
|
||||
pub mod io;
|
||||
pub mod heap;
|
||||
pub mod env;
|
||||
pub mod process;
|
||||
pub mod sync;
|
||||
pub mod thread;
|
||||
pub mod time;
|
||||
|
||||
mod panic;
|
||||
|
||||
pub use sys::Status;
|
||||
|
||||
/// Define the C `main` that crt1.o calls, forwarding to `fn() -> i32`.
|
||||
#[macro_export]
|
||||
macro_rules! entry {
|
||||
($main:path) => {
|
||||
const _: () = {
|
||||
// Exported as the C symbol `main`; the Rust name stays free so
|
||||
// the program can call its own entry function `main`.
|
||||
#[unsafe(export_name = "main")]
|
||||
extern "C" fn __montauk_rt_main(argc: i32, argv: *const *const u8) -> i32 {
|
||||
// SAFETY: crt1.o passes its static argv table, which lives
|
||||
// for the whole program and is never modified after startup.
|
||||
unsafe { $crate::env::init(argc, argv) };
|
||||
let f: fn() -> i32 = $main;
|
||||
f()
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
use core::fmt::Write;
|
||||
use core::panic::PanicInfo;
|
||||
|
||||
#[panic_handler]
|
||||
fn panic(info: &PanicInfo) -> ! {
|
||||
let mut out = crate::io::Stdout::new();
|
||||
let _ = write!(out, "\nthread {} panicked", crate::thread::current_id());
|
||||
if let Some(loc) = info.location() {
|
||||
let _ = write!(out, " at {}:{}:{}", loc.file(), loc.line(), loc.column());
|
||||
}
|
||||
let _ = writeln!(out, ":\n{}", info.message());
|
||||
out.flush();
|
||||
crate::process::abort()
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
//! Process control.
|
||||
|
||||
use crate::sys;
|
||||
|
||||
unsafe extern "C" {
|
||||
// libc exit(): runs atexit handlers (and .fini_array), then SYS_EXIT.
|
||||
#[link_name = "exit"]
|
||||
fn libc_exit(code: i32) -> !;
|
||||
}
|
||||
|
||||
/// Exit the process. From a sibling thread this ends only that thread
|
||||
/// (kernel semantics); use [`abort`] to take the whole process down.
|
||||
pub fn exit(code: i32) -> ! {
|
||||
unsafe { libc_exit(code) }
|
||||
}
|
||||
|
||||
pub fn id() -> u32 {
|
||||
unsafe { sys::syscall0(sys::SYS_GETPID) as u32 }
|
||||
}
|
||||
|
||||
/// Terminate the whole process from any thread.
|
||||
pub fn abort() -> ! {
|
||||
unsafe {
|
||||
if crate::thread::current_id() == id() {
|
||||
sys::syscall1(sys::SYS_EXIT, 134);
|
||||
} else {
|
||||
// Sibling: SYS_EXIT would end only this thread. Self-kill flags
|
||||
// every thread; park here until the kernel reaps us.
|
||||
sys::syscall1(sys::SYS_KILL, id() as u64);
|
||||
}
|
||||
loop {
|
||||
sys::syscall0(sys::SYS_YIELD);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
//! Futex-backed synchronization (SYS_FUTEX_WAIT / SYS_FUTEX_WAKE).
|
||||
|
||||
use core::cell::UnsafeCell;
|
||||
use core::ops::{Deref, DerefMut};
|
||||
use core::sync::atomic::{AtomicU32, Ordering};
|
||||
use core::time::Duration;
|
||||
|
||||
use crate::sys::{self, Status};
|
||||
|
||||
/// Block while `*word == expected`. `Ok(())` means woken by [`futex_wake`];
|
||||
/// `Err(WouldBlock)` means the word had already changed; `Err(TimedOut)`
|
||||
/// means the deadline passed. Always re-check your condition afterwards.
|
||||
pub fn futex_wait(word: &AtomicU32, expected: u32, timeout: Option<Duration>) -> Result<(), Status> {
|
||||
let ns = match timeout {
|
||||
None => sys::FUTEX_WAIT_FOREVER,
|
||||
Some(d) => d.as_nanos().min((sys::FUTEX_WAIT_FOREVER - 1) as u128) as u64,
|
||||
};
|
||||
let rc = unsafe {
|
||||
sys::syscall3(sys::SYS_FUTEX_WAIT, word.as_ptr() as u64, expected as u64, ns)
|
||||
};
|
||||
Status::from_result(rc).map(|_| ())
|
||||
}
|
||||
|
||||
/// Wake up to `count` waiters on `word`, longest-waiting first.
|
||||
/// Returns how many were woken.
|
||||
pub fn futex_wake(word: &AtomicU32, count: u32) -> u32 {
|
||||
let rc = unsafe { sys::syscall2(sys::SYS_FUTEX_WAKE, word.as_ptr() as u64, count as u64) };
|
||||
Status::from_result(rc).unwrap_or(0) as u32
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Mutex: Drepper's three-state futex mutex ("Futexes Are Tricky", #3).
|
||||
// 0 = unlocked, 1 = locked, 2 = locked with possible waiters.
|
||||
// ====================================================================
|
||||
|
||||
pub struct Mutex<T: ?Sized> {
|
||||
state: AtomicU32,
|
||||
data: UnsafeCell<T>,
|
||||
}
|
||||
|
||||
unsafe impl<T: ?Sized + Send> Send for Mutex<T> {}
|
||||
unsafe impl<T: ?Sized + Send> Sync for Mutex<T> {}
|
||||
|
||||
impl<T> Mutex<T> {
|
||||
pub const fn new(value: T) -> Self {
|
||||
Mutex { state: AtomicU32::new(0), data: UnsafeCell::new(value) }
|
||||
}
|
||||
|
||||
pub fn into_inner(self) -> T {
|
||||
self.data.into_inner()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> Mutex<T> {
|
||||
pub fn lock(&self) -> MutexGuard<'_, T> {
|
||||
if self.state.compare_exchange(0, 1, Ordering::Acquire, Ordering::Relaxed).is_err() {
|
||||
self.lock_contended();
|
||||
}
|
||||
MutexGuard { mutex: self }
|
||||
}
|
||||
|
||||
pub fn try_lock(&self) -> Option<MutexGuard<'_, T>> {
|
||||
self.state
|
||||
.compare_exchange(0, 1, Ordering::Acquire, Ordering::Relaxed)
|
||||
.ok()
|
||||
.map(|_| MutexGuard { mutex: self })
|
||||
}
|
||||
|
||||
#[cold]
|
||||
fn lock_contended(&self) {
|
||||
// Brief spin first: most critical sections are short.
|
||||
for _ in 0..100 {
|
||||
if self.state.load(Ordering::Relaxed) == 0
|
||||
&& self.state.compare_exchange(0, 1, Ordering::Acquire, Ordering::Relaxed).is_ok()
|
||||
{
|
||||
return;
|
||||
}
|
||||
core::hint::spin_loop();
|
||||
}
|
||||
while self.state.swap(2, Ordering::Acquire) != 0 {
|
||||
let _ = futex_wait(&self.state, 2, None);
|
||||
}
|
||||
}
|
||||
|
||||
fn unlock(&self) {
|
||||
if self.state.swap(0, Ordering::Release) == 2 {
|
||||
futex_wake(&self.state, 1);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_mut(&mut self) -> &mut T {
|
||||
self.data.get_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Default> Default for Mutex<T> {
|
||||
fn default() -> Self {
|
||||
Mutex::new(T::default())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MutexGuard<'a, T: ?Sized> {
|
||||
mutex: &'a Mutex<T>,
|
||||
}
|
||||
|
||||
unsafe impl<T: ?Sized + Sync> Sync for MutexGuard<'_, T> {}
|
||||
|
||||
impl<T: ?Sized> Deref for MutexGuard<'_, T> {
|
||||
type Target = T;
|
||||
fn deref(&self) -> &T {
|
||||
unsafe { &*self.mutex.data.get() }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> DerefMut for MutexGuard<'_, T> {
|
||||
fn deref_mut(&mut self) -> &mut T {
|
||||
unsafe { &mut *self.mutex.data.get() }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> Drop for MutexGuard<'_, T> {
|
||||
fn drop(&mut self) {
|
||||
self.mutex.unlock();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
//! Raw syscall interface. Numbers and status codes are generated from
|
||||
//! programs/include/Api/Syscall.hpp and kernel/src/Common/StatusCodes.hpp.
|
||||
|
||||
use core::arch::asm;
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/abi.rs"));
|
||||
|
||||
/// `SYS_FUTEX_WAIT` timeout meaning "no deadline".
|
||||
pub const FUTEX_WAIT_FOREVER: u64 = !0;
|
||||
/// `SYS_FUTEX_WAKE` count meaning "every waiter".
|
||||
pub const FUTEX_WAKE_ALL: u32 = u32::MAX;
|
||||
|
||||
impl Status {
|
||||
/// Convert a raw syscall result: negative values are negated status
|
||||
/// codes, everything else (handles, counts, sizes) is success.
|
||||
pub fn from_result(rc: i64) -> Result<u64, Status> {
|
||||
if rc >= 0 {
|
||||
Ok(rc as u64)
|
||||
} else if rc < i32::MIN as i64 {
|
||||
Err(Status::GenericFailure)
|
||||
} else {
|
||||
Err(Status::from_code(-(rc as i32)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The kernel's SYSCALL path clobbers rcx/r11 (return RIP/RFLAGS) and, like
|
||||
// the C++ wrappers in montauk/syscall.h, is treated as clobbering every
|
||||
// argument register.
|
||||
|
||||
#[inline(always)]
|
||||
pub unsafe fn syscall0(nr: u64) -> i64 {
|
||||
let ret: i64;
|
||||
unsafe {
|
||||
asm!("syscall", inlateout("rax") nr as i64 => ret,
|
||||
out("rcx") _, out("r11") _, out("rdi") _, out("rsi") _, out("rdx") _,
|
||||
out("r8") _, out("r9") _, out("r10") _, options(nostack));
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub unsafe fn syscall1(nr: u64, a1: u64) -> i64 {
|
||||
let ret: i64;
|
||||
unsafe {
|
||||
asm!("syscall", inlateout("rax") nr as i64 => ret, inlateout("rdi") a1 => _,
|
||||
out("rcx") _, out("r11") _, out("rsi") _, out("rdx") _,
|
||||
out("r8") _, out("r9") _, out("r10") _, options(nostack));
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub unsafe fn syscall2(nr: u64, a1: u64, a2: u64) -> i64 {
|
||||
let ret: i64;
|
||||
unsafe {
|
||||
asm!("syscall", inlateout("rax") nr as i64 => ret, inlateout("rdi") a1 => _,
|
||||
inlateout("rsi") a2 => _,
|
||||
out("rcx") _, out("r11") _, out("rdx") _,
|
||||
out("r8") _, out("r9") _, out("r10") _, options(nostack));
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub unsafe fn syscall3(nr: u64, a1: u64, a2: u64, a3: u64) -> i64 {
|
||||
let ret: i64;
|
||||
unsafe {
|
||||
asm!("syscall", inlateout("rax") nr as i64 => ret, inlateout("rdi") a1 => _,
|
||||
inlateout("rsi") a2 => _, inlateout("rdx") a3 => _,
|
||||
out("rcx") _, out("r11") _,
|
||||
out("r8") _, out("r9") _, out("r10") _, options(nostack));
|
||||
}
|
||||
ret
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
//! Threads (SYS_THREAD_SPAWN / SYS_THREAD_JOIN).
|
||||
//!
|
||||
//! The kernel uses the caller's stack verbatim and delivers one argument in
|
||||
//! %rdi. A thread cannot free the stack it runs on, so the [`JoinHandle`]
|
||||
//! owns it and frees it after a successful join; dropping an unjoined
|
||||
//! handle leaks the stack (the thread may still be running on it).
|
||||
|
||||
use alloc::alloc::{alloc, dealloc, Layout};
|
||||
use alloc::boxed::Box;
|
||||
|
||||
use crate::sys::{self, Status};
|
||||
|
||||
pub const DEFAULT_STACK_SIZE: usize = 256 * 1024;
|
||||
const STACK_ALIGN: usize = 16;
|
||||
|
||||
pub struct JoinHandle {
|
||||
tid: u32,
|
||||
stack: *mut u8,
|
||||
stack_layout: Layout,
|
||||
}
|
||||
|
||||
unsafe impl Send for JoinHandle {}
|
||||
|
||||
impl JoinHandle {
|
||||
pub fn id(&self) -> u32 {
|
||||
self.tid
|
||||
}
|
||||
|
||||
/// Wait for the thread to finish and return its exit code.
|
||||
pub fn join(self) -> Result<i32, Status> {
|
||||
let mut code: i32 = 0;
|
||||
let rc = unsafe {
|
||||
sys::syscall2(sys::SYS_THREAD_JOIN, self.tid as u64, &mut code as *mut i32 as u64)
|
||||
};
|
||||
// The kernel reports a bad tid as -1 (GenericFailure).
|
||||
Status::from_result(rc)?;
|
||||
unsafe { dealloc(self.stack, self.stack_layout) };
|
||||
Ok(code)
|
||||
}
|
||||
}
|
||||
|
||||
/// Spawn a thread running `f`; its return value becomes the exit code.
|
||||
pub fn spawn<F>(f: F) -> Result<JoinHandle, Status>
|
||||
where
|
||||
F: FnOnce() -> i32 + Send + 'static,
|
||||
{
|
||||
spawn_with_stack(DEFAULT_STACK_SIZE, f)
|
||||
}
|
||||
|
||||
pub fn spawn_with_stack<F>(stack_size: usize, f: F) -> Result<JoinHandle, Status>
|
||||
where
|
||||
F: FnOnce() -> i32 + Send + 'static,
|
||||
{
|
||||
let stack_layout = Layout::from_size_align(stack_size.max(4096), STACK_ALIGN)
|
||||
.map_err(|_| Status::InvalidArgument)?;
|
||||
let stack = unsafe { alloc(stack_layout) };
|
||||
if stack.is_null() {
|
||||
return Err(Status::OutOfMemory);
|
||||
}
|
||||
let closure = Box::into_raw(Box::new(f));
|
||||
let top = (stack as usize + stack_layout.size()) & !(STACK_ALIGN - 1);
|
||||
|
||||
let rc = unsafe {
|
||||
sys::syscall3(sys::SYS_THREAD_SPAWN, trampoline::<F> as *const () as u64, closure as u64, top as u64)
|
||||
};
|
||||
match Status::from_result(rc) {
|
||||
Ok(tid) => Ok(JoinHandle { tid: tid as u32, stack, stack_layout }),
|
||||
Err(e) => unsafe {
|
||||
drop(Box::from_raw(closure));
|
||||
dealloc(stack, stack_layout);
|
||||
Err(e)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn trampoline<F>(arg: *mut F) -> !
|
||||
where
|
||||
F: FnOnce() -> i32,
|
||||
{
|
||||
let f = unsafe { Box::from_raw(arg) };
|
||||
let code = f();
|
||||
unsafe { sys::syscall1(sys::SYS_THREAD_EXIT, code as u32 as u64) };
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
/// The calling thread's id (equals the process id on the main thread).
|
||||
pub fn current_id() -> u32 {
|
||||
unsafe { sys::syscall0(sys::SYS_THREAD_SELF) as u32 }
|
||||
}
|
||||
|
||||
pub fn yield_now() {
|
||||
unsafe { sys::syscall0(sys::SYS_YIELD) };
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
//! Time and sleeping.
|
||||
|
||||
use core::time::Duration;
|
||||
|
||||
use crate::sys;
|
||||
|
||||
/// Milliseconds since boot (monotonic).
|
||||
pub fn uptime_ms() -> u64 {
|
||||
unsafe { sys::syscall0(sys::SYS_GETMILLISECONDS) as u64 }
|
||||
}
|
||||
|
||||
/// Sleep for at least `d`, rounded up to the 1 ms scheduler tick.
|
||||
pub fn sleep(d: Duration) {
|
||||
let ms = d.as_millis().min(u64::MAX as u128) as u64 + u64::from(d.subsec_nanos() % 1_000_000 != 0);
|
||||
if ms > 0 {
|
||||
unsafe { sys::syscall1(sys::SYS_SLEEP_MS, ms) };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
[toolchain]
|
||||
channel = "nightly"
|
||||
components = ["rust-src"]
|
||||
@@ -0,0 +1,7 @@
|
||||
[package]
|
||||
name = "rusthello"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
montauk-rt = { path = "../montauk-rt" }
|
||||
@@ -0,0 +1,148 @@
|
||||
//! rusthello - Phase 1 Rust smoke test for MontaukOS.
|
||||
//!
|
||||
//! Exercises alloc collections, formatting, argv, a #[thread_local]
|
||||
//! (PT_TLS + per-thread %fs blocks), threads, and the futex mutex.
|
||||
//! `rusthello panic` / `rusthello panic-thread` check that a panic on
|
||||
//! either thread takes the whole process down; `rusthello fault-thread`
|
||||
//! checks the same for a page fault on a worker thread.
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
#![feature(thread_local)]
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
use alloc::collections::BTreeMap;
|
||||
use alloc::format;
|
||||
use alloc::string::String;
|
||||
use alloc::sync::Arc;
|
||||
use alloc::vec::Vec;
|
||||
use core::cell::Cell;
|
||||
use core::time::Duration;
|
||||
|
||||
use montauk_rt::sync::Mutex;
|
||||
use montauk_rt::{entry, println, thread, time};
|
||||
|
||||
entry!(main);
|
||||
|
||||
static mut FAILURES: u32 = 0;
|
||||
|
||||
fn check(ok: bool, name: &str) {
|
||||
println!("{} {}", if ok { "PASS" } else { "FAIL" }, name);
|
||||
if !ok {
|
||||
unsafe { FAILURES += 1 };
|
||||
}
|
||||
}
|
||||
|
||||
#[thread_local]
|
||||
static TLS_COUNTER: Cell<u64> = Cell::new(7);
|
||||
|
||||
const THREADS: usize = 4;
|
||||
const ITERS: u64 = 20_000;
|
||||
|
||||
fn main() -> i32 {
|
||||
println!("rusthello: Rust on MontaukOS (no_std + alloc)");
|
||||
|
||||
let args: Vec<&str> = montauk_rt::env::args().collect();
|
||||
println!("argv = {:?}", args);
|
||||
match args.get(1).copied() {
|
||||
Some("panic") => panic!("requested panic on the main thread"),
|
||||
Some("fault-thread") => {
|
||||
// A CPU fault (not a panic) on a worker must still kill the
|
||||
// whole process, with the SIGSEGV exit code (267).
|
||||
let h = thread::spawn(|| {
|
||||
unsafe { core::ptr::write_volatile(8 as *mut u64, 0xdead) };
|
||||
0
|
||||
})
|
||||
.unwrap();
|
||||
let _ = h.join();
|
||||
thread::yield_now();
|
||||
println!("FAIL process survived a worker-thread fault");
|
||||
return 1;
|
||||
}
|
||||
Some("panic-thread") => {
|
||||
let h = thread::spawn(|| panic!("requested panic on a worker thread")).unwrap();
|
||||
let _ = h.join();
|
||||
println!("FAIL process survived a worker-thread panic");
|
||||
return 1;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// --- alloc ---
|
||||
let mut v: Vec<u64> = (1..=100).collect();
|
||||
v.retain(|x| x % 3 == 0);
|
||||
check(v.len() == 33 && v.iter().sum::<u64>() == 1683, "Vec collect/retain/sum");
|
||||
|
||||
let mut s = String::new();
|
||||
for word in ["Montauk", "speaks", "Rust"] {
|
||||
if !s.is_empty() {
|
||||
s.push(' ');
|
||||
}
|
||||
s.push_str(word);
|
||||
}
|
||||
check(s == "Montauk speaks Rust", "String building");
|
||||
check(format!("{:>6.2}|{:#x}|{:?}", 3.14159f64, 255, Some("x")) == " 3.14|0xff|Some(\"x\")",
|
||||
"format! with floats, hex, Debug");
|
||||
|
||||
let mut map = BTreeMap::new();
|
||||
for (i, w) in s.split(' ').enumerate() {
|
||||
map.insert(w, i);
|
||||
}
|
||||
check(map.get("Rust") == Some(&2) && map.len() == 3, "BTreeMap");
|
||||
|
||||
let big: Vec<u8> = alloc::vec![0xA5; 1 << 20];
|
||||
check(big.iter().all(|&b| b == 0xA5), "1 MiB allocation");
|
||||
|
||||
#[repr(align(4096))]
|
||||
struct PageAligned([u8; 64]);
|
||||
let page = alloc::boxed::Box::new(PageAligned([1; 64]));
|
||||
check((&*page as *const PageAligned as usize) % 4096 == 0 && page.0[63] == 1, "over-aligned Box (4096)");
|
||||
|
||||
// --- thread-local storage ---
|
||||
check(TLS_COUNTER.get() == 7, "#[thread_local] initial value on main thread");
|
||||
TLS_COUNTER.set(1000);
|
||||
let tls_seen = thread::spawn(|| {
|
||||
let initial = TLS_COUNTER.get();
|
||||
TLS_COUNTER.set(55);
|
||||
initial as i32
|
||||
})
|
||||
.unwrap()
|
||||
.join();
|
||||
check(tls_seen == Ok(7), "worker thread gets a fresh TLS block");
|
||||
check(TLS_COUNTER.get() == 1000, "main thread TLS untouched by worker");
|
||||
|
||||
// --- threads + futex mutex ---
|
||||
let counter = Arc::new(Mutex::new(0u64));
|
||||
let mut handles = Vec::new();
|
||||
for t in 0..THREADS {
|
||||
let counter = counter.clone();
|
||||
handles.push(
|
||||
thread::spawn(move || {
|
||||
for i in 0..ITERS {
|
||||
let mut g = counter.lock();
|
||||
let v = *g;
|
||||
if i % 512 == 0 {
|
||||
thread::yield_now(); // widen the race window under the lock
|
||||
}
|
||||
*g = v + 1;
|
||||
}
|
||||
t as i32 + 10
|
||||
})
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
let codes: Vec<_> = handles.into_iter().map(|h| h.join()).collect();
|
||||
check(codes.iter().enumerate().all(|(t, c)| *c == Ok(t as i32 + 10)), "thread exit codes via join");
|
||||
check(*counter.lock() == THREADS as u64 * ITERS, "futex Mutex keeps the counter exact");
|
||||
|
||||
// --- time ---
|
||||
let t0 = time::uptime_ms();
|
||||
time::sleep(Duration::from_millis(25));
|
||||
let dt = time::uptime_ms() - t0;
|
||||
check((25..1000).contains(&dt), "sleep(25 ms)");
|
||||
|
||||
let failures = unsafe { FAILURES };
|
||||
println!("rusthello: {}", if failures == 0 { "all passed" } else { "FAILURES" });
|
||||
if failures == 0 { 0 } else { 1 }
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"arch": "x86_64",
|
||||
"cpu": "x86-64",
|
||||
"data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128",
|
||||
"llvm-target": "x86_64-unknown-none-elf",
|
||||
"os": "montauk",
|
||||
"vendor": "unknown",
|
||||
"target-pointer-width": 64,
|
||||
"max-atomic-width": 64,
|
||||
"executables": true,
|
||||
"linker-flavor": "gnu-cc",
|
||||
"linker": "x86_64-montauk-gcc",
|
||||
"no-default-libraries": false,
|
||||
"crt-objects-fallback": "false",
|
||||
"relocation-model": "static",
|
||||
"code-model": "small",
|
||||
"position-independent-executables": false,
|
||||
"static-position-independent-executables": false,
|
||||
"relro-level": "off",
|
||||
"disable-redzone": true,
|
||||
"has-thread-local": true,
|
||||
"tls-model": "local-exec",
|
||||
"panic-strategy": "abort",
|
||||
"plt-by-default": false,
|
||||
"stack-probes": { "kind": "inline" },
|
||||
"metadata": {
|
||||
"description": "MontaukOS userspace (static ET_EXEC, Montauk libc)",
|
||||
"host_tools": false,
|
||||
"std": false,
|
||||
"tier": 3
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,7 @@ static void print_exit_code(const int exitCode) {
|
||||
montauk::print(c);
|
||||
}
|
||||
|
||||
if (exitCode > 0 && exitCode <= 14) { // 0-14 - current range of status codes within StatusCodes enum
|
||||
if (exitCode > 0 && exitCode <= montauk::MaxStatusCode) {
|
||||
const char* statusText = montauk::GetStatusText((montauk::StatusCode)exitCode);
|
||||
|
||||
if (statusText != nullptr) {
|
||||
|
||||
Reference in new issue
Block a user