Skip to content

C9 — Gradual adoption: the GPT parser in Nova (plan and report) ​

Status: C9a and C9b complete 2026-10-26: the GPT parser and writer are Nova in CosmoOS itself (merged as 68925d14) (the GPT parser and writer, held to CosmoOS's tests out of tree). Roadmap acceptance for C9: evaluate further components; a per-component ADR (roadmap.md). Prompt: cosmoos-native.md C9 ("evaluate which additional components benefit from migration; do not mandate a wholesale rewrite"). The language additions the component needed are ADR 0014. Scripts: integrations/cosmoos/c9.py, benchmarks/gpt/run.py.

On 2026-10-26 the owner stopped CosmoOS development and allowed Nova to make any integration it needs there ("You can go ahead with any integration you need"). C9a still ran as C6 did, out of tree with CosmoOS unchanged (c9.py checks that), so its results compare the port against the C exactly as CosmoOS ships it. Building the port into CosmoOS itself is the next step (below).

Choice of component ​

The recommendation (2026-10-26), from C6's short list: GPT, kernel/block/gpt.c.

  • It parses untrusted disk input in the kernel: a disk's partition table, read at boot and when a disk appears. A bug there is reachable from any disk image or device. This is the code memory safety pays for.
  • It is self-contained: a pure API over a read callback, free of kernel dependencies by design (gpt.c's header comment), and linked by the kernel, the host tests, the fuzzer and the installer.
  • CosmoOS already tests it three ways: a host test (tests/host/test_gpt.c), a fuzz target (tests/fuzz/fuzz_gpt.c) and an in-kernel test (kernel/block/parttest.c).
  • It is not performance-critical, and it was blocked in C6 only by structs and the read callback, which ADR 0008 and M8d have since provided.

Audit (CosmoOS 75e1e15f, 2026-10-25; read, not copied) ​

ItemWhereWhat it means for the port
APIkernel/include/kernel/gpt.hgpt_parse (sector size, sector count, gpt_read_fn callback and its context, a GPT_SCRATCH_BYTES scratch buffer, struct gpt_table *out, const char **why) returning enum gpt_result (OK, NONE, BAD, IO); the writer gpt_build with struct gpt_build_part; gpt_build_first_usable/last_usable; gpt_crc32/_update; gpt_guid_format/parse; the data gpt_type_esp, gpt_type_cosmo_root
Result structsgpt.hstruct gpt_table holds uint8_t disk_guid[16] and struct gpt_part parts[32]; gpt_part and gpt_build_part hold uint8_t type[16], uuid[16]. Imported structs could not have array fields, and type is a Nova keyword: ADR 0014
Datagpt.c:12-18two const uint8_t[16] objects. Nova could not export data, nor declare constant arrays: ADR 0014
Parsergpt.c:120-30824 refusal reasons, in a fixed order; the backup header's reason is replaced by "backup header", its array's by "backup entry array CRC"; partial writes to out before a refusal; the scratch layout (a sector at 0, entries at 4096)
Writergpt.c:310-378protective MBR, both headers and arrays; partial writes before a refusal (an out-of-range index)
Userskernel/block/part.c, kernel/core/bootfs.c, userland/system/install_gpt.c (#includes gpt.c for cosmo-install and blkid)an in-tree replacement must serve all three
Teststests/host/host.mk:54,88 (HOST_GPT_SRCS, built with -DCMDLINE_HOST_TEST=1), tests/fuzz/fuzz.mk:46 (FUZZ_GPT_SRCS)the host test also covers cmdline.c; c9.py swaps only gpt.c

What was built ​

  • ADR 0014 and its implementation (type-system §10, §28.4, §31, §33.1; c-backend §2.3): array fields in imported structs (lowered as Nova's array struct over the member, with exact _Generic checks), arrays of imported types, @cname for C member names that are Nova keywords, add/sub/offset on pointers to imported types, constant arrays, and exported constants as C data. Tests: tests/corpus/ffi/c-arrays.nova (run under every C configuration, with its C half reading what Nova wrote), check-fail/c-arrays.nova, check-fail/c-array-moves.nova, and updated expectations in check-fail/arrays.nova, check-fail/imported-c-types.nova, check-fail/c-state-types.nova and parse-fail/struct-errors.nova.
  • integrations/cosmoos/c9/gpt.nova (615 lines, formatted, against gpt.c's 378): the parser and writer in safe Nova over slices, with gpt.h's structs imported from the header. Disk bytes reach the parser only as slices of the caller's scratch buffer; refusals are a Reason enum, worded as gpt.c words them only at the boundary. unsafe is confined to the seven exported wrappers at the end of the file (raw pointers to slices and references, and the call through gpt_read_fn, inside a closure that the safe parser receives as its reader). gpt_build_last_usable keeps C's unsigned wrap-around (wrappingSub) for disks too small for a table, which gpt_build then refuses.
  • integrations/cosmoos/c9/diff_gpt.c: the differential test. Tables made by gpt_build, then damaged in six ways (none; flipped bytes; a header field rewritten to an edge value and every checksum made valid again; an entry rewritten in both arrays and resealed; the MBR damaged), with failed reads, shorter disks, wrong sector sizes and a null why. Both implementations must agree on the result, the reason, every byte of the table (prefilled alike), the scratch buffer, and the sequence of reads; and on the writer's answer and bytes, CRC-32, GUID text both ways, and the type GUIDs.
  • integrations/cosmoos/c9.py, which reuses C6's steps (c6.check_component, now parameterised by milestone): CosmoOS's host test and fuzz target built twice with its own flags, the differential test, and C4's object checks under MODULE_CFLAGS.
  • benchmarks/gpt/: bench.c and run.py (method in its README).

Results (run 2026-10-26) ​

Tests ​

python3 integrations/cosmoos/c9.py --arch aarch64 and --arch x86_64 (macOS, Apple clang 21; defaults: 20,000 fuzz runs, 20,000 differential cases, seed 1):

CheckC originalNova port
CosmoOS test_gpt (ASan, UBSan, -Werror)PASSPASS
CosmoOS fuzz_gpt, 20,000 runsPASSPASS
Differential test, 20,000 cases—0 mismatches
Object under aarch64 MODULE_CFLAGS, C4's checks—PASS: relocations R_AARCH64_ABS64, ADD_ABS_LO12_NC, ADR_PREL_PG_HI21, CALL26, JUMP26; undefined memmove, memset, panic; largest frame gpt_parse, 576 B
Object under x86_64 MODULE_CFLAGS, C4's checks—PASS: relocations R_X86_64_32S, R_X86_64_64, R_X86_64_PLT32; undefined memcpy, memmove, memset, panic; largest frame gpt_parse, 552 B
CosmoOS working tree unchangedPASS

A longer differential run (diff_gpt 50000 7, 2026-10-26): 0 mismatches over 10,979 valid tables, 7,732 NONE, 27,751 BAD and 3,538 IO results. It reaches 23 of gpt.c's 24 refusal reasons; the 24th cannot be reported (finding 1).

Performance ​

python3 benchmarks/gpt/run.py (both implementations in one binary, -O2, median of 7; parses and builds of a 2,048-sector disk's table from memory, per second, and CRC-32 over 32 KiB in MB/s; Nova/C shown):

Machine, compilerparse (4 shapes)build (4 shapes)crc32
Apple M1, macOS 27, Apple clang 21.0.00.98–1.000.99–1.001.00
Ryzen 5 PRO 3600, Debian 12, clang 18.1.80.99–1.001.001.03
same, gcc 12.2.01.091.14–1.151.07
same, gcc 12.2.0 -falign-loops=320.98–1.071.12–1.201.06

The port is at parity under clang and faster under gcc. Part of gcc's difference moves with loop alignment, so code placement explains some of it; the rest is not investigated (gcc's code for the two bitwise CRC loops is the likely place, inferred). A parse takes about 0.2 ms either way, at boot and when a disk appears. The full numbers are in benchmarks/gpt/README.md.

Findings ​

  1. "entry array overlaps its header" is never reported (gpt.c:154-155). For the primary header (LBA 1), the check before it already refuses an array that starts below LBA 2, so the array cannot contain the header. For the backup header it can fire, but gpt_parse then replaces the reason with "backup header" (gpt.c:212-214). The check works (such tables are refused); its wording never reaches a caller. diff_gpt.c builds such tables (n - GPT_BUILD_ARRAY_SECTORS(ss) as the backup's array start) and both implementations report "backup header". For CosmoOS's backlog, not a defect in the port.
  2. gpt_build_first_usable(0) divides by zero in C (GPT_BUILD_ARRAY_SECTORS(0), undefined behaviour); the port panics. No caller passes 0, and gpt_build and gpt_parse refuse sector sizes other than 512 and 4096 before using the macro.
  3. An exported function whose header prototype returns enum gpt_result is defined by Nova as returning uint32_t. GCC and Clang give an enum without negative values the type unsigned int, so the declarations are compatible (checked with clang 21 and gcc 12, 2026-10-26); a compiler that chose otherwise would reject the build, not miscompile it (ADR 0014, consequences).

What C9a proves, and what it does not ​

  • Proved: the kernel's partition-table parser, a parser of untrusted input with a callback API and structs with arrays, can be written in Nova against CosmoOS's own header with the same behaviour byte for byte, CosmoOS's tests and fuzzer passing, no measured cost, and an object that meets the module loader's rules on both arches.
  • Proved: the language additions were small and general (ADR 0014): they apply to any C header with array members, keyword-named members or exported tables, which Linux's headers are full of.
  • Not done: running the port inside CosmoOS. gpt.c is linked into the kernel image (and #included by the installer), so that needs CosmoOS's build to use the Nova object: stage S1 (build-integration.md), which the owner's 2026-10-26 instruction now allows. That step, C9b, would replace gpt.c in the kernel, the host test, the fuzz target and the installer, and run parttest.c in QEMU on both arches.

C9b — the port in CosmoOS (2026-10-26) ​

With the owner's choices (2026-10-26): generated C checked into CosmoOS (stage S1 of build-integration.md, so CosmoOS builds without .NET), the C original removed, and the boot tests run locally in QEMU as well as in CosmoOS's CI. CosmoOS branch nova-gpt, PR #459.

What changed in CosmoOS ​

FileWhat
kernel/block/gpt.cnovac's C for c9/gpt.nova, with a "generated, do not edit" banner. Keeping the name keeps every build that compiles or #includes it unchanged
kernel/include/nova/rt.h, kernel/nova/nova_rt_core.cNova's runtime header and freestanding core (generated)
kernel/nova/nova_panic.cthe kernel's panic hook: a failed Nova check is a kernel panic naming the Nova line (as C4's nova_rt_kernel.c)
userland/system/install_gpt.cthe installer and blkid take the runtime with gpt.c, and abort on a failed check
tests/host/host.mk, tests/fuzz/fuzz.mk, tests/host/nova_panic.ctest_gpt and fuzz_gpt link the runtime and a panic hook that aborts
kernel/kernel.mkthe runtime and the panic hook in KERNEL_GENERIC_SRCS
docs/kernel/device/design.mdgpt.c is generated: change the Nova source and regenerate

c9/vendor.py wrote the generated files (since 2026-10-27 the source is CosmoOS's own kernel/block/gpt.nova and CosmoOS's make nova writes them; build-integration.md §3.1); --check fails if CosmoOS's copies differ from novac's output. Once the tree builds the port, c9.py takes the C original from CosmoOS dc1d5166 (the last commit with it) and leaves the tree's runtime files out of both comparison builds (c6.check_component's original_file and in_tree_nova).

Results (run 2026-10-26, macOS, CosmoOS nova-gpt) ​

Checkx86_64aarch64
make host-test (all host tests, test_gpt included)PASS(host)
make fuzz (all targets, fuzz_gpt included, 20,000 mutations each)PASS(host)
make analyzeclean: 7 baselined diagnostics, 0 unexpected; none in the new filesclean
debug make testPASS: 514 self-tests, blk-gpt ok (119 ms)PASS: 514 self-tests, blk-gpt ok (113 ms)
BUILD=release make testPASS (18.9 s)PASS (21.2 s)
the boot disk's table, parsed by the Nova parserahci0p0p1 … type espvdb1 … type esp
vendor.py --checkmatch
c9.py against the branch (C original from dc1d5166)0 mismatches in 5,000 cases; both builds of both tests passobject checks pass

blk-gpt's refusal case logs part: rama: partition table refused: header CRC: the Nova reason reaches the kernel's log unchanged. The kernel's check that no FP or vector register is touched passes with the Nova code linked.

One environment problem, not CosmoOS's or Nova's: the first aarch64 make test failed before booting, because out/busybox-aarch64/cc and cc-dyn (generated wrapper scripts) named a sysroot in another session's scratch worktree. Deleting the two generated files let Make regenerate them; the boots then passed.

CosmoOS's CI on the PR passed every check (47 of 47, 2026-10-26: build, boot and stress, install, fsck, upgrade, encryption, network, release reproducibility and the other image tests, on both arches), and the owner merged it (68925d14). CosmoOS's kernel, installer, blkid, host test and fuzz target now run the Nova parser and writer.

Next ​

  • Further C9 components, each with its own evaluation: the module ELF validator (kernel/module/modelf.c, in the loader's trust path, now unblocked by ADR 0008 and 0014) is the next candidate from C6's list.

Pre-1.0: the language and its tools are still changing.