From f15b74ccf74567b4b4a88ac23c68c953c2d4481a Mon Sep 17 00:00:00 2001 From: japabu Date: Sat, 3 Oct 2026 22:39:16 +0200 Subject: [PATCH 1/3] The kernel's crates are ours where security asks, and a host test reds on a kernel that resolves libc Root CLAUDE.md's Dependencies sentence on crates now carries the owner's ruling of 2026-10-03: where security or another weighty reason asks, above all in the kernel or a decoder of untrusted input, ToyOS writes its own crate with a clear boundary, held to the no-panic track's lints and tested against hostile input; the kernel takes as few community crates as it can justify and depends on no libc. The general rule (general, widely used, used as published, no driver crate, a fork goes when upstream has it) stands unchanged. kernel/Cargo.lock names libc, windows-sys and windows-link. They are dlmalloc's target-gated edges, cfg(all(unix, not(target_arch = "wasm32"))) and cfg(target_os = "windows"); neither kernel target has a target_family (both are target_os = "none" under the stable and the fork compiler), so cargo resolves, compiles and links none of them. Measured for both targets: cargo tree -i prints nothing, the unit graph holds cfg-if, dlmalloc, hashbrown and rustc-demangle as the only registry crates, a fresh build's deps directory has no libc or windows rlib, and each kernel ELF has no undefined symbol and no needed library. A lock is platform-independent, so the entry stays as long as dlmalloc declares the edge; dlmalloc 0.2.14, the newest published, declares it unconditionally on unix. Reading the lock therefore cannot show the clause holds, and a dependency bump that widened such an edge to a none target would change no line naming libc. build::tests now asks cargo tree, per Arch::kernel(), with every kernel feature, for the kernel's normal non-proc- macro graph, and reds on libc. Negative control: the kernel given a libc dependency builds green with a libc rlib compiled in, and the test reds. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01WcU2Dsw6mDYtwYfzVHPzM8 --- CLAUDE.md | 2 +- src/build.rs | 31 +++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/CLAUDE.md b/CLAUDE.md index 2e89c781e4d..446c44f371c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -55,7 +55,7 @@ There are no spec documents. A rule a reader can check lives in an agent's promp ## Dependencies -**Rust** and **QEMU** for development, on any host OS and architecture — the development machine is nothing special. Beside them, where no Rust tool does the job, only C or C++ tools ToyOS can one day build and run (Python, Perl, CMake, make), each declared where `reviewer.md`'s Arrivals says. No binary for one host OS alone: a macOS binary is a hard no, and "only for tests" does not soften it. ToyOS's own code is Rust; it writes no Python, Perl or shell of its own. Only general and widely used crates — one that does *our* job we write ourselves, and a driver crate never; third-party crates are used as published, and a fork carries a change written to upstream quality and goes when upstream has it. No upstream pull request is sent for now. Third-party source is used unmodified, pinned by hash, wherever only its packaging has to change; a fork is for source changes only. The north star is **self-hosting**: ToyOS rebuilds itself on ToyOS and reproduces the host's bytes, and nothing — build, test, or verification — rests on a host binary; a bootstrap from source with no binary seed is out of scope. Ask of anything new: could this ever run inside ToyOS? +**Rust** and **QEMU** for development, on any host OS and architecture — the development machine is nothing special. Beside them, where no Rust tool does the job, only C or C++ tools ToyOS can one day build and run (Python, Perl, CMake, make), each declared where `reviewer.md`'s Arrivals says. No binary for one host OS alone: a macOS binary is a hard no, and "only for tests" does not soften it. ToyOS's own code is Rust; it writes no Python, Perl or shell of its own. Only general and widely used crates, used as published — one that does *our* job we write ourselves, and a driver crate never — and a fork carries a change written to upstream quality and goes when upstream has it. Where security or another weighty reason asks, above all in the kernel or a decoder of untrusted input, we write our own crate with a clear boundary, held to the no-panic track's lints and tested against hostile input; the kernel takes as few community crates as it can justify and depends on no libc. No upstream pull request is sent for now. Third-party source is used unmodified, pinned by hash, wherever only its packaging has to change; a fork is for source changes only. The north star is **self-hosting**: ToyOS rebuilds itself on ToyOS and reproduces the host's bytes, and nothing — build, test, or verification — rests on a host binary; a bootstrap from source with no binary seed is out of scope. Ask of anything new: could this ever run inside ToyOS? Vendor firmware a device or CPU verifies by its maker's signature may be shipped: pinned by version and hash, redistributable unmodified, recorded in `NOTICE`. A device's is loaded only by its own driver through its IOMMU domain and never executes on the CPU; CPU microcode is loaded by the kernel. `NOTICE` names every committed third-party file with its hash, upstream and licence. diff --git a/src/build.rs b/src/build.rs index ee955a2ada5..178e4e508c1 100644 --- a/src/build.rs +++ b/src/build.rs @@ -2760,6 +2760,37 @@ mod tests { ); } + /// The kernel depends on no libc (root `CLAUDE.md`, Dependencies), and its + /// `Cargo.lock` cannot show it: a lock records every platform's edges and + /// none of their `cfg`s, so it names `libc`, `dlmalloc`'s edge on unix, + /// whether a kernel target takes that edge or not. + #[test] + fn the_kernel_resolves_no_libc_for_either_target() { + let root = Path::new(env!("CARGO_MANIFEST_DIR")); + for arch in Arch::ALL { + let out = Command::new("cargo") + .args(["tree", "--locked", "--all-features", "-e", "normal,no-proc-macro"]) + .args(["--prefix", "none", "--format", "{p}", "--target", arch.kernel()]) + .arg("--manifest-path") + .arg(root.join("kernel/Cargo.toml")) + .output() + .expect("cargo tree failed to launch"); + let tree = String::from_utf8_lossy(&out.stdout); + assert!( + out.status.success() && tree.starts_with("kernel "), + "cargo tree --target {} exited {} and printed no kernel: {}", + arch.kernel(), + out.status, + String::from_utf8_lossy(&out.stderr) + ); + assert!( + !tree.lines().any(|package| package.starts_with("libc ")), + "the {} kernel resolves libc:\n{tree}", + arch.kernel() + ); + } + } + /// Every negative control the model crates declare — every feature name /// besides the structural ones they carry for other reasons. /// From 24b11b69a9ca2083f07a61768bd19bc84b664592 Mon Sep 17 00:00:00 2001 From: japabu Date: Sat, 3 Oct 2026 23:15:04 +0200 Subject: [PATCH 2/3] The crate rule says only what the owner said or accepted, and the libc test names no third-party edge Answers review round 1 of #696 (comment 5973524429). Root CLAUDE.md's crate sentence now reads: "Where security or another important reason asks, above all on a trust boundary or in the kernel, we write our own crate, with a clear boundary and well tested; the kernel takes as few community crates as it can justify and depends on no libc." f15b74ccf called its whole sentence the owner's ruling of 2026-10-03. It was not, and history keeps that message, so this one says where each part of the sentence comes from: - The owner's words, 2026-10-03: "if it makes sense ... due to security or other important reasons we do it" (where security or another important reason asks, we write our own crate), "a crate with a clear boundary and is well tested", "Especially in the kernel" (above all in the kernel), and "Our kernel must not depend on libc". - A formulation the orchestrator put to him, which he accepted ("Yes add a fitting rule to claudemd"): "a crate on a trust boundary or in the kernel is ours, with a clear boundary and tests, and everywhere else the ecosystem's". "On a trust boundary" is its words. It replaces f15b74ccf's "a decoder of untrusted input", which was neither his nor accepted by him. - Neither: "the kernel takes as few community crates as it can justify" is the brief's statement, as a rule, of his worry about community crates in the kernel. f15b74ccf's "held to the no-panic track's lints and tested against hostile input" was neither his words nor part of what he accepted. It is replaced by his "well tested". The libc test's doc comment drops the clause that named dlmalloc's current edge on unix. That clause would go false the day dlmalloc changes, and the reason before it already stands on its own. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01WcU2Dsw6mDYtwYfzVHPzM8 --- CLAUDE.md | 2 +- src/build.rs | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index d773e677522..63d9bae5a99 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -55,7 +55,7 @@ There are no spec documents. A rule a reader can check lives in an agent's promp ## Dependencies -**Rust** and **QEMU** for development, on any host OS and architecture — the development machine is nothing special. Beside them, where no Rust tool does the job, only C or C++ tools ToyOS can one day build and run (Python, Perl, CMake, make), each declared where `reviewer.md`'s Arrivals says. No binary for one host OS alone: a macOS binary is a hard no, and "only for tests" does not soften it. ToyOS's own code is Rust; it writes no Python, Perl or shell of its own. Only general and widely used crates, used as published — one that does *our* job we write ourselves, and a driver crate never — and a fork carries a change written to upstream quality and goes when upstream has it. Where security or another weighty reason asks, above all in the kernel or a decoder of untrusted input, we write our own crate with a clear boundary, held to the no-panic track's lints and tested against hostile input; the kernel takes as few community crates as it can justify and depends on no libc. No upstream pull request is sent for now. Third-party source is used unmodified, pinned by hash, wherever only its packaging has to change; a fork is for source changes only. The north star is **self-hosting**: ToyOS rebuilds itself on ToyOS and reproduces the host's bytes, and nothing — build, test, or verification — rests on a host binary; a bootstrap from source with no binary seed is out of scope. Ask of anything new: could this ever run inside ToyOS? +**Rust** and **QEMU** for development, on any host OS and architecture — the development machine is nothing special. Beside them, where no Rust tool does the job, only C or C++ tools ToyOS can one day build and run (Python, Perl, CMake, make), each declared where `reviewer.md`'s Arrivals says. No binary for one host OS alone: a macOS binary is a hard no, and "only for tests" does not soften it. ToyOS's own code is Rust; it writes no Python, Perl or shell of its own. Only general and widely used crates, used as published — one that does *our* job we write ourselves, and a driver crate never — and a fork carries a change written to upstream quality and goes when upstream has it. Where security or another important reason asks, above all on a trust boundary or in the kernel, we write our own crate, with a clear boundary and well tested; the kernel takes as few community crates as it can justify and depends on no libc. No upstream pull request is sent for now. Third-party source is used unmodified, pinned by hash, wherever only its packaging has to change; a fork is for source changes only. The north star is **self-hosting**: ToyOS rebuilds itself on ToyOS and reproduces the host's bytes, and nothing — build, test, or verification — rests on a host binary; a bootstrap from source with no binary seed is out of scope. Ask of anything new: could this ever run inside ToyOS? Vendor firmware a device or CPU verifies by its maker's signature may be shipped: pinned by version and hash, redistributable unmodified, recorded in `NOTICE`. A device's is loaded only by its own driver through its IOMMU domain and never executes on the CPU; CPU microcode is loaded by the kernel. `NOTICE` names every committed third-party file with its hash, upstream and licence. diff --git a/src/build.rs b/src/build.rs index 109c5ec72a1..4eec33ff4cd 100644 --- a/src/build.rs +++ b/src/build.rs @@ -2754,8 +2754,7 @@ mod tests { /// The kernel depends on no libc (root `CLAUDE.md`, Dependencies), and its /// `Cargo.lock` cannot show it: a lock records every platform's edges and - /// none of their `cfg`s, so it names `libc`, `dlmalloc`'s edge on unix, - /// whether a kernel target takes that edge or not. + /// none of their `cfg`s. #[test] fn the_kernel_resolves_no_libc_for_either_target() { let root = Path::new(env!("CARGO_MANIFEST_DIR")); From b219c991fbd4b70d8cbc77cd7684d772b183458b Mon Sep 17 00:00:00 2001 From: japabu Date: Sat, 3 Oct 2026 23:37:27 +0200 Subject: [PATCH 3/3] The owner's allocator ruling and the libc test's compiler gap are filed Review round 2 of #696 (comment 5973692927), its second and third NOTEs. issues/kernel/toyos-has-its-own-allocator.md records the owner's ruling of 2026-10-03, as the orchestrator relayed it: our own allocator crate, one core with two fronts; the kernel's first, built host-first and swapped in after the three steps of the latency track; std's for programs later, after measuring against dlmalloc; and the 2 MiB heap-growth stall fixed now. No file recorded that ruling before. Its exit deletes build::tests::the_kernel_resolves_no_libc_for_either_target once dlmalloc leaves the kernel: the test sees what the lock cannot only while a kernel dependency names libc for another platform, and after dlmalloc a libc line arriving in kernel/Cargo.lock is something a reviewer reads. issues/build/the-kernels-libc-test-reads-the-host-compilers-cfg-not-the-forks.md records the gap the branch found: the test resolves with the host's compiler, stable on CI, whose host job installs no ToyOS toolchain, and the kernel builds with the fork. Measured with rustc --print cfg on both kernel targets, stable 1.98.1 against the fork's 1.99.0-dev, every run exiting 0: the fork prints every line stable does and adds more, target_feature="x87" on x86_64 among them; the two agree on target_os, target_arch and the absence of unix, which are what dlmalloc 0.2.13 gates its libc and windows-sys edges on. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01WcU2Dsw6mDYtwYfzVHPzM8 --- ...ds-the-host-compilers-cfg-not-the-forks.md | 33 +++++++++++++++++++ issues/kernel/toyos-has-its-own-allocator.md | 32 ++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 issues/build/the-kernels-libc-test-reads-the-host-compilers-cfg-not-the-forks.md create mode 100644 issues/kernel/toyos-has-its-own-allocator.md diff --git a/issues/build/the-kernels-libc-test-reads-the-host-compilers-cfg-not-the-forks.md b/issues/build/the-kernels-libc-test-reads-the-host-compilers-cfg-not-the-forks.md new file mode 100644 index 00000000000..a27d6ef932a --- /dev/null +++ b/issues/build/the-kernels-libc-test-reads-the-host-compilers-cfg-not-the-forks.md @@ -0,0 +1,33 @@ +--- +status: open +kind: tooling +opened: 2026-10-03 +--- + +# The kernel's libc test reads the host compiler's `cfg`, not the fork's that builds the kernel + +`build::tests::the_kernel_resolves_no_libc_for_either_target` (`src/build.rs`) +runs `cargo tree --target ` for each kernel target with whatever `cargo` +and `rustc` the host gate runs: the host's default, stable on CI, whose host +job installs no ToyOS toolchain (`src/ci.rs`'s `host`). Cargo decides each +target-gated edge against that compiler's `rustc --print cfg`. The kernel +builds with the fork, whose `cfg` for the kernel targets only +`assert_kernel_is_softfloat` (`src/build.rs`) asks. + +The two answers differ. `rustc --print cfg --target `, stable `1.98.1` +against the fork's `1.99.0-dev`, every run exiting 0: on both +`x86_64-unknown-none` and `aarch64-unknown-none-softfloat` the fork prints +every line stable prints, and adds `overflow_checks`, `ub_checks`, +`fmt_debug`, `relocation_model`, `target_has_threads`, +`target_object_format`, and the unstable `target_has_atomic*` and +`target_has_reliable_*` names; on x86_64 it adds `target_feature="x87"` too. +Both print the same `target_os` and `target_arch` and no `unix`, the names +`dlmalloc` 0.2.13 gates its `libc` and `windows-sys` edges on, so the test's +verdict is the fork's today. An edge gated on a name only the fork +prints resolves one way in the kernel's build and the other in the test. + +Owner: the orchestrator. + +**Exit**: the test resolves the kernel's dependencies against the `cfg` of +the compiler that builds the kernel, or it is deleted, as +`issues/kernel/toyos-has-its-own-allocator.md`'s exit deletes it. diff --git a/issues/kernel/toyos-has-its-own-allocator.md b/issues/kernel/toyos-has-its-own-allocator.md new file mode 100644 index 00000000000..ca62befbde8 --- /dev/null +++ b/issues/kernel/toyos-has-its-own-allocator.md @@ -0,0 +1,32 @@ +--- +status: open +kind: track +opened: 2026-10-03 +--- + +# ToyOS has its own allocator + +Ruled (owner, 2026-10-03): ToyOS writes its own allocator crate, one core +with two fronts. The kernel's front comes first: built host-first, and +swapped in for the kernel's `dlmalloc` (`kernel/src/mm/alloc.rs`) after the +three steps of `issues/kernel/toyos-beats-linuxs-latency-on-the-t14.md`. +std's front, for programs, comes later, after it is measured against the +`dlmalloc` ToyOS's std allocates with (`library/std/src/sys/alloc/toyos.rs` +in the `rust/` fork). The 2 MiB heap-growth stall is fixed now, on +`dlmalloc`, and waits for neither front. + +The stall: when the kernel heap grows, `dlmalloc` calls +`KernelPageSource::alloc` (`kernel/src/mm/alloc.rs`) inside +`ALLOCATOR.dlmalloc.lock()`, and `pmm::alloc_page` (`kernel/src/mm/pmm.rs`) +scans the page bitmap and zeroes the whole 2 MiB page before it returns, so +every CPU that allocates from the heap meanwhile spins on its lock. How long +that takes on the T14 is unmeasured. + +Owner: the orchestrator. + +**Exit**: no heap growth allocates or zeroes a 2 MiB page inside the kernel +heap's lock; the kernel allocates through ToyOS's allocator, and `dlmalloc`, +`libc`, `windows-sys` and `windows-link` are gone from `kernel/Cargo.toml` and +`kernel/Cargo.lock`, with `build::tests::the_kernel_resolves_no_libc_for_either_target` +(`src/build.rs`) deleted in the same pull request; and ToyOS's std allocates +through the std front, measured against `dlmalloc` before it is swapped in.