Spec M16 — Linux arm64: ELF64 relocatable objects, Linux system layer, external linker
Goal: [target] os = "linux" in mc.toml makes mc build emit an ELF64 .o, invoke the configured
linker (ld.lld) against musl (or no libc), and the result runs in Docker linux/arm64. Nothing in
stage0 changes; the ELF writer is a backend in .mc.
ELF64 relocatable writer (src/backend_elf.mc, backend name elf-obj) #
- Reuses
gen_lower+gen_encode_all(sections, symbols, relocs are format-neutral). - Header:
ELFCLASS64,ELFDATA2LSB,ET_REL,EM_AARCH64(183),e_flags 0. - Sections (in this order): null,
.text(PROGBITS, AX),.rodata(A) for__cstring,.data(WA),.bss(NOBITS, WA), custom#section SEG SECTmapped to.SEG.SECTlowercase without underscores (__TEXT,__hot->.text.hot, flags from the Mach-O flags: pure-instructions -> AX, zerofill -> NOBITS),.rela.text/.rela.data/.rela.<custom>(only when non-empty),.symtab,.strtab,.shstrtab. Alignment:.text4, data 16 (as today), rodata 1. - Symbols: strip the leading
_of every Mach-O name (_main->main,l_str0stays local as.Lstr0); locals first then globals (sh_info= first global);STT_FUNCfor functions in text,STT_OBJECTfor data,STT_NOTYPEfor undefined (SHN_UNDEF); section symbols not needed. - Relocations (
Elf64_Rela, addend 0 unless noted):R_BRANCH26->R_AARCH64_CALL26(283);R_PAGE21->R_AARCH64_ADR_PREL_PG_HI21(275);R_PAGEOFF12->R_AARCH64_ADD_ABS_LO12_NC(277) when the instruction isadd, elseR_AARCH64_LDST{8,16,32,64}_ABS_LO12_NC(278/284/285/286) by access width (reuse the classifier ofbackend_exe.mc);R_UNSIGNED(8 bytes in data) ->R_AARCH64_ABS64(257). Sorted by offset ascending (ELF convention). - Determinism: same ordering rules as Mach-O (
docs/determinism.md). - Verification tool:
llvm-readobj --all/llvm-objdump -dr(Homebrew LLVM) on our.ovs a.oproduced byclang --target=aarch64-linux-musl -cfrom equivalent C, field by field.
Linux system layer #
-
Default: link against musl.
lib/sys.mcneeds no change (write/read/close/exit/createxist in musl'slibc.a;openstays out because it is variadic).mc.tomlfor linux:[target] os = "linux" arch = "aarch64" [linker] cmd = "ld.lld" args = ["-o", "{out}", "{sysroot}/crt1.o", "{sysroot}/crti.o", "{obj}", "{libs}", "{sysroot}/libc.a", "{sysroot}/crtn.o"] [sysroot] path = "build/sysroot/linux-aarch64" # {sysroot} placeholder; populated from Alpine musl-dev - No-libc option:
<sys/linux>with raw syscalls (svc #0, number inx8: read 63, write 64, openat 56 withAT_FDCWD = -100, close 57, exit_group 94, fchmod 52) and a_startwritten with#opcodethat loadsargc/argvfrom the entry stack and callsmain, thenexit_group. Linked with-nostdlib -e _start.
Driver #
[target].os = "linux"selectself-objfor the object and requires[linker](no direct executable for Linux in this milestone).{sysroot}placeholder from[sysroot].path.scripts/sysroot-linux.sh:docker run --platform linux/arm64 alpine:3+apk add musl-dev, copiescrt1.o crti.o crtn.o libc.a(andlibc.sofor reference) intobuild/sysroot/linux-aarch64. Cached;make checkskips Linux tests with a clear message when Docker is unavailable.
Tests #
scripts/test-linux.sh: for everytests/*.mc,mc buildwith a generated linuxmc.toml(entry = the test, musl link), then run insidedocker run --rm --platform linux/arm64 -v build:/w alpine /w/<test>and compare stdout/exit with the headers ->N/N tests passed on linux/arm64. Tests that use#opcode svcfor macOS (032-svc) are skipped by a header// skip-linux.examples/apion Linux:mc buildwith a linux TOML variant (mc.linux.toml), sqlite via Alpine'ssqlite-static/sqlite-devlibsqlite3.ain the sysroot;test.shrun inside the container.make checkgainstest-linux(guarded by Docker availability). Golden rewritten once.