qld 0.1.1

A fast, parallel linker compatible with GNU ld, gold, lld and mold
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
# Workstreams

qld is one crate, but the work splits into areas that barely touch each other.
Each workstream owns a directory under `src/`, so several agents (or people)
can work at the same time without colliding.

## Ground rules

1. **Stay inside your directory.** A workstream edits only the files it owns,
   plus its own tests and fixtures. Adding a file means adding a `mod` line to
   your own `mod.rs`, which you own.
2. **Shared files are frozen.** `Cargo.toml`, `src/lib.rs`, `src/error.rs`,
   `src/diag.rs`, `src/ids.rs`, `src/target.rs` and `src/main.rs` are edited by
   the integrator only. If your workstream needs a change there — a new
   dependency, a new `Error` variant, a new module — say so in your report and
   work around it meanwhile; do not edit the file.
3. **The dependency set is fixed:** `rayon`, `memmap2`, `hashbrown`,
   `foldhash`. Pure Rust, no `-sys` crates, no C build steps. Ask before
   adding anything.
4. **Follow `docs/development.md`**: MSRV 1.89, edition 2024, no panics on
   malformed input, checked arithmetic, deterministic results, `&[u8]` for
   symbol names, rustdoc on every `pub` item.
5. **`unsafe` is confined** to `src/input/` and `src/output/`, and needs a
   `// SAFETY:` comment. Everywhere else the crate-level `deny(unsafe_code)`
   applies.
6. **Before you finish**, all four must pass:
   ```sh
   cargo fmt --all
   cargo clippy --all-targets --all-features -- -D warnings
   cargo test --all-features
   cargo +1.89 check --all-targets --all-features
   ```
7. **Tests come with the code.** Unit tests go beside the code. A workstream
   may also add one integration test file, `tests/<area>.rs`, and data under
   `tests/data/<area>/`. The rest of `tests/` belongs to W9.
8. **Report** what you built, what you had to stub, and any shared-file change
   you need.

## Status

