SchOS/.claude/memory/schunix-build-speed.md
Nexoniarz a1c965da1e include: one folder per subsystem, and includes that name it
Sixty-five headers sat side by side in include/, so `#include "irq.h"`
said nothing about where irq.h was or what it belonged to, and errno.h,
stat.h and fcntl.h at the top shadowed the system's for any host tool
built with -I./include. They are in folders now -- arch/ core/ fs/
dev/ gfx/ net/ uapi/ (and ldls/ for the Linux driver layer) -- and
every include names the folder, as Linux's do: "core/proc.h".

Done mechanically and checked the hard way: the kernel built from this
tree disassembles identically to the one built from the tree before
it. The full image boots, and userland is the same set of programs.
Paths in docs and comments follow; docs/guides/layout.md has a table
of what is where, and tools/fix-includes.py rewrites old-style
includes from the folders in the tree, for code written before this.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
2026-10-23 15:05:12 +02:00

50 lines
2.6 KiB
Markdown

---
name: schunix-build-speed
description: The build is fast now (no-op ~2s); the four things that made it slow and the traps that will make it slow again
metadata:
node_type: memory
type: project
originSessionId: a665e14b-86bd-4b41-a228-51377f3e9225
modified: 2026-08-04T17:27:56.099Z
---
**A no-op `make` went from 1m48s to ~2s on 2026-08-04**, and a full kernel
rebuild from ~110s to ~7s. Almost none of it was compilation — with only 2.4s
of CPU in that 1m48, it was I/O across WSL's `/mnt/d` boundary. Four causes,
all still worth not reintroducing:
- **`tools/mkfs` built the image on the file**, one `lseek` + one 512-byte
`write` per block: 300,000 syscall pairs, 150,000 of them just to zero it.
It now assembles the image in RAM and writes once. It also stops at the last
non-zero block and `ftruncate`s, so ~2 MB is written instead of 147 MB.
- **The image was rebuilt on every invocation.** `sysroot-install` is phony, so
everything depending on it always ran. There is now a `build/sysroot.stamp`
whose mtime only advances when a `find`-based summary of the staged tree
actually changes (the `cmp -s ... || mv` idiom — **do not add a `touch`, and
note `touch -c` still updates an existing file's mtime**, which is exactly
the bug I wrote and had to remove).
- **`ports/toybox` re-linked its 236 applets every build**, because port.mk
declares `install` .PHONY for every port. It hangs off
`$(BUILDDIR)/toybox.installed` now. This was the sneaky one: it bumped the
mtime of 236 files in `sysroot/` and so invalidated the stamp above.
- **mkfs and mkschfs re-read every file to dedup it.** They now check
`(st_dev, st_ino)` first — toybox's applets are real hard links, so 236 of
them are settled by one `fstat` instead of ~500 MB of reads.
Two real bugs found in the Makefile while doing this:
- **`$(BUILD)/kernel/**/*.d` only matched one directory level.** make's wildcard
has no recursive `**`. `arch/x86_64/` and `fs/schfs/` therefore had **no
header dependency tracking at all** — editing `include/fs/schfs.h` left stale
objects that linked fine. It uses `$(shell find ...)` now.
- **`CFLAGS` is recursively expanded and had a `$(shell)` appended**, so the
stack-protector probe re-ran on every expansion: one extra gcc per object
file. Probed once into `NO_SSP :=` now.
`schunix.img` is no longer `.PHONY` (it is a real file), and `MAKEFLAGS +=
-j$(nproc)` is on by default — use `make -j1` for readable output from a
failing build.
Verify with `make schfs-check` plus [[schunix-verify-by-driving-shell]]; the
superblock checksum is printed by mkfs and should not change when only build
mechanics change.