markdown · 4753 bytes Raw Blame History

Sprint 19: CLI Surface + Diagnostics (-map, -why_live)

Prerequisites

Sprints 18–18.5 — executable and dylib milestones reached.

Goals

Full ld-compatible CLI surface for the flags armfortas already uses and those fortsh is likely to invoke. Includes the two diagnostics surfaces we declared launch-blocking: -map (text link map) and -why_live (dead-strip reason chain). No polish-tier deferral.

Deliverables

1. Full flag list

Recognized:

Inputs/outputs:

  • -o <path>
  • positional <input>
  • -l<name> / -l <name>
  • -L <dir>
  • -framework <name>
  • -weak_framework <name>
  • -force_load <archive>
  • -all_load
  • -ObjC (skippable no-op unless inputs have ObjC — they won't from armfortas today)

Target & platform:

  • -arch arm64
  • -syslibroot <path>
  • -platform_version macos <min> <sdk>

Output kind:

  • (default) executable
  • -dylib
  • -r (relocatable — deferred; errors for now)
  • -bundle (deferred; errors for now)

Entry & startup:

  • -e <symbol> (default _main for executables)

Runtime search paths:

  • -rpath <path>
  • -install_name <path> (dylib only)
  • -compatibility_version <v> (dylib only)
  • -current_version <v> (dylib only)

Symbol handling:

  • -undefined <error|warning|suppress|dynamic_lookup> (default: error)
  • -exported_symbols_list <file>
  • -unexported_symbols_list <file>
  • -exported_symbol <sym>
  • -unexported_symbol <sym>
  • -x (strip locals)
  • -S (strip debug)

Layout & output metadata:

  • -no_uuid
  • -dead_strip (gates Sprint 23 pass)
  • -icf=safe / -icf=none (gates Sprint 24 pass)
  • -fixup_chains / -no_fixup_chains

Diagnostics:

  • -map <path>: emit text link map
  • -why_live <symbol>: print dead-strip reason chain
  • -t / -trace: print input file paths as they are loaded
  • -v / --version
  • -h / --help

Passthrough / compat:

  • -Wl,<comma-separated>: normalize into separate flags.
  • Unknown flags: error with suggestion (Levenshtein-3 over the list above).

2. -map <path> output format

Text file mirroring ld's link map:

# Path: <output path>
# Arch: arm64
# Object files:
[  0] linker synthesized
[  1] hello.o
[  2] libarmfortas_rt.a(runtime.o)
...

# Sections:
# Address          Size         Segment   Section
0x100003f9c        0x00000018   __TEXT    __text
0x100003fb4        0x00000024   __TEXT    __stubs
...

# Symbols:
# Address          Size         File    Name
0x100003f9c        0x00000014   [  1]   _main
0x100003fb0        0x00000004   [  1]   .alt_entry_of_main
0x100003fb4        0x0000000c   linker  _printf (stub)
...

# Dead stripped:
<file>               <symbol>
[  2]                _unused_helper

3. -why_live <symbol> output

Walks the live-edge graph from Sprint 23 backward from the named symbol to a root:

_main is live because:
  _main is in -e _main (GC root)

_afs_write_char is live because:
  _afs_write_char is reachable from _afs_print
  _afs_print is reachable from _main
  _main is in -e _main (GC root)

When used before -dead_strip has been applied, the diagnostic explains that -dead_strip was not requested. Multiple -why_live names allowed.

4. Exported / unexported symbols files

Each line of the file is a symbol name. Wildcards: * matches any chars, ? matches one. Used to adjust the final export trie and to mark symbols N_PEXT when -unexported_symbol is set. Consumed by Sprint 14's symbol-table construction (which this sprint amends).

5. CLI parser

afs-ld/src/args.rs:

  • Hand-rolled, no clap.
  • Streaming argv scan.
  • Error messages cite the flag, the invalid value, and the expected format.
  • -Wl,-map,foo.txt normalized to -map foo.txt before dispatch.

6. -t trace output

As each input file is loaded:

afs-ld: loading hello.o
afs-ld: loading libarmfortas_rt.a
afs-ld: loading libarmfortas_rt.a(io.o)
afs-ld: loading /usr/lib/libSystem.tbd

Testing Strategy

  • One test per flag: parse the flag, assert LinkOptions field set correctly.
  • Error-message snapshot tests for every invalid-flag case.
  • -map differential: produce a map, compare shape (not exact byte) to ld's map on hello-world.
  • -why_live _main produces a root-only explanation.
  • -why_live <transitively-reachable-sym> produces a chain.
  • -Wl,-map,foo.txt parsed identically to -map foo.txt.

Definition of Done

  • Every flag listed above parses and wires correctly.
  • -map produces human-readable output covering object files, sections, symbols, dead-stripped entries.
  • -why_live produces a coherent chain on fixtures with dead-strip enabled.
  • Unknown-flag errors include a did-you-mean suggestion.
  • CLI surface passes a snapshot test against the --help output.
View source
1 # Sprint 19: CLI Surface + Diagnostics (`-map`, `-why_live`)
2
3 ## Prerequisites
4 Sprints 18–18.5 — executable and dylib milestones reached.
5
6 ## Goals
7 Full `ld`-compatible CLI surface for the flags armfortas already uses and those fortsh is likely to invoke. Includes the two diagnostics surfaces we declared launch-blocking: `-map` (text link map) and `-why_live` (dead-strip reason chain). No polish-tier deferral.
8
9 ## Deliverables
10
11 ### 1. Full flag list
12 Recognized:
13
14 **Inputs/outputs**:
15 - `-o <path>`
16 - positional `<input>`
17 - `-l<name>` / `-l <name>`
18 - `-L <dir>`
19 - `-framework <name>`
20 - `-weak_framework <name>`
21 - `-force_load <archive>`
22 - `-all_load`
23 - `-ObjC` (skippable no-op unless inputs have ObjC — they won't from armfortas today)
24
25 **Target & platform**:
26 - `-arch arm64`
27 - `-syslibroot <path>`
28 - `-platform_version macos <min> <sdk>`
29
30 **Output kind**:
31 - (default) executable
32 - `-dylib`
33 - `-r` (relocatable — deferred; errors for now)
34 - `-bundle` (deferred; errors for now)
35
36 **Entry & startup**:
37 - `-e <symbol>` (default `_main` for executables)
38
39 **Runtime search paths**:
40 - `-rpath <path>`
41 - `-install_name <path>` (dylib only)
42 - `-compatibility_version <v>` (dylib only)
43 - `-current_version <v>` (dylib only)
44
45 **Symbol handling**:
46 - `-undefined <error|warning|suppress|dynamic_lookup>` (default: error)
47 - `-exported_symbols_list <file>`
48 - `-unexported_symbols_list <file>`
49 - `-exported_symbol <sym>`
50 - `-unexported_symbol <sym>`
51 - `-x` (strip locals)
52 - `-S` (strip debug)
53
54 **Layout & output metadata**:
55 - `-no_uuid`
56 - `-dead_strip` (gates Sprint 23 pass)
57 - `-icf=safe` / `-icf=none` (gates Sprint 24 pass)
58 - `-fixup_chains` / `-no_fixup_chains`
59
60 **Diagnostics**:
61 - `-map <path>`: emit text link map
62 - `-why_live <symbol>`: print dead-strip reason chain
63 - `-t` / `-trace`: print input file paths as they are loaded
64 - `-v` / `--version`
65 - `-h` / `--help`
66
67 **Passthrough / compat**:
68 - `-Wl,<comma-separated>`: normalize into separate flags.
69 - Unknown flags: error with suggestion (Levenshtein-3 over the list above).
70
71 ### 2. `-map <path>` output format
72 Text file mirroring ld's link map:
73 ```
74 # Path: <output path>
75 # Arch: arm64
76 # Object files:
77 [ 0] linker synthesized
78 [ 1] hello.o
79 [ 2] libarmfortas_rt.a(runtime.o)
80 ...
81
82 # Sections:
83 # Address Size Segment Section
84 0x100003f9c 0x00000018 __TEXT __text
85 0x100003fb4 0x00000024 __TEXT __stubs
86 ...
87
88 # Symbols:
89 # Address Size File Name
90 0x100003f9c 0x00000014 [ 1] _main
91 0x100003fb0 0x00000004 [ 1] .alt_entry_of_main
92 0x100003fb4 0x0000000c linker _printf (stub)
93 ...
94
95 # Dead stripped:
96 <file> <symbol>
97 [ 2] _unused_helper
98 ```
99
100 ### 3. `-why_live <symbol>` output
101 Walks the live-edge graph from Sprint 23 backward from the named symbol to a root:
102 ```
103 _main is live because:
104 _main is in -e _main (GC root)
105
106 _afs_write_char is live because:
107 _afs_write_char is reachable from _afs_print
108 _afs_print is reachable from _main
109 _main is in -e _main (GC root)
110 ```
111
112 When used before `-dead_strip` has been applied, the diagnostic explains that `-dead_strip` was not requested. Multiple `-why_live` names allowed.
113
114 ### 4. Exported / unexported symbols files
115 Each line of the file is a symbol name. Wildcards: `*` matches any chars, `?` matches one. Used to adjust the final export trie and to mark symbols `N_PEXT` when `-unexported_symbol` is set. Consumed by Sprint 14's symbol-table construction (which this sprint amends).
116
117 ### 5. CLI parser
118 `afs-ld/src/args.rs`:
119 - Hand-rolled, no clap.
120 - Streaming argv scan.
121 - Error messages cite the flag, the invalid value, and the expected format.
122 - `-Wl,-map,foo.txt` normalized to `-map foo.txt` before dispatch.
123
124 ### 6. `-t` trace output
125 As each input file is loaded:
126 ```
127 afs-ld: loading hello.o
128 afs-ld: loading libarmfortas_rt.a
129 afs-ld: loading libarmfortas_rt.a(io.o)
130 afs-ld: loading /usr/lib/libSystem.tbd
131 ```
132
133 ## Testing Strategy
134 - One test per flag: parse the flag, assert `LinkOptions` field set correctly.
135 - Error-message snapshot tests for every invalid-flag case.
136 - `-map` differential: produce a map, compare shape (not exact byte) to `ld`'s map on hello-world.
137 - `-why_live _main` produces a root-only explanation.
138 - `-why_live <transitively-reachable-sym>` produces a chain.
139 - `-Wl,-map,foo.txt` parsed identically to `-map foo.txt`.
140
141 ## Definition of Done
142 - Every flag listed above parses and wires correctly.
143 - `-map` produces human-readable output covering object files, sections, symbols, dead-stripped entries.
144 - `-why_live` produces a coherent chain on fixtures with dead-strip enabled.
145 - Unknown-flag errors include a did-you-mean suggestion.
146 - CLI surface passes a snapshot test against the `--help` output.