feat: Rust std target + ruststd tests; SYS_EXIT_GROUP; fix mkdir/create under missing parent

This commit is contained in:
daniel committed 2026-10-17 10:30:24 +02:00
1 parent 9e66c3b249
commit e87bd358a6
20 files changed
+657 -97

No files matched your search

+74 -39
View File
@@ -1,18 +1,31 @@
# 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.
Rust programs are built for `x86_64-unknown-montauk`, a built-in target of a
forked rustc with a native `std` (`target_os = "montauk"`, not a Unix). Both
`std` programs and `no_std` programs (on `montauk-rt`) are supported.
## Setup (once)
```bash
# rustup, plus a nightly whose cargo the linked toolchain borrows
curl --proto '=https' -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain none
~/.cargo/bin/rustup toolchain install nightly --component rust-src
~/.cargo/bin/rustup toolchain install nightly
# The fork: rust-lang/rust, branch `montauk` (bootstrap.toml is set up)
cd ~/dev/mtk/rust
./x build library --stage 1 --target x86_64-unknown-linux-gnu,x86_64-unknown-montauk
rustup toolchain link montauk ~/dev/mtk/rust/build/host/stage1
```
`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
The stage1 build takes about five minutes (it downloads CI LLVM). After
changing only `library/`, rebuild with
`./x build library --stage 1 --target x86_64-unknown-montauk` (seconds); the
link stays valid.
`rust-toolchain.toml` selects the `montauk` toolchain for this directory. It
has no cargo of its own; rustup falls back to the nightly's cargo.
`make programs` builds the Rust programs when that toolchain is linked, and
prints a notice and skips them otherwise. It never downloads a toolchain
(`RUSTUP_AUTO_INSTALL=0`).
## Building
@@ -26,45 +39,67 @@ 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`.
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.
`make rust` relinks the programs when the sysroot libc or the toolchain's std
is newer than the last build (cargo tracks neither).
## 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`) |
| `.cargo/config.toml` | target = `x86_64-unknown-montauk`, linker = `x86_64-montauk-gcc` |
| `rust-toolchain.toml` | the linked `montauk` toolchain |
| `ruststd/` | `std` smoke test (`ruststd`, `ruststd net <host> [port]`) |
| `montauk-rt/` | `no_std` runtime: syscalls, allocator, `print!`, argv, threads, futex `Mutex`, panic handler |
| `rusthello/` | `no_std` smoke test (`rusthello`, `rusthello panic`, `rusthello panic-thread`, `rusthello fault-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.
## The std port (in the fork)
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.
| Fork path | What |
|-----------|------|
| `compiler/rustc_target/src/spec/base/montauk.rs`, `targets/x86_64_unknown_montauk.rs` | Target: static non-PIE ET_EXEC, no red zone, local-exec TLS, panic=abort, linker `x86_64-montauk-gcc` |
| `library/std/src/sys/pal/montauk/` | Platform layer: `abi.rs` (syscall numbers, status codes, `SYSCALL` wrappers) |
| `library/std/src/sys/*/montauk.rs` | alloc, env, fs, io errors, net, paths, process, random, stdio, futex, thread, time |
| `library/std/src/os/montauk/` | `std::os::montauk::ffi::{OsStrExt, OsStringExt}` |
## Writing a program
std calls the kernel directly. The libc is used only where C code in the same
process must agree: the heap (`malloc`), the environment table (`setenv`),
`exit` (atexit handlers) and line-buffered terminal input (`read(0)`). The
syscall numbers in `abi.rs` are a hand-kept subset of `Api/Syscall.hpp`.
```rust
#![no_std]
#![no_main]
extern crate alloc;
## Platform behaviour
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.
- Errors: the raw OS error of an `io::Error` is the kernel status code
(positive, `montauk/statuscodes.h`); `ErrorKind` is mapped from it.
- Paths: `0:/os/x.elf` is absolute; joining onto it replaces the base. Search
paths (`PATH`, `env::split_paths`) are separated by `;`, not `:`.
- Files: kernel handles have no position, so `File` keeps its own cursor.
`OpenOptions::truncate` deletes and recreates the file; `set_len` can only
grow a file. No links, permissions are read-only, `fsync` is a no-op.
- Threads: stacks are mapped with `SYS_MMAP_ANON` (no guard page). A detached
thread is reaped (joined, stack freed) by the next spawn or join, so its
kernel slot does not leak. `available_parallelism()` is an error (the kernel
does not report a CPU count). `std::process::exit` from any thread ends the
process (libc `exit` -> `SYS_EXIT_GROUP`).
- Sync: `Mutex`, `RwLock`, `Condvar`, `Once` and thread parking use the
generic futex implementations over `SYS_FUTEX_WAIT`/`SYS_FUTEX_WAKE`.
- `thread_local!` uses native `#[thread_local]` (PT_TLS); destructors run when
a spawned thread exits (not on the main thread).
- Time: `Instant` has 1 ms resolution; `SystemTime` comes from the wall clock
(local time minus the timezone offset) with 1 s resolution.
- Processes: `Command` supports arguments, environment, `current_dir` (entered
in the parent for the duration of the spawn), `wait`, `try_wait` and `kill`.
Only inherited stdio is supported; piped/null stdio and `output()` return
`Unsupported`, because a redirected child's output stream disappears with
the child. A bare program name is searched in `PATH`, else `0:/os` and
`0:/games`; `.elf` may be omitted. `ExitStatus` shows abnormal ends as
"terminated by signal N".
- Net: IPv4 only. TCP connect/accept/read/write with read timeouts and
non-blocking mode, UDP send_to/recv_from/connect, DNS via `SYS_RESOLVE`.
No socket options, no `peek`, no `shutdown(Write)`, and accepted
connections do not know the peer's address.
- `panic = "abort"`: a panic on any thread prints the message and kills the
whole process (exit status "signal 6").
- `HashMap`'s random keys come from `SYS_GETRANDOM`, which is not a CSPRNG.