| ID | Area | Owns | Depends on | Ready |
| --- | --- | --- | --- | --- |
| W1 | Command-line parsing | `src/args/**` | — | merged |
| W2 | Input files and archives | `src/input/**` | — | merged |
| W3 | Linker scripts | `src/script/**` | — | merged |
| W4 | Symbol table and interning | `src/symbols/**` | — | merged |
| W5 | Output writer | `src/output/**` | — | merged |
| W6 | GC / ICF / merge passes | `src/passes/**` | — | merged |
| W7 | ELF reading | `src/elf/read/**` | — | merged |
| W8 | ELF layout, writing and link driver | `src/elf/**` (extends `read/` as needed) | W7 | merged (M1) |
| W9 | Test harness and fixtures | `tests/**` except other workstreams' `tests/<area>.rs` and `tests/data/<area>/` | — | merged |
| W10 | DWARF | `src/debug/**` | W7 | merged |
| W11 | Dynamic ELF on x86-64 (M2) | `src/elf/**` | W8 | merged (M2) |
| W12 | Symbol resolution follow-ups | `src/symbols/**` | W4 | merged |
| W13 | Symbol hints and demangling | `src/hints/**`, `src/demangle/**` | — | merged |
| W16 | M2 exit criteria: real-world builds | `src/elf/**`, `tests/projects/**` | W11 | merged (M2 met) |
| W14 | PE/COFF reading | `src/coff/**` | — | merged |
| W17 | LTO plugin host | `src/plugin/**` | — | merged |
| W18 | LTO in the ELF driver (M6) | `src/elf/{inputs,resolve,object,lto}*`, `tests/lto*` | W17 | merged (M6) |
| W19 | Linker-script layout and raw outputs (M3) | `src/elf/{rules,place,layout,write,defined,script_layout,rawout}*`, `tests/script_link*` | W3, W16 | merged (M3) |
| W20 | AArch64 ELF (M4) | `src/elf/arch/**`, `src/arch/**`, thunk/relaxation hooks in `src/elf/{layout,write,scan,synth}.rs`, `tests/aarch64*` | W16 | merged |
| W21 | PE/COFF linking (M7) | `src/coff/**` (beyond `read/`), `tests/coff_link*` | W14 | merged |
| W23 | Write-based (`pwrite`) output backend | `src/output/**`, writer call sites in `src/elf/` and `src/coff/` | W5 | merged (default) |
| W24 | Benchmarks and performance (M5) | `benches/**`, `tests/projects/bench*`, performance changes in `src/elf/**`, `src/symbols/**`, `src/passes/**`, `src/output/**`, `src/input/**`, `src/debug/**` | W23 | merged |
| W25 | Mach-O linking (M8) | `src/macho/**`, the ld64 front end in `src/args/`, `tests/macho_link*` | W15 | merged |
| W22 | PE command-line options | `src/args/**`, `src/coff/options.rs` | W21 | merged |
| W26 | Performance round 2 (M5) | `src/main.rs` (fork on exit, agreed), `--fork`/`--no-fork` in `src/args/`, performance changes in `src/elf/**`, `src/symbols/**`, `src/passes/**`, `src/output/**` (except `hash/sha256.rs`), `src/input/**`, `src/debug/**`, `benches/**`, `tests/projects/bench*` | W24 | merged |
| W27 | Mach-O follow-ups (M8) | `src/macho/**`, `src/args/darwin.rs`, `src/output/hash/sha256.rs` (moved from `src/macho/`), `tests/macho_link*` | W25 | merged |
| W28 | Symbol resolution redesign (M5) | `src/symbols/**`, `src/elf/{resolve,object}*.rs`, `benches/**`, `tests/projects/bench*` | W26 | merged |
| W29 | Debug and ordering outputs (M5) | new `src/debug/{gdb_index,debug_names}*`, new `src/elf/{ordering,separate_debug}*`, hooks in `src/elf/{layout,write,synth}.rs`, `tests/debug_index*`, `tests/ordering*` | W24 | merged |
| W30 | RISC-V 64 (M4) | `src/elf/arch/riscv*`, `src/arch/riscv*`, relaxation/shrinking hooks, `tests/riscv*`, `tests/fixtures/riscv64-*` | W20 | merged |
| W31 | PowerPC64 LE, ELFv2 (M4) | `src/elf/arch/ppc64*`, `src/arch/ppc64*`, `tests/ppc64*`, `tests/fixtures/ppc64le-*` | W20 | merged |
| W32 | LoongArch64 (M4) | `src/elf/arch/loongarch*`, `src/arch/loongarch*`, `tests/loongarch*`, `tests/fixtures/loongarch64-*` | W20 | merged |
| W33 | PE i386 and ARM64 (M7) | `src/coff/**`, `tests/coff_link*` and its data | W21 | merged |
| W34 | Mach-O completeness (M8) | `src/macho/**` except `lto*`, `src/args/darwin.rs`, `tests/macho_link*` | W27 | merged |
| W35 | Mach-O LTO through libLTO (M8) | new `src/plugin/liblto*`, `src/macho/lto*`, one hook in `src/macho/link.rs`, `tests/macho_lto*` | W17, W27 | merged |
| W36 | Library API for 1.0 (M9) | `examples/**`, `tests/api*`, new `src/input/source*`, API-only changes in `src/args/options.rs`; proposals for `lib.rs` | — | merged |
| W37 | AArch64 completeness (M4) | `src/elf/arch/aarch64.rs`, `src/arch/aarch64.rs`, `tests/aarch64*`, `tests/fixtures/aarch64-*` | W20 | merged |
| W38 | Scripts and M1/M3 leftovers | `src/script/**`, `src/elf/{script_layout,defined,rules}*`, `tests/script_link*`, musl tests under `tests/projects/musl*` | W19 | merged |
| W39 | Packaging and releases (M9) | `packaging/**`, `.github/workflows/release.yml`, `tests/projects/packaging*` | — | merged |
| W40 | ELF32 and big-endian ELF (M4) | the ELF reader/writer generics in `src/elf/**`, `src/elf/arch/{i386,arm}*`, `src/arch/{i386,arm}*`, `tests/elf32*`, `tests/fixtures/{i386,arm,s390x,ppc64be}-*` | W30, W31, W32, W37 | merged |
| W41 | Library API decisions for 1.0 (M9) | `src/args/options.rs`, `examples/**`, `tests/api*`, `tests/projects/api-review.md`; proposals for `lib.rs` | W36 | merged |
| W42 | Performance round 3 (M5) | performance changes in `src/elf/{inputs,object,resolve,dynsym,layout,place}*`, `src/symbols/**`, `src/input/**`, `benches/**`, `tests/projects/bench*` | W28, W40 | merged |
| W43 | x32 (M4) | `src/elf/arch/x86_64*` (x32 parts), `tests/x32*`, `tests/fixtures/x32-*` | W40 | merged |
| W44 | RV32 (M4) | `src/elf/arch/riscv*` (word-size parameter), `tests/riscv32*`, `tests/fixtures/riscv32-*` | W30, W40 | merged |
| W45 | s390x, the first big-endian target (M4) | `src/elf/arch/s390x*`, `src/arch/s390x*`, big-endian enablement in the ELF format layer, `tests/s390x*`, `tests/fixtures/s390x-*` | W40 | in progress |
| W46 | 32-bit ARM (M4) | `src/elf/arch/arm*` (not `aarch64*`), `src/arch/arm*`, `tests/arm32*`, `tests/fixtures/arm-*` | W40 | merged |
| W47 | Single-thread speed and scaling (M5) | hot paths in `src/elf/{write,scan,reloc,object,inputs,layout}*`, `src/symbols/**`, `src/input/**`, `src/main.rs` (allocator tuning, agreed), `benches/**`, `tests/projects/bench*` | W42 | in progress |
| W48 | PowerPC64 BE and big-endian ELF32 (M4) | `src/elf/arch/ppc64*` (ELFv1), the `Elf32Be` instantiation, `tests/ppc64be*`, `tests/fixtures/ppc64-*` | W45 | merged |
| W49 | 32-bit Arm completeness (M4) | `src/elf/arch/arm*`, `src/arch/arm*`, `tests/arm32*`, `tests/fixtures/arm-*` | W46, W48 | merged |
| W50 | `-r`, `--emit-relocs` and debug indexes for every class | `src/elf/{relocatable,emit}.rs`, `src/debug/{gdb_index,debug_names}*`, `tests/relocatable*` | W29, W45 | merged |
| W51 | Diagnostics framework and parser robustness (M0) | `src/diag.rs` (agreed), diagnostic call sites, `tests/diag*`, `tests/corrupt*` | — | merged |
| W52 | LoongArch shrinking and PowerPC64 LE leftovers (M4) | `src/elf/arch/{loongarch,ppc64}*` (LE parts), `src/arch/{loongarch,ppc64}*`, `tests/{loongarch,ppc64}*` | W30, W31, W32 | merged |
| W53 | Option coverage and GNU differences | `src/args/**`, the x86-64 difference fixes in `src/elf/{defined,dynsym,synth}*`, `tests/args*` | W22, W43 | merged |
| W55 | Amiga Hunk output and m68k (M4/M7-style) | `src/hunk/**`, `src/elf/arch/m68k*`, `src/arch/m68k*`, `tests/hunk*`, `tests/m68k*`; agreed additions to `src/target.rs` and `src/lib.rs` | — | in progress |
| W15 | Mach-O reading | `src/macho/**` | — | merged |

W1–W7 and W9 can all run at once. They share no files.

---

## W1: Command-line parsing

**Goal:** parse GNU ld / gold / lld / mold command lines into `LinkOptions`.

**Owns:** `src/args/**`. `LinkOptions` (in `src/args/options.rs`) is yours to
extend — add fields as you implement the options that set them.

**Build:**

- An option table covering every option of GNU ld 2.4x, gold, lld and mold,
  each marked *implemented*, *accepted-ignored* or *unsupported*
  (`docs/compatibility.md` defines the policy). A data-driven table beats a
  hand-written `match` chain: tests will enumerate it.
- The parsing rules in `docs/compatibility.md`: one- or two-dash long options
  with the `-o` exception, joined and separate values, `-l:name`, `-z`
  keywords, `@response` files, `=`/`$SYSROOT` prefixes, `--` terminator.
