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
821
822
823
824
825
826
827
828
829
830
831
# Command-line compatibility

qld takes the command lines that compiler drivers already pass to existing
linkers. This document defines how argv is interpreted, and lists every place
where qld *intentionally* behaves differently from the linker it replaces.

## Flavors

A flavor is a command-line dialect together with its default target family.

| Flavor | Emulates | Selected by |
| --- | --- | --- |
| `gnu` | GNU ld (BFD), gold, lld (ELF), mold | `argv[0]` = `qld`, `ld`, `ld.qld`, `ld.*`; default |
| `gnu` + PE emulation | GNU ld for MinGW (`i386pep`, `i386pe`, `arm64pe`) | `gnu` flavor with `-m i386pep`/`i386pe`/`arm64pe`, or a COFF first input |
| `darwin` | Apple ld64 / ld-prime | `argv[0]` = `ld64`, `ld64.qld`; `-flavor darwin` |
| `msvc` *(future)* | `link.exe` / `lld-link` | `argv[0]` = `lld-link`, `link.exe`; `-flavor link` |

The priority order is: an explicit `-flavor <name>` as the first argument,
then the basename of `argv[0]`, then `gnu`.

## Target selection (GNU flavor)

1. `-m <emulation>`, if given (`elf_x86_64`, `elf_i386`, `elf32_x86_64`,
   `aarch64linux`, `aarch64elf`, `elf64lriscv`, `elf32lriscv`,
   `armelf_linux_eabi`, `elf64lppc`, `elf64ppc`, `elf64loongarch`, `elf64_s390`,
   `i386pep`, `i386pe`, `arm64pe`, …)
2. Otherwise, the machine type of the first object file input (as lld does)
3. Otherwise, `OUTPUT_FORMAT`/`OUTPUT_ARCH` from a linker script
4. Otherwise, the host target

An input whose machine type conflicts with the selected target is an error.

## Option syntax (GNU flavor)

These rules follow GNU ld's parser:

- A multi-letter option can take one or two dashes: `-soname` = `--soname`.
  **Exception:** a multi-letter option starting with `o` needs two dashes
  (`--omagic`, `--output`), because `-omagic` means `-o magic`.
- Values can be joined or separate: `-Lpath` / `-L path`, `-lc` / `-l c`,
  `--library-path=path` / `--library-path path`. `-l:libfoo.a` searches for
  the exact file name.
- `-z keyword` and `-zkeyword` are equivalent. Unknown `-z` keywords produce a
  warning (as in GNU ld), not an error.
- `@file` response files are expanded recursively, with GNU quoting rules.
  On Windows hosts, and for the `msvc` flavor, Windows quoting rules apply.
- Paths given to `-L`, `-T` and `-rpath-link` whose *value* starts with `=`
  or `$SYSROOT` are resolved relative to `--sysroot` (`-L=/usr/lib`,
  `-L =/usr/lib`, `-rpath-link =/x`). In `--rpath-link=/x` the `=` is the
  value separator, not a sysroot prefix. With no `--sysroot`, the prefix is
  simply removed. The parser records the prefix; resolution is a pure string
  step before any file is opened.
- `--` ends option parsing; everything after it is an input file.
- **Single-dash long names win over short options with joined values**, as in
  lld: `-export-dynamic` is `--export-dynamic`, not `-e xport-dynamic` as GNU
  ld's getopt would read it.
- **Not supported:** GNU's unique-prefix abbreviations (`--whole-arch` for
  `--whole-archive`) and clustered short options (`-sS`). Both are
  error-prone and no compiler driver emits them.
- **A missing response file is an error.** GNU ld keeps `@file` as a literal
  file name when it doesn't exist.

### Positional options

These options change the state that applies to the input files *after* them
on the command line. qld records them as attributes on each input.

`--whole-archive`/`--no-whole-archive`, `--as-needed`/`--no-as-needed`,
`-Bstatic` (aliases `-dn`, `-non_shared`, `-static`) / `-Bdynamic` (aliases
`-dy`, `-call_shared`), `--start-group`/`--end-group` (`-(`/`-)`),
`--push-state`/`--pop-state`, `--copy-dt-needed-entries`, `-b`/`--format`,
`--start-lib`/`--end-lib` (lld). Unbalanced groups, lib markers and
`--pop-state` are errors.

### Output kind

- The last of `-shared` and `-pie`/`-no-pie` wins, as in GNU ld. `-r`
  combined with either is an error.
- Whether the output is static is decided by the `-Bstatic`/`-Bdynamic` state
  at the **end** of the command line, as in mold. rustc's
  `-Bstatic … -Bdynamic` sequence therefore still produces a dynamic PIE, and
  gcc's `-static -pie --no-dynamic-linker` produces a static PIE.
- With several of `-s` and `-S`, the strongest wins (strip all). GNU ld uses
  the last one given.

### Option handling policy

The option table lists every option from GNU ld, gold, lld and mold, and gives
each one a status:

| Status | Behavior |
| --- | --- |
| **implemented** | Works as documented upstream |
| **accepted-ignored** | Parsed and ignored with no diagnostic. Only for options that have no observable effect for qld (e.g. `--no-keep-memory`, `--reduce-memory-overheads`, `--hash-size`) |
| **unsupported** | An error that names the option and, when support is planned, the roadmap milestone (`unsupported option: --subsystem (not implemented yet (roadmap M7: PE/COFF))`). Used when silently ignoring it could produce a wrong binary |

The table lives in `src/args/table.rs` (about 600 options and 100 `-z`
keywords), and `qld --help` is generated from it.

An option that appears in no table is an error (`qld: error: unknown option: --foo`),
as in GNU ld.

## Version probing

Build systems detect the linker by its version output, so qld prints:

```
$ qld -v
qld 0.1.0 (compatible with GNU linkers)
```

