Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
---
status: open
kind: defect
opened: 2026-10-04
---

# std's and libc's allocators spin on one flag, so a waiter that outranks the holder spins in its place

Every allocation a ToyOS program makes goes through one `dlmalloc` behind a
process-wide `AtomicI32` that a waiter takes with `swap(1, Acquire)` in a
`spin_loop()` loop, never sleeping on a futex:

- std's: `library/std/src/sys/alloc/toyos.rs` in the `rust/` fork at
`a0d44493`, lines 57–72 (`LOCKED`, `lock`, `DropLock`), taken by `alloc`,
`alloc_zeroed`, `dealloc` and `realloc`, lines 74–96.
- libc's, in a program linked without std: `userland/libc/src/lib.rs`, lines
174–188, taken by `LibcAllocator`'s `alloc`, `dealloc` and `realloc`, lines
192–207. Beside std (`std-runtime`), libc's C allocator calls std's
(`userland/libc/src/memory.rs`).

A holder that is preempted, or that is inside the `mmap` or `munmap` syscall
`dlmalloc` makes under the lock through its backing allocator, leaves every
other thread of its process that allocates spinning through its slice. A
real-time thread outranks a fair one by right
(`issues/kernel/cpu-time-is-a-band-and-not-a-reservation.md`), so a real-time
waiter on the holder's CPU keeps a fair holder off that CPU while it spins:
priority inversion. How long such a spin lasts, and whether it ends before the
holder is run elsewhere, is unmeasured, and so is whether two threads ever meet
on this lock: `thread::spawn` allocates, and several programs allocate from more
than one thread, among them `userland/sshserver`, `userland/supervisor` and
`userland/soundserver`.

The futex both need is there: libc's `futex_lock` and `futex_unlock`
(`userland/libc/src/pthread.rs`, lines 116–131), and std's `sync::Mutex`, which
is the futex mutex on ToyOS (`library/std/src/sys/sync/mutex/mod.rs`, over
`library/std/src/sys/pal/toyos/futex.rs`).

Owner track: `issues/kernel/toyos-has-its-own-allocator.md`, whose std front
replaces std's allocator; it does not rule whether this lock is fixed on
`dlmalloc` first. Owner: the orchestrator.

**Exit**: neither file's allocator spins: a contended allocation waits on a
futex and a release with waiters wakes one, or std's front of ToyOS's
allocator has replaced both.
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
---
status: open
kind: defect
opened: 2026-10-04
---

# The sysroot's `core` and `alloc` carry the debug assertions of a profile written for compiler contributors

`std_config` (`src/sysroot.rs`) writes `profile = "compiler"` into the
bootstrap configuration every ToyOS library is built under: the kernel's and
the loader's `core` and `alloc` (`Libraries::Freestanding`) as well as every
program's `std`. In the `rust/` fork at `a0d44493`, that profile
(`src/bootstrap/defaults/bootstrap.compiler.toml`) sets
`rust.debug-assertions = true`; bootstrap's `std_debug_assertions` falls back
to it when `rust.debug-assertions-std` is unset
(`src/bootstrap/src/core/config/config.rs`), which `std_config` leaves it, and
passes it to the library crates' cargo profile
(`src/bootstrap/src/core/builder/cargo.rs`). Upstream's distributed libraries
are built without. The libraries' assertions were never chosen: `std_config`'s
doc comment takes the profile so the libraries are built as the compiler was.
The guest profile keeps debug assertions on in ToyOS's own code "because
fail-fast beats speed here" (`kernel/Cargo.toml`, `userland/Cargo.toml`, the
root `Cargo.toml`); whether the libraries should too is the decision nobody
made.

So every kernel and program ships `alloc`'s and `core`'s `debug_assert!`s,
`RawVecInner::finish_grow`'s alignment check among them
(`library/alloc/src/raw_vec/mod.rs`), and the precondition checks `ub_checks`
gates in the code compiled into those crates. The same profile's
`frame-pointers = true` reaches them as `-Cforce-frame-pointers=true`.

**Measured.** A link-time no-panic proof: a `no_std` binary for
`x86_64-unknown-none` whose panic handler calls a symbol nobody defines,
linking a fallible parser. The binary with no parser links in every arm. Each
of a `Vec::push`, a `handle_alloc_error`, an over-wide shift and an
out-of-range index fails to link in every arm without `-Zub-checks=no`; with
it, only the `handle_alloc_error` control ran, against stable's and nightly's
libraries, and failed, and the ToyOS sysroot's rows ran no failing control.
The parser, built with the
binary's own debug assertions off, links against stable 1.98.1's and nightly's
upstream libraries and fails against a ToyOS sysroot, at `opt-level = 2` and
under fat LTO alike. In the parser's arm that writes into a vector's spare
capacity and sets its length in `unsafe`, lld's `--why-live` names `core::panicking::panic` and `panic_fmt`,
live through `RawVecInner::finish_grow` alone; in its default arm
`alloc::raw_vec::handle_error` and `handle_alloc_error` are live beside them,
and against upstream's libraries nothing is. Under the guest
profile with `-Zub-checks=no` it links against stable's and fails against
ToyOS's. The matrix, the `--why-live` logs and the spike's source are a
comment on the pull request that added this file. So stage 2 of
`issues/kernel/a-panic-is-never-an-accident.md` can prove a parser panic-free
against an upstream library on the host, and against none the kernel links.

**Not measured**: the `ub_checks` share of what the proof found, which no log
attributes; what the libraries' assertions, checks and frame pointers cost a
kernel or a program in time or in image bytes, on QEMU or the T14.

Owner: the orchestrator. Stage 2 of
`issues/kernel/a-panic-is-never-an-accident.md` exits on a host step against
upstream libraries, so it does not wait on this file.

**Exit**: `std_config` sets `rust.debug-assertions-std` by a line of its own,
so the compiler profile decides nothing about the libraries' assertions; and
either it is `false` and the proof's parser, built with its own debug
assertions off, links against the sysroot that configuration builds, or a
ruling keeps it `true` and this file becomes `rejected`, citing the ruling.
2 changes: 1 addition & 1 deletion issues/design-debt/what-is-owed-on-file-size.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ what survives it is a different question.
`userland/soundserver/mixer/`), 2026-08-20; the effects
shell is `userland/soundd/` over eight files with a 261-line `main.rs`, from a
2,366-line one. What the note asked for is what it got: **the mixing is
sample-exact and proven so.** `toyos-mixer/fixtures/mix-corpus.txt` was
sample-exact and proven so.** `userland/soundserver/mixer/fixtures/mix-corpus.txt` was
written by `soundd/src/main.rs` before a line of it moved, and
`the_corpus_is_reproduced_bit_for_bit` holds the crate to it byte for byte.
The transcript is compact because exhausted domains are digested rather than
Expand Down
Loading