Sprint 18: HELLO WORLD MILESTONE (Executable)
Prerequisites
Sprints 0–17 — full read, resolve, atomize, layout, reloc-apply, dyld-info, function-starts, unwind pipeline.
Goals
Produce a runnable arm64 PIE executable. afs-ld takes hello.o + libarmfortas_rt.a + libSystem.tbd + -e _main and emits a binary that, when executed on an M-series Mac, prints "Hello, World!". Not a demo — an exit criterion for everything that came before.
Deliverables
1. Staging fixture
tests/corpus/hello/ contains:
hello.f90: trivial Fortran program withprint *, "Hello, World!".hello.o: assembled by afs-as from armfortas'shello.soutput.- Expected output on run:
Hello, World!\n(Fortran print adds a leading blank on some paths — match what armfortas currently produces).
2. End-to-end link
Invocation:
afs-ld hello.o libarmfortas_rt.a \
-lSystem -syslibroot "$(xcrun --show-sdk-path)" \
-e _main -no_uuid -platform_version macos 11.0 14.0 \
-o hello
Expected:
- Output file passes
file hello→Mach-O 64-bit executable arm64. otool -lV helloaccepts without errors and showsLC_MAIN, expected segments, dylibs.codesign -dv helloreports no signature (Sprint 22 adds ad-hoc signing — binaries still run unsigned on Apple Silicon from Xcode or a trusted source, but./hellofrom a Terminal prompt requires signing. Document this caveat.)../hello(after the Sprint 22 signature) prints the expected string and exits 0.
3. Differential gate
tests/hello_world.rs:
let ours = link_with_afs_ld(&inputs, &args);
let theirs = link_with_system_ld(&inputs, &args);
let diff = diff_macho(&ours, &theirs);
assert!(diff.critical.is_empty(), "critical diffs: {:#?}", diff.critical);
Allowed tolerated diffs:
- UUID bytes (we emit zero with
-no_uuid, ld may or may not — both should honor-no_uuid). - String table ordering within a partition as long as every symbol resolves to the same address.
- LC_DYLD_INFO vs LC_DYLD_CHAINED_FIXUPS if defaults disagree — gate with the same
-fixup_chains/-no_fixup_chainsflag.
4. Load-command otool -lV parity
Run otool -lV on both outputs; diff should be empty after normalizing absolute file offsets (ld and afs-ld may interleave __LINKEDIT regions slightly differently — document and justify any remaining diffs).
5. Execution gate (requires Sprint 22's ad-hoc signing, but staged here)
Two cases:
- Unsigned path:
./hellofails with "killed: 9" (correct Apple Silicon behavior);codesign -s - hello && ./helloworks. - Once Sprint 22 lands, afs-ld's own output is signed ad-hoc and
./helloworks directly.
This sprint declares success as soon as codesign -s - hello && ./hello prints the expected string.
6. Audit
Brutal audit after Sprint 18. Same rules as armfortas audits:
- No "placeholder" or "stub" explanations that hide wrong output.
- Test every claim. Wrong output from a linker is critical.
- If the binary runs but produces extra or missing newlines, investigate — don't rationalize.
Testing Strategy
tests/hello_world.rsruns the differential gate.tests/hello_world_run.rsexecutes the binary (gated on CI-locally-on-Mac) and asserts stdout.- Regression fixtures: any hello-world variant that once broke gets its own test.
Definition of Done
tests/hello_world.rspasses — zero critical diffs vsld.- Binary runs and produces correct output (after
codesign -s - hellopre-Sprint-22). otool -lVdiff empty after documented normalizations.- Audit passes.
View source
| 1 | # Sprint 18: HELLO WORLD MILESTONE (Executable) |
| 2 | |
| 3 | ## Prerequisites |
| 4 | Sprints 0–17 — full read, resolve, atomize, layout, reloc-apply, dyld-info, function-starts, unwind pipeline. |
| 5 | |
| 6 | ## Goals |
| 7 | Produce a runnable arm64 PIE executable. afs-ld takes `hello.o + libarmfortas_rt.a + libSystem.tbd + -e _main` and emits a binary that, when executed on an M-series Mac, prints "Hello, World!". Not a demo — an exit criterion for everything that came before. |
| 8 | |
| 9 | ## Deliverables |
| 10 | |
| 11 | ### 1. Staging fixture |
| 12 | `tests/corpus/hello/` contains: |
| 13 | - `hello.f90`: trivial Fortran program with `print *, "Hello, World!"`. |
| 14 | - `hello.o`: assembled by afs-as from armfortas's `hello.s` output. |
| 15 | - Expected output on run: `Hello, World!\n` (Fortran print adds a leading blank on some paths — match what armfortas currently produces). |
| 16 | |
| 17 | ### 2. End-to-end link |
| 18 | Invocation: |
| 19 | ``` |
| 20 | afs-ld hello.o libarmfortas_rt.a \ |
| 21 | -lSystem -syslibroot "$(xcrun --show-sdk-path)" \ |
| 22 | -e _main -no_uuid -platform_version macos 11.0 14.0 \ |
| 23 | -o hello |
| 24 | ``` |
| 25 | |
| 26 | Expected: |
| 27 | - Output file passes `file hello` → `Mach-O 64-bit executable arm64`. |
| 28 | - `otool -lV hello` accepts without errors and shows `LC_MAIN`, expected segments, dylibs. |
| 29 | - `codesign -dv hello` reports no signature (Sprint 22 adds ad-hoc signing — binaries still run unsigned on Apple Silicon **from** Xcode or a trusted source, but `./hello` from a Terminal prompt requires signing. Document this caveat.). |
| 30 | - `./hello` (after the Sprint 22 signature) prints the expected string and exits 0. |
| 31 | |
| 32 | ### 3. Differential gate |
| 33 | `tests/hello_world.rs`: |
| 34 | ```rust |
| 35 | let ours = link_with_afs_ld(&inputs, &args); |
| 36 | let theirs = link_with_system_ld(&inputs, &args); |
| 37 | let diff = diff_macho(&ours, &theirs); |
| 38 | assert!(diff.critical.is_empty(), "critical diffs: {:#?}", diff.critical); |
| 39 | ``` |
| 40 | |
| 41 | Allowed tolerated diffs: |
| 42 | - UUID bytes (we emit zero with `-no_uuid`, ld may or may not — both should honor `-no_uuid`). |
| 43 | - String table ordering within a partition as long as every symbol resolves to the same address. |
| 44 | - LC_DYLD_INFO vs LC_DYLD_CHAINED_FIXUPS if defaults disagree — gate with the same `-fixup_chains` / `-no_fixup_chains` flag. |
| 45 | |
| 46 | ### 4. Load-command `otool -lV` parity |
| 47 | Run `otool -lV` on both outputs; diff should be empty after normalizing absolute file offsets (ld and afs-ld may interleave `__LINKEDIT` regions slightly differently — document and justify any remaining diffs). |
| 48 | |
| 49 | ### 5. Execution gate (requires Sprint 22's ad-hoc signing, but staged here) |
| 50 | Two cases: |
| 51 | - Unsigned path: `./hello` fails with "killed: 9" (correct Apple Silicon behavior); `codesign -s - hello && ./hello` works. |
| 52 | - Once Sprint 22 lands, afs-ld's own output is signed ad-hoc and `./hello` works directly. |
| 53 | |
| 54 | This sprint declares success as soon as `codesign -s - hello && ./hello` prints the expected string. |
| 55 | |
| 56 | ### 6. Audit |
| 57 | Brutal audit after Sprint 18. Same rules as armfortas audits: |
| 58 | - No "placeholder" or "stub" explanations that hide wrong output. |
| 59 | - Test every claim. Wrong output from a linker is critical. |
| 60 | - If the binary runs but produces extra or missing newlines, investigate — don't rationalize. |
| 61 | |
| 62 | ## Testing Strategy |
| 63 | - `tests/hello_world.rs` runs the differential gate. |
| 64 | - `tests/hello_world_run.rs` executes the binary (gated on CI-locally-on-Mac) and asserts stdout. |
| 65 | - Regression fixtures: any hello-world variant that once broke gets its own test. |
| 66 | |
| 67 | ## Definition of Done |
| 68 | - `tests/hello_world.rs` passes — zero critical diffs vs `ld`. |
| 69 | - Binary runs and produces correct output (after `codesign -s - hello` pre-Sprint-22). |
| 70 | - `otool -lV` diff empty after documented normalizations. |
| 71 | - Audit passes. |