- Positional state tracked per input: `--whole-archive`, `--as-needed`,
  `-Bstatic`/`-Bdynamic`, `--push-state`/`--pop-state`, groups.
- Flavor selection from `argv[0]` and `-flavor`; a stub `parse_darwin` is fine.
- `--help` covering the implemented options, and the exact `--version` line.

**Do not:** touch the filesystem. No path resolution, no `-l` searching, no
reading response files off disk — take a closure or trait for reading files so
tests stay hermetic.

**Done when:** the option corpus in `tests/` parses, unknown options error,
every table entry has a test, and `qld --help` lists what works.

---

## W2: Input files and archives

**Goal:** get bytes and identity for every input.

**Owns:** `src/input/**`.

**Build:**

- A file table that maps inputs with `memmap2`, holds them for the link, and
  hands out `&'a [u8]`. Fall back to reading for small or unmappable files.
  `unsafe` allowed here, with `SAFETY` comments.
- Format identification from magic: ELF, COFF, Mach-O, fat, `!<arch>`,
  `!<thin>`, LLVM bitcode, GCC LTO IR, text.
- A complete `ar` reader: GNU (`/`-terminated names, `//` long-name table),
  BSD (`#1/<len>` names), and thin archives; the `/` symbol index and the
  64-bit `/SYM64/` variant; member iteration that is lazy and parallel-safe.
- Path resolution for `-l`: `libfoo.so` then `libfoo.a` across search paths,
  `-l:exact`, sysroot handling.

**Done when:** archives produced by GNU `ar`, LLVM `llvm-ar` and macOS `libtool`
all parse, symbol indexes are read, malformed archives produce
`Error::Malformed` instead of panicking, and truncated or corrupted archives
never panic in randomized tests.

---

## W3: Linker scripts

**Goal:** a full GNU linker script front end.

**Owns:** `src/script/**`.

**Build:** lexer, parser and expression evaluator for the language listed in
`src/script/mod.rs`, plus input-section pattern matching. Errors carry file and
line.

**Start with** the input-script subset (`INPUT`, `GROUP`, `AS_NEEDED`,
`OUTPUT_FORMAT`), because M2 needs it to read glibc's `libc.so`. Then the
`SECTIONS`/`MEMORY`/`PHDRS` language for M3.

**Done when:** the scripts shipped by glibc and musl parse; a `SECTIONS`
script for a bare-metal target parses and evaluates to the same addresses GNU
ld computes (compare with `ld --verbose` output); corrupted scripts never
panic in randomized tests.

---

## W4: Symbol table and interning

**Goal:** the concurrent global symbol table.

**Owns:** `src/symbols/**`.

**Build:**

- Name interning over `&'a [u8]` with hashes precomputed during parsing
  (`foldhash`), and a sharded table (`hashbrown` + per-shard locks) keyed by
  name and optional version.
- Per-symbol state: current definition (file, section, value, kind) and atomic
  flag bits (needs GOT/PLT/copy/TLS, address-taken, exported).
- A `Resolver` trait for format-specific precedence, so ELF rules live in the
  ELF backend. Provide the ELF rules as a test implementation only.
- The archive-extraction loop shape: rounds of "insert definitions, collect
  undefined, extract members", to a fixpoint, with ties broken by input
  position.

**Done when:** a synthetic benchmark inserting millions of symbols from many
threads gives the same result as a single-threaded run, every time, and scales
with core count.

---

## W5: Output writer

**Goal:** write the output file fast and deterministically.

**Owns:** `src/output/**`.

**Build:**

- An `OutputFile` that creates the output next to its final path, sets its
  length, maps it writable, and replaces any old file on commit; plus an
  in-memory variant for library callers. *(Done: merged.)*
- Safe splitting into disjoint `&mut [u8]` chunks for parallel writers.
- Parallel build-id hashing (block hashes combined into one value), with
  `fast`, `md5`, `sha1`, `uuid` and explicit-hex modes. Pure Rust MD5 and SHA-1
  implementations belong here — they are small; do not add a dependency.
- Finishing touches: permissions (`0o755`), atomic replacement, and optional
  background deletion of a large previous output.

**Done when:** writing a 1 GiB output through N threads gives identical bytes
for every N, and benchmarks show the write stage is I/O bound rather than
CPU bound.

---

## W6: GC / ICF / merge passes

**Goal:** the format-neutral optimization engines.

**Owns:** `src/passes/**`.

**Build:**

- A graph interface the backends implement (sections, edges, roots) so the
  passes do not know about ELF.
- Parallel mark-and-sweep GC with atomic mark bits, plus `--print-gc-sections`
  and a `--why-live` reference chain.
- ICF: parallel hashing then iterative class refinement, `safe` and `all`
  modes, deterministic representative selection.
- Merge-section splitting and deduplication, with optional tail merging.

**Done when:** each pass has property tests on synthetic graphs (results
independent of thread count and of input permutation beyond the documented
tie-break), and micro-benchmarks on graphs of a million sections.

---

## W7: ELF reading

**Goal:** zero-copy parsing of ELF objects and shared libraries.

**Owns:** `src/elf/read/**` (create it), and `src/elf/mod.rs` until W8 starts.

**Build:**

- Header, section header, symbol table, relocation, note, group and
  `.eh_frame` readers, generic over class and endianness through a trait with
  `Elf64Le` as the first instantiation. No run-time branching in hot loops.
- Typed views that never assume alignment and never panic: every accessor
  bounds-checks and returns `Result`.
- Dynamic-object reading: `.dynsym`, `.dynamic`, `SONAME`, `NEEDED`, and the
  version tables.
- Constants for the ELF ABI and for x86-64 relocations, in their own module.

**Done when:** parsing every `.o` and `.so` on the test machine (a sweep of
`/usr/lib`) produces the same symbol and section inventory as `readelf`, with
no panics, and truncated or corrupted objects never panic in randomized tests.

---

## W8: ELF layout and writing (starts after W7)

**Goal:** turn resolved inputs into a static x86-64 executable (roadmap M1).