- libtool and autoconf treat a linker as GNU ld when `$LD -v` contains `GNU`,
  and qld's string contains it.
- `--version` prints the same first line followed by license text.
- Meson and CMake identify a linker from `-Wl,--version` output. Upstream
  detection will need to recognize qld; until then it is detected as a
  generic GNU-compatible linker.

## Using qld from compiler drivers

| Driver | Method |
| --- | --- |
| clang | `-fuse-ld=qld` (looks up `ld.qld` in `PATH`), or `--ld-path=/path/to/qld` |
| gcc | `-B<dir>`, where `<dir>/ld` is a symlink to qld. Newer GCC versions may accept `-fuse-ld=` values other than bfd/gold/lld/mold; check your version. |
| rustc | `-C linker=clang -C link-arg=--ld-path=/path/to/qld`. On targets where rustc links with its bundled `rust-lld` by default (x86-64 Linux on recent stable), also pass `-C linker-features=-lld`, otherwise rustc's own `-fuse-ld=lld` wins over a later `-B` or `-fuse-ld` |
| Apple clang | `-fuse-ld=/path/to/ld64.qld` or `--ld-path=` |

## Intentional behavioral differences

Any change to this list needs a matching entry in the changelog.

### Archive resolution order

**GNU ld:** an archive is scanned only at its position on the command line.
A member that defines a symbol referenced only by a *later* object is not
extracted, unless `--start-group` is used.

**qld:** order does not matter, as in lld and mold. A lazy archive member is
extracted whenever any live object references a symbol it defines, wherever
that object appears. `--start-group`/`--end-group` are accepted and have no
further effect.

**What is kept:** when several archives or objects can satisfy a symbol, the
one that appears *first on the command line* wins, which is the same choice
GNU ld makes in all of the links it accepts. A future
`--warn-backrefs` option (as in lld) will report links that GNU ld would
reject.

