amd64 seed-kernel TODO
Working doc. Captures the tcc 0.9.26 limitations we worked around
to bring up boot6 amd64 and DRIVER=seed ./scripts/boot.sh amd64,
plus what's still unvalidated. Pairs with docs/OS.md (kernel
contract), docs/TCC.md (compiler), and docs/SEED-RISCV64-TODO.md
(parallel write-up for the riscv64 path).
Goal
DRIVER=seed ./scripts/boot.sh amd64 should run the full
boot0→boot6 chain entirely inside the tcc-built amd64 seed kernel
(the kernel is its own build driver, podman only mints the first
kernel image). This validates every kernel path the chain depends
on under real workloads.
What works (May 2026)
scripts/boot6.sh amd64(DRIVER=podman) builds a clean PVH ELF atbuild/amd64/boot6/kernel.elf. Boots underqemu-system-x86_64 -machine microvm -kernel ...; runs scheme1 + tcc3 + the user smoke tests underseed-kernel/run.sh ARCH=amd64.DRIVER=seed scripts/boot6.sh amd64self-rebuilds the kernel inside itself (closes the bootstrap loop).DRIVER=seed scripts/boot{0,1,2}.sh amd64complete cleanly on the tcc-built kernel (boot0 ≈1 s, boot1 ≈14 s, boot2 ≈43 s under TCG). Outputs arebuild/amd64/boot{0,1,2}/....
tcc 0.9.26 limitations worked around for amd64
These are amd64-specific gotchas; the existing docs/TCC.md and
the simple-patches in scripts/simple-patches/tcc-0.9.26/ cover the
shared/aarch64/riscv64 issues.
.quadliteral truncation. tcc's assembler silently truncates a.quadvalue to its low 32 bits when the high half is non-zero (gen_le64(int64_t c)is fed a value already lost through the parser path). The amd64 GDT entries (0x00af9a000000ffff,0x00af92000000ffff) are the natural trip wire — without the workaround, the bootloader'slgdtloads zero P/L bits, the long-modeljmpraises #GP, and the kernel never prints anything.- Workaround:
seed-kernel/arch/amd64/kernel.Sencodes each descriptor as.long lo, hiso the high half is parsed as a fresh 32-bit literal. Comment in-place explains why. - TODO: add a
simple-patchto fix.quadparsing in tccasm.c so the source can use the natural form. Suspected site is the parser path that intermediates 64-bit values through a 32-bitintbefore reachinggen_le64.
- Workaround:
No PT_NOTE program header / SHT_NOTE section type. tcc's linker emits exactly two PT_LOAD phdrs for static EXEs (no PT_NOTE), and
find_sectiondefaults every assembler-created section to SHT_PROGBITS regardless of name. QEMU's PVH-kernelpath scans PT_NOTE phdrs for the Xen 18 note that names the 32-bit entry; without one it errors out with "Error loading uncompressed kernel without PVH ELF Note".- Workaround:
seed-kernel/scripts/elf-pvh-note.cis a tiny tcc-built post-link tool that locates.note.Xenvia the section header table, retypes the section as SHT_NOTE, and writes a fresh PT_NOTE phdr atphoff + phnum*phentsize(the gap between Ehdr/Phdrs and the first PT_LOAD's content at offsets_align= 0x200000 has plenty of room). Wired intoboot6-gen-runscm.shandboot6.shfor amd64 only. - TODO: patch tcc to (a) detect ".note.*" section names and
create them as SHT_NOTE, and (b) bump phnum + emit PT_NOTE
phdrs for each SHT_NOTE alloc section after
layout_sections. That removes the need for the post-link tool.
- Workaround:
amd64 kernel additions (not tcc workarounds)
These are real kernel features that aarch64/riscv64 already had in some form and amd64 was missing. Listing them so the riscv64-style TODO has full context, not because they're broken.
- MSR-based
syscallentry path. scheme1 (and any tcc-built user binary that follows the SysV/Linux amd64 ABI) emits thesyscallinstruction, which goes through MSR_LSTAR — not the IDT. The previous kernel only handledint $0x80. Addedamd64_syscall_entryinkernel.SandMSR_EFER.SCE | MSR_STAR | MSR_LSTAR | MSR_SFMASKsetup inmmu.c::setup_cpu_tables. - CR0.MP / CR4.OSFXSR / CR4.OSXMMEXCPT. tcc emits xmm-spill
prologues (
movq %xmm7, -0x20(%rbp)) in user binaries; without these CR bits set, the first such instruction raises #UD/#NM. The gcc-built kernel +-mno-sseuser (user/hello.c) avoided this; tcc has no-mno-sseequivalent so we enable SSE in long-mode init. - PVH
hvm_start_infocmdline parsing. microvm has no DTB. PVH passes thehvm_start_infophys addr in EBX; we preserve it through long-mode init and pass tokmain.parse_dtbinkernel.crecognises magic 0x336ec578 and readscmdline_paddrat +0x18 to populatedt.bootargs. - Legacy
SYS_open(2)alias. amd64 stage0hex0-seedissuessyscall 2(Linux's legacy open) directly. AddedARCH_SYS_openin amd64 arch.h and a switch case that aliases tosys_openat(AT_FDCWD, ...).
What's not yet validated
- boot3 → boot6 under DRIVER=seed amd64. boot3 (scheme1
driving cc.scm to compile tcc.flat.c + libc.flat.c → tcc0)
is compute-bound under TCG-emulated x86_64 on Apple Silicon
(no hvf for amd64). The aarch64 path uses hvf and runs at
near-native speed; amd64 is 5–20× slower. Concretely: boot3
timed out at the previous 1800 s default. We bumped the
timeout knob (
BOOT3_TIMEOUT,BOOT4_TIMEOUT) and a 4 h boot3 run is in progress — seebuild/amd64/.boot3-stage/ transcript.txtfor the live log. - Fixed-point check. Once boot3/4/5 complete, the artifacts (catm/scheme1/tcc0/tcc1/tcc2/tcc3/libc.a/...) should byte-match the DRIVER=podman amd64 outputs. Untested.
scripts/run-tests.sh amd64under DRIVER=seed. Untested.- boot4's tcc fixed-point assertion (
tcc2 == tcc3) under DRIVER=seed amd64. Untested.
Cost / acceleration note
The amd64 seed driver runs under qemu-system-x86_64 -cpu max
with TCG (no hvf for x86_64 on Apple Silicon). A full
DRIVER=seed scripts/boot.sh amd64 is realistically a multi-hour
operation. The aarch64 path is the fast/CI iterating loop; the
amd64 seed path is mostly there for parity validation.