**Owns:** all of `src/elf/**`, including the driver in `src/elf/link.rs`
(which `crate::link` calls inside a `--threads`-sized pool). W7 is merged, so
W8 may extend `src/elf/read/` where layout needs more from the reader.

**Build:** output section assignment matching GNU ld's default script,
segments, linker-defined symbols, GOT/PLT synthesis, `.eh_frame_hdr`,
x86-64 relocation application and relaxation, and TLS local-exec.

**Done when:** a static "hello world" links, runs, and passes `readelf -a`
inspection.

---

## W9: Test harness and fixtures

**Goal:** the infrastructure every other workstream tests against.

**Owns:** `tests/**`, except the per-area files other workstreams add.

**Build:**

- The `test.toml` fixture runner described in `docs/testing.md`: compile with
  the host toolchain, link with qld, run, compare stdout and `readelf`
  patterns. It must skip cleanly (not fail) while linking is unimplemented.
  No new dependencies: parse the small TOML subset fixtures use by hand.
- A differential runner that links the same inputs with GNU ld and compares
  the normalized properties listed in `docs/testing.md`.
- The captured-argv corpus for W1: real command lines from gcc, clang, rustc,
  meson and cmake builds, with the expected parse results.
- Fuzzing is deferred: `cargo fuzz` needs a separate crate, and qld is kept
  to one crate. Until that is decided, parsers get randomized
  malformed-input tests (truncation and byte-flipping of valid inputs) in
  their unit tests.
- A determinism harness: link with 1, 2 and N threads, compare bytes.

**Done when:** `cargo test` runs the fixture and differential suites, and the
skip behavior keeps CI green until M1 lands.

---

## W10: DWARF (starts after W7)

**Goal:** debug information handling.

**Owns:** `src/debug/**`.

**Build:** tombstone values for relocations into dead sections, `SHF_COMPRESSED`
and `.zdebug_*` decompression, zlib/zstd output compression, and the lazy
line-table lookup that turns a section offset into `file:line` for diagnostics.

---

## W11: Dynamic ELF on x86-64 (M2)

**Goal:** roadmap M2 — PIE, shared object inputs and outputs, PLT/GOT,
`.dynamic`, symbol versioning, RELRO, `DT_RELR`, `-r`.

**Owns:** `src/elf/**` (taking over from W8), `tests/elf_link.rs`, new fixture
directories.

**Done when:** every M2 fixture in `tests/fixtures/` passes, the differential
runner agrees with GNU ld on dynamic symbols, `DT_*` tags and dynamic
relocations, and the PIE and shared-library outputs load under glibc `ld.so`.

---

## W12: Symbol resolution follow-ups

**Goal:** the two requests W8 made of `src/symbols/`: per-round work
proportional to the round's files (not all files), and a hook between loading
a round's files and interning their symbols so backends can claim COMDAT
groups before insertion.

**Owns:** `src/symbols/**`, `tests/symbols.rs`.

---

## W13: Symbol hints and demangling

**Goal:** "intelligent library symbol matching" diagnostics
(`docs/optimizations.md`) and a demangler for Itanium C++ and Rust names.

**Owns:** `src/hints/**`, `src/demangle/**`, `tests/hints.rs`,
`tests/demangle.rs`. Wiring into the ELF driver is a later integration step.

---

## W16: M2 exit criteria (real-world builds)

**Goal:** make ROADMAP M2's exit criteria pass: coreutils, curl, openssl,
zlib, Python, the Rust compiler and LLVM/clang build with qld as the system
linker and pass their test suites; qld-built shared objects load under glibc
and musl `ld.so`. Every bug found gets a minimal regression fixture.

**Owns:** `src/elf/**`, `tests/projects/**` (build scripts), new fixtures.

---

## W14: PE/COFF reading

**Goal:** the input side of M7, like W7 for ELF: zero-copy readers for COFF
objects (including bigobj), short and long import libraries, PE images
(exports of DLLs linked directly), `.drectve` directives and `.def` files.

**Owns:** `src/coff/**`, `tests/coff_read.rs`, `tests/data/coff_read/`.

---

## W15: Mach-O reading

**Goal:** the input side of M8: zero-copy readers for Mach-O objects and
dylibs (arm64, x86_64), fat slice selection, `.tbd` text stubs (v3–v5), and
the per-atom information dead stripping needs (`.subsections_via_symbols`).

**Owns:** `src/macho/**`, `tests/macho_read.rs`, `tests/data/macho_read/`.

---

## W17: LTO plugin host

**Goal:** the linker half of the GNU linker plugin API (`-plugin`,
`-plugin-opt`), loading GCC's `liblto_plugin.so` and LLVM's `LLVMgold.so`,
as a standalone API: claim IR inputs, report their symbols, take
resolutions, and collect the native objects the plugin produces. Wiring it
into the ELF driver's resolution rounds is a later integration step.

**Owns:** `src/plugin/**`, `tests/plugin.rs`, `tests/data/plugin/`.

---

## W18: LTO in the ELF driver (M6)

**Goal:** `gcc -flto` and `clang -flto`/`-flto=thin` links work end to end:
IR inputs (including archive members) are claimed through
`plugin::Session`, take part in resolution, and are replaced by the native
objects the plugin produces. Meets ROADMAP M6's exit criteria.

**Owns:** `src/elf/inputs.rs`, `src/elf/resolve.rs`, `src/elf/object.rs`, a
new `src/elf/lto.rs`, `tests/lto.rs`, new fixtures. Shares `src/elf/link.rs`
with W19 (keep edits there small).

---

## W19: Linker-script layout and raw outputs (M3)

**Goal:** `-T script` drives layout (`SECTIONS`, `MEMORY`, `PHDRS`, `AT>`,
`KEEP`, `/DISCARD/`, `PROVIDE`, `ASSERT`, `INSERT`), `-Ttext`/`--section-start`,
and `--oformat binary|ihex|srec` plus `-b binary` inputs. Meets ROADMAP M3's
exit criteria (Linux kernel boots; bare-metal addresses match GNU ld).

**Owns:** `src/elf/rules.rs`, `src/elf/place.rs`, `src/elf/layout.rs`,
`src/elf/write.rs`, `src/elf/defined.rs`, new `src/elf/script_layout*` and
`src/elf/rawout*` modules, `tests/script_link.rs`, new fixtures. Shares
`src/elf/link.rs` with W18 (keep edits there small).