**Shared library versus archive member:** as in GNU ld, an archive listed
before a shared library that defines the same symbol has its member
extracted (gcc's `-lgcc --as-needed -lgcc_s` relies on this); a symbol only
referenced weakly still binds to the shared library. The one remaining
difference is the order-independence above: a member of an earlier archive
is also extracted when only a later object refers to it.

### Default library search paths

GNU ld has built-in `SEARCH_DIR`s from its default linker script. qld, like
lld, has **none**: compiler drivers always pass `-L` for the system
directories. A bare `qld -lc` without `-L` therefore fails where GNU ld would
succeed. The error message names the missing search path.

### Other differences

- **Unknown `-z` keywords** produce a warning, not an error (same as GNU ld).
- **Output file replacement.** The output is written to a temporary file and
  renamed over the old one, rather than truncated in place. As with gold, lld
  and mold, hard links to the old output are not updated, and a symlink at
  the output path is replaced rather than followed. Paths that are pipes or
  devices (anything under `/dev` or `/proc`, such as `-o /dev/stdout`) are
  written into directly.
- **Build ID values.** `--build-id=md5` and `--build-id=sha1` produce digests
  of the right length and kind, but not the same values as GNU ld: qld hashes
  1 MiB blocks in parallel and then hashes the block digests.
  `--build-id=fast` is an 8-byte xxHash64 tree hash. Values are stable
  across platforms and thread counts.
- **Compiler drivers.** `clang -fuse-ld=qld` finds `ld.qld` (and
  `ld64.qld` for Darwin targets). gcc accepts only `bfd`, `gold`, `lld`,
  `mold` and (gcc 16) `wild` for `-fuse-ld`, so gcc users pass
  `-B<prefix>/libexec/qld`, a directory whose `ld` is qld; the packages
  install it. On macOS, qld invoked as plain `ld` parses a GNU command line,
  so gcc on macOS cannot use it yet.
- **Threads.** Parallel by default. `--threads=N`, `--no-threads` and
  `--thread-count=N` (gold) are honored. Without them, inputs are mapped
  with at most 16 threads and the rest of the link uses one thread per 4 MiB
  of input (counting compressed debug sections at their inflated size), at
  most 16, because small links run faster on few threads. A
  library caller's own rayon pool is respected. Output never depends on the
  thread count. With more than 16 threads, every stage except the
  relocation scan and section merging runs on a 16-thread pool, which is
  faster on large machines.
- **`--fork` (default on Unix).** As in mold and wild, `qld` links in a
  child process (the same executable, started again with `QLD_FORK_CHILD`
  in its environment) and returns as soon as the output is complete; the
  child then frees memory and unmaps inputs. Output written to pipes is
  relayed so callers see end of file at once. The child's stdin reads as end
  of file. A link whose arguments name a path under `/dev` or `/proc` (such
  as `-Map=/dev/stdout`) stays in process. A signal sent to the parent alone
  lets the child finish; one sent to the process group stops both. If the
  child dies before reporting, the parent exits with its status (128 + the
  signal number when killed). `--no-fork` links in process; the library
  never forks.
- **TLS relaxation in executables.** Initial-exec accesses to thread-local
  symbols that the executable defines and exports are relaxed to local-exec;
  GNU ld keeps `R_X86_64_TPOFF64` dynamic relocations for them.
- **`--help`** lists `supported targets` and `supported emulations` only for
  what qld links today (libtool reads the first line to enable shared
  libraries).
- **Executable stack.** An object without a `.note.GNU-stack` section does not
  make the stack executable (lld's choice); GNU ld treats such objects as
  needing one. Use `-z execstack` to request it.
- **Hidden symbols in `.symtab`.** Hidden global symbols are written as
  `LOCAL`, as lld does; GNU ld keeps them `GLOBAL` in static executables.
- **No tail merging of `.debug_str` by default.** GNU ld tail-merges mergeable
  strings at every level; qld, like lld, does it only at `-O2`, so debug-heavy
  outputs can be a few percent larger.
- **`--print-gc-sections` and `--print-icf-sections`** write their lines as
  `qld: note: …` diagnostics.
- **`PT_GNU_RELRO`** is not emitted in static executables yet (M2).
- **Debug tombstones.** A relocation in `.debug_loc` whose target was
  discarded gets `1` (lld's value), so the location list is not cut short.
  GNU ld 2.46 writes `0` there, which ends the list early. `.debug_ranges`
  gets `1` in both linkers; `.debug_names` gets `-1` as in lld; everything
  else gets `0`. Override with `-z dead-reloc-in-nonalloc=<glob>=<value>`.
- **Compressed debug output bytes** differ from GNU ld's (different zlib
  implementation and chunked parallel compression); the decompressed contents
  are the same.

### Dynamic linking and `-r`

- **COMDAT selection** takes the earliest resolution round, then the lowest
  input position. GNU ld keeps the first copy it loads; the two differ only
  when an archive member extracted for a later file's reference carries the
  group (GNU keeps the member's copy, qld the later object's). The copies are
  interchangeable by definition.
- **Static executables get `PT_GNU_RELRO`**, as with dynamic ones.
- **GNU quirks kept on purpose:** a `.plt` header is emitted even when only
  `.plt.got` entries exist, and calls to undefined weak symbols in static PIEs
  go through `.plt.got`; copy-relocated aliases share one copy (lld style).
- Imports appear in `.symtab` as `name@VERSION`.
- **`-r`:** output sections appear in first-appearance order (GNU uses its
  script order); `SHF_MERGE` sections are concatenated, not deduplicated;
  `.eh_frame` is kept as-is (GNU removes duplicate CIEs); with
  `--gc-sections`, unreferenced common symbols are kept; `--defsym x=sym+off`
  makes `x` relative to `sym`'s section (GNU makes it absolute).
- **`--emit-relocs`:** relocations to discarded COMDAT copies become
  `R_*_NONE` (GNU redirects debug relocations to the kept copy).
- **`--cref`** leaves out symbols mentioned only by shared libraries; **`-y`**
  prints `qld: note: main.o: reference to puts`.
- **Compressed debug output** uses zlib level 1 below `-O2`, so a section
  that barely compresses can end up stored uncompressed where GNU ld
  compresses it, or the reverse.

### LTO plugins

- qld reports GNU ld version 2.44 to plugins and sends no gold version (GCC's
  plugin changes behaviour when it believes it runs under gold). It
  negotiates plugin API level 1, which GNU ld 2.46 does not offer.
- Plugins are not `dlclose`d, and a plugin library serves one link per
  process.
- The plugin `message` callback formats integer and string arguments;
  floating-point arguments are shown unformatted.
- A fatal plugin message ends the link with an error. Used as a library, qld
  returns the error instead of exiting, and the plugin is not called again.

### LTO links

- Plugins are loaded only when an IR input appears, so a `-plugin` path that
  does not exist is not an error for a link with no IR (compiler drivers pass
  `-plugin` unconditionally).
- Archives of IR members **without** a symbol index are accepted (members are
  claimed to learn their symbols); GNU ld rejects them.
- Libraries a plugin asks for that are not found are skipped, and none are
  added for `-r`.
- IR that only the code generated by LTO needs is an error: it cannot be
  compiled after code generation.
- A fatal plugin message ends the link. The `qld` binary exits inside the
  plugin's callback as GNU ld does; a library caller gets an error instead.

### AArch64

- TLSDESC is bound eagerly through `.rela.dyn`, as lld does; GNU ld uses a
  lazy TLSDESC PLT (`DT_TLSDESC_PLT`/`DT_TLSDESC_GOT`). Both work under glibc.
- A preemptible function that also has a GOT entry goes through `.plt.got`,
  which GNU ld's AArch64 port does not have, so `.plt` has one fewer entry.
- Only the PLT header gets a `bti c` landing pad, as in GNU ld: entries are
  reached by direct branches.
- `-z separate-code` is off by default, matching GNU ld.
- Range-extension thunks are pooled per output section, so a single output
  section holding more than 128 MiB of code reports a relocation overflow
  instead of splitting the pool.

### Linker scripts

qld's script parser follows GNU ld's grammar and tokenization (including its
surprises: `foo=1` at top level is a single name, and `INPUT(a.o, b.o)` names
a file `a.o,`). Deliberate differences:

- **More lenient** where GNU ld rejects: a stray `;` inside `SECTIONS`
  (including after `ASSERT(...)`), an empty `INPUT()`, output section
  attributes in any order, a keyword used as a symbol name where no keyword
  could appear, and in version scripts `local:` before `global:` and an
  optional `;` before `}`.
- **Short-circuit evaluation**: `&&` and `||` do not evaluate their right
  operand when the result is already known. GNU ld evaluates both, which only
  matters when the right side would be an error.
- **Stricter**: invalid characters are errors (GNU ld warns); division or
  modulo by zero is always an error; `i64::MIN / -1` yields `i64::MIN`
  (GNU ld crashes with `SIGFPE`).
- **Nesting limits**: expressions 128 levels deep, `INCLUDE` 10 (as GNU ld),
  `AS_NEEDED` and version `extern` blocks 32.
- **Not supported**: MRI scripts (`-c`).
- `OVERWRITE_SECTIONS` is an lld extension and follows lld's semantics.

Errors are reported GNU-style as `file:line:column: message`.

Layout from scripts follows GNU ld's algorithms, including orphan placement
and the address fixpoint ("address assignment did not converge after 12
passes" when it cannot settle). Known differences:

- `--verbose` does not dump the effective default script.
- **`-r` with `-T`** runs the script for relocatable output, with GNU's rules:
  COMDAT members never match wildcards, orphans follow the script's sections,
  and `. = ALIGN(n)` pads as GNU does. Without a script, x86-64 uses GNU's
  built-in `-r` section order; other architectures keep first-appearance
  order.
- **`--defsym`** takes full linker-script expressions (`ADDR`, `SIZEOF`,
  `ALIGN`, `LOADADDR`, `MAX`, `DEFINED`, `CONSTANT`, forward references).
  Arithmetic gives an absolute symbol and a lone symbol keeps its section, as
  in GNU ld.
- **Symbol tables** follow GNU ld: hidden input symbols stay GLOBAL in
  executables and become local in shared objects; linker and script symbols
  that are hidden are local without visibility; each input contributes a
  `FILE` symbol.
- Remaining `-r` differences from GNU ld: section addresses are written as 0;
  `.eh_frame` keeps the FDEs of discarded COMDAT copies and the last FDE's
  padding. In PIEs, hidden functions called through `PLT32` stay
  `GLOBAL HIDDEN`.
- Other known differences, with the reason for each:
  - `.dynstr` is not tail-merged: the table is built once, in parallel, from
    the exported names, and suffix merging would need a second sorted pass
    over every string to save a few hundred bytes.
  - `.dynamic` has no spare `DT_NULL` slots (`--spare-dynamic-tags` is
    accepted and reserves none) and orders tags by qld's own emission
    order. The spare slots existed for `prelink`, which is gone, and the
    order is not specified.
  - There is no version-definition symbol in `.symtab`: GNU ld adds one
    `STT_OBJECT` per `.gnu.version_d` entry, and nothing reads them.
  - No empty `.got.plt` is kept.
- **Linker-defined symbols in a shared object are exported**, as in GNU ld
  and lld: `_end`, `_edata`, `__bss_start`, `_etext` and `--defsym` symbols
  with default visibility, and `__start_SEC` / `__stop_SEC` protected
  (`-z start-stop-visibility=` overrides). The per-module ones
  (`__ehdr_start`, `__executable_start`, `_DYNAMIC`,
  `_GLOBAL_OFFSET_TABLE_`, `_TLS_MODULE_BASE_`) stay hidden.
  `__executable_start` follows lld, since GNU ld's shared-object script
  does not define it at all.
- **`GLIBC_ABI_GNU_TLS` and `GLIBC_ABI_GNU2_TLS`** version dependencies are
  added when the output keeps GNU TLS or TLS descriptors and a needed
  library defines the version, as GNU ld 2.46 does; `--no-gnu-tls-tag` and
  `--no-gnu2-tls-tag` turn them off. lld has neither option.
- **`--dynamic-list-cpp-new` and `--dynamic-list-cpp-typeinfo`** match the
  mangled prefixes `_Znw*`, `_Zna*`, `_Zdl*`, `_Zda*`, `_ZTI*` and `_ZTS*`.
  GNU ld matches the demangled names through `extern "C++"`; under Itanium
  mangling the two sets are the same.

### Diagnostics

- Diagnostics are held back and sorted by input position, so they read the
  same however the link was scheduled. They therefore appear after anything
  qld writes to stdout, such as the link map; GNU ld and lld stream theirs.
- The rendering follows lld: `>>> referenced by` with the source position,
  and the object reference aligned under it. A note attached to a
  diagnostic renders `>>> note: …`, where lld writes a bare `>>>`.
- No `N errors` summary is printed, matching both linkers.
- `--error-limit` defaults to 20 with lld's message and its
  `--error-limit=0` escape hatch; GNU ld has no such option.
- `--color-diagnostics` uses lld's colours exactly, and `auto` means stderr
  is a terminal.
- `--fatal-warnings` promotes warnings to errors and fails the link, as in
  both linkers; `-w` drops warnings and cancels it.
- An input section whose contents lie outside the file is rejected, as GNU
  ld rejects it (lld accepts). An `sh_addralign` above `UINT32_MAX` is
  rejected, as in lld; on a non-allocated section it is capped at 64 KiB,
  since there it only pads the file.

### Demangled names

Diagnostics, map files and `--print-*` output demangle names unless
`--no-demangle` is given.

- Itanium C++ output is byte-identical to `c++filt` from binutils 2.46 on
  every name it accepts (checked on 880k names from this machine's
  libraries). qld also demangles forms `c++filt` rejects: Mach-O `__Z`/`__R`
  prefixes, clone suffixes on data symbols (`_ZL3foo.llvm.123`),
  `_GLOBAL__sub_I_<file>` (shown as "global constructors keyed to"), and
  Clang's template-parameter declarations in template arguments.
- Rust names: the legacy `17h<hash>E` suffix is hidden by default, as
  `rustfilt` does. Rust v0 constants wider than 64 bits print as correct hex,
  where `c++filt` garbles them.
- Not yet demangled (left as-is): C++20 `requires` clauses and a few other
  recent Itanium extensions that `llvm-cxxfilt` handles.

### PE/COFF output (MinGW flavor)

- Input sections are ordered inside an output section by archive-member
  position; GNU ld uses extraction order. Addresses stay self-consistent.
- `IMAGE_COMDAT_SELECT_LARGEST` picks the largest copy within one resolution
  round; an earlier round's claim is final.
- The output symbol table carries globals and section symbols, not locals.
- MSVC import-library helper objects (`__IMPORT_DESCRIPTOR_*`,
  `__NULL_IMPORT_DESCRIPTOR`, `*_NULL_THUNK_DATA`) are dropped and the import
  directory is synthesized instead.
- `.pdata` is always sorted by address.
- `.drectve` `-defaultlib:`/`-include:` are honoured from command-line objects
  but not from archive members extracted later.
- `--out-implib` writes the long `dlltool` form of an import library.

### PE/COFF inputs (MinGW flavor)

The readers exist; linking PE output is M7. Behaviour already fixed by them:

- **`.drectve` quoting**: values are split outside quotes first, so
  `-export:"a,b"` is one name (GNU ld's reading; lld splits it).
- **`.def` files** accept the union of GNU dlltool/ld and lld syntax:
  `NONAME`, `DATA`, `PRIVATE`, `CONSTANT` in any case, commas between flags,
  `DESCRIPTION`, `SECTIONS`, `IMPORTS`, `CODE`/`DATA`, and `NONAME` without an
  ordinal (lld rejects these). Numbers may be `0x` hex; a leading `0` is
  decimal, not octal as in GNU. `EXPORTAS` is a keyword (GNU dlltool 2.4x
  reads it as two more exports).
- **Section alignment** with no `IMAGE_SCN_ALIGN_*` flag is left for the
  linker to choose (lld uses 1, MSVC and GNU ld use 16).
- **Data exports of a DLL linked directly** are recognized from the section's
  code/execute flags; GNU ld looks at the section name.

## Debug indexes and section ordering

- **`--symbol-ordering-file`** follows lld, including its warnings and
  `--no-warn-symbol-ordering`. Ties keep input order, where lld's order is
  unspecified.
- **`--call-graph-profile-sort`** implements lld's hfsort/C3 and cdsort, from
  `.llvm.call-graph-profile` or `--call-graph-ordering-file`. qld sorts only
  when asked; lld sorts by default when the inputs carry a profile.
- **`--gdb-index`** writes version 8 (lld writes 7) and is otherwise
  byte-identical to lld's. Objects without `.debug_gnu_pub*` have their DIEs
  scanned, so the index covers them too.
- **`--debug-names`** merges the inputs' DWARF 5 indexes as lld 18+ does. It
  also merges type unit lists, which lld drops with a warning, and it is
  compressed with the other debug sections. String offsets follow qld's own
  `.debug_str`.
- **`--separate-debug-file[=FILE]`** follows mold, since GNU ld has no such
  option: the output keeps `.gnu_debuglink` and loses its debug sections,
  `.symtab` and `.strtab`; the debug file defaults to `OUTPUT.dbg`.
- `-r` with `--gdb-index` or `--debug-names` is an error, and section
  ordering with `-r` is unimplemented.

## AArch64 ELF

- **ADRP relaxations** (ADRP+LDR→ADRP+ADD, ADRP+ADD→NOP+ADR) are on by
  default, as in lld. GNU ld does neither; `--no-relax` gives GNU ld's code.
- **Cortex-A53 843419** is always fixed through a veneer, as in lld. GNU ld
  rewrites the ADRP to ADR when the target is in range. The
  `--fix-cortex-a53-843419=adr|adrp|full` values are not accepted.
- **Cortex-A53 835769** uses GNU ld's detection and veneers (lld has no fix).
- Both erratum options are refused when a linker script drives layout.
- **`.plt.got` entries** are never authenticated: they jump through GOT
  slots filled by `GLOB_DAT`, which nothing signs.
- **`-z pac-plt`** sets no PAC property and gives no warning, as GNU ld does
  (lld does both).
- **TLSDESC** is bound eagerly: there is no `DT_TLSDESC_PLT` or
  `DT_TLSDESC_GOT`.
- **BTI:** executables' PLT entries start with `bti c` when the output has the
  BTI property.
- **`__bss_start__`, `_bss_end__`, `__bss_end__`, `__end__`** are defined as
  in GNU ld's AArch64 default script (when referenced, and under
  `-rdynamic`).
- **Absolute symbols in PIEs:** a GOT slot for an absolute symbol (such as
  static glibc's `_nl_current_LC_CTYPE_used`) keeps the constant; GNU ld adds
  the load base with an `R_AARCH64_RELATIVE`.

## PE long section names

- An image truncates a section name longer than eight bytes, as the
  PE/COFF specification requires, **unless** the link is unstripped and the
  output carries `.debug_*` sections. Then GNU ld's extension writes every
  long name through the string table so GDB can find the DWARF, and qld
  does the same. `--enable-long-section-names` and
  `--disable-long-section-names` override the choice.
- lld is stricter: it truncates even in a `-g` link and keeps long names
  only for discardable debug sections.
- Whether a MinGW link carries DWARF depends on how the runtime was built,
  not on `-g`: MSYS2's runtime has debugging information, so its images keep
  long section names even without `-g`, while a runtime built without it
  truncates them.

## PE/COFF: i386 and ARM64

- **SafeSEH** is a qld addition; GNU ld has none. It follows link.exe:
  - `.sxdata` becomes a sorted handler table;
  - a default link builds the table only when every object is SafeSEH-compatible;
  - `--no-seh` sets `NO_SEH`;
  - the library-only `PeOptions::safe_seh` rejects incompatible objects.
- **Load-config directory size** follows GNU: the structure's own size, or 64
  for i386 images with subsystem version 5.01 or older.
- **ARM64 defaults** follow lld's MinGW driver: OS and subsystem version 6.0.
  `--disable-dynamicbase` is refused on ARM64. ARM64EC and ARM64X are refused.
- **Import libraries:** for a `.def` export bound to a stdcall-decorated
  symbol by stdcall fixup (`StdFunc` → `_StdFunc@8`), qld's import library
  names `_StdFunc`, where GNU writes `_StdFunc@8`.
- **Pseudo-relocations** are also accepted for PC-relative x86 references
  (REL32 auto-import).
- **Debug sections** follow GNU:
  - `.stab` and `.debug_*` sections come after `.reloc`, in the order GNU's
    PE scripts give;
  - no base relocations are written for addresses in debug sections;
  - `IMAGE_FILE_DEBUG_STRIPPED` is set only when the image has no debug
    sections.
- **Linking:**
  - archive members for another machine are skipped;
  - x86 code padding is `nop`;
  - DLLs use a fixed default image base, not GNU's automatic one.

## RISC-V 64

- **Relaxation follows lld**, not GNU ld. gp-relative relaxation is off
  unless `--relax-gp` is given; GNU ld does it by default.
- **`__global_pointer$`** is lld's: `.sdata` + 0x800, or the image base +
  0x800.
- **Output bytes:**
  - code gaps are zero-filled;
  - `.got[0]` does not hold `_DYNAMIC`.
- **Attributes:**
  - the merged architecture string does not add implied extensions;
  - attribute conflicts are warnings;
  - a floating-point ABI mismatch is an error.
- **Relocations:**
  - section-symbol offsets into relaxed code are mapped to the moved code
    (lld leaves them);
  - `-r` does not synthesize `R_RISCV_ALIGN` as lld does.
- **Endianness:** big-endian RISC-V (`elf64briscv`) is rejected.

## x32

- The interpreter is `/libx32/ld-linux-x32.so.2`; GNU ld's built-in default
  is `/lib/ldx32.so.1`.
- TLS descriptors are resolved eagerly, so there is no `DT_TLSDESC_PLT` or
  `DT_TLSDESC_GOT`, as on x86-64 and AArch64.
- A `GOTPCRELX` load with no REX prefix whose preceding byte could be one
  relaxes to a `lea` rather than an immediate.
- A `__tls_get_addr` call removed by a relaxation keeps its PLT entry only
  when the symbol is preemptible.
- Binutils before 2.46 puts TLS descriptor relocations in `.rela.plt`; 2.46
  and qld put them in `.rela.dyn`.
- **GNU ld 2.46 corrupts the x32 local-dynamic sequence** when the
  `__tls_get_addr` call is indirect: it writes the 12-byte form over 13
  bytes, leaving a stray byte. qld writes the intended 13-byte sequence.
- lld rejects x32's `GOTTPOFF` and TLSDESC instruction forms, so it is only
  compared on position-independent output.

## x86-64 differences settled against GNU ld and lld

- **`GOTPCRELX` in position-dependent output:** GNU ld rewrites a GOT load
  of a non-preemptible symbol to `mov $addr, %reg`; qld writes
  `lea addr(%rip), %reg`, as lld does. Same address, same length.
- **A data pointer to an IFUNC** gets a `RELATIVE` relocation to the
  canonical PLT stub, as in lld, so `&f == f` holds. GNU ld writes an
  `IRELATIVE` there, which stores the resolved address and makes the
  comparison false — C requires it to be true.
- **`SHF_GNU_RETAIN` does not make the output `ELFOSABI_GNU`.** GNU ld drops
  the flag and leaves `ELFOSABI_NONE`; lld sets `ELFOSABI_GNU`. qld follows
  GNU ld, and stamps `ELFOSABI_GNU` for an `STT_GNU_IFUNC` or
  `STB_GNU_UNIQUE` symbol, as both linkers do.
- `__ehdr_start` and `__executable_start` are relative to the first
  allocated section, as in GNU ld and lld. They used to be `SHN_ABS`, which
  also left their GOT slots unrelocated in a PIE.

## Relocatable output and the debug indexes

- `-r` and `--emit-relocs` work for every ELF class and byte order. The
  records are the output's class and byte order, and the relocations keep
  the architecture's form: `SHT_REL` in `.rel<name>` for i386 and 32-bit
  Arm, `SHT_RELA` elsewhere, as GNU ld chooses. A `SHT_REL` entry has no
  addend field, so the offset a redirected section reference needs is added
  to the field being patched, as BFD does.
- An input whose relocation form is not the architecture's is refused.
- `--emit-relocs` keeps `.ARM.exidx` relocations that lld drops, and on
  RISC-V keeps debug-section relocations lld drops.
- `.gdb_index` is little-endian on every target, as in GDB's format and
  lld's output; `.debug_names` follows the output's byte order. Both work
  for big-endian output.
- Still open: section ordering with `-r`, `-r` with `--gdb-index` or
  `--debug-names`, GNU's built-in `-r` layout for architectures other than
  x86-64, and `R_LARCH_ALIGN` synthesis in LoongArch `-r`.

## x86 family (i386, x32, x86-64)

- An undefined weak symbol is zero in **every** executable, position
  independent ones included: no dynamic relocation and no `.dynsym` entry, as
  GNU ld's `UNDEFINED_WEAK_RESOLVED_TO_ZERO` does. Shared objects keep the
  relocation. GNU additionally keeps a relocation when the symbol has a GOT
  or PLT relocation and no other reference in code; qld does not track that
  distinction and resolves to zero there too.

## 32-bit Arm

- Exception-index entries are merged per input section, as lld does, rather
  than per entry as GNU ld does (`--no-merge-exidx-entries` turns it off).
  qld always writes the trailing sentinel and aligns `.ARM.exidx` to 4, as
  lld does; GNU ld drops a sentinel that repeats the last
  `EXIDX_CANTUNWIND` and aligns to 1.
- Thunks carry the caller's instruction state, so a Thumb `bl` gets a Thumb
  thunk where lld reuses an A32 thunk through `blx`.
- Mapping symbols are written for qld's own code: the PLT is marked
  `$a`/`$d`/`$a` exactly where GNU ld marks the same PLT, and every thunk
  carries its instruction state plus `$d` for its padding. `-x` and `-s`
  drop them, and script-driven layout writes none because it places no
  thunks.
- Thunk pools sit every 8 MiB of an output section's content (64 MiB on
  AArch64, 16 MiB on PowerPC64) plus one at the end, and a caller uses the
  nearest. Pools can only go between input sections, so a single input
  section longer than a branch's reach still fails, as do the short Thumb
  branches (`R_ARM_THM_JUMP19`, `THM_JUMP8`) when the nearest pool is
  further than they reach. A `-T` layout places no pools.
- BE8 is refused. `--target1-rel`, `--target2=`,
  `--be8`, `--fix-cortex-a8`, `--long-plt` and `--pic-veneer` are not
  supported.
- Group relocations, `THM_PC8`, `THM_JUMP6` and the 12-bit GOT and TLS
  forms are linked; a checked group form reports an overflow when a residual
  is left. GNU TLS descriptors (`-mtls-dialect=gnu2`) are still reported as
  unsupported relocations. RWPI `SBREL` is refused, as GNU ld refuses it
  too ("dangerous relocation: unsupported relocation").

## AmigaOS Hunk and m68k

- **Section to hunk:** qld applies GNU ld's output-section rules, so
  `.text.late` merges into `.text`; vlink gives every distinct input
  section name its own hunk. The same for `.text`, `.rodata`, `.data` and
  `.bss`, which is what vasm and vbcc emit.
- **Out-of-range PC-relative branches are an error**, where vlink silently
  truncates a `bsr.w` that cannot reach. There are no range-extension
  thunks yet.
- Gaps in m68k code are zero-filled, as GNU ld's m68k backend leaves them,
  not `nop`-filled.
- Hunk object output, overlays and Hunk input are not implemented; `-r` on
  a Hunk target reports "not implemented yet". Chip and fast memory
  attributes are always `MEMF_PUBLIC`.
- m68k dynamic output, TLS beyond local-exec and IFUNC are refused with a
  diagnostic rather than written wrong.

## PowerPC64 BE (ELFv1)

- **Static output only.** Dynamic output needs ELFv1's `.plt` of function
  descriptors, which qld does not build yet; `-shared`, PIE and an
  executable that links a shared object are refused with a clear error
  rather than written wrong.
- Static IFUNCs use `R_PPC64_IRELATIVE` against a `.got.plt` word with a
  seven-instruction stub; GNU ld uses `R_PPC64_JMP_IREL` against a `.iplt`
  entry that is itself the descriptor. glibc's static startup accepts both.
  Taking an IFUNC's address is reported as unimplemented.
- `--gc-sections` does not split `.opd`, so a descriptor keeps its function
  alive; GNU ld's `ppc64_elf_edit_opd` drops unreferenced ones.
- `.gnu.attributes`: qld keeps the first input's section, where GNU ld
  merges the tag values, so a hard-float and soft-float mix is not
  diagnosed. (qld used to concatenate them, which produced a section
  `readelf` refused to parse; that affected PowerPC64 LE too.)

## RISC-V 32

- The backend is shared with RV64; only the width-dependent parts (GOT and
  TLS entry size, the PLT's `lw` and slot shift, the dynamic relocation
  types, `c.jal` relaxation) differ.
- IFUNC stubs go in `.plt` before `.text`, as in GNU ld, where lld uses
  `.iplt` after it; an address-taken IFUNC symbol keeps its resolver's
  address, where lld redirects it to the canonical PLT entry.
- A dynamic output with no PLT entries still reserves `.got.plt` and emits
  `DT_PLTGOT`, as GNU ld does; lld emits neither.
- A dynamic output with no PLT entries still reserves `.got.plt` and emits
  `DT_PLTGOT`, as GNU ld does; lld emits neither.

## i386 ELF

- Relaxing TLS general-dynamic or descriptor code to initial-exec uses a
  negative TPOFF entry, as lld does; GNU ld uses a positive `TPOFF32` with
  `subl`/`negl`.
- `R_386_TLS_LE` in a shared object is an error; GNU ld accepts it with text
  relocations.
- A library found through `-l`, `-l:` or a script's `GROUP(-l…)` that was
  built for another machine, class or byte order is skipped with
  `skipping incompatible <path> when searching for -l<name>` and the search
  continues, as in GNU ld. Differences in the detail:
  - qld prefixes it `warning:`, and `-w` suppresses it; GNU ld has no `-w`.
  - GNU ld replays the whole search after `cannot find -lfoo`, repeating
    every message, and prints the message twice for `-l:name`; qld prints
    each once.
  - qld's `cannot find -lfoo` has no `: No such file or directory` suffix.
  - A file named **directly** on the command line is still an error. For a
    shared object qld gives the architecture wording, where GNU ld says
    `error adding symbols: file in wrong format`.
  - A directly named incompatible **archive** is an error in qld even when
    nothing extracts a member; GNU ld only fails when a member is actually
    pulled in. A thin archive found by search is not pre-checked, so its
    members fail individually instead of the archive being skipped.
- Initial-exec TLS uses GNU's `addl` form, and GOT32X relaxations are done
  as in GNU ld (lld does neither).
- Inputs for another machine, class or byte order are rejected with GNU's
  "is incompatible with" wording.
- Without `-m`, the target is that of the first input that names one (ELF
  objects, shared libraries, GCC LTO objects, LLVM bitcode), else the host's,
  as GNU ld's default emulation would be.
- `-r` and `-b binary` are not implemented for 32-bit ELF yet (clear errors).
  `-b binary` wrappers are machine-neutral (`EM_NONE`) on the other targets.
- GNU ld 2.46 adds the `GLIBC_ABI_GNU_TLS` / `GLIBC_ABI_GNU2_TLS` version
  dependencies (`--gnu-tls-tag`, `--gnu2-tls-tag`); qld adds neither yet, on
  x86-64 as well.

## PowerPC64 LE

- Call stubs for PLT calls are in `.plt.sec`, not placed near callers (lld)
  or in `.text` (GNU ld). `.plt` holds the glink code and `.got.plt` the
  slots; GNU ld and lld call these `.glink` and `.plt`.
- A `bl` to an undefined weak symbol becomes a `nop`, as in GNU ld (lld
  branches to itself).
- Thunks never use Power10 instructions (no `--power10-stubs`).
- In dynamic outputs, IFUNC `IRELATIVE` relocations go to `.rela.dyn`, as
  GNU ld does: glibc's loader does not apply them from `.rela.plt`.
- `.toc` goes into the output `.got`, as GNU ld's `elf64lppc` script does
  (`*(.got .toc)`), so both stay within ±32 KiB of the TOC pointer; lld
  keeps `.toc` a separate orphan. `.got` is 8-aligned, where GNU ld aligns
  it to 256.

## LoongArch64

- **Relaxation shrinks sections**, as lld does: the `nop`s that relaxed
  sequences leave are deleted, and `R_LARCH_ALIGN` padding is trimmed to
  `(2^n − 4) − needed`, or dropped entirely past its max-bytes limit.
- `-r` synthesizes `R_LARCH_ALIGN` before each input section that follows
  relaxable code, so a later relaxing link keeps the alignment, as lld does.
- There are no B26 range-extension thunks, as in lld; a branch out of range
  is an error.
- lld 22 reserves an unused initial-exec GOT entry for each thread-local
  symbol in the extreme code model; lld 23 and qld do not. The comparison
  tests tolerate lld's extra entries but never qld's.
- **GOT relaxation:**
  - only adjacent instruction pairs are relaxed;
  - the GOT entry that becomes unused is dropped (lld keeps it);
  - `--no-relax` also turns GOT relaxation off.
- **Undefined weak branches:** a `b`/`bl` to an undefined weak symbol
  branches to itself.
- **`.got.plt[0]`** holds `_DYNAMIC`, where lld writes 0.

## ld64 flavor notes

- Single-dash long options only (`-dylib`, `-framework Foo`, `-arch arm64`).
- `-arch` may be given several times. qld then links each architecture
  (in parallel) and writes a universal binary. This is an extension:
  ld-prime requires `lipo` for this.
- **Atomization** for `-dead_strip` follows lld: `N_ALT_ENTRY` symbols don't
  start atoms, and ld64's special handling of `L`/`l` labels is not
  reproduced (clang does not put those labels in the symbol table).
- Selecting `x86_64` from a universal input also accepts a lone `x86_64h`
  slice.
- `@file` arguments stay literal when no such file exists, so `@rpath/...`
  and `@executable_path/...` pass through.
- References to exported weak definitions bind through weak lookup, as ld64
  does. Implicit re-exports (a public sub-library such as `libc++abi` under
  `libc++`) are followed.
- `ZERO_AR_DATE` in the environment (read by the `qld` binary, not by a
  library link; the same holds for `LD_RUN_PATH` and `LD_LIBRARY_PATH`)
  zeroes the debug-map (`N_OSO`)
  timestamps.
- `-order_file` accepts ld64's syntax: one symbol per line, optional
  `arch:` and `object.o:` prefixes, `#` comments.
- Known differences:
  - unused CIEs in `__eh_frame` are dropped;
  - legacy (`LC_DYLD_INFO_ONLY`) output binds every non-lazy import at load
    time, and it does write the weak binding stream.
- With `-dead_strip`, only undefined symbols that live code reaches are
  errors, as in ld64. Symbols named on the command line are always reported:
  the entry point, `-u`, and `-alias` targets.
- Objective-C:
  - Method lists are relative by default from macOS 11 / iOS 14, as in ld64
    and lld.
  - Category merging needs `-objc_category_merging`; it is off by default,
    as in lld.
- The compiler optimization level that clang passes to a linker named with
  `-fuse-ld=<path>` (`-O`, `-O<n>`, `-Os`, `-Oz`, `-Ofast`) is accepted and
  ignored. So are obsolete ld64 options: `-single_module`, `-prebind`,
  `-noprebind`, `-seglinkedit`.
- Relative `-L` and `-F` paths are not looked up under `-syslibroot`; only
  absolute ones are, as in ld64.
- Weak definitions marked "can be hidden" in every object are hidden, as in
  ld64 and lld.
- `-r` keeps `.subsections_via_symbols`, compact unwind, `__eh_frame`,
  linker options, and merges weak definitions and literals. DWARF sections
  are dropped with a warning (link the original objects for debug info),
  data-in-code and linker optimization hints are dropped, and export lists
  and `N_INDR` symbols are rejected. ld64.lld has no `-r`; qld's output is
  compared with Apple's `ld -r` in CI.
- `-init` is an error unless `-dylib`, as in ld64 (ld64.lld ignores it).
- `-force_flat_namespace` (executables only) and `-alias_list` are
  supported.
- arm64e: authenticated pointers keep their key, address diversity and
  discriminator; chained fixups use `DYLD_CHAINED_PTR_ARM64E` up to macOS 11
  and `_USERLAND24` from macOS 12, as ld64 chooses; imports go through
  `__auth_stubs`/`__auth_got`; `-no_fixup_chains` is an error. arm64e
  selector stubs use a plain `br`.
- Merged Objective-C categories keep the first category's slot in
  `__objc_catlist` (lld moves it first), and qld reuses existing selector
  references where lld makes new ones.
- **LTO** goes through the libLTO C API, as ld64 does, not through the GNU
  plugin API.
  - **Finding libLTO:** `-lto_library`, then the libLTO next to the clang
    that `xcrun` or `PATH` finds, then Xcode's.
  - **Options:** `-object_path_lto`, `-cache_path_lto`, the ThinLTO cache
    pruning options, `-mllvm`, `-mcpu` and `-flto-codegen-only` are honored.
  - **Differences from ld64:**
    - Dead stripping runs after LTO, not before, so references from native
      code that would have been stripped still keep their bitcode targets.
    - The LTO objects, and the native members of archives that held
      bitcode, are placed after the command-line inputs.
    - `-hidden-l` is not applied to archives containing bitcode (a warning
      says so).
    - `linkonce_odr unnamed_addr` symbols are not preserved in dylibs, as in
      lld.
    - A mix of ThinLTO and full-LTO modules is compiled as full LTO.
    - `-object_path_lto` gets an `.<arch>` suffix in multi-arch links.
    - LLVM reads `-mllvm` options once per process per libLTO.
    - ThinLTO errors inside LLVM end the process, because the C API cannot
      return them.
    - Bitcode reached only through `LC_LINKER_OPTION` auto-linking, or passed
      as an in-memory input, is not supported yet.