Milestone 8 — Systems and freestanding support (plan and acceptance)
Status: complete 2026-10-24 (started 2026-10-24). Decisions: ADR 0007. Normative: unsafe.md (the unsafe contract), type-system.md §27 (raw pointers and unsafe), and later sections named per increment. Each increment writes its spec first, then the code and tests.
Master prompt §14, §15 and Milestone 8 ask for: a core library, freestanding compilation, a C FFI, raw-pointer unsafe operations, custom entry points and target-specific capabilities. C4 (a freestanding Nova object inside CosmoOS's rules) builds on all of it.
Increments
| # | Content | Spec | Status |
|---|---|---|---|
| 8a | Unsafe and raw pointers: unsafe { } blocks and unsafe { e } expressions, unsafe fn, the *T / *mut T types, pointer casts, pointer methods and Ptr.* / Slice.fromRaw (read, write, arithmetic, null, references and slices from raw parts), slice .ptr; the U1 capability check and the U2 /// Safety: rule; C lowering without undefined pointer arithmetic; interpreter checks (null, dead, out of bounds, misaligned, uninitialised) | unsafe.md, type-system §27, grammar | done 2026-10-24 |
| 8b | C FFI: attributes, extern "C" imports, @export C-callable functions, @repr(C) structs and @repr(uN) enums, CChar, nova_exports.h, linking C sources into a build; safe wrappers over libc (Ex17); the interpreter's model of the C library | type-system §28, c-backend §2.3 | done 2026-10-24 |
| 8c | Profiles: --profile hosted|freestanding, the per-profile capability table (hosted-only built-ins rejected in freestanding code), the runtime split (the core runtime without platform services; nova_panic from the environment), --emit obj; a freestanding object whose undefined symbols are an allowlist (llvm-nm -u) | profiles.md §4 (new), c-backend §1 | done 2026-10-24 |
| 8d | Entry points, callbacks, MMIO, lib/core and target capabilities: custom entry points (exported entry functions, no generated main), C function pointers (moved from 8b, ADR 0007), the bundled lib/core Nova sources (moved from 8c: its first contents are the MMIO types), Volatile<T> / MmioPtr<T> / PhysAddr / VirtAddr in lib/core, @align, Cpu.spinHint(), target queries, volatile accesses (Ex18) | type-system §29 | done 2026-10-24 |
| 8e | Acceptance: a freestanding demo object for the host and both CosmoOS triples (x86_64-unknown-none-elf, aarch64-unknown-none-elf): the llvm-nm -u allowlist, no .init_array, TLS, .eh_frame or COMMON symbols; docs | this file | done 2026-10-24 |
Acceptance criteria (roadmap.md)
- A freestanding Nova object with no libc:
llvm-nm -ulists only allowlisted symbols (the memory functions and the profile's hooks), on the host and on both CosmoOS triples. - Unsafe is contained: every operation of U1 outside an unsafe context is rejected (compile-fail corpus, two tests per operation at least);
unsafe fnand safe functions containingunsafewithout a/// Safety:comment are rejected; safe code is checked exactly as before (the whole corpus is unchanged). - Raw-pointer programs are UB-free: they run under ASan and UBSan, and the interpreter agrees with native output on every one; deliberately broken unsafe programs are interpreter faults.
- C FFI: Ex17's running form (
run-pass/ffi-libc.nova, safe wrappers over the C library) runs, and C code calls an@exportNova function;@repr(C)layouts equal the C compiler's (_Static_asserton size, alignment and offsets). - Profiles: hosted-only names are rejected in freestanding code; the run corpus is unchanged in the hosted profile.
- Docs:
unsafe.md,type-system.md,grammar.md,c-backend.md,profiles.md,intermediate-representation.md,diagnostics.md,roadmap.md,risks.md(R-1, R-3, R-10),testing.md.
Results by increment
| Increment | Evidence (run 2026-10-24) |
|---|---|
| 8a | Spec: unsafe.md (new, normative), type-system.md §27, grammar (doc comments, unsafe fn, unsafe blocks and expressions, *T/*mut T), ADR 0007, diagnostics N0280–N0283. Compile-fail corpus: check-fail/unsafe-capabilities.nova (every U1 operation outside an unsafe context, two or more cases each, and where a block or expression ends; plus the safe operations, which are not errors), raw-pointer-types.nova (pointees, operations, casts, comparisons), unsafe-safety-comments.nova (the Safety rule, ////, interrupted doc comments, unsafe main, interface and impl methods). Run corpus: run-pass/raw-pointers.nova and raw-slices.nova (arithmetic in both directions, struct pointees, references and slices from raw parts, a safe wrapper, pointer casts and equality, null, pointers in arrays and to pointers) pass in every C configuration, including ASan+UBSan and the strict warning set with -Wcast-qual, and in the interpreter. UnsafeTests: invalid accesses through pointers are interpreter faults (ended memory, out of bounds, misaligned, null, a write to static memory, a slice from raw parts past its array). Placement rule 1c: placement/address-exposed.nova. Parse snapshot parse-pass/unsafe-and-pointers.ast reviewed. The rest of the corpus is unchanged (1,880 tests pass; no existing snapshot changed). Two spellings changed from the first draft of the spec, because the words are keywords: Ptr.borrow is Ptr.toBorrow, and Ptr.null<T>() became 0 as *T. Found while testing: s.ptr moved a SliceMut instead of reborrowing it. Not done: move-type pointees (ADR 0007 decision 3), pointers to type parameters, borrow tracking in the interpreter |
| 8b | Spec: type-system.md §28, c-backend §2.3, grammar (attributes, extern blocks), intermediate-representation.md §5.4 (the interpreter's C model), ADR 0007 decision 6, Ex17 updated, diagnostics N0290–N0292. Extern prototypes use C's own types (size_t, T const *, char through the pointee-only CChar), so they are compatible with the C library's declarations, and calls go through generated wrappers. @repr(C) structs keep their Nova names and members in C. Compile-fail corpus: check-fail/ffi-declarations.nova (every N0290/N0291/N0292 case, duplicate C names, calls outside unsafe, CChar as a value, reads through *CChar). ffi/exports.nova with its C half exports.c (FfiTests): C calls exports taking and returning @repr(C) structs, @repr(u8) and @repr(i32) enums, bool, isize/usize and pointers; Nova calls C with pointers to structs and an out-parameter; a bad enum value from C panics. It passes under every C configuration, ASan+UBSan included, and its nova_exports.h is a reviewed snapshot. run-pass/ffi-libc.nova (safe wrappers over strlen, puts, abs, memcmp, memcpy, memset) runs in every configuration, in the interpreter's model of the C library, and in the guests. The interpreter faults on any other C call (FfiTests). Parse snapshot parse-pass/attributes-and-extern.ast reviewed. 1,897 tests pass. Not done: C function pointers (moved to 8d), arrays and @repr enums inside @repr(C) structs, enums with explicit values, C's long (no Nova type matches it on every target) |
| 8c | Spec: cosmoos/profiles.md §4 (new, normative: capability table, runtime and the nova_panic hook, --emit obj, the symbol allowlist), c-backend §1, diagnostic N0293. `--profile hosted |
| 8d | Spec: type-system.md §29 (volatile accesses, lib/core, CPU and target queries, @align, C function pointers, entry points), §27.1 (pointers to type parameters), c-backend §2.3, IR §5.4, ADR 0007 decision 7, Ex18 updated. lib/core (lib/core/mmio.nova, address.nova): Volatile<T>, MmioPtr<T>, PhysAddr, VirtAddr, written in Nova over raw pointers; only what a program uses is emitted (every earlier C snapshot is unchanged). Also implemented: @noncopy (OD-7, accepted in M0), and borrows through a call's reference result (regs.getMut().data.write(…), HirBorrowPath). Found while building it: Volatile was first Copy, and get().status.read() then read a temporary copy of the register with an ordinary load; Volatile is @noncopy now, so that is a move error (check-fail/mmio-moves.nova). Tests: run-pass/mmio.nova (a driver over a register block in memory, @align, the address types, target queries), run-pass/function-pointers.nova (extern and exported functions as values, in a struct field, null, equality), ffi/callbacks.nova + .c (C calls Nova through a pointer, Nova calls C's own function through one, a callback in a @repr(C) struct; reviewed callbacks.h), freestanding/uart.nova + .c (Ex18 as a freestanding object: the allowlist holds on the host and both CosmoOS triples, and a C environment drives it), check-fail/systems-capabilities.nova, emit-c/volatile.c and parse-pass/systems-syntax.ast (reviewed). 1,932 tests pass. Not done: DmaAddr (kernel profile), Nova-level function values (OD-9), conditional compilation, packed structs |
| 8e | FreestandingTests now also checks every object for x86_64-unknown-none-elf and aarch64-unknown-none-elf against cosmoos/profiles.md §4.4: no .init_array/.fini_array/.ctors/.dtors, no .tdata/.tbss, no .eh_frame, empty .data/.bss (no mutable global state), and no COMMON symbols (llvm-objdump -h, llvm-nm), for both freestanding packages (demo, uart). The freestanding profile now always adds -fno-asynchronous-unwind-tables -fno-unwind-tables, so no target's default can add unwind tables. The checks were seen to fail: an object built with unwind tables and a mutable global shows .eh_frame and a 4-byte .data |
Acceptance results (2026-10-24)
| # | Criterion | Evidence | Result |
|---|---|---|---|
| 1 | Freestanding object with no libc; llvm-nm -u allowlist on the host and both CosmoOS triples | FreestandingTests on freestanding/demo.nova and uart.nova (Ex18): the only undefined symbols are nova_panic and the packages' own C functions (allowed: nova_panic, memcpy, memmove, memset, memcmp, declared externs), for the host object (joined with -r), for each cross object, and for the ld.lld -r join where ld.lld exists; no constructor, TLS or unwind sections, no mutable globals, no COMMON symbols; a C environment links and runs each host object | met |
| 2 | Unsafe is contained | check-fail/unsafe-capabilities.nova (every U1 operation outside an unsafe context, at least two cases each; where blocks and expressions end; the safe operations), systems-capabilities.nova (volatile accesses, calls through function pointers, MmioPtr.at), ffi-declarations.nova (extern calls); the Safety rule (unsafe-safety-comments.nova, N0283); the whole earlier corpus unchanged | met |
| 3 | Raw-pointer programs are UB-free; broken ones are interpreter faults | run-pass/raw-pointers, raw-slices, mmio, function-pointers, ffi-libc under ASan+UBSan and every other C configuration, and in the interpreter (InterpreterTests); UnsafeTests: ended memory, out of bounds, misaligned, null, writes to static memory and slices past their array are faults; the CosmoOS guests run them too (c2.py, 146 programs on both arches) | met |
| 4 | C FFI: Ex17 runs, C calls an @export, @repr(C) layouts equal C's | run-pass/ffi-libc.nova (Ex17's running form); ffi/exports.nova and ffi/callbacks.nova with their C halves, which include the generated nova_exports.h and call Nova (FfiTests, every C configuration); every struct, @repr(C) ones included, is followed by _Static_asserts on its size, alignment and field offsets against Nova's layout | met |
| 5 | Profiles: hosted-only names rejected in freestanding code; the hosted run corpus unchanged | check-fail/freestanding-hosted-names.nova (N0293 for every hosted-only name and main); every earlier C snapshot unchanged through M8 (lib/core is emitted only when used); the run corpus, fault sweep and guests pass | met |
| 6 | Docs | unsafe.md (new), type-system.md §27–§29, grammar.md, c-backend.md §1, §2, §2.3, cosmoos/profiles.md §4, intermediate-representation.md §5, diagnostics.md (N0280–N0283, N0290–N0293), language-spec.md Ex17 and Ex18, ADR 0007, roadmap.md, risks.md (R-1, R-2, R-3, R-10), testing.md, this file | met |
Not done (recorded): user-defined allocators (so freestanding code has no heap; C5's KernelHeap needs them); move-type pointees; Nova-level function values and closures (OD-9); conditional compilation; packed structs; arrays and enums inside @repr(C) structs; enums with explicit values; C's long; DmaAddr; borrow tracking in the interpreter.