---

## W20: AArch64 ELF (M4)

**Goal:** qld links AArch64 Linux ELF: relocations, range-extension thunks,
GOT/PLT, TLS (including TLSDESC), BTI/PAC properties, and the ADRP
relaxations. Exit criterion: the fixture suite passes for
`aarch64-unknown-linux-gnu`, compared against that target's GNU ld, and a
CI job on an arm64 runner runs the binaries.

**Owns:** `src/elf/arch/**`, `src/arch/**`, the architecture hooks in
`src/elf/{layout,write,scan,synth,values}.rs`, `tests/aarch64.rs`, new
`aarch64-*` fixtures.

---

## W21: PE/COFF linking (M7)

**Goal:** qld produces Windows PE32+ executables and DLLs from COFF objects,
MinGW flavor: symbol resolution, layout, imports and exports, base
relocations, SEH, and `--out-implib`. The readers are merged (W14).

**Owns:** `src/coff/**` outside `read/`, `tests/coff_link.rs`, new fixtures.

---

## W24: Benchmarks and performance (M5)

**Goal:** a reproducible benchmark suite comparing qld with GNU ld, lld, mold
and wild on real links (clang, rustc's `librustc_driver`, the Linux kernel,
large debug-info binaries), then profiling-driven work toward M5's exit
criterion: wall time at or below mold and wild at 8 and 64 cores, peak RSS no
higher than lld's, identical output across thread counts.

**Owns:** `benches/**`, benchmark scripts under `tests/projects/`, and
performance changes in the link pipeline modules.

**Merged:** the capture/replay suite and 16 measured speedups (16–22% at
default threads, byte-identical output). Results in
`tests/projects/bench.md`; M5's exit criterion is not met. Open:
- run the link in a child process and exit the parent once the output is
  written, as mold and wild do (`src/main.rs`, frozen; no `unsafe` needed);
- idle rayon workers and system time past 16 threads;
- symbol resolution (clang: 49 ms, wild ~15 ms); single-threaded speed;
- corpus gaps: chromium, gold, `librustc_driver`; per-commit publishing.

---

## W25: Mach-O linking (M8)

**Goal:** qld links arm64 and x86_64 macOS executables and dylibs through an
ld64-compatible command line, including chained fixups, `__unwind_info`, the
ad-hoc code signature and universal binaries; the macOS CI runner executes
the results. The readers are merged (W15).

**Owns:** `src/macho/**` outside `read/` (extending `read/` as needed), the
ld64 flavor parser in `src/args/`, `tests/macho_link.rs`, new fixtures.

---

## W26: Performance round 2 (M5)

**Goal:** close the gap to mold and wild found by W24
(`tests/projects/bench.md`). In priority order: fork on exit so the parent
returns once the output is written (`src/main.rs`, agreed with the user;
`--no-fork` disables it, and the library API never forks); scaling past 16
threads; symbol resolution; single-threaded speed. Every change is measured
with `benches/run.py` and keeps output byte-identical.

**Owns:** `src/main.rs`, the `--fork`/`--no-fork` options, performance
changes in the ELF pipeline modules, `benches/**`, `tests/projects/bench*`.

**Merged:** fork on exit (`LinkOptions::fork`, `on_output_complete`; the one
`lib.rs` change runs the hook on success), the 16-thread nested pool,
parallel archive member discovery, faster interning. M5 met against mold at
default/64 threads. Open: a resolution design that matches wild's speed
while keeping symbol IDs; dynamic and layout stages at 8 threads;
single-thread relocation processing; parallel `madvise(MADV_DONTNEED)`
teardown for `--no-fork` and library links; overlapping COMDAT claims with
interning.

---

## W27: Mach-O follow-ups (M8)

**Goal:** the rest of M8's exit criteria and the gaps W25 left: a Rust
`aarch64-apple-darwin` binary linked by qld and run in the macOS job, `-r`,
cstring deduplication, `-init`, `-bundle_loader`, and moving the SHA-256
implementation into `src/output/hash`.

**Owns:** `src/macho/**`, `src/args/darwin.rs`, `src/output/hash/sha256.rs`,
`tests/macho_link*` and its data.

---

## W28: Symbol resolution redesign (M5)

**Goal:** resolution (currently 35–45 ms on clang; wild takes ~15 ms)
reaches wild's speed, keeping symbol IDs, and so output, identical.

## W29: Debug and ordering outputs (M5)

**Goal:** `--gdb-index`, `--debug-names`, `--separate-debug-file` with
`.gnu_debuglink`, `--symbol-ordering-file`, `--call-graph-profile-sort`.

## W30–W32: RISC-V 64, PowerPC64 LE, LoongArch64 (M4)

**Goal:** each architecture links the fixture suite, validated against
ld.lld locally and run under qemu-user in CI. ELF32 and big-endian (i386,
ARM, RV32, x32, ppc64 BE, s390x) wait for a generic ELF32/BE pass, which
touches the whole ELF pipeline and is scheduled after this round.

## W33: PE i386 and ARM64 (M7)

**Goal:** PE32 (i386, with SafeSEH) and ARM64 PE32+ outputs, run in CI.

## W34: Mach-O completeness (M8)

**Goal:** the broader `-fuse-ld` suite (M8 exit criterion), ObjC category
merging and relative method lists, arm64e.

## W35: Mach-O LTO through libLTO (M8)

**Goal:** `-flto` links on macOS through Apple's/LLVM's `libLTO` C API.

## W36: Library API for 1.0 (M9)

**Goal:** API review, in-memory inputs and outputs, cancellation, rustdoc
examples; proposals for frozen files.

**Merged**, including the agreed `lib.rs`/`error.rs` changes. Open:
- PE and Mach-O support for `input_provider`, `output_buffer` and `cancel`
  (one-line hooks each; W33, W34/W35).
- Cancellation checks inside resolution (W28).
- `-T` scripts and version scripts read through the provider (W38).
- Replace struct literals in `tests/coff_link.rs` and `tests/input.rs` so
  `LinkOptions`, `InputSpec` and `InputAttrs` can be `#[non_exhaustive]`.
- The 1.0 blockers in `tests/projects/api-review.md`.

## W37: AArch64 completeness (M4)

**Goal:** ADRP relaxations, Cortex-A53 843419 workaround, `-z force-bti`,
`-z pac-plt`, lazy TLSDESC decision.

## W38: Scripts and M1/M3 leftovers

**Goal:** `-r` with `-T`, `--defsym` with full expressions, musl C
programs verified.

## W39: Packaging and releases (M9)

**Goal:** release workflow with prebuilt binaries, `ld.qld`/`ld64.qld`
links, distribution package recipes.

---

## W40: ELF32 and big-endian ELF (M4)

**Goal:** one generic ELF path that handles 32-bit and big-endian output as
well as it handles LP64 little-endian, monomorphized so the common case
costs nothing, then i386 on top of it. It unlocks 32-bit ARM, RV32, x32,
PowerPC64 BE (ELFv1) and s390x.

## W41: Library API decisions for 1.0 (M9)

**Goal:** settle the 1.0 blockers in
[api-review.md](../tests/projects/api-review.md): `Default` versus `new()`,
string-typed options, `darwin.inputs`, printing and environment reads inside
the library, and nested thread pools.

---

## W42–W46: round 5

- **W42, performance:** close the gap to wild. Member loading stops scaling
  past ~8 threads (kernel page-fault cost; slimmer `ObjectInput` /
  `InputSection`), the dynamic and layout stages do not scale at 8 threads,
  and single-threaded relocation processing and parsing lag lld.
- **W43–W46, architectures on the generic ELF32 / big-endian layer:** x32,
  RV32, s390x (which enables big-endian output end to end) and 32-bit ARM.
  PowerPC64 big-endian (ELFv1) follows once s390x has exercised big-endian
  output.

All of them keep the x86-64 clang link's instruction count within 0.5% of
their base, with identical output.

---

## W47–W53: round 6, closing the gaps

The aim of this round is the list in ROADMAP.md that is still open:
single-thread speed against lld, the last architectures (PowerPC64 BE,
big-endian ELF32 and Arm BE8), `-r` and the debug indexes for every ELF
class, the M0 diagnostics item, LoongArch shrinking relaxation, and the
remaining option and GNU-difference gaps.

---

## W55: Amiga Hunk output and m68k

**Goal:** qld links Motorola 68000 objects into AmigaOS Hunk executables.
vasm assembles the inputs and vlink is the oracle; nothing is run, since
the output needs an Amiga emulator and the user asked not to use one.

---

## Integration follow-ups

- **Test scratch directories are not race-safe.** `scratch(name)` removes
  and recreates the directory, so two parallel tests that pass the same
  name delete each other's files: this made
  `gdb_index_matches_lld_on_big_endian` fail on the arm64 runner, where the
  whole suite runs at full parallelism. Fixed there by probing once through
  a `OnceLock`. `tests/macho_link.rs` calls `scratch("hello")` twice inside
  one test, which is sequential but equally fragile. Giving `scratch` a
  unique suffix per call, or making it not delete, would remove the class.

- **W56 (incompatible libraries, user-reported):** fixed for ELF and
  PE/COFF; Mach-O already ignored them as ld64 does. Left open: a directly
  named incompatible archive errors even when no member is extracted (GNU
  ld waits for extraction), thin archives found by search are not
  pre-checked, and the architecture names in the message are qld's
  (`X86_64`) rather than BFD's (`i386:x86-64`).

- **W55 (Hunk/m68k):** a Hunk-specific section→hunk placement rule (vlink
  gives each input section name its own hunk); Hunk object output
  (`HUNK_UNIT`/`HUNK_EXT`), overlays and Hunk input; chip/fast memory
  attributes, which need an option since no ELF section header carries
  them; `HUNK_RELOC32SHORT` selection; m68k range-extension thunks and
  dynamic output.

- **W51 (diagnostics):** wording still unlike GNU ld in two places:
  `unknown option: --foo` (GNU: `unrecognized option '--foo'` plus a usage
  hint) and `malformed file format not recognized` (GNU: `file not
  recognized: file format not recognized`). A relocation overflow could
  carry lld's `is not in [min, max]` if `ApplyError` carried the value.
  `SourceLocation::column` is always `None` until the DWARF line table
  tracks a column. `Stderr` holds every diagnostic until flush.

- **W54 (PE):** an unrecognised orphan section is placed differently from
  GNU ld (GNU puts `.averylongname` right after `.data`; qld puts it between
  `.tls` and `.reloc`). Orphan placement in `src/coff/layout.rs`.

- **W53 (options):** `--print-sysroot` and `--print-output-format` need new
  `ParseOutcome` variants and a line in the frozen `src/main.rs`. Left
  unsupported with reasons: `--unique` (needs per-input output sections),
  `--execute-only` (AArch64 segment splitting), `--no-define-common`,
  `--no-fortran-common`. `extern "C++"` version-script patterns are still
  matched against mangled names.

- **W48 (PowerPC64 BE):** dynamic ELFv1 output is the big one — `DT_PLTGOT`
  names a `.plt` of 24-byte descriptors the dynamic linker fills, with
  `.glink` holding the code, which does not fit qld's "code in `.plt`,
  words in `.got.plt`" model and needs `synth.rs`/`rules.rs`/`layout.rs`
  rather than an arch hook. Then `.opd` splitting for `--gc-sections`,
  IFUNC addresses, and `.gnu.attributes` merging.
- **`.toc` folded into `.got`** is done for PowerPC64 LE (W52) and still
  open for BE (`rules.rs`).

- **W52 (PowerPC64 LE):** still open, with notes on the size of each:
  `_savegpr*`/`_restgpr*` (lld implements these, so they are verifiable
  locally; needs a new synthetic section), inline PLT
  (`PLTSEQ`/`PLTCALL`/`PLT16_*`, GNU ld only), `R_PPC64_TOC` (overlaps
  W48's ELFv1 work), `ADDR64_LOCAL`, `GOT_DTPREL*` (needs a new `GotKind`),
  `DT_PPC64_OPT`, multi-TOC.
- **Thunks under linker-script layout** are missing generically: the script
  path returns before the thunk fixpoint (`layout.rs`) and
  `script_layout/engine.rs` hard-codes an empty thunk list. Fixing it there
  fixes AArch64, PowerPC64 and Arm at once.

- **W44 (RV32):** 
  `target.rs::default_target()` has no riscv32 host case;
  `__rela_iplt_end` takes the following section's index at a boundary;
  `tests/riscv32.rs` duplicates ~600 lines of the RV64 symbolizer.

- **W32/W52 (LoongArch64):** open: move the `R_LARCH_*` constants to
  `src/elf/read/consts/`; remove
  the per-relocation lookahead cost on x86-64/AArch64 (in progress).
- **W40 (ELF32/big-endian):** next architectures, smallest first: x32 (the
  x86-64 PLT and 8-byte GOT with 4-byte `RELATIVE`, its own TLS forms), RV32
  (RV64 code with a word-size parameter), s390x (testable in CI with qemu),
  PowerPC64 BE ELFv1 (`.opd`), then 32-bit ARM (Thumb interworking,
  `.ARM.exidx`, BE8). `-l` should skip libraries for another machine, as GNU
  ld does. Big-endian output has not been exercised end to end yet. The
  second pipeline copy grows the release binary.
- **W28 (resolution):** `mallopt(M_TOP_PAD)` at startup is worth ~3 ms on
  clang but needs an `unsafe extern` call and a frozen-file change; slimmer
  `ObjectInput`/`InputSection`; COMDAT slots keyed by symbol ID.
- **W29 (debug indexes):** `.debug_names` built from DIEs for objects with no
  index; section ordering with `-r`.
- **W38 (scripts):** `-r` `.eh_frame` editing; `.dynstr` tail merging; GNU's
  spare `.dynamic` slots and tag order; version-definition symbols in
  `.symtab`; a built-in `-r` layout for architectures other than x86-64.
- **W37 (AArch64):** erratum fixes with linker-script layout; a patch pool
  per 128 MiB of code; `$x` and `__CortexA53843419_*` symbols for patches;
  check `DT_AARCH64_VARIANT_PCS`. `src/elf/arch/aarch64_errata.rs` and
  `thunk.rs` belong to the AArch64 owner.
- **W34 (Mach-O):** relative method lists add a second link attempt when a
  method name has no selector reference; fold it into the selector-stub
  attempt. arm64_32 later.
- **W30 (RISC-V):** remove the +1.7% x86-64 instruction cost (in progress);
  reuse the previous layout between relaxation passes; `.sbss` next to
  `.sdata` for `--relax-gp`; `PT_RISCV_ATTRIBUTES` under scripts;
  `DT_RISCV_VARIANT_CC`. LoongArch shrinking can now plug into
  `src/elf/arch/shrink.rs`.
- **W33 (PE):** a BRANCH26 thunk test past ±128 MB; GNU's automatic DLL image
  base; `__nm_` symbols in qld-written import libraries; export hints;
  `--oformat pe-i386` without `-m` still selects ELF; import libraries and
  `--output-def` bypass the output buffer.

- **W25/W27 (Mach-O):** done: dispatch, the `macho-macos` job, SHA-256 in
  `output::hash`, `-r`, literal merging, `-init`, `-alias`,
  `-bundle_loader`, `-flat_namespace`, Rust binaries. Open: arm64e; LTO via
  libLTO; ObjC relative method lists and category merging; DWARF,
  data-in-code and LOHs in `-r`; `-force_flat_namespace`, `-alias_list`;
  undefined symbols reported before dead stripping; legacy dyld-info output
  binds everything at load time; `-v` with inputs prints a note instead of
  the version on stdout; a broader `-fuse-ld` suite for the M8 exit
  criterion. The `src/output/hash/mod.rs` doc comment could mention SHA-256
  (code signatures).

Changes requested by merged workstreams that need a frozen shared file, or
that cross workstream boundaries. The integrator does these between merges.

| From | Change | Status |
| --- | --- | --- |
| W2 | `Error` variant for "library not found" (`cannot find -lfoo`) | done: `Error::NotFound` |
| W2 | `Error` variant for "too many input files" | done: `Error::Limit` |
| W5 | `Error` variant for internal/layout errors | done: `Error::Internal` |
| W6 | `Error::Internal` so backend-bug `InputError`s convert into `crate::Error` | done |
| W4 | `const fn` ID accessors and `Default` on IDs | done |
| W1 | Emit `LinkOptions::warnings` to the diagnostic sink, honoring `--no-warnings` / `--fatal-warnings` | done (`main.rs`) |
| W1 | Re-export `parse_gnu_with` from the crate root | done |
| W3 | `Error` variant for script errors with line and column | done: `Error::Script` |
| W3 | Layout-side script semantics: `DATA_SEGMENT_*` relro adjustment, `PROVIDE` only-if-referenced, `NEXT_SECTION`, section-relative symbols from `.` | done (W19) |
| W1 | Let `ParseOutcome` grow print-and-exit variants (`--print-sysroot`, `--print-output-format`); `main.rs` must handle them | open |
| W1 | `target.rs`: more operating systems (FreeBSD, …) and architectures (MIPS, PowerPC32, …) for their `-m` emulations | open, when needed |
| W6 | Split `merge_sections` into a parse-time split step and a post-GC dedup/offset step, so the relocation scan can map references to pieces (see architecture stage 4 and 8) | done: `split_section` + `merge_split_sections` |
| W8 | `Location` label so duplicate symbols print `>>> defined at` | done: `Diagnostic::detail` |
| W8 | Let the ELF driver size the pool when `--threads` is absent | done (`lib.rs`) |
| W8 | `resolve_symbols` runs parallel iterators over all files every round, including inactive archive members: 17 ms at 64 threads vs 2.5 ms on one for static hello. Iterate only the round's files, or `with_min_len` | done (W12) |
| W8 | `ResolveFile` hook between loading and interning, so COMDAT groups can be claimed before insertion | done (W12) |
| W8 | Switch the local debug tombstone helper in `elf/write.rs` to W10's rules, handle compressed inputs and `--compress-debug-sections` via `crate::debug`, and pass `LinkOptions::dead_reloc_in_nonalloc` | done (W11) |
| W10 | `-z dead-reloc-in-nonalloc=` parsing in `src/args` | done |
| W13 | Call `hints::Hinter` from the ELF driver's undefined-symbol report (integration code in W13's report: collect `LinkedLibrary` with `--as-needed`/`-Bstatic` state, then `hints::attach`), and demangle symbol names in diagnostics with `hints::display_symbol` | done (W11) |
| W13 | Duplicate-definition explanations (which reference extracted which member) need an extraction trace from `symbols` | open |
| CI | Merge x86 `GNU_PROPERTY_X86_ISA_1_USED` / `FEATURE_2_USED` properties (OR-AND semantics: kept only if every input has them) instead of dropping them; GNU `as` emits them and GNU ld keeps them | done (W11) |
| W17 | Integrate `plugin::Session` into the ELF driver: claim IR inputs in `RoundHook::after_load` (deterministic order), feed claimed symbols to resolution (arena-owned names), settle `LDPR_*` resolutions after the fixpoint, re-resolve with `LtoOutput::files`/`libraries`, `finish` after writing (sketch in W17's report) | done (W18) |
| W17 | `Error::Plugin(String)` variant; a "referenced from a non-IR file" symbol flag for `PREVAILING_DEF` vs `_IRONLY` | `Error::Plugin` done; the symbol flag is open |
| W18 | `-r` with a shared-library input writes `R_X86_64_NONE` for references bound to it (GNU ld's `-r` implies `-Bstatic`) — `src/elf/relocatable.rs` | open |
| W20 | Run the AArch64 fixtures natively | done (`arm64-linux` job) |
| W20 | `-z force-bti` needs an option (the backend has the landing-pad PLT form); `--help` still says `supported emulations: elf_x86_64`; `--oformat elf64-littleaarch64` is rejected | open, for W22 (`src/args`) |
| W20 | Script layout uses a 4 KiB page size instead of the architecture default (64 KiB on AArch64), so `-T` links need `-z max-page-size=0x10000` | open |
| W20 | `--emit-relocs` maps relaxed relocations back to `R_X86_64_*` names; `-b binary` inputs are stamped `EM_X86_64`; `--icf=safe` treats every non-`PLT32` relocation as address-taking | open |
| W20 | The fixture harness skips a non-host target when `qemu-<arch>` is missing even for fixtures with no `run` line | open |
| W23 | Single-threaded links are 5–12% slower with the write backing (one extra copy); build-id with ~1 MiB sections reads blocks back; a 320 MiB synthetic link with `--build-id` spends ~600 ms in a stage that does not scale with threads under every backing | open |
| Integrator | Regression from W20, found by W23: GD/TLSDESC→IE relaxations looked up the wrong GOT slot (clang-23 failed to link). Fixed, with fixture `tls-gd-to-ie-shared`; nothing in CI covered it before | done |
| W21 | Wire `coff::link` into `crate::link` | done |
| W21 | Run qld-built PE images on a Windows CI runner | done (`pe-windows` job) |
| W21 | i386 (PE32) and ARM64 PE output; i386 SafeSEH; delay-load imports; `-r`, `--gc-sections`, `--icf`, `--wrap`, `--defsym` and linker scripts for PE | open |
| W21 | Local symbols in the PE output symbol table; auto-import of PC-relative references; `--enable-stdcall-fixup`; `--add-stdcall-alias` | open |
| W19 | `-r` with `-T`, and `--defsym` expressions beyond `symbol+offset` | open |
| W19 | `-r` output of the kernel's `vmlinux.o` makes objtool fail ("can't find starting instruction") — `src/elf/relocatable.rs` | open |
| W19 | GLOBAL HIDDEN inputs emitted LOCAL in static links; no `FILE` symbols; `sym = othersym` script assignments do not copy type/size | open |
| W19 | vdso64 differs from GNU ld in `.hash` alignment/size, `.dynstr`, `.dynamic` and an empty `.got.plt` | open |
| W19 | A hook in `src/elf/inputs.rs` for script-provided input lists and `-b binary`, to drop the workaround in `script_layout/load.rs` | open |
| W18 | clang's `.eh_frame` keeps `SHT_X86_64_UNWIND` where GNU ld writes `PROGBITS` — `src/elf/write.rs`/layout | open, for W19 |
| W12 | Adopt `RoundHook` + `GroupClaims` for COMDAT in `src/elf/` (claim before insertion; delete `redirect_discarded`) | done (W11) |
| W10 | Fill `Location::source` for undefined-symbol diagnostics with `debug::dwarf::LineLookup` | done (W11) |
| W8 | `ifunc-static` fixture prints "same address: no" when built with clang, under GNU ld too: fixture/toolchain issue | open |
| W7 | `elf_read::basic_object_matches_readelf` failed once under the full parallel test run, passed on reruns: possible flake | done: two tests raced on the shared `basic.o`; it is now built once |
| W5 | Pre-allocate output with `fallocate` | dropped: measured on btrfs, `posix_fallocate` changes a 1 GiB mapped write by 0–15%; the cost is page-fault contention, which grows with threads (400 ms at 1 thread, ~860 ms at 16), while `pwrite` stays at ~260–370 ms. No crate would be needed anyway (a hand-declared `posix_fallocate`, like the plugin host's `dlopen`). Replaced by W23 |

## Launching an agent

Give the agent this brief:

> You are implementing workstream **W\<n\>** in the qld repository at
> `/home/magicaltux/projects/qld`. Read `CLAUDE.md`, `docs/workstreams.md`
> (your section), `docs/architecture.md`, `docs/development.md` and the
> module doc comment in the directory you own. Implement the workstream.
> Touch only the files your workstream owns. Run the four checks listed in
> the ground rules before reporting. Report what you built, what is stubbed,
> and any change you need in a frozen shared file.

Run each agent on its own branch (`ws/1-args`, `ws/2-input`, …) or in its own
worktree, and merge through review. Because ownership does not overlap, merges
should be clean; the only conflicts to expect are in shared files, which
agents are not allowed to edit.