If an object file defined foo and its alias foo@VER, references to foo
in a shared object were bound locally, although the definition is
exported as foo@VER, so an executable's copy relocation or interposing
definition didn't reach them. Now foo, if not exported itself, shares
foo@VER's dynamic symbol and is as preemptible as foo@VER.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A shared object's references to an IFUNC that it exports were bound
locally with IRELATIVE relocations, so a definition that interposes the
IFUNC didn't reach them. They now get JUMP_SLOT and GLOB_DAT relocations
against the symbol, as with GNU ld and lld.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The copyrel-relro tests wrote to each buffer and caught SIGSEGV with
setjmp/longjmp to see if it was read-only. They now read(2) from a pipe
into the buffer instead, which fails with EFAULT if the buffer isn't
writable, so the tests no longer need a signal handler.
.openbsd.randomdata holds values such as the stack protector's guard,
which the OpenBSD loader fills with random bytes and which are not
supposed to change afterwards. OpenBSD's GNU ld and lld put the section
into PT_GNU_RELRO so that it becomes read-only after relocation, and
mold now does the same. Upstream lld does it only for ELFOSABI_OPENBSD
inputs, but mold already creates PT_OPENBSD_RANDOMIZE by the section
name alone, so it doesn't check the OSABI.
A static executable had no .dynamic, so -E, --dynamic-list and the
other options that export symbols were silently ignored. Such an
executable now has .dynamic and .dynsym with the exported symbols, as
the options ask. It still has no .interp and runs as before.
-static -shared output, which also lacked .dynamic, now has one.
The build ID in LC_UUID is a hash of the contents, but its version and
variant bits were those of a version-4 UUID, the random kind. They are
now those of version 3, the kind made by hashing, as in ld-prime's
output. Nothing reads these bits; they only label the value. A
-random_uuid UUID stays version 4.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The function writes the build ID into LC_UUID, and returns the page
hashes the code signature is made of, which it computes even when no
build ID is wanted. write_build_id is the name of its counterpart in
the ELF linker. Its comment now explains what the build ID is, and the
comments of the function and of Args::uuid say that -no_uuid leaves
LC_UUID out, as it does, rather than zeroed.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
merge_literals binned the literals' hashes into 64 shards one by one
and then filled a hashbrown table for each shard on one thread, meeting
the copies in input order. It now deduplicates them as the ELF linker's
merged sections do: each kind of literal (a segment, section name and
type, as ld-prime merges only within one) has a ConcurrentMap sized for
2/3 occupancy from a HyperLogLog estimate, and every copy is inserted
in parallel, prefetching a few insertions ahead. The copies of an
element race for its entry with an atomic minimum of a rank that orders
them as before, the most aligned first and the first in input order
among equals, so the winners and the output don't change.
is_mergeable_literal becomes has_mergeable_literals, a test of the
section alone; merge_literals skips the elements with relocations
itself.
The pass is up to 30% faster on large links (iTerm2's debug dylib:
9.4ms to 6.6ms), within the noise of the whole link, and the peak
memory is unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
link returned a LinkResult, Result<i32, &str>: an exit status, or the
target the inputs are actually for. The status was always 0, as a link
that fails exits in the middle, so link now returns Option<&str>, the
other target if there is one, and the driver returns nothing.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The Mach-O driver guessed a target, parsed the options for it, and
started over with the right target when the guess was wrong, as the ELF
driver does. But nothing in the Mach-O option parser depends on the
target until it has found the target, from -arch or the input files, so
a wrong guess only parsed everything twice and gave the options'
warnings twice.
parse_args now parses the options once and settles the target, and the
driver calls that target's link function with the result, which first
resolves what depends on the target (resolve_args). The driver no longer
takes an initial target or loops, and a target's link function no
longer reports another target or an exit status: a failed link exits in
the middle, so it returns nothing.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- The mergeable record's reader and writer chose ARM64_RELOC_* or
X86_64_RELOC_* by the CPU type; they now use the Target trait's
RELOC_UNSIGNED, RELOC_SUBTRACTOR and RELOC_GOTPC.
- delay_refs and lazy_helper_refs built each kind's fixed list of
split-info references in a Vec, with closures computing the
offsets; they now return a static table per kind.
- The bundle hook's embedded object was copied to the heap and leaked
back; its MappedFile now refers to the static bytes.
- parse_object's alive argument, which its one caller always passed
as true, is gone.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Removing macho/util.rs had turned its push16/32/64 into a push variant
of every reader and writer in mold-common's endian.rs, twelve functions
of which the Mach-O linker used three. Appending to a Vec doesn't belong
with reading and writing integers in place, so endian.rs is back as it
was, and push16, push32 and push64 live in macho.rs beside the Mach-O
linker's other little-endian helpers.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The comment assumed the reader already knew what frameworks, resources,
images and mergeable libraries are. It now explains them from the
ground up: how a library in a framework finds its data files through
class_getImageName(), why linking it statically, or from
ReexportedBinaries as a debug build does, breaks that, and how the
macOS linker fakes class_getImageName() to work around it, before
coming to mold's implementation.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
tests/elf and tests/macho each had a crate of their own for running
their shell scripts, which did much the same thing. A single mold-tests
crate directly under tests/ now runs both: lib.rs discovers, schedules,
times out and reports the tests, and elf.rs and macho.rs only pick the
targets, the latter booting a simulator device for each simulator
target in turn.
The two runners' options and conventions are made one. Both take
--native, --all, --triple and --cpu, the Mach-O tests' --native now
running the targets the Mac runs directly, its own architecture and
x86_64 under Rosetta, as --host did. The work directory links mold
under every name the scripts use, a log line ending in "skipped" marks
a skipped test, and unavailable targets are reported the same way.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The ELF and Mach-O linkers each had a copy of the same response file
reader. They now share mold-common's expand_response_files. Its macho
flag keeps the Mach-O linker's one difference: an argument starting
with @rpath, @loader_path or @executable_path is a dylib path, not a
response file.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The Mach-O linker held back the warnings it gave while reading the
options until it knew the parse was for the right target, so that a
link it parsed again for another target gave each one once, and so
that -w and -fatal_warnings applied to them wherever they appeared. The
ELF linker does neither, and the hold is gone from mold-common.
-w, -fatal_warnings and $LD_TREAT_WARNINGS_AS_ERRORS now take effect as
they are read, as the ELF linker's options do, so a warning given while
the options are read obeys only a -w or -fatal_warnings before it, and
a link parsed twice may give it twice. The tests no longer count such
warnings, and give -w and -fatal_warnings before the options that warn.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Moving the Mach-O linker to mold-common's diagnostics left calls such
as mold_common::warn!(...). Each file now imports the macros it uses,
as the ELF linker's files do, and calls warn!, error! and fatal!.
mold-common gains a notice! macro for notice, as out! is for out.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The Mach-O linker had its own error.rs. It printed names byte for byte
through raw(), which marked each byte that isn't UTF-8 for its own
renderer to write back, and it sorted the messages of parallel passes.
It now reports through mold-common's error module, as the ELF linker
does: names print with a U+FFFD for each byte that isn't UTF-8, and a
message from another thread may come in any order, though never mixed
with another one.
mold-common's error module takes over the warnings held back while the
options are read and notice, which the Mach-O linker uses, and its
demangle flag serves both linkers. display_name moves to symbol.rs.
The tests of diagnostics that named non-UTF-8 bytes now look for
U+FFFD.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
macho/archive_file.rs was a copy of common/archive_file.rs without thin
archive support. The shared reader now does what the copy did better:
it trims the space padding of a BSD short member name, which the ELF
linker kept, skips every __.SYMDEF variant, and returns each member's
date, which the Mach-O linker writes to debug stabs. MappedFile::member
makes a member a Mach-O input named "archive(member)".
Mach-O records had plain integer fields, read and written in the host's
byte order, so the linker assumed a little-endian host as well as
target, and with their natural alignment they were copied out of the
input files, except for a symbol table that happened to be aligned.
Their integer fields are now byte-backed little-endian U16, U32 and
U64, read and written with get and set, as the ELF linker's are in its
target's byte order. Records have alignment one, each checked by a
const assertion as ELF's are, so the symbol table and the relocations
are viewed in the input files, wherever they are.
FileRecord, with record_from_bytes and records_from_bytes, moves from
the ELF linker to mold-common, and both linkers use it. It gains
as_bytes, which the Mach-O linker uses.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The Mach-O linker's util module mostly re-exported mold-common's
helpers. Its files now import them from mold-common as the ELF linker's
do, and call the little-endian accessors by their mold-common names
(read_ul32 for read32, and so on).
What it defined itself goes where it belongs: push16/32/64 become
endian's push_ul16/32/64, align_to_mod moves to bits, path_bytes to
path, name_sort_key to the symbol table and reserve_arena to
input_files. sha256 and sha1 are gone; their callers call the sha2 and
sha1 crates.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
-random_uuid makes its UUID with Uuid::new_v4, as the ELF linker's
--build-id=uuid does, and a content UUID is stamped as version 4 with
Builder::from_random_bytes instead of by hand; the bits it sets are the
same. The trace files format the UUID with the crate too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
util.rs held helpers of half a dozen kinds. They now live in modules
named for what they do: bits.rs (alignment, bit fields, sign
extension), leb128.rs, bytes.rs (byte strings), path.rs (lexical path
manipulation), mem.rs (huge pages and leaked allocations), and
parallel.rs (SyncUnsafeCell and atomic_or). The LEB128 and byte string
helpers that only the Mach-O linker had move to them too.
random_bytes is gone: its callers call getrandom::fill.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The rest of macho/util/demangle.rs, display_name, can't move to
mold-common with demangle_cpp: it prints through the Mach-O linker's
own diagnostics, with its -demangle setting and its raw. It now lives
in error.rs beside them. That leaves util/ with mod.rs alone, which
becomes util.rs.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Only the Mach-O linker uses CityHash64, but nothing in it depends on
Mach-O, so it belongs with the other hash functions in mold-common.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
macho/util/ had its own copies of the glob matcher, SipHash, WorkerLocal,
the C++ demangler and a dozen small helpers that mold-common already
has. The Mach-O linker now uses mold-common's, and its util module
re-exports the helpers it used to define.
The two glob matchers differed only in how they read bracket
expressions and backslashes: ld-prime ends a bracket expression at the
first `]` and knows no negation or escapes, and takes a trailing
backslash as itself. GlobBuilder::add_ld_prime adds a pattern read that
way, and add_literal a name that matches itself alone.
is_space moves to mold-common, and the ELF linker uses it too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The port followed libc++'s __murmur2_or_cityhash, which is under the
Apache License with LLVM exceptions. It now follows CityHash v1.0.3 at
https://github.com/google/cityhash, which libc++'s is derived from and
which is under the MIT license as mold is. Its COPYING is included in
the file.
A mergeable record must hash its names as ld-prime does, with libc++'s
std::hash, so the two places where libc++ differs from v1.0.3 are kept
and marked: 4 to 8 bytes are hashed with a 32-bit shift, and z is added
to rather than set for 33 to 64 bytes. The output is unchanged, and a
new test checks it against values from libc++.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The Mach-O linker has only two targets, so they don't need a directory
of their own as the ELF linker's twenty do. The target trait is now in
macho/arch.rs, which re-exports the targets from macho/arm64.rs and
macho/x86_64.rs, so crate::arch::Arm64 and crate::arch::X86_64 still
name them.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
rustc emits asynchronous unwind tables, which compact unwind can't
encode, so the unwind info of most functions goes to __eh_frame. In a
debug build, that section exceeds the 16 MiB that compact unwind can
refer to, and Apple's ld warns that exception handling may be slower.
rustc reports the linker's output as a linker_messages warning, so we
allow that lint in debug builds on Apple hosts.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The test linked mold into a toolchain's bin directory as ld, but mold
acts as the Mach-O linker only under the name ld64.mold, so the copy
ran the ELF linker and failed on -arch. The test checks that the
linker loads the libLTO in lib beside its bin directory, which doesn't
depend on the name.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The ELF test harness took the host's compiler for a native ELF target,
and on macOS `cc -dumpmachine` reports arm64-apple-darwin, which it
read as 32-bit arm. It then ran the whole ELF suite with Apple's clang,
which builds Mach-O programs, and most tests failed. The integration
test now skips the ELF tests on macOS, as the Mach-O integration test
skips its tests on other hosts.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
e_shnum is a 16-bit field, but its values from SHN_LORESERVE (0xff00)
up are reserved. With that many sections or more, e_shnum must be 0
and the real number goes into the first section header's sh_size. We
did that only past 65535 sections.
Now that the Mach-O linker is part of mold, its banner no longer says
mold-macho, and it carries the commit hash as the ELF linker's does.
The -sdk_imports report names the linker mold-<version>. The tests
tell mold from Apple's ld with a new is_mold helper in common.inc,
which looks for the banner.
elf/build.rs looked up the commit mold is built from and made the ELF
linker's version string from it. The Mach-O linker needs the hash as
well, so mold-common's build script now looks it up and exports it as
mold_common::GIT_HASH. The ELF linker's version string is unchanged.
The rest of the Mach-O linker's Unix-only code gets Windows versions.
The output is written with seek_write and created without a file mode.
std gives no file IDs on Windows, so input files are told apart by
their canonical paths. LTO is reported as unsupported on Windows, as
it is for the ELF linker.
The Windows CI jobs now build a Mach-O target as well.
macho/subprocess.rs was a copy of common/subprocess.rs, except that its
signal handler removed the Mach-O linker's output rather than the ELF
linker's temporary file. The Mach-O linker now registers its output
with mold-common's set_tmpfile, so the shared signal handler removes it,
as do the fatal error path and the panic hook.
macho/util/perf.rs was a copy of common/perf.rs without its Windows
code. The only real difference was that the Mach-O linker prints the
timers to stderr, as ld64 prints -print_statistics, so Timers::print
now takes the stream to print to.
std::os::unix::ffi::OsStrExt doesn't exist on Windows. On Unix,
OsStr::as_encoded_bytes returns the same bytes as OsStrExt::as_bytes,
and mold-common's os_str, which the ELF linker uses, converts bytes
back to an OsStr. This is a step toward building the Mach-O linker on
Windows.
The Mach-O shell tests are now the mold package's macho-integration
test. The executable is built with the Mach-O targets alone, and only
the Mach-O crates' unit tests run on the macOS runner.
The mold executable now acts as the Mach-O linker only under that name,
so the harness makes a symlink to it in its working directory and
passes that to the scripts as $mold. The tests run as the
macho-integration test of the mold package; they are skipped on hosts
other than macOS.
As in mold 1.x, one executable holds both linkers and chooses one by
the name it is run as: ld64.mold (or ld64) for Mach-O, anything else
for ELF. That's the name clang looks for with -fuse-ld=mold on macOS,
as it looks for ld64.lld with -fuse-ld=lld.
The Mach-O targets get features of their own, macho-arm64 and
macho-x86_64, which are on by default.
The harness calls macOS-only APIs such as confstr's
_CS_DARWIN_USER_TEMP_DIR, so the workspace failed to build on Linux.
The scripts drive Apple's toolchain and can't run elsewhere anyway, so
the crate is now empty on other hosts.
mold-macho is a Mach-O linker for macOS, written in Rust as a port of
mold. Its tree already follows mold's layout (macho/, arch/macho-*,
tests/macho/), so this merge keeps its history and commit hashes as
they are.
Where both trees have a file, this merge keeps mold's. The Mach-O
crates join the workspace, but the mold executable doesn't use them
yet; the following commits wire them in.
mold's cli has features named arm64 and x86_64 for its ELF targets.
Prefixing ours keeps the names apart once this linker moves into mold.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
mold keeps its ELF tests in tests/elf. Putting ours in tests/macho
gives the two trees the same layout.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Rewrite comments whose English was ungrammatical or unidiomatic, and
correct comments that no longer matched the code, such as references to
C++-era functions and files, wrong option and field names, and incorrect
instruction listings. Also fix a typo in a RISC-V error message.
Four tests assumed the compiler's default deployment target was
older than macOS 27. diagnostic-options and option-check-order link
for macOS 26 objects the compiler now builds for 27, and the linker's
warning that the objects are newer broke their checks for an output
with no warnings; the objects are built for 26 now. missing-argument
and syslibroot look for a -lazy-l (-lazy_library) dylib in otool -L,
which from macOS 27 on, as in a simulator, loads lazily through no
load command; on macOS they link for 26.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
macOS 27's dyld calls each initializer inside a handler
(dyld4::LibSystemHelpers::callInitializer) that catches an exception
leaving it and aborts through abort_report_np, with nothing on stderr,
so the test's uncaught exception never reached libc++abi's terminate
message there, whichever linker made the program (ld-prime's aborts the
same way). An abort is what the uncaught exception comes to there; a
second program catches it inside the initializer, which the unwinding
reaches through the same frames on any version.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Since Rust 2021, a closure captures the place `*ctx` rather than the
variable `ctx`, so a closure that only reads `ctx` gets a shared
reference even if `ctx` is `&mut Context`. Rebinding `ctx` as `&Context`
before a parallel loop is therefore unnecessary. We keep it only where a
`move` closure captures `ctx` by value.
In a position-dependent executable, the address of an ifunc is that of
its .plt.got entry, so an indirect call to the function lands on the
entry. Like a canonical PLT entry, such an entry now starts with
endbr64 so that it works with IBT. This makes x86-64 .plt.got entries
16 bytes long instead of 8.
GCC 16 records -mbranch-protection in AArch64 build attributes
(.ARM.attributes) instead of .note.gnu.property, so an output lost the
BTI, PAC and GCS bits even if all its inputs were compiled with the
flag, and -z force-bti warned about every object file. We now read the
aeabi_feature_and_bits subsection and merge it into
GNU_PROPERTY_AARCH64_FEATURE_1_AND.
Fixes https://github.com/rui314/mold/issues/1725
PLT entries jump to the PLT header with an indirect branch, so the
header needs a landing pad if BTI is enabled. Like the x86-64 PLT
header, which starts with endbr64 whether or not IBT is enabled, we now
always write `bti c` to it. The header runs only when a symbol is
resolved lazily, so the extra instruction doesn't matter. PLT entries
are on the hot path and still get one only if BTI is enabled.
-z gcs-report-dynamic reports shared libraries that lack the GCS bit of
GNU_PROPERTY_AARCH64_FEATURE_1_AND in their .note.gnu.property. As in
GNU ld, it defaults to `none`, and -z gcs=always implies `warning`
independently of -z gcs-report. OpenEmbedded passes
-z gcs-report-dynamic=none when building systemd, whose link probes use
--fatal-warnings, so the unknown-option warning made configure fail.
A thunk jumps to its destination with an indirect branch, but the
compiler doesn't start a function that is only called directly with a
landing pad, and SYSVABI64 makes the linker responsible for that case.
If the output is BTI-enabled, we now create a landing pad consisting of
`bti c` and `b <function>` in the first thunk after such a function's
section, and other thunks jump to it instead.
Thunk::fixed_offsets is renamed upper_bound_offsets, and
ThunkLayout::entry_size max_entry_size, because ARM64 thunk entries vary
in size and the first pass only needs upper bounds of the offsets.
batch_size() and branch_distance() now name the PPC targets explicitly.
-z pac-plt turns on the PAC bit of the output's
GNU_PROPERTY_AARCH64_FEATURE_1_AND, makes PLT entries authenticate
.got.plt entries with autia1716, and sets DT_AARCH64_PAC_PLT.
-z gcs=implicit|never|always controls the GCS bit of the output's
GNU_PROPERTY_AARCH64_FEATURE_1_AND, and -z gcs-report diagnoses input
files lacking it. -z gcs=always implies -z gcs-report=warning.
-z force-bti turns on the BTI bit of the output's
GNU_PROPERTY_AARCH64_FEATURE_1_AND, and -z bti-report diagnoses input
files lacking it. As in lld, -z force-bti implies -z bti-report=warning.
CetReportKind is renamed ReportKind because -z bti-report shares it.
If the output is BTI-enabled, the PLT header and the PLT entries that
may be reached by an indirect branch start with `bti c`, which makes
PLT entries 24 bytes long, and DT_AARCH64_BTI_PLT is set. Since the PLT
size now depends on the merged property, we merge .note.gnu.property
before computing section sizes.
PLT_SIZE didn't mean the same thing on all targets. PPC64V1 PLT entries
are 8 or 12 bytes long depending on their indices, and SPARC64 switches
to a different entry format for large PLTs, so plt::entry_offset() had
special cases for them, and their PLT_SIZE was unused or described only
some entries. Each target now computes the offset of a PLT entry by
itself next to the code that writes the entries, and PLT_HDR_SIZE is no
longer needed outside the targets. The function takes the context
because an ARM64 PLT entry needs room for a BTI landing pad if BTI is
enabled.
We used to merge only x86 properties in .note.gnu.property, so an ARM64
output lost GNU_PROPERTY_AARCH64_FEATURE_1_AND even if all its inputs
were compiled with -mbranch-protection. We now AND-merge it as GNU ld
and lld do, and merge x86 properties only on x86.
extend() maps the grown output file again when the appended data does
not fit in the existing mapping. That happens mainly when the address
space was too tight to map the file with room to grow, yet the new
mapping was created while the old one still existed, so it needed
address space for both. We now release the old mapping first, as the
C++ version did. The data written through it stays in the file because
the mapping is shared.
Adjust the R_X86_64_PC32 relocation check to handle objdump output
where the relocation is printed on the same line as the associated
lea instruction.
This format is produced by the ALT Linux binutils 2.46 objdump.
The previous check only looked at the preceding line, causing the
arch-x86_64-emit-relocs-relax test to fail even though the
relocation was attached to the correct instruction.
Keep accepting relocations printed on the following line while
also accepting the inline format used by ALT Linux.
chained_fixups.rs had push16, push32 and push64, unwind_info.rs the
same as closures, and dtrace.rs appended its u16 fields by hand and
its u32 and u64 lists in helpers of its own. They are util's push16,
push32 and push64 now, beside its read and write helpers, and
dtrace's list helpers append with them. code_signature's push_be32
and push_be64, the only big-endian ones, stay its own.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
relocatable.rs built its LC_LINKER_OPTION commands itself
(linker_option_command). The load commands are chunks/mod.rs's
create_*_cmd builders, which -r already takes LC_BUILD_VERSION's from;
this one is create_linker_option_cmd there now.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Whether the stub helper is legacy LINKEDIT's, without a header and
with entries that enter dyld_stub_binding_helper, depends on the
options, not the target, but x86-64's write_stub_helper made the
choice and called a helper of its own. stub_helper::copy_buf chooses
now, between Target::write_stub_helper and a new
Target::write_legacy_stub_helper, which x86-64 implements with that
helper; no other target has legacy LINKEDIT.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
split_info's Places::sym placed a symbol with no section of its own at
its stub as Symbol::addr finds it, but looked the stubs up again, a
lazily loaded dylib's call helper and a delay-init stub before the
plain stub, the other way around from Symbol::addr; got_slot looked
up the GOT and __lazy_load_got slots as Symbol::got_addr does. Both
resolve through Symbol now: stub_entry, the chunk and address of the
stub, call helper, delay-init stub or selector stub that stands in
for a symbol, which addr returns the address of, and got_slot, the
chunk and address of the slot, which got_addr returns the address of.
The orders differ only for a dylib symbol with both a __stubs entry
and one of the others, which a lazily loaded dylib's never has (the
link refuses it) and a delay-init dylib's has only where the linker
makes the stub itself (_dlopen's, _objc_msgSend's for small selector
stubs, an entry point's); there the split info now records the stub
the calls go to.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
rebase_info::rebase_locations collected every pointer a loader slides,
the output sections', the selector references', the lazy pointers' and
the GOT's, and split_info's Places listed the references of the stubs,
the lazy pointers, the stub helper, the GOT, the thunks, the lazy-load
and delay-init helpers, the selector stubs and references, the
synthesized records and the rewritten method lists. mold's chunks
write their own dynamic relocations (got.rs's and output_section.rs's
write_dynrels and relr_offsets). Here each chunk's module lists its
own: output_section, objc_stubs, lazy_ptrs and got have
rebase_locations, and stubs, lazy_ptrs, stub_helper, got,
output_section (the thunks and the synthesized records), lazy_helpers,
delay_init, objc_stubs and objc_methlist have split_info_entries,
which rebase_locations and split_info::construct call; Places, Place,
Entry and push are the chunks' to use. Both lists are sorted, so
their order is the same. rebase_info's pointer_relocs, which the
fixup chunks use, is output_section's, as mold's
collect_abs_relocations is, and data_blob_fields, data_blob_pointers
and data_blob_binds are input_files.rs's, beside DataBlob.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
MergeableRecordSection, the LC_ATOM_INFO chunk of a -make_mergeable
dylib, and its copy_buf were make_mergeable's, the only chunk defined
outside chunks/. The chunk is chunks/mergeable_record.rs's now, as
mold's chunks/mod.rs holds the .gdb_index chunk; make_mergeable still
builds its contents, as mold's gdb_index.rs builds that chunk's.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
arch/mod.rs's load_helper looked a rewritten GOT load up in the sites
of the lazy-load helpers and then in those of the delay-init helpers.
Each chunk looks its own up now, lazy_helpers::load_helper and
delay_init::load_helper, and the targets ask one and then the other.
LazyUse and DelayUse stay apart: a lazy-load helper also stands in for
calls, which a delay-init helper never does (a delay-init stub does).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The tails of __objc_methname and __objc_selrefs - the selector names
and references of the selector stubs, and the method lists' extra
references - were written by output_section's copy_buf, though
objc_stubs holds them. objc_stubs writes them now (write_methnames and
write_selrefs), as objc_methlist writes the lists placed in an output
section. objc.rs's add_relative_method_list, which adds a rewritten
list to __objc_methlist, is objc_methlist's, and add_classref_stand_ins,
which makes the stand-ins for the GOT entries of the classes whose
__objc_classrefs slots stay, is got.rs's.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The symbol table's object_locals listed the local symbols a final
image keeps of an object, with keep_local_symbol and is_listed_out
deciding which, and relocatable.rs had a twin object_locals listing a
-r output's. mold's ObjectFile lists its own (populate_symtab, which
should_write_to_local_symtab filters), for a final link and -r alike.
ObjectFile::populate_symtab in input_files.rs lists them now for both:
a final image keeps the non-external symbols but the assembler
temporaries and those the -non_global_symbols lists filter out, each
with no flags and its value filled in when the table is written; a -r
output keeps its labels too, demotes the private externals it defines
and hides rather than drops what -x or the lists filter out, each
with its flags and its value. relocatable.rs's Local is LocalSymbol,
beside it; keep_local_symbol is should_write_to_local_symtab, and it
and is_listed_out are in input_files.rs, as is SymtabBlock, which
mold keeps there.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The symbol table's linker_locals built the local symbols of every
chunk the linker synthesizes: the selector stubs, the lazy-load
helpers and slots, the delay-init stubs and helpers, and the
range-extension thunks' entries. mold's chunks each produce their own
(chunks/mod.rs's populate_symtab calls those of the PLT, the GOT and
the output sections); here chunks::populate_symtab calls those of
objc_stubs, lazy_helpers, lazy_load_got, delay_init (one for each of
its two chunks) and output_section, whose populate_symtab lists the
section's branch islands, as mold's lists its thunks' symbols.
thunks::island_symbols, which names them and which the map lists too,
is output_section's now, of one section. linker_locals still lists
the locals on synthesized data first, then calls the chunks in the
order the table had them.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
check_weak_imports stood among the duplicate and weak-export checks,
ahead of claim_unresolved_symbols and report_undef_errors, which the
driver runs before it. It follows them and their helpers now, as mold's
passes.rs follows its driver.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The version sets (VersionSet and VERSION_2012_FALL and the like) decide
the defaults the options take for a deployment target, which the
passes ask through Args::targets and predates; they were in macho.rs,
with the file format. They are in cmdline.rs now, beside Args, as
mold's option defaults are. input_files::exported_classes, the classes
a re-exported library exports, which only the hook for the classes of
mergeable libraries asks, is bundle_hook.rs's, with is_class_export;
input_files.rs lends it read_tbd and read_dylib_binary.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Context still answered a few questions it has no part in: whether an
object is one LTO compiled (is_lto_obj), whether the link strips dead
code (strips_dead_code), and the image base the layout starts from
(image_base); and it defined BoundarySym, the boundary symbols'
record. mold's ObjectFile says what made it (origin), its driver
checks args.gc_sections itself, and the layout and synthetic symbols
are its passes'. An object made by LTO says so (lto_output, read by
ObjectFile::is_lto_obj), strips_dead_code is dead_strip.rs's, and
image_base, which only the layout asks, and BoundarySym are
passes.rs's, image_base beside mach_header_addr and BoundarySym
beside claim_boundary_symbols.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
init_function, the -init function if it is defined, check_tlv_template,
the check of thread-local data a rename moved out of the template, and
write_merged_bitcode, which only called lto::write_merged_bitcode, had
one caller each, as mold would have them written out: convert_init_offsets,
set_osec_offsets and the driver have them in place now, their comments
with them. mark_live_objects loaded the members defining the entry
point and -u symbols with a copy of load_owner, which it calls for
them now. check_initializers and its helpers come before
convert_init_offsets, which the driver runs after it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
dead_strip_dylibs ended with two diagnostics of its own: that an image
bound for the dyld shared cache links only libraries in it
(check_shared_cache_deps), and that a dynamic image loads a dylib at
all (check_libsystem_linked). They read what it leaves but are no part
of stripping, so the driver runs them right after it as passes of their
own, as mold's driver runs checks like check_shlib_undefined. They
follow dead_strip_dylibs in passes.rs too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Three helpers of the layout read the options alone, passed the whole
context: dyld_slides, whether dyld loads the image anywhere it likes;
seg_page_size, a segment's -seg_page_size or the page; and
follows_pinned_segment, whether -segment_order lists a segment after
one -segaddr pins. They are Args methods now, beside segaddr, as mold
keeps what the options decide in its Context's arg. dyld_slides is no
opposite of rebase_info::is_never_slid, which a -static image fails
too, so the two stay apart. is_pin_no_base, which place_segments
alone asked, is written out there.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Some helpers of passes.rs were about one chunk: finish_unwind_info
encodes __unwind_info again once every segment is placed, init_func
makes an __init_offsets entry of a symbol, resolve_entry took the
address LC_MAIN and LC_UNIXTHREAD name, and resolve_stub_binder finds
the helper legacy LINKEDIT's stub helper jumps to. They are
unwind_info::finish_unwind_info, InitFunc::new and
stub_helper::resolve_stub_binder, beside ensure_stub_binder, now, and
the entry point's address is chunks/mod.rs's entry_addr, as in mold,
which the load commands ask rather than a Context field that a pass
set after layout. An undefined entry point is still reported by a
pass, check_entry_point, where the driver ran resolve_entry.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The passes that resolve symbols looped over each object's symbols
themselves: race_definitions, claim_definitions and claim_locals, and
weak_def_losers and import_references, passed an object's index. mold
has the file resolve its own symbols (ObjectFile::resolve_symbols and
initialize_local_symbols), the passes only running the files in
parallel. The per-object loops are ObjectFile's race_definitions,
claim_definitions and initialize_local_symbols now, and
weak_def_losers and import_references ObjectFile methods; the passes
keep their parallel loops over the objects. claim_locals follows
mark_live_objects, after which resolve_symbols runs it.
The dylibs' side of resolution stays in passes.rs: it ranks the dylibs
of the link as a whole and claims symbols table-wide, from the
objects' ranks of the round, rather than reading a file. dylib_ranks,
which nothing else calls, is private.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
InputSection's label and name choose the symbol that names a
subsection, by input_files.rs's subsec_name_rank among the labels
is_private_label doesn't drop. Both are in input_sections.rs now,
beside msym_label_key, which label also uses; icf.rs, the map file
and the mergeable records take them from there.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Context's bind_ordinal took a dylib's index and its export_bind_ordinal
read -flat_namespace for an interposable export. The ordinal of an
import is DylibFile's bind_ordinal now, which is passed whether the
image is flat namespace, as sold's DylibFile carries its dylib_idx;
the export's is Symbol's export_bind_ordinal, beside the bind_ordinal
that picks between them. An import left to dynamic lookup has no
dylib, and Symbol::bind_ordinal gives it the flat lookup, as
binds_weak_lookup tells it apart; the symbol table, the export trie
and the chained fixups, which took the ordinal of a symbol's dylib by
its index, ask the symbol for it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
input_files.rs resolved a dylib's re-exported install names to files
(resolve_dylib_ref, find_dylib_ref and find_by_leaf, find_reexport
for the stubs prefetched ahead of the link, and loader_rpath and
dir_of for @loader_path), through reader.rs's Prober and under_root.
sold's input-files.cc has its find_external_lib, but mold looks for
libraries in reader.rs, as find_library; the reexports' search is
there now too, beside find_library and find_framework, and
find_reexport is private to it.
reader::warn_about_sections walked the staged objects to warn of
their sections; it is StagedObject's warn_about_sections now, which
load_pending calls for each object in input order.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The targets' read_relocs took the section a non-extern relocation
points into from arch/mod.rs's section_target. It is input_files.rs's
now, beside the StagedObject code that turns that section and offset
into a subsection, as sold's ObjectFile::find_subsection does both;
read_relocs needs nothing from it but the section headers it already
gets.
arch/mod.rs had the targets' little-endian read32, write32 and write64
and mergeable/mod.rs its own read16, read32 and read64, which took an
offset besides the slice. They are one set in util now, of the form
the targets used, which mold's read_ul32 and the like have too: the
integer at the start of a slice.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
What the link synthesizes into the internal object was added from
four places: Context's add_synthetic_section and add_data_word,
objc.rs's add_placed_isec and add_data_blob with the DataBlob and
DataField records they make, and delay_init's add_cstring. A data blob
is no Objective-C record alone: bundle_hook's class table, the lazy
loads' flags and __dyld_private are blobs too. mold's internal object
is an ObjectFile that ObjectFile::internal makes, in input_files.rs;
these are functions of input_files.rs beside it now. They add
subsections to the link's arena and blobs to its list, not to the
object alone, so they take the context rather than being the
object's methods. Their callers bind the section header they add to
a local first.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
mold has no output_sections.rs: the rules that name an input section's
output section and bin it there (output_name, output_section_key,
create_output_sections) are in passes.rs, as are the passes that order
and size the sections, and each chunk's own layout is in its module.
The section naming and assignment rules, the -order_file sorting, the
placement of the synthesized sections, the ordering of the sections
and segments and the checks on them move to passes.rs, as they are,
in the order the driver runs them. append_tail, which gives an output
section its tail, moves to chunks/output_section.rs; data_seg, the
segment the GOT, the lazy pointers and the image info take, to
chunks/mod.rs; trace_symbol_layout to mapfile.rs, beside the map whose
symbols it lists; and common_owners to symbol_moves.rs, its only user.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The pass that drops the FDEs of folded functions, gives the CIEs and
FDEs their offsets and sizes __eh_frame, and the warning about FDEs
beyond __unwind_info's reach, were output_sections.rs' lay_out_eh_frame
and warn_eh_frame_too_large. mold builds its .eh_frame in
chunks/eh_frame.rs' construct, beside the code that writes it; the
layout is construct there now, and the warning a helper it calls.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
mold has no one function that lays the output out: its driver bins the
input sections with create_output_sections, makes the linker's own
sections with create_synthetic_sections, and orders and sizes them with
sort_output_sections and compute_section_sizes, one pass at a time.
output_sections::create_output_sections ran all of that in one
function. It is split into those passes now, and the Mach-O-only steps
between them (sort_section_members, rename_synthetic_sections,
add_boundary_sections, create_segments, the order checks and so on)
into passes of their own, which the driver calls one by one in the
order they ran. The stubs, the GOT and the __LINKEDIT tables are added
by create_synthetic_sections, as mold adds its .got and .plt there and
sold its tables in create_synthetic_chunks. The driver finds the symbol
moves, which the input sections' assignment and the method lists'
layout both follow, and hands them to the two passes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Several functions did what one of mold's does under another name. They
take mold's now: relocatable::link is combine_objects, and its
assign_file_offsets, sort_sections and build_symtab are
set_osec_offsets, sort_output_sections and create_output_symtab, as in
mold's relocatable.rs; output_sections' sort_chunks is
sort_output_sections; thunks' needs_thunk is requires_thunk; icf's
is_candidate and edge_of are is_eligible and edge_target; the symbol
table's write_symtab is its copy_buf; copy_mach_header is
write_mach_header, as mold's write_ehdr; Target::apply_relocs is
apply_reloc_alloc; OutputFile's create and finish are open and close;
the chunks that build their contents (the dyld info streams, function
starts, data in code, split info, the relocations, the mergeable
record) do so in construct; the driver's print_statistics is passes'
show_stats; and an object's is_alive is is_reachable.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
InputSection read its bytes through data(), set its visited bit with
mark_visited and was marked dead with set_alive(false). mold's
InputSection has contents(), visit() and kill() for these; the
methods are named so here too, and the passes that drop a subsection
call kill(). set_alive stays for dead stripping, which sets the bit
to what its walk found.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The compact-unwind records and the __eh_frame CIEs and FDEs (Cie and
Fde) were defined in input_files.rs, which parses them. mold keeps
its CieRecord and FdeRecord in input_sections.rs, beside the sections
they describe, and sold its UnwindRecord with Relocation. UnwindRecord,
UNWIND_NONE and the CIE and FDE records are in input_sections.rs now,
the latter named CieRecord and FdeRecord as mold and sold name them,
and StagedObject::parse_eh_frame is parse_ehframe, mold's name for
the function that reads them. input_files.rs still parses them, and
CieRecord reads the DW_EH_PE_sdata4 encoding from there.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Context named a subsection in a diagnostic (subsec_label,
subsec_label_index, subsec_name and subsec_ref, with the free
label_key and msym_label_key), reported a relocation it can't apply
(fixup_error, target_has_address) and noted text relocations
(check_text_reloc, note_text_reloc), each passed the subsection's
index. mold's InputSection names itself (display, func_symbol) and
reports its own relocations (check_range, report_out_of_range,
record_undef_error). They are InputSection's label, label_index,
name, location, fixup_error, target_has_address and check_text_reloc
now, label_key a method of its own and msym_label_key beside it in
input_sections.rs. check_text_reloc still takes the subsection's
index, which the text relocations it notes are recorded by. The
targets' helpers that report a fixup error take the subsection rather
than its index.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A relocation's target - its symbol, the subsection it lives in, whether
it is thread-local data, its address and its name in a diagnostic -
came from Context (reloc_target_sym, reloc_target_isec,
reloc_target_is_tls, reloc_target_addr and reloc_target_name), passed
the index of the object the relocation is of. sold asks the relocation
(Relocation::sym, subsec, refers_to_tls and get_addr); here they are
Reloc::sym, subsec, refers_to_tls and addr, and target_name, passed the
object itself, as mold's InputSection::rels is, since a Reloc names its
symbol by its index in the object's symbol list. The callers that
looked a relocation's target up in a loop take the object once, the
DTrace site helpers of the targets included, and thunks.rs, which
sliced a subsection's relocations by hand, and the two places that
looked a header up by hand ask the subsection too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A subsection's output address, section ordinal, parent header and
relocations came from Context (isec_addr, isec_sect_idx, hdr_of,
isec_relocs) or, for callers holding a borrow of ctx.isecs, from
input_files::isec_relocs_of, and literal-merge redirects were followed
by Context::resolve_isec. mold asks the section itself:
InputSection::addr(ctx), InputSection::rels(file) and
InputSection::sh_type(file), the header coming through the object's
section list. They are InputSection::addr, sect_idx, hdr(file) and
rels(file) now, isec_relocs_of going with isec_relocs, and the
redirects are followed by the arena, InputSections::resolve, as sold
follows a subsection's replacer.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Symbol's flag setters repeated their getters' is_ (set_is_imported
for is_imported); mold pairs is_imported with set_imported, and so do
all of sym_flag!'s setters now (set_extern, set_weak_def,
set_private_extern and the rest; set_no_dead_strip already was).
SymbolTable::get, which looks a name up without interning it, is
lookup, mold's name for it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A symbol's address, its GOT slot's, its stub's, the pointer its stub
jumps through and where a branch to it goes were Context methods
taking the symbol's id (sym_addr, sym_got_addr, sym_stub_addr,
stub_ptr_addr, branch_target_addr). mold's are Symbol methods taking
the context: addr, got_addr, plt_addr and gotplt_addr. These are now
Symbol::addr, got_addr, stub_addr (mold's plt_addr, keeping the
Mach-O name of the section), stub_ptr_addr (its gotplt_addr) and
branch_target_addr. objc_msgsend_got_addr stays on Context, as it
names no symbol.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
What dyld does with a symbol - whether it is an import of a lazy or
delay-init dylib, a TLV descriptor, a weak definition dyld may
coalesce, an interposable export, whether dyld binds its slots and
pointers, by what ordinal, and whether a GOT load of it relaxes - was
asked of Context with the symbol's id. mold asks the symbol, with the
context: Symbol::is_local(ctx), is_pcrel_linktime_const(ctx) and the
like. These are Symbol methods taking the context now;
is_absolute_symbol is is_absolute, mold's name, sym_bind_ordinal is
bind_ordinal, and is_dtrace_pointer_target, which looks at the symbol
alone, takes no context. Where a caller has the symbol at hand, it
asks it directly, and the scans over the whole table iterate the
symbols.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The synthetic-slot side table was a Vec on Context, read through
Context::sym_aux, which handed back a shared all-absent record
(NONE_AUX) for the symbols without an entry, and written through
sym_aux_mut, or sym_aux_mut_in where another part of the context was
borrowed at the same time. mold keeps the table in its SymbolTable:
Symbol::aux(&symbols) returns the symbol's record if it has one, and
SymbolTable::aux_mut allocates it on first use; the getters got_idx,
plt_idx and the like return the indices as Options, has_plt and
has_got say whether they are there, and thunk_addr is a method of
Symbol. The table and its accessors are now those, the record is
SymbolAux, mold's name, and its stub_idx getter is mold's plt_idx;
writing the thunk addresses no longer needs the split borrow.
Where the code reads an index the symbol must have - the GOT slot of
a GOT load, the stub the lazy-load helpers call - it unwraps it, as
mold's got_addr and plt_addr do, where it took NO_IDX for one before.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An x86-64 dylib for macOS before 10.6 fixes itself up through legacy
LINKEDIT, which the shared cache's builder doesn't read, so ld-prime
refuses one that -add_split_seg_info or its install name makes cache
eligible ("Shared cache eligible dylibs must use bind opcodes or
chained fixups"). mold-macho went on and crashed writing its split
info, which indexed the stub helper's __dyld_private, a word legacy
LINKEDIT doesn't have. It now refuses the dylib as ld-prime does.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The objc stubs' entry size and the stub helper's header size moved to
their chunks while the sizing code still called them through Context;
now that the sizing is in the chunks too, the forwards go.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
passes.rs's copy_chunks copied the chunks and then wrote the rest of
the output itself: it reported the text relocations, wrote the fixups,
applied the target's optimization hints, wrote the symbol table, the
mach header, the UUID and the code signature, and queued each range of
the buffer to the output file as soon as it was final. In mold,
copy_chunks is in driver.rs and only copies, and the driver calls the
passes after it one by one (finish_output, rewrite_endbr,
write_build_id). copy_chunks is in driver.rs now and copies only, and
the driver runs the rest in the same order: report_text_relocs and
compute_uuid are pub passes of passes.rs, and the fixups, the symbol
table, the header and the signature are written by the chunk modules
as before. The driver queues each range at the same point as
copy_chunks did, so the file is still written while the rest of it is
produced.
compute_uuid keeps its name. It is mold's write_build_id, as its doc
comment now says, but everywhere else the Mach-O build ID is the UUID
(LC_UUID, -no_uuid, ctx.uuid), and the name says what it computes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The layout looked a __LINKEDIT table's alignment up in a table of its
own in passes.rs (chunk_p2align), each table's header saying 0. sold
sets each chunk's p2align in its constructor, the __LINKEDIT tables'
too, and its layout aligns each chunk to its header's. The tables' new
functions now set the alignment ld-prime gives them - 8 bytes for the
symbol and string tables, the export trie, the function starts, the
data-in-code entries, the split info, the mergeable record and the
external relocations, 4 for the indirect symbol table and 16 for the
code signature - and layout_segment reads the header's, as it does a
section's. The others keep the 0 of ChunkHeader::linkedit, which says
why ld-prime does not align them.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The layout in passes.rs encoded __unwind_info (encode_unwind_info),
found the chains __chain_starts lists (finish_chain_starts) and stored
the local and external relocations with the tables' sizes
(collect_relocations). As sold's chunks size and fill themselves in
their compute_size, chunks/unwind_info.rs encodes the section in its
compute_size, which layout_segment and finish_unwind_info call (the
name encode_unwind_info being the encoder's there),
chunks/chain_starts.rs finishes its starts, and the local_relocs and
extern_relocs builders store their records and size their tables,
called from build_linkedit_tables as the mergeable record's is.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
compute_section_sizes in output_sections.rs laid out each output
section's members itself. mold's compute_section_sizes calls
chunks/output_section.rs's layout for each section in parallel and
records the sizes; the per-section layout is now
chunks::output_section::layout here too. It returns the members'
offsets rather than writing them, as an InputSection's offset is no
atomic here, and compute_section_sizes still records them serially.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
output_sections.rs laid out __objc_methlist (lay_out_objc_method_lists)
and merged the objects' __objc_imageinfo records
(merge_objc_image_info), with merge_objc_info and objc_image_flags from
passes.rs. They are in chunks/objc_methlist.rs and
chunks/objc_imageinfo.rs now, the latter as create, after sold's
ObjcImageInfoSection::create, which makes the section from the
objects' records; passes.rs's check_objc_flags, which reports the
records that disagree, calls merge_objc_info there. The two flags they
test are in macho_consts.rs with the other Mach-O constants.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
add_stub_and_got_chunks sized the stubs, the stub helper, the lazy and
lazy-load pointers, the delay-init and lazy-load helpers and the GOT
itself, and create_output_sections and add_objc_stubs the
__init_offsets and __objc_stubs sections. In mold each chunk sizes
itself in its module's update_shdr (plt.rs's, got.rs's), as sold's
chunks do in compute_size. Each of these modules has an update_shdr
now, called where the sizing was; output_sections.rs still decides
which of them the output has. The GOT's places the stand-ins for the
__objc_classrefs slots that stay, which are the GOT's entries, and the
delay-init module has one for each of its two sections.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
-sdk_imports_api_list's ApiList and read_api_list were in mapfile.rs,
which writes the report the list limits, though the list is read as
the option is, its one caller in cmdline.rs. They are cmdline.rs's
now, as the other files an option names are read there.
The "$ld$..." directives of a library - LdSymbols, which reads them
for the link's target, with $ld$previous's PreviousDirective and
directive_version, interpret_ld_symbols, which applies them to a
stub's library, and the LdDirectives and MovedExport it leaves - were
in input_files.rs. As in sold, whose tapi.cc has interpret_ld_symbols,
they are tapi.rs's now. A dylib binary's directives are applied in
input_files.rs still, by interpret_binary_ld_symbols with the same
LdSymbols, since the binary is read there.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The export trie had uleb_len and write_uleb_at of its own and
input_files.rs a read_uleb_at, next to util's encode_uleb and
encode_sleb. util/mod.rs has them now, as mold's common/util.rs keeps
its LEB128 helpers together: read_uleb, mold's, which reads from a
slice it advances (the optimization hints and the CIE and FDE
augmentation data read so), and write_uleb and uleb_size, as sold's
common.h names them (mold's overwrite_uleb rewrites an encoding in
place at its old length, which the trie's zeroed buffer has none of).
A test checks that they agree with encode_uleb. dwarf.rs's Reader
keeps its own uleb and sleb, which return None for a number cut short
by its section's end, where the shared one panics: the reader skips
debug info it can't read.
make_mergeable's CityHash, libc++'s std::hash of a string, is
util::cityhash, next to util::siphash.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The Thunk struct was defined in chunks/output_section.rs, which also
wrote the thunks into the section, while thunks.rs creates them. As in
mold, whose thunks.rs has Thunk and the copy_buf that writes one,
thunks.rs has both now; output_section::copy_buf calls
thunks::copy_buf for the section's thunks.
The search for a thunk entry a branch reaches, thunks::
reachable_thunk_addr, is SymAux::thunk_addr, as mold's
Symbol::thunk_addr looks through the symbol's thunk_addrs. And the
x86-64 target's test of whether an import has no stub of any kind -
neither a __stubs entry nor a lazy-load call helper nor a delay-init
stub - is SymAux::has_stub, mold's Symbol::has_plt. sym_addr and split
info take each kind's address in turn, so they keep testing the
indices one by one.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Some questions about one value were free functions in the module of a
chunk that asks them, though other modules ask them too:
rebase_info::is_pointer_reloc, whether a relocation writes a pointer,
which the bind, weak-bind and chained-fixup builders and the external
relocations use as well; unwind_info's function_personality and
function_lsda, an unwind record's personality and LSDA found through
its FDE in DWARF mode, which ICF hashes; eh_frame::lsda_pos, where an
FDE's LSDA pointer is, which ICF, split info and mergeable records
read; and objc::objc_ref_addr, where an ObjcRef points.
As mold's Target::is_absrel says whether a relocation is a word-size
absolute one, is_pointer_reloc is Target::is_absrel, a provided method
taking the Reloc. function_personality and function_lsda are
UnwindRecord's methods next to personality() and lsda(), lsda_pos is
Fde's, and objc_ref_addr is ObjcRef::addr next to ObjcRef::import.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A relocatable output wrote its merged __objc_imageinfo record by hand,
the same version word and flags chunks/objc_imageinfo.rs's copy_buf
writes in a final image. It calls copy_buf now, so that the record has
one writer.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The context had chained_import_ordinal and msym_library_ordinal, how a
chained-fixups import and an undefined symbol's desc encode a library
ordinal, though each has one user: the chained fixups' import table
and the symbol table's import entries. They are private functions of
chunks/chained_fixups.rs and chunks/symtab.rs now, as mold keeps a
chunk's encodings in its module. Context::bind_ordinal, which both
build on and the bind streams use too, stays.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Where an entry of __stubs, __stub_helper, __delay_stubs or
__objc_stubs lies was worked out by hand wherever it was needed: in
the context's address helpers, the lazy pointers, the symbol table,
LC_SEGMENT_SPLIT_INFO and the targets' writers, arm64's stub helper
with its header's size spelled out. As mold's plt::entry_offset does
for .plt, each of those chunks' modules now has an entry_offset
(delay_init's, of its stubs, is stub_offset), next to the stub
helper's header_size and the objc stubs' entry_size, as mold has
gotplt::header_size and entry_size. The lazy pointers have a
slot_addr, as the GOT and the lazy-load GOT have, and the addresses of
the selector references and of the lazy-load and delay-init helpers
and stubs are their chunks' methods rather than the context's. The
context keeps sym_addr, sym_stub_addr and stub_ptr_addr on top of
them, and objc_stub_size and stub_helper_header_size as forwards that
output_sections.rs sizes the chunks with.
Split info took the lazy pointers to point E::STUB_HELPER_HEADER_SIZE
into __stub_helper, where the pointers hold header_size(), which is 0
in legacy LINKEDIT. No image tells them apart: ld-prime refuses split
info in legacy LINKEDIT, whose fixups are no bind opcodes, and
mold-macho's split info of a legacy LINKEDIT image with lazy pointers
never gets past __stub_helper's header (it panics on the
__dyld_private it has none of). Both use entry_offset now.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
mold keeps its shell tests, their harness crate, common.inc and its
helper scripts directly in tests/; mold-macho had them one level down,
in tests/macho/. They are in tests/ now, and the workspace, the cli
crate's integration harness and common.inc's default linker path
follow.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
find_subsec and find_symbol_subsec were free functions of
input_files.rs that took an object's subsections as a slice, which the
chunks and make_mergeable passed &obj.subsecs for. sold asks the object
(ObjectFile::find_subsection). They are methods now, of ObjectFile and
of StagedObject, whose parsing looks its own subsections up the same
way, as symbol_ranges! gives both types local_range and global_range.
symbol_subsec_before stays a function of its own: find_symbol_subsec
and StagedObject::find_symbol_subsecs, which searches precomputed
addresses, both call it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
lazy_load.rs, delay_init.rs and chunks/stub_helper.rs called passes.rs
to synthesize a zero word in __DATA,__data (add_data_word) and to bind
a symbol the linker's own code calls (bind_linker_import). In mold no
chunk reaches into passes.rs for a helper. Both synthesize what the
linker's own code needs, a word of the internal object or an import,
as Context::add_synthetic_section adds a section to that object, and
they are Context methods next to it now.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
objc.rs imported redirect_symbols_to_replacements from passes.rs to
point the symbols of the Objective-C records it merges at the copies
that stay, as merge_literals does for literals. mold reattaches the
symbols of a merged section to its fragments in input_files.rs
(ObjectFile::reattach_section_symbols), and the function is there now,
after the unwind records' upkeep, which also follows subsections that
passes removed.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
remove_unreachable_files and dead strip's sweep each dropped the
unwind records and FDEs of dead subsections with the same code, which
remapped the records' links to the FDEs that stayed and then had
passes.rs's refresh_unwind_ranges rebuild each subsection's range of
records. The two share remove_dead_unwind_info now, and it and
refresh_unwind_ranges are in input_files.rs, where the records and
FDEs are parsed, next to StagedObject::set_unwind_ranges, which gives
the subsections their ranges in the first place. lay_out_eh_frame's
compaction stays its own: it drops the FDEs of folded copies rather
than of dead subsections, and with them only the records that point
at those FDEs.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
How the link reads an input section was decided in three files:
input_files.rs held most of the predicates on a section header, but
passes.rs had is_swift_reflection_section and is_mergeable_literal,
and output_sections.rs canonical_section_flags, which only parsing an
object applies, and standard_section_flags, its table. mold judges an
input section as it reads it, in input_files.rs (is_debug_section,
is_known_section_type, ObjectFile::initialize_sections), and these are
there now, next to is_unknown_ld_section and has_merged_subsecs.
output_sections.rs, which still reads the table for the output
sections' flags, and symbol_moves.rs import them.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
passes.rs ranked an object's definitions and claimed the symbols the
winners define with free functions of its own, definition_rank,
weak_definition_rank and claim_definition, next to SymbolSlots, the
view of the symbol table the parallel claims write through. mold ranks
a definition in input_files.rs (symbol_rank_from_fields), next to its
SymbolEditor, and claims the symbol in ObjectFile::resolve_symbols, as
sold's ObjectFile does with get_rank. The three are ObjectFile's
methods now, at the end of input_files.rs with SymbolSlots, while
resolve_symbols' passes stay in passes.rs. SymbolSlots keeps its name:
it trusts its callers to write disjoint symbols where mold's
SymbolEditor locks each one. Whether a dylib's exports moved away is a
question of the dylib, DylibFile::exports_moved_away.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
mold's ElfSym answers is_undef() and is_weak() itself, and sold's
MachSym is_undef() and common_p2align, while passes.rs spelled the
tests out on a MachSym's fields: is_weak_def as a function of its own,
and the reference test and a tentative definition's alignment where
they were needed. They are MachSym's is_weak_def(), is_undef() and
common_p2align() now. is_undef() keeps what the tests it replaces
asked, so a tentative definition, N_UNDF too, counts as one.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
passes.rs drove libLTO itself - it picked the symbols that must
survive (lto_roots), created the code generator, ran ThinLTO and the
merged compilation and named the objects they made - and
input_files.rs read bitcode files into placeholder objects. In mold,
passes::do_lto only calls lto::run_plugin and resolves symbols again,
and lto.rs reads an IR object too (read_lto_object, which reader.rs
calls). That code is in lto.rs now: run_plugin compiles the live
bitcode modules and hands passes::do_lto the objects to put in place
of the bitcode files, and read_lto_object is what parse_bitcode was.
The driver's passes::write_merged_bitcode, for a -r link of bitcode
alone, calls lto.rs's; links_only_bitcode and has_lto_obj stay
passes, as has_lto_obj is in mold.
command_line_symbols, which reads only the command line, is
Args::command_line_symbols. dead_strip::exported_before_lto, which
only lto_roots uses, moved with it; native_refs_before_lto stays in
dead_strip.rs, as it walks the subsections with dead stripping's own
private machinery.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The architectures of thin and universal files (arch_name, takes_arch,
foreign_arch, is_subtype_mismatch, fat_arches, fat_arch_names,
fat_slice, split_fat_arch and without_fat_arch) were worked out in
input_files.rs. mold's filetype.rs has get_machine_type and
get_elf_target, and mold-macho's already has get_macho_target, so they
are in filetype.rs now.
mold detects the machine type in reader.rs (detect_machine_type), for
its driver to call; mold-macho's option parser read the inputs itself,
in cmdline.rs's detect_target, open_for_target and infer_platform. They
are in reader.rs now, detect_target as detect_machine_type. The parser
still calls them from resolve_target, as the defaults of many options
depend on the target and the platform they settle.
The library and framework search paths go the other way, from reader.rs
to cmdline.rs (set_search_paths, search_dirs, push_search_dir and the
STANDARD_*_DIRS): mold finalizes library_paths in cmdline.rs, and sold
adds the search paths in cmdline.cc (add_search_path). unreadable_file,
the words for a file MappedFile can't open, is in mapped_file.rs, where
mold's must_open is; is_new_os is in cmdline.rs, with all its callers,
rather than in macho.rs; and reader::collect_file is read_file, mold's
name for it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
report_undef_errors both turned the symbols -undefined dynamic_lookup
or -U lets dyld look up into imports and reported the rest. mold has
the two jobs in claim_unresolved_symbols and report_undef_errors, and
sold has a claim_unresolved_symbols too. They are separate passes now,
still run where the one was: ld-prime reports undefined symbols before
duplicate definitions, which it then leaves unreported.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
mold keeps its Target trait and one file per instruction set in
elf/arch/; mold-macho called the same directory macho/target/. It is
macho/arch/ now, and the module crate::arch.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
scan_relocations' helpers stayed in passes.rs as functions of their
own: scan_branch, scan_got_load and check_tlv, which each target's
scan called, create_slots, and add_stub with ensure_stub_binder. mold
writes what a relocation needs inline in each target's scan, and turns
the flags into slots in scan_relocations' own loop, as the targets and
scan_relocations now do. A stub and what it jumps through are made by
chunks::stubs::add_symbol, the stub helper's dyld_stub_binder by
chunks::stub_helper, and is_thread_local_sym is Context::is_tlv, as
sold's Symbol::is_tlv.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A target classified each relocation type into a RelocClass (a branch,
a GOT reference, a GOT load, a TLV load, or a plain one) for the
passes to match on, a classification mold has no counterpart of. As in
mold, which has a target's R_FUNCALL and a relocation's
is_func_call(), a target now names its branch relocation type
(RELOC_BRANCH, which Reloc::is_func_call tests) and the types that load
through a GOT slot (RELOC_GOT_LOADS), and the passes that look for a
pointer in data test for RELOC_UNSIGNED, the only plain relocation
type that writes one.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
passes.rs kept add_got and the allocation of a stub. As mold's
got::add_got_symbol and plt::add_symbol do, chunks::got and
chunks::stubs now hand out the slots and stubs. passes::add_stub
remains as what decides what a stub jumps through: its lazy pointer, or
its GOT slot.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
mold-macho named its symbol table and relocation records' fields as
Apple's headers do: NList with n_strx, n_sect, n_desc and n_value, and
MachRel with r_address and the r_symbolnum, r_length and r_type
accessors. sold gave them plain names instead, which read better and
which this port follows elsewhere. NList is now MachSym, with stroff,
sect, desc and value (the raw n_type byte keeps its name, and its
N_TYPE bits are ty()), and MachRel's are offset, idx(), sect(),
p2size() and ty(). Reloc::r_type is ty, an object's nlists are its
mach_syms, a chunk's section ordinal is sect_idx as in sold, and the
variables, helpers and comments follow. A bad relocation's diagnostic
now prints type=, p2size=, pcrel= and extern=. Only the structs'
comments keep Apple's names, nlist_64 and relocation_info.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
scan_relocations classified relocations on all cores, but then decided
serially, relocation by relocation, which symbols take a stub or a GOT
slot, and gave each its slot as it first met it. The passes after it
(the entry point's stub, the unwind personalities, the objc stubs'
_objc_msgSend, the folded class references) added slots of their own,
so the slots' order followed the order of the passes as well.
As in mold and sold, each target now scans its relocations by type and
sets NEEDS_GOT and NEEDS_STUB on the symbols in parallel, the passes
that need slots set the flags before the scan, and scan_relocations
turns the flags into slots once, in the order of the files that own the
symbols: each object's in its symbol table's order, then each dylib's
by name, the order in which a dylib lists its exports (and ld-prime's
order of the stubs the tests check).
fold_objc_classrefs runs before the scan now, which sees the class
loads it rewrites as GOT loads; the stand-ins of the slots that stay
take their offsets when the GOT is laid out. x86-64's one-byte
branches and kexts' direct calls are decided in the x86-64 target. A
thread-local referred to as regular data, or the reverse, is an error
for each reference rather than a fatal error at the first. Symbol's
boolean attributes are `bits` next to the new `flags`, as in mold.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The ELF test scripts and their harness now live in tests/elf/, and the
harness crate is renamed mold-elf-tests, as mold-macho keeps its own in
tests/macho/ as mold-macho-tests. This prepares for merging the two
repositories.
Twenty-three tests walked load commands, section headers or the
symbol table in Python of their own, or found a section by parsing
otool -l, before the few lines that check or patch what they are
about. They now get those from macho.py, and symtab-hidden-weak's two
copies of one check are a function. macho.py's 0x3a is
LC_LAZY_LOAD_DYLIB_INFO, which it had misnamed.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
common.inc keeps the tests' Python from writing bytecode next to
macho.py, but running unwind-lookup.py or dof-dump.py by hand writes
tests/macho/__pycache__.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
common.inc's unwind_lookup was a Python program in a shell heredoc,
which found __unwind_info and the symbols by parsing otool -l and nm.
It is now tests/macho/unwind-lookup.py, which reads them with
macho.py, as dof-dump.py now does too. Both print what they did, byte
for byte: the lookups of 3000 functions' symbols in arm64 and x86-64
images, and the DOF of both.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Nine tests carried their own copy of a Python snippet that walks an
object's load commands to rewrite a section's flags or alignment, and
common.inc another to rewrite LC_BUILD_VERSION. tests/macho/macho.py
is a small reader and patcher of Mach-O files that does these as
commands, behind common.inc's set_section_flags, set_section_align and
mark_firmware, and that the tests' other Python can import, as
common.inc puts its directory on PYTHONPATH. A rewrite of a section
the file lacks now fails instead of doing nothing.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
clang calls methods through _objc_msgSend$<selector> stubs on arm64
by default, so its object has no __objc_selrefs: the linker makes the
stubs and their selector references. The test's rewrite of that
section's type silently did nothing there, and only x86-64 checked
that a __objc_selrefs typed as C strings is read as selector
references. The object is now built with
-fno-objc-msgsend-selector-stubs, which keeps __objc_selrefs.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The iOS simulator's debuginfo test timed out on the hosted runners
although its lldb session got through all its commands. That session
takes ten seconds on a Mac: lldb waits five seconds for a simulator's
process to end, whether killed, detached or left to exit. On the
runners the run's first lldb session ran past the harness's 60 seconds.
As mold's CI gives its slow configurations a longer --timeout, ours
now passes 120, running the unit tests in a step of their own, as
libtest's binaries reject the option.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
dtrace.inc held two helpers of the dtrace tests, dtrace_header and
dof_dump, now that the DOF dumper is a Python script of its own. They
are in common.inc with the other helpers, and the tests no longer
source a file of their own.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
dtrace.inc's dof_dump was a Python program in a shell heredoc. It is
now tests/macho/dof-dump.py, which dof_dump runs; its output is the
same, byte for byte, for arm64 and x86-64 images, bad sites included.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
mold's CI workflow is .github/workflows/ci.yml, named CI, as ours was.
Ours is now macho.yml, named Mach-O CI, so it can be added to mold's
workflows as it is.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
mold's tests/ holds its ELF tests and their harness, and 26 of our
scripts, common.inc, lib.rs and Cargo.toml have the same names as
files there. Our tests and their harness crate now live in
tests/macho/, where the ELF harness, which runs the scripts directly
in tests/, doesn't look, so the two suites can sit side by side.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The output is built in a buffer of its whole size, which was allocated
with the size cast to usize, so on a 32-bit host an output of 4 GiB or
more got a buffer cut to the size modulo 4 GiB. No Rust object can be
larger than isize::MAX bytes, so as mold does (mold 99173586), an
image or a -r output of 2 GiB or more is a fatal error on such a host,
reported before the output file is created.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A release build aborts on a panic, skipping the cleanup that a fatal
error or a SIGSEGV/SIGBUS does, and since our output is written in
place from the moment it is created, a panic while copying left a
partial file at the output path. A panic hook now removes it before
the default hook reports the panic, as in mold (mold a0d62d97). No
input is known to panic after the file exists, so a panic injected
into copy_chunks showed it: a release build left the file without the
hook, and none with it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The parent of the forked linker waits for the child once its pipe
closes, and read the untouched zero status as success when waitpid
failed, as it does when an ignored SIGCHLD, inherited across exec, got
the child reaped first. macOS reaps such a child only if it exits
before the parent waits, and the parent waits from the pipe's close,
so none of 300 tries here lost the status; it is a failure now all the
same, as in mold (mold 25d2a3ec).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
mold's workspace crates are published together at version 3.0.0, each
requiring the exact version of the others, as they depend on each
other's internals, and declare Rust 1.95 as their minimum (mold
37c07568, 6bdd6379). Ours now do the same, describe themselves as
mold's crates do, and inherit rust-version and repository from the
workspace, so their manifests fit mold's workspace as they are.
mold -v says mold-macho 3.0.0.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A crates.io package cannot depend on a git repository, so mold takes
its mimalloc_rust fork from the fork's published crate, mold-mimalloc
(mold 37c07568), instead of a pinned git revision. We do the same, as
the executable will be mold's.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
i386 aligns 64-bit integers to 4 bytes, so a Reloc, whose addend is
an i64, is 20 bytes there, and the assertion that it is exactly 24
failed to compile on i686, where mold's CI checks the workspace build.
As mold does (mold f9828086), assert an upper bound instead, which
still catches growth on 64-bit hosts.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
std has no AtomicU64 on hosts such as 32-bit PowerPC, where mold's CI
checks the workspace build, so ICF's digests and symbol resolution's
definition ranks did not compile there. As mold does (mold 0ea3c18b),
take AtomicU64 from portable-atomic, which uses the std type where it
exists and falls back to locks elsewhere.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
mold names its ELF target crates after the file format, mold-elf-*,
so that ours can be mold-macho-*, and names each one's directory after
the crate without the mold- prefix (mold adff957a). Ours were
mold-macho-target-* in arch/arm64 and arch/x86_64; they are now
mold-macho-arm64 and mold-macho-x86_64 in arch/macho-arm64 and
arch/macho-x86_64.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
clang -faddrsig (and rustc's -addrsig, which mold's release build
sets) adds __DATA,__llvm_addrsig, an address-significance table for a
linker's safe ICF: eight placeholder bytes with an UNSIGNED relocation
into them per symbol whose address is significant. We copied it as
data, and with two relocations or more the fixups of its one pointer
collided into a value no rebase encodes, so the link failed where
ld-prime's succeeds. An image now drops it; -r output keeps it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
macho/c/bundle-hook.c.sha256 recorded the hash of bundle-hook.c that
the prebuilt objects were made from, and a unit test compared it with
the source's, to catch an edit to the source that forgot to rebuild
the objects. It plays no part in a link, so it goes, with the test
and the line of build-bundle-hook.sh that wrote it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
mold moved its ELF linker to elf/ and its C sources to elf/c/ to make
room for this one in macho/ (mold 2505c77b). This is the same move on
our side: src/ and c/ are now macho/ and macho/c/, macho/Cargo.toml
defines the mold-macho package, and the root Cargo.toml only defines
the workspace.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ICF used to leave address-taken sections alone, but all it needs is to
keep two such sections from being merged with each other. Sections
whose addresses don't matter can be merged into one of them.
Address-taken sections are now ICF vertices, and an address-taken
section becomes the leader of its class if there is one. A reference to
an address-taken section is still hashed by the section's identity,
because the section stays even if it isn't the leader. A reference from
a section to itself remains an edge.
ICF hashed a reference to an absolute or linker-synthesized symbol by
the symbol's value alone. Symbols defined by the linker, such as
--defsym'd ones, don't get their values until after ICF, so two
functions referring to different such symbols were merged.
Hash such a symbol's identity as well. Absolute symbols in input files
keep being compared by value, which matters for the null symbol that
R_RISCV_RELAX and similar relocations refer to in every file.
simctl boot returns as soon as a device starts booting. A new device's
first boot migrates its data and starts its system apps, keeping every
core here busy for a minute, and the CI runners create a fresh device
each run, so the first three tests of the iOS simulator stalled past
their timeout in plain compiles and links. The runner now boots with
simctl bootstatus -b, which returns once the device has finished
booting (9 seconds for a device booted before, 23 for a new one here).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
lldb starts a simulator's program on a device of its own choosing
(an iPhone 17e among iOS 27's here), booting it beside the one the
runner booted: a second whole OS, whose boot outlasted the 60-second
timeout on the CI runners, where both lldb tests of the iOS simulator
failed so. A simulator's scripts now give lldb -O 'platform select
ios-simulator' and -O 'platform connect <udid>', which run before the
program loads; once a target exists it keeps the platform it made.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Xcode 26's dyld_info -fixups (dyld-1267), which the CI runners have,
misreads classic bind opcodes that bind one symbol with two addends:
after the SET_ADDEND_SLEB, each bind is shown with the next target in
its list, the last with none, and some programs crash it. It reads
ld-prime's output the same way, and dyld binds both right, so the
test decodes the binds with objdump --macho --bind, alike in both
Xcodes. Xcode 27's dyld_info reads them right.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Without MH_SUBSECTIONS_VIA_SYMBOLS each section of an object is one
subsection. ld-prime makes the symbol that names it, one at the
section's start, non-weak (a .weak_def_can_be_hidden one also hidden),
so two such copies collide. mold keeps STB_WEAK as written, and so do
we now: the copies coalesce, a strong definition overrides one, and an
image exports it weak. A losing copy's section that holds other symbols
stays, as before.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The tentative definitions (common symbols) of one name merge into one.
mold takes the largest size, the greatest alignment and the most
restrictive visibility of all copies; ld-prime takes the largest copy
whole, alignment and visibility too, and the first of equal sizes. Its
way lets `.comm _v,16,2` drop the 32-byte alignment that the code of
`.comm _v,8,5` may need, and makes visibility hang on input order, so
we merge as mold does again.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A live tentative definition (a common symbol) loads an archive member
that defines the name if the member's definition wins. mold ranks a
tentative definition below every real one, code or data, and a member's
own one below a live one, so it loads nothing for that. ld-prime loads
only a member with data and indexes members' tentative definitions, for
which we ran resolution again. mold settles it in one round; a dylib
still yields to a live one but under -commons use_dylibs.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An arm64 b/bl reaches 128 MiB and an adrp 4 GiB. ld-prime decides before
layout which segments -segaddr puts 4 GiB apart, taking an unpinned one
to be a page past the base, and sends a branch between them through a
stub jumping through a rebased __got slot, keeping GOT loads unrelaxed.
mold has no such shim: a branch out of reach takes a range extension
thunk, and a reference no thunk's adrp reaches is an error. This linker
now does the same, judging by the addresses the layout gives.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Fifteen scripts run checks of mold's own messages only when `$mold -v`
says mold-macho, which they read from stdout; since 085d250 printed the
banner on stderr the probe failed under mold too, and those checks
never ran. They read both streams now. The one check that then failed,
of a function in a __DATA section marked pure_instructions, used
__DATA,__data, a name whose attributes the assembler drops: the input
was not code, so mold's warning was right; it takes a name of its own.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
a9c2e98 took up ld-prime's report, "duplicate symbol '<name>' in:"
followed by the defining files and "<N> duplicate symbols", although
mold-macho keeps mold's diagnostics. Each definition that lost to the
first now gets mold's error again, "duplicate symbol: <its file>: <the
winner's file>: <name>", as undefined symbols get theirs. What is an
error stays ld-prime's: under -dead_strip only a live kept definition,
and -allow_dead_duplicates excuses one whose rivals are all dead.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
LTO's code generator may call routines no input called, such as memset
for a loop filling an array. ld-prime "softloads" eight of them in a
-static or -preload image or with -lto_softload_runtime_symbols, loading
the archive members that define them before LTO, called or not. mold
has no such list: the resolution after LTO loads the members the code
needs, as it now does here. A routine only a bitcode member defines
needs -u, as under ld-prime's -no_lto_softload_runtime_symbols.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A dylib the link may drop stays only if something binds to it. ld-prime
resolves bitcode's references before LTO and keeps every dylib they
reached, even where the code LTO compiled no longer calls into it; mold
resolves again after LTO and keeps the as-needed DSOs the compiled code
uses. Now the bitcode modules' imports are not bound after LTO, so a
droppable dylib (any under -dead_strip_dylibs, an auto-linked one
always) that only a function LTO removed called goes, as in mold.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A symbol no live file defines goes to a lazy archive member or a dylib
export. mold ranks a strong one of those before every weak one and lets
input order break only ties; we ranked them by order alone, ignoring a
dylib's weak exports. ld-prime loads the first member defining the name
even if weak, yet ranks a dylib's weak export after any member's, so it
is neither. Strong now wins as in mold, so a weak fallback first in an
archive or in a dylib (libc++'s operator new) can't hide a strong one.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Deduplication decides that two functions are one by a digest of their
bytes, relocations and unwind records. mold hashes the CIE and FDEs in;
ld-prime only the personality and LSDA, so it folds a function with
unwind information into one without, or one compact encoding into
another, and an exception through a caller of a folded copy is unwound
by the survivor's rules. The digest now takes each record's offset,
length, encoding and DWARF CFI (address fields zeroed), as mold's does.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A binary built on a newer Alpine may use functions that an older musl
or libgcc doesn't have, just as a binary built against a newer glibc
may not run on an older one. The musl builds now use Alpine 3.21, the
oldest release still supported, instead of 3.20, which is past its end
of life.
The header comment now also describes the musl tarballs' file names
and why they are not reproducible: Alpine keeps only the latest version
of each package.
common/ is the new mold-common crate, which the Mach-O linker will share
with the ELF linker. It has error reporting, input file mapping, archive
reading, output files, process management and utilities such as glob
matching and parallel algorithms. Items that the ELF linker uses are now
public.
This prepares subprocess.rs and output_file.rs for moving to a crate that
the Mach-O linker will share. -run embeds the mold-wrapper.so that
elf/build.rs builds, so it stays with the ELF linker in run.rs.
OutputFile::open now takes a path, and open_output_file in driver.rs
applies --chroot and --filler.
The crates.io name libmold is kept for a library for in-process
linking, which has to be a crate above the per-target crates that
instantiate the linker. The internal crates are named after the file
format they handle, so that the Mach-O linker's crates can be
mold-macho and mold-macho-*. Each target crate's directory is named
after the crate, as in arch/elf-x86_64/ for mold-elf-x86_64.
This makes room for the Mach-O linker from
https://github.com/rui314/mold-macho, which will be in macho/. The
root Cargo.toml now only defines the workspace. build.rs looks for .git
in the parent directory, as the crate is no longer at the root of the
source tree.
A dylib symbol is a weak import, bound with the weak-import flag and
marked N_WEAK_REF, when every reference to it is weak; one strong
reference makes it strong. ld-prime counts the references of the files
it links. mold resolved over every archive member first, loaded or not,
and kept the strong mark of a member the link left out through the
final round, so a weak import such as SKAdNetwork's turned strong.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
-ObjC makes the linker load every archive member with Objective-C or
Swift metadata, since such a member registers classes without being
referenced. ld-prime applies it only to archives the command line
names: one that only an auto-link option or -possible-l names, such as
a static framework a Swift import auto-links, contributes the members
its symbols are needed from, while -all_load still loads it whole. mold
loaded the ObjC members of every archive, adding unreferenced classes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An archive member the link does not load still has its symbols in the
symbol table, but its sections are dead and its unwind records gone. A
no-dead-strip symbol it defines, such as an __attribute__((used))
function, made its subsection a dead-strip root anyway: the walk then
kept alive what that code references, and read the unwind records of
a dead subsection past the end of the compacted list and panicked.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
cargo test now adds the arm64 iOS simulator to macOS's configurations
when its runtime is installed (silently not, else); --all adds x86_64
iOS (iOS 17's runtime, under Rosetta), tvOS and visionOS. macOS's run
first, then each simulator's alone, its device booted for it and shut
down after, the run holding the user's one simulator slot (a directory
naming its pid): a simulator is a whole OS, and three at once got their
system apps killed by the launch watchdog.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime deprecates -flat_namespace on every platform but macOS, so an
OS dylib that -no_shared_cache_eligible keeps out of the shared cache
still draws a warning in a simulator: that it is deprecated on the
platform (iOS-simulator, ...). No warning of the cache's restrictions
comes with it, which is what the test checks.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
clang gives an x86_64 macOS object an FDE for every function, but an
x86_64 simulator's object one only for a function compact unwind can't
describe, -femit-dwarf-unwind=always or not, so the tests' checks of
x86_64 __eh_frame handling run on macOS alone. There both linkers say
-stack_size must be <= 512MB "on x86_64 platforms", and on x86_64
LC_VERSION_MIN_IPHONEOS names the iOS simulator, not the device.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The x86_64 simulator runs only on iOS 17's runtime, so its programs are
built for iOS 17, older than the releases some tested features came
with: delay-init (iOS 18, as macOS 15), lazy dylibs (27), class
references in __DATA_CONST (17.4) or folded into the GOT (18), and
__interpose in __DATA_CONST (18). common.inc's simulator_older_than
<version> compares the triple's version, and such a test skips there.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A dylib installed in /usr/lib or /System/Library is bound for the dyld
shared cache, and on macOS so is one under /Library/Apple/usr/lib or
/Library/Apple/System/Library, which macOS's cache takes too. For iOS,
its simulator, tvOS's, visionOS's and firmware, ld-prime gives such a
dylib no split info and refuses it as a dependency of a cache-bound one
(measured), where mold took /Library/Apple as the cache's everywhere.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
-warn_unused_dylibs, on by default for a dylib bound for the shared
cache, lets off libSystem, libc++ and Foundation, which drivers and
templates link by habit. ld-prime matches the install name's start:
/usr/lib/libSystem, /usr/lib/libc++. and Foundation.framework/ under
/System/Library/Frameworks (silent for libSystemX.dylib, libc++.2.dylib
and Foundation.framework/Bar, not libc++abi.dylib); mold compared whole
macOS names, so it warned of a simulator's shallow Foundation.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
From macOS 15 on, a class reference slot whose address code takes
stays, as its class's GOT entry. -make_mergeable recorded the slot as a
class reference entry the code's adrp and add (or leaq) pointed at,
which ld-prime never writes: it records references to the GOT entry
(ARM64_ADRP_ADD_GOT for the adrp and add as one, X86_64_RIP_GOT, and
PTR64_TO_GOT for a pointer), with no entry for the slot. Xcode 26's
ld-prime merged mold's arm64 record wrong, the adrp and add pointing at
the start of __DATA. Merging, mold read ld-prime's ARM64_ADRP_ADD_GOT as
a GOT load, which it relaxes to the class's own address, and its
PTR64_TO_GOT as a 64-bit pointer to a GOT entry, which no linker takes;
it now makes both of the class reference slot the objects had.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
LC_LAZY_LOAD_DYLIB asks dyld to map a library only when the image first
calls into it. Firmware has no dyld, and ld-prime counts a -preload
image, which none loads either, as firmware's: there a library that
-lazy-l, -lazy_library or -lazy_framework names links as -l or
-framework would, as it already did in mold, and ld-prime warns of each
that it changes it to a regular link. mold now warns too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An executable dyld loads names its entry point in LC_MAIN, and dyld
calls it as main(argc, argv, ...) once it has set up the process.
LC_UNIXTHREAD images instead start at crt1.o's "start", which build
scripts written for them still pass with -e. ld-prime ignores -e start
(the last -e given) with a warning where the image starts from LC_MAIN
and keeps _main; mold made start the LC_MAIN entry point, or failed
when nothing defined it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A simulator configuration builds every test object for its triple with
the simulator SDK, $SDK, and runs programs with $RUN. The tests of
per-platform rules that pick their platforms themselves (device
encryption, the iOS minimum, the deprecations, platformless objects,
simulator header padding and MH_SIM_SUPPORT dylibs) skip there; the
__objc_imageinfo tests link the configuration's own objects, whose
image info has the simulator bit, and check that the output drops it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
__objc_imageinfo tells the Objective-C runtime what an image's code is:
its Swift versions, category class properties (0x40) and signed
class_ro_t pointers (0x10). Its other bits are clang's simulator mark
(0x20), the old garbage collector's, and dyld's optimizer's own (0x08,
0x80). ld-prime keeps only the former, a lone object's too, and ANDs
0x10 over the objects with classes, warning of a mismatch; mold wrote a
lone object's flags as they were and dropped 0x10 from merged ones.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A simulator's processes run on the Mac, against the simulator SDK's
libraries, but dyld also loads into one a macOS dylib whose header has
MH_SIM_SUPPORT, which -simulator_support sets. ld-prime counts such a
dylib as built for macOS and every simulator, at no version of theirs,
so a simulator link takes it; a device link still refuses it, naming
those platforms. mold refused it in every link but a macOS one.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Where only a part of a test depends on a macOS version, that part runs
on macOS alone and the rest in a simulator too, which ld-prime passes
there: a simulator's SDK keeps its frameworks in shallow bundles and
inlines libSystem's libraries, its shared cache has no /Library/Apple,
ld-prime deprecates -undefined dynamic_lookup there, names the platform
$PLATFORM_NAME (iOS-simulator) and loads -lazy_library dylibs lazily.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A simulator configuration builds every object for the triple's OS
version: clang overrides -mmacosx-version-min with -target. A test whose
point is a macOS version's rule (chained fixups from 12, __DATA_CONST
folds from 15, an x86_64 image before 10.6), macOS's own platform
handling, its $ld$ directives or TBD versions 1-3 (which can't name an
arm64 simulator) skips there, as mold's ELF tests skip under QEMU.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The inputs of another architecture (x86_64, arm64e, x86_64h) came from
the host's cc -arch, built for macOS, which a simulator link refuses
for its platform before it looks at the architecture. They now come
from ${CC/$ARCH/<arch>}, the configuration's compiler with the other
architecture in its -arch or -target; and Swift sources are compiled
with $SWIFTC, swiftc with the triple and the SDK in a simulator.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A script that links with mold itself names the deployment target with
-platform_version macos, at a version where nothing it tests changes.
In a simulator configuration the objects are the simulator's, which a
macOS link refuses, so such a script gives ${PLATFORM_VERSION:-macos X
X}: common.inc's PLATFORM_VERSION holds the simulator's platform and
the triple's version, and is empty on macOS.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
macOS's dyld resolves a relative LC_RPATH entry against the current
directory, but a simulator's dyld_sim ignores one, as iOS's dyld does:
a program linked with -rpath,$t found no @rpath library there, though a
relative install name still loads from the directory $RUN changes to.
The scripts now pass $PWD/$t, which the linker resolves the same way.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A .tbd stub serves a link only through a target of the link's platform
and architecture: ld-prime refuses a stub built for macOS in a simulator
link. The stubs the scripts write name their targets with $PLATFORM,
macos or ios-simulator (tvos-simulator, xros-simulator), as TBD
versions 4 and 5 spell them, in the YAML target lists and in the JSON
target_info records alike.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The scripts that link with mold directly, or name a library of the SDK,
looked the SDK up with xcrun --show-sdk-path, which is macOS's whatever
the configuration. They now take common.inc's $SDK, the simulator's SDK
in a simulator configuration, whose libSystem.tbd lists the simulator
platform and inlines the system libraries it re-exports, where macOS's
SDK has a file for each.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Every place a script runs a program it linked now goes through $RUN, as
mold's ELF tests write $QEMU $t/exe: nothing on macOS, simctl spawn in
a simulator configuration. A simulator passes a program only the
variables simctl's environment names SIMCTL_CHILD_<name>, so the one
test that inserts a library sets SIMCTL_CHILD_DYLD_INSERT_LIBRARIES
there; exit codes and signals come back as a shell reports them.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
As mold's ELF suite runs a cross target's programs under $QEMU, TRIPLE
names a simulator to build for with its SDK and run on with $RUN (not
emulated): simctl spawn on a booted device, via a small program that
changes to the script's directory first, as simctl starts it in the
device's own. The runner boots one device per runtime, shared by
concurrent runs through lock files and shut down by the last; --all
adds the simulators, which the default will once mold links for them.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An object normally says what it was built for in an LC_BUILD_VERSION
or LC_VERSION_MIN_* command; one assembled for no OS, or an old one,
has neither. ld-prime takes such an object for the link's platform and
warns, naming that platform, on every platform but firmware, which takes
code built for any: so not for a -preload image nor a -r output for no
platform, which it counts as firmware's. mold warned in macOS links only.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
dyld binds a two-level namespace image's imports in the dylibs that
name them; -flat_namespace and -undefined dynamic_lookup have it search
every loaded image instead, which macOS keeps for plug-ins. ld-prime
deprecates the former everywhere but macOS, the latter in images dyld
loads but on macOS and firmware, and -bind_at_load wherever the image
has chained fixups, which dyld binds at load anyway; it counts -preload
images and -r outputs for no platform as firmware's.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The free space between a Mach-O image's load commands and its first
section is where tools add commands in place: codesign appends a
16-byte LC_CODE_SIGNATURE there, and with no room writes it over the
start of __text. ld-prime leaves a simulator's image 16 bytes more than
a Mac's or a device's, -headerpad or not. codesign, install_name_tool
and vtool need no more in one than in the other, so what for is unknown.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A loader that knows only SHA-1 page hashes needs a SHA-1 code
directory ahead of the SHA-256 one: macOS before 10.11.4 (not 10.12,
as mold had it) and iOS and tvOS before 11. A simulator's pages are
checked by the Mac's kernel and visionOS came later, so ld-prime never
gives them one; mold gave every x86-64 image off macOS one, meant for
firmware, whose loader ld-prime can't tell.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
LC_BUILD_VERSION came with macOS 10.14 and iOS and tvOS 12; the
loaders of older releases read the deployment target from the legacy
LC_VERSION_MIN_* {version, sdk}, which has a command per OS but none
for a simulator: on x86-64 it is the simulator's, so an arm64
simulator image needs LC_BUILD_VERSION at any version. mold wrote the
legacy command for x86-64 macOS alone; it now does for iOS and tvOS.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
dyld learned to walk fixup chains in iOS 13.4, and ld-prime makes them
the default there, in tvOS 14, in the simulators from 15 on either
architecture, and in every visionOS - per OS, not by a version set.
The same versions are its "new OS" for the rest of what follows the
chains: __init_offsets in place of __mod_init_func, the -no_pie
deprecation and the unaligned-pointer error. mold knew macOS's alone.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld64 enables what a release of dyld or the Objective-C runtime first
supports by comparing the deployment target with a version set, the
OS versions of one season (version2019Fall: macOS 10.15, iOS and tvOS
13, every visionOS); simulators count as their devices, and firmware
has the sets up to 2021's. mold compared macOS versions alone, so
iOS 14 got no relative method lists and iOS 12 got __DATA_CONST, and
firmware no relative method lists. The gates now consult the sets.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An image's LC_BUILD_VERSION (or LC_VERSION_MIN_IPHONEOS) minos is the
oldest OS release that may load it. iOS 4.3 was the first to slide a
main executable, and ld-prime, which makes every image position
independent, links for no older iOS: a minos below 4.3, from the options
or the first object, is an error for iOS and its simulator (counted in
iOS versions) in every kind of output but -preload, -r included. tvOS
and visionOS, which began later, take any version.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The App Store encrypts the code of an iOS, tvOS or visionOS app in
place: LC_ENCRYPTION_INFO_64 names the pages, from the first __TEXT
section to the last but __oslogstring, that the kernel decrypts as it
maps them, and cryptid 0 says they aren't encrypted yet. ld-prime gives
every device executable, dylib and bundle the command and that layout
unless -no_encryption or $LD_NO_ENCRYPT (set to anything) says not to;
mold did only for -encryptable, which also applies to -static images.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Xcode 26's dyld_info -shared_region prints where each entry refers
from and to but not its kind, which Xcode 27's prints last, so the
tests that looked for an entry of a kind found none on the CI runners.
They now check the kind only where it is printed.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Only Xcode 27's clang knows -target <arch>-apple-firmware; Xcode 26's,
which the CI runners have, makes an ELF object for it. The tests now
build a macOS object and give its LC_BUILD_VERSION what clang's
firmware objects have, platform 13, version 1.0 and no SDK: all mold
reads of them, as it reads no LC_TARGET_TRIPLE. A firmware program
links through the compiler driver only where clang knows firmware.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
clang gives every link -L/usr/local/lib, and the CI's arm64 runners
have no such directory, so since mold warns of a missing search path
the command line gives, every link there said "search path
'/usr/local/lib' not found", and each test that checks a link's
messages failed. ld-prime says nothing of that one directory, as -L or
-F, with or without -syslibroot or -Z, though it warns of
/usr/local/lib/, //usr/local/lib, /usr/lib and any other. The test
hides /usr/local/lib with sandbox-exec.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Apple silicon runs no arm64 code without a code signature, if only an
ad-hoc one, and a simulator's processes run on the Mac's own CPU, so
ld-prime signs an arm64 simulator image as it does a macOS one. A
device's image stays unsigned: packaging the app signs it with the
developer's identity, which replaces any signature the linker wrote. An
x86_64 simulator image runs under Rosetta, which takes unsigned code.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
LC_BUILD_VERSION numbers iOS 2, tvOS 3, visionOS 11 and their
simulators 7, 8 and 12. A simulator is a platform of its own: its code
runs on the Mac's CPU against the simulator SDK, so ld-prime refuses a
device object in a simulator link. -platform_version names each by its
OS (visionOS as xros too) plus "-simulator", -target by clang's triple,
and -ios_version_min the device only. Input checks and .tbd selection
already handled any platform; only the options and inference refused.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The release workflow only built binaries linked against glibc, which
don't run on musl-based systems such as Alpine Linux. dist.sh now takes
x86_64-musl and aarch64-musl, which build in an Alpine 3.20 container
and produce mold-$version-$arch-linux-musl.tar.gz.
Rust links musl statically by default, but a static executable can't
dlopen() the LTO plugin, so these builds link musl dynamically.
strip and debuginfo extraction tools don't see the library inside the
mold executable, so when CFLAGS contains -g, as it does in distribution
builds, its debug info stayed in the stripped executable. The library
was 49 KB instead of 22 KB with -g.
https://github.com/rui314/mold/issues/1661
crates.io packages cannot depend on git repositories or unversioned
paths. mold now uses the mimalloc fork published as mold-mimalloc, and
each workspace crate requires the exact version of the others because
they depend on each other's internals. The packages also get crates.io
metadata, and the libmold package includes only the files needed to
build it.
To make mold installable with `cargo install mold`, the package that
builds the mold executable must be named `mold`. The library package,
which the per-target crates and the executable depend on, is now
`libmold`.
The help message now says only what the linker does, which is to set
DF_1_GLOBAL; what the flag means at runtime is up to the dynamic loader.
This also adds the option to docs/mold.md and makes the test executable.
`mold -run` used to look for mold-wrapper.so next to the executable or
under $LIBDIR/mold, so the library had to be installed as a separate
file at a distribution-dependent location, which `cargo install` cannot
do.
mold now embeds the library and passes it to the command in a sealed
memfd, which child processes inherit and preload with
LD_PRELOAD=/proc/self/fd/<fd> on Linux or LD_PRELOAD_FDS=<fd> on FreeBSD.
Before each exec, the library puts a new copy at the descriptor if a
process has closed it, or removes itself from the program's environment
if another file has taken the number. Nothing is written to the file
system. -run is now supported only on Linux and FreeBSD.
With the default of 16 codegen units, generic and inline functions are
copied into each unit that uses them, and ICF can merge only the copies
that compile to the same machine code. With one unit per crate, the
stripped x86-64 executable shrinks from 20.0 MB to 14.6 MB. Thin LTO,
also suggested in the issue, makes it larger by itself and adds nothing
on top of this.
Rebuilding after a change in src/ takes 32 s instead of 16 s on a
64-core machine, as each crate under arch/ is now compiled on one core.
https://github.com/rui314/mold/issues/1706
mold used to accept and ignore --no-mmap-output-file. With this change,
the option makes mold assemble a regular output file in memory, as it
already does for special files such as standard output, and write it to
the temporary file on close instead of through a memory mapping. This
may be faster on a filesystem that is slow at random writes. The
temporary file is then renamed to the output file as usual.
Link: https://github.com/rui314/mold/pull/1705
install-mold.sh always put the ld symlink for GCC's -B option in
$PREFIX/libexec/mold, the manual pages in $PREFIX/share/man and the
license in $PREFIX/share/doc/mold, but distributions differ; Arch, for
example, does not allow /usr/libexec. LIBEXECDIR, MANDIR and DOCDIR now
set these directories. The ld symlink stays relative, with one ".." per
directory below $PREFIX instead of a fixed "../..".
MOLD_LIBDIR is renamed LIBDIR to match.
Fixes https://github.com/rui314/mold/issues/1703
libz-sys links to the system zlib if it finds one and otherwise builds
the stock zlib it bundles. The release binaries and all Windows builds
got the bundled one, because dist.sh's build image has no zlib headers
and libz-sys never looks for a system zlib when targeting Windows.
zlib-rs is a Rust port of zlib-ng, so every build now uses the same
zlib-ng-derived implementation, and the compressed output no longer
depends on which zlib mold was linked against. libz-rs-sys provides the
zlib C API, including deflatePending and deflatePrime, which the
compressor needs for its libbacktrace workaround.
Fixes https://github.com/rui314/mold/issues/1704
src/target was easy to confuse with Cargo's build directory, target/, at
the top of the source tree. The crates that instantiate the linker
already live in arch/, and the tests are named arch-*, so the module is
now arch and the crates are mold-arch-*. The Target trait keeps its
name, as each type in the module describes a target: an architecture
with a byte order, a word size and an ABI.
These tests built an array so that --target=$TRIPLE was passed only when
cross-compiling, and a native run got no empty argument.
${TRIPLE:+--target=$TRIPLE} does the same.
When a file has no address-significance table, ICF infers from its
relocations which sections have their addresses taken. It marked the
file's own data sections and the functions it referred to other than
by calls, but not data in other files. So when a file with the table
defined two identical constants and a file without one compared their
addresses, ICF merged them even with --icf=safe. Now any reference to
data marks the data, as lld does for files without the table.
The test uses Clang because GCC doesn't emit the table.
ObjectFile kept the .llvm_addrsig section as an InputSection, whose size
depends on the target's InputSectionExtra. That shifted ObjectFile's
field offsets on RISC-V, LoongArch and ARM32, so functions that touch
ObjectFile compiled to different code for them than for other targets
with the same byte order and word size, and ICF couldn't merge them.
Only the section contents are used, so keep just those.
This shrinks the .text of mold linked by itself with --icf=all from
18.1 MB to 17.6 MB.
The default linker for most targets in the dist.sh images is GNU ld,
which doesn't support ICF. dist.sh now relinks the executable with the
mold it has just built, passing --icf=all. Only the executable is
relinked, so this adds little to the build time. With the previous
commits, the stripped aarch64 executable shrinks from 37.0 MB to
18.2 MB.
The linker is instantiated for each target, and many functions compile
to the same machine code for several targets. ICF can merge them, but
rustc doesn't emit .llvm_addrsig tables by default, so linkers have to
assume that every piece of read-only data has a significant address.
They then can't merge functions that refer to identical constants, such
as the panic locations each target crate has its own copies of.
.cargo/config.toml now passes -Cllvm-args=-addrsig to rustc, and
cli/build.rs passes --icf=all to the linker in release builds if a
probe link with the option succeeds, as GNU ld doesn't support it.
Linked by lld, or by mold with the previous commits, the .text of a
release build shrinks from 35.7 MB to 18.4 MB.
LLVM pads the last record of .eh_frame with DW_CFA_nops to align the
section, so an FDE that happens to be last in its file has more zero
bytes than the same FDE elsewhere. ICF hashed them, so such a function
and its callers didn't match their copies in other files. Two records
that differ only in trailing zeros mean the same thing, so ICF no
longer hashes them.
Together with the previous commit, linking mold itself, compiled with
-Cllvm-args=-addrsig, with --icf=all shrinks .text from 25.9 MB to
18.4 MB.
A relocation that refers to a mergeable section through a section
symbol is redirected to a dummy symbol whose value is the offset in the
fragment minus the addend. ICF hashed the symbol value and the addend
separately, so two references to the same string from different files
didn't match if the string was at different offsets in their input
sections. Compilers usually refer to string literals that way, so this
kept many otherwise identical functions apart.
ICF now hashes the sum, which is the offset in the fragment.
An --as-needed DSO was kept only if an object file referred to it. GNU
ld also keeps it if a needed DSO refers to one of its symbols without
listing it in DT_NEEDED, so that an underlinked library still finds its
dependency at run time. The test suites of dovecot, AdjoinableMPI and
libtool depend on that, and lld behaves as mold did.
mark_live_file now follows references between DSOs regardless of
--allow-shlib-undefined, and the final pruning, now a pass of its own
instead of the tail of check_shlib_undefined, keeps such dependencies.
A DSO that a needed DSO lists in DT_NEEDED is not added, as in GNU ld.
GNU ld 2.39 and later warn about RWX segments and executable stacks,
and these options turn the warnings, or the errors made of them, off.
mold never reports RWX segments and never makes an executable stack an
error, so it accepts the options as no-ops, as it already does
--no-warn-execstack.
Running the suite on GCC Compile Farm machines found test scripts that
don't work as intended on some hosts:
- on_qemu() took KVM guests on POWER for qemu-user
- the native POWER10 target ignored CPU and shared ppc64le's directory
- readelf 2.35 appends "[<localentry>: 1]" to POWER10 function names
- compressed debug sections hid .debug_str from `readelf -p`
- some tests assumed Linux, glibc, GCC-only flags or g++
- an x86-64 executable was run without $QEMU on a non-x86 host
- old binutils lack multiple .symver and SHF_LINK_ORDER
- GCC 8's `gcc -r` links start files into relocatable output
- debug-macro-section.sh grepped for an offset format objdump doesn't print
With -g3, GCC emits a COMDAT group for the .debug_macro contents of each
header file, and other .debug_macro sections refer to it. mold doesn't
deduplicate these groups because it would resolve references to discarded
copies to 0 (https://github.com/rui314/mold/issues/438), but -r output
dropped the groups while keeping SHF_GROUP on their members. Now -r keeps
every copy as a COMDAT group. GNU ld -r deduplicates them and redirects
the references to the kept copies; without that redirection, mold leaves
deduplication to the final link, where GNU ld, lld and mold handle the
groups as they would in the original objects.
--print-dependencies reads the relocations of every input section,
including non-allocated ones whose CREL tables are not decoded when the
file is read. It read them as an array, which reinterpreted such a table
as RELA records and failed an assertion. C++ mold's get_rels() decoded
the table on demand instead.
A read-only pass doesn't need the table materialized, so iterate with
the streaming relocation iterator as --stats does, and document when
the array accessor can be used.
rustc's LoongArch targets use the medium code model, whose calls reach
±128 GiB, but the cc crate passes no -mcmodel to the C compiler unless
-Ccode-model is given explicitly, so C code got the normal code model,
whose direct calls reach only ±128 MiB. A debug build of mold has more
than 128 MiB of text, so calls from C code such as zstd, mimalloc and
lto-message.c to the PLT failed to link with R_LARCH_B26 overflows on
a native LoongArch host. We now give -Ccode-model=medium explicitly.
A section group whose flag word is 0 is not a COMDAT group. It is never
deduplicated; it only ties its members together so that they are kept or
discarded as a unit. annobin emits such groups, e.g. into crt1.o on
AlmaLinux 8. mold ignored them, but -r output kept SHF_GROUP on their
members, leaving SHF_GROUP sections that no SHT_GROUP lists, which the
gABI forbids and objdump 2.30 rejects. -r output now keeps each such
group with flag word 0, as GNU ld and lld do. Other links still treat
its members as ordinary sections.
CREL tables of non-allocated sections are not decoded when an object
file is read; their relocations are streamed when they are applied.
On ppc64v1, rewrite_opd() also rewrites relocations of non-allocated
sections such as .debug_info, and rels_mut() reinterpreted such a table
as RELA records. Linking a CREL object built with -g failed an
assertion, and a table whose size was a multiple of 24 would be misread.
A CREL table can't be rewritten in its compressed form, so rels_mut()
now decodes it into the side table first, as get_rels() in C++ mold
did. relocation_iter() then returns the rewritten records from there.
This reverts aed5a38d1, which made -e accept a number as an entry
address if no symbol has that name, as GNU ld and lld do. New features
are deferred until after 3.0.0.
This reverts f03b74595, 48428d389 and 3a1b9c6c2, which merged
GNU_PROPERTY_AARCH64_FEATURE_1_AND, emitted BTI PLTs and landing pads
for range extension thunks, and added -z force-bti, pac-plt, gcs,
bti-report and gcs-report.
GCC 16 no longer records -mbranch-protection in .note.gnu.property but
in AArch64 build attributes, which we don't read, so outputs silently
lost the BTI, PAC and GCS bits and -z force-bti warned about every
object file. We'll bring the feature back with build attribute support
after 3.0.0. Until then, ARM64 outputs have no .note.gnu.property, as
in 2.42.1.
These sorts have keys that are unique among the elements, so a stable
sort produces the same order. Using an unstable sort, as C++ mold did,
avoids the stable sort's buffer and signals that ties don't matter.
Two mapping symbols can share an offset, e.g. when `ld -r` places a
section ending with a mapping symbol before one starting with another.
The later one in the symbol table applies, as in the C++ version, which
used stable_sort. An unstable sort could leave BE8 code unswapped.
Default features are turned off because uuid's std feature refers to
optional wasm32-only crates, and Cargo puts those into Cargo.lock even
though nothing builds them. With them off, uuid is the only new package.
When run in an extracted source archive, dist.sh cloned the release tag
as a bare repository into .git to get the commit hash and timestamp.
Cargo refuses to build in a directory whose .git is a bare repository,
and the embedded hash named the tag rather than the files actually
built. A worktree doesn't work either, because its .git file points
outside the build container and the hash is silently omitted. Stop with
an error unless .git is a directory.
The prefetch knew the stubs it had taken up by path, so SwiftUICore's
10 MB stub, named by -framework SwiftUICore as
SwiftUICore.framework/SwiftUICore.tbd and found for SwiftUI's
re-export as Versions/A/SwiftUICore.tbd, was read by two tasks at
once: the cache of read stubs is filled only when a read is done, and
the two paths share their contents. A timeline of IINA's prefetch had
both reads on its critical path. The stubs are now known by contents.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Each auto-link round read the options of the objects new to the link
on the main thread, rebuilding every LC_LINKER_OPTION command as a list
of strings: 4 of the 26 ms of iTerm2's first round, whose 2,000 objects
(most of them Swift's) carry dozens each. They are now read on all
cores, each object's warnings kept and given in object order, as
mold-rust parses its input files in parallel and reports in order.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The command line's objects fill the symbol, subsection, unwind, CIE and
FDE arenas exactly, so the first auto-link round, which adds a few
archive members, made each of them double by copying itself whole: on
ASan iTerm2 the round's gather took 5 ms to copy 57 MB of symbols and
its integrate 6 ms for 82 MB of subsections and the rest, both mostly
page faults on the fresh copy. An arena that grows now keeps an eighth
to spare, which costs address space until it is written.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Prober::exists took one lock for the answers it remembers, which the
threads of the auto-link prefetch, each looking for one library in
some 25 places, contended for: a System Trace of IINA's link showed
220 more waits for a pthread mutex (psynch_mutexwait, 10 to 20 us of
kernel time each) than without the memo. The answers are now kept in
64 shards by the path's hash, as the symbol table's names are, which
takes the waits back to those of the link without the memo.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
LdSymbols::read parsed every field of each $ld$previous directive, the
versions of those for other platforms too, and split them with a byte
predicate: half of SwiftUICore's 30,000 directives are for Mac Catalyst,
and Foundation repeats its 8,500 for three platforms and ranges. The
fields are now split with memchr and the platform is checked first,
which takes 10-17% off reading the two stubs' directives (1.6 and 2 ms,
the largest piece of reading them after the parse).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A re-exported library is looked for by the prefetch, quietly, and
again by the serial loop loading the stub that re-exports it, which
notes what it doesn't find for -dependency_info: 450 of the 2,200
stat(2) calls of IINA's first auto-link round were repeats, run on the
main thread, where each walks the 15 or so components of a path into
the SDK while the other threads spin idle. Prober::exists now asks the
file system once per path and remembers the answer.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The prefetch found the auto-linked libraries, opened them and read the
stubs among them in three parallel passes, then the stubs they re-export
in waves, looking each re-exported name up on the main thread between
waves (a name once per stub re-exporting it: libobjc six times). Each
option's task now opens and reads what it finds, and a stub read spawns
the reading of those it re-exports, each name looked up once, as
mold-rust's mark_live_files spawns a task per file found and its
read_input_files looks a library up in the task reading it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The parallel prefetch only parsed the stubs; the serial loop then
applied each library's "$ld$..." directives and hashed its exports into
sets, 5.6 of the 15 ms it took in IINA's first auto-link round
(SwiftUICore's 30,000 directives 2 ms, Foundation's 25,000 1.7 ms). A
stub is now read whole, each library in it to its export sets, in the
prefetch, as mold parses its shared libraries in parallel; the loop
clones the sets, and a merge copies the larger of two before adding.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
install_name_tool grows a renamed dylib command into the room between a
final image's load commands and its first section. ld-prime places the
sections after an estimate of the commands that runs over by up to 8
bytes per dylib command and 32 for the others, and post-link scripts
written against Xcode spend that room: Sequel Ace's renames three dylibs
to @loader_path paths, 48 bytes, and failed with -headerpad's 32 alone.
Leave 32 bytes and 8 per dylib more than -headerpad, never less room.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Loading a dylib or stub resolved the directory of its file, symbolic
links and all, for the libraries it re-exports to be found from, though
only an @loader_path install name needs it: the realpath(3) of a path
in an SDK framework costs some 17 getattrlist calls, about 1,750 of them
in the first auto-link round of IINA (2.3 of its 15 ms serial loop).
A re-exported library now keeps the file of the library naming it, and
the walk takes its directory when it resolves an @loader_path name.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An SDK framework's stub is reached by two paths: -framework Foundation
finds Foundation.framework/Foundation.tbd, a symbolic link to the
Versions/C/Foundation.tbd a re-exported install name finds. Opens were
memoized by path only, so each path mapped the file anew and the stub
parse cache, keyed by data address, parsed it twice: 24 of iTerm2's 137
stub parses were such repeats, 9.6 of 24 ms (SwiftUICore's 10 MB stub
3 ms each time). Files now share their contents by device and inode.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The liveness walk looked at every nlist of each live object, its local
symbols and stabs too (a million of them in iTerm2 ASan), to find its
undefined external ones, which only the external part of a symbol
table holds - the part every resolution round already reads alone.
As mold's mark_live_file walks a file's globals, the walk now reads
that part only (the whole table, where it isn't partitioned).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Finding each symbol's subsection as an object is staged (sym_subsecs)
added a seventh to staging on iTerm2 ASan: the binary search read the
address of each subsection it passed from the subsection itself, a
cache line apiece. The addresses of the object's subsections now go
in a row of their own first, which the searches go through; the rest
of the search is find_symbol_subsec's, factored out to be shared.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The parallel prefetch looked for the file of each auto-link option,
and then the serial loop loading them looked for each again, path by
path, to note the files it didn't find for -dependency_info and to
warn of a library next to its stub. The parallel search now keeps
those notes and warnings with the file it found (ProbeLog), and the
serial loop gives them in its turn instead of searching again.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The serial loop that loads stubs checked every export for a "$ld$"
directive and looked each up among the hidden ones even in a stub
that has no directive at all, which most libraries are: iTerm2's
auto-linked libraries have 300,000 exports. The parallel parse now
notes the directives (TbdFile::ld_symbols, as DylibBinary::ld_symbols
does for a binary), and a stub without one keeps its exports as is.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Two loops of every resolution round walked the whole symbol table on
one thread (1.4 million symbols in iTerm2 ASan) to copy the round's
references into each symbol's flags, and each dylib new to the link
looked its exports up in the symbol table on one thread (iTerm2's 129
dylibs export 300,000 names). As the mold paper has every pass do (4,
a parallel-for over homogeneous elements), they now split the symbols,
and a dylib's exports, among all threads.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
-print_statistics showed resolve_symbols as one number (with the
staging of objects auto-link options load), though it is made of
rounds that run a varying number of times. As mold's resolve_symbols
times resolve_symbols_pass, mark_live_objects and clear_symbols, ours
now does too, and times claim_locals and load_autolink_deps, so that
how a link spends its resolution time shows without a profiler.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
When the liveness walk loaded a member with a tentative definition the
round hadn't ranked as one (1-8 symbols in iTerm2 ASan, Hammerspoon,
Amethyst), the whole round and walk ran again, in case an archive
member defines the symbol as data. Only those symbols can make another
walk load anything, so their definitions alone now race for them, and
the round runs again only if a lazy member would win one: in a link of
the corpus, none; in tests/common-archive.sh, one.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Each time auto-link options loaded objects, resolution started over,
its final round over live files and the claims of local symbols
included: iTerm2 ran five rounds and claim_locals twice. As in mold's
resolve_symbols, the rounds over all files and the liveness walk now
repeat until the options load nothing they affect, and the final round
runs once, ranking the dylibs as before the last load, whose new
dylibs then claim what is left (claim_new_dylibs), as before.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
convert_objc_method_lists walked every subsection of the link twice
(in parallel) reading its section name: once for the class, category,
protocol and class-ro lists the runtime follows, once for the
selector references. The section headers now name those sections
(subsecs_of_sections, as in merge_objc_categories and
coalesce_objc_refs), and only their subsections are visited, the
lists still a block at a time with the first visit of a list counting.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A referenced symbol that no live object defines was looked up in each
dylib's export set in search order, hashing its name again for each:
iTerm2 links 22,000 such names (references from dead-stripped code)
against 129 dylibs, 2.9 million lookups in every resolution round. As
in the mold paper (4.2, each file claims the symbols it defines) and
mold's SharedFile::resolve_symbols, each dylib now races its place in
the search order into its DylibFile::symbols, looked up once.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
coalesce_objc_refs read the segment and section name of every
subsection of the link, serially, to find the 51K selector, class,
super and protocol references and CFStrings among iTerm2's ASan
dylib's 1.46M: about 20 ms single-threaded. The section headers now
name those sections (subsecs_of_sections), the keys are computed in
parallel in subsection order, and a serial walk keeps the first of
each, as before: about 10 ms single-threaded, 5 ms on all cores.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Every resolution round looked up the subsection of each definition it
ranked or claimed with find_symbol_subsec's binary search over the
object's subsections, twice per weak definition and again for every
local symbol: a third of resolve_symbols on iTerm2 ASan. An ELF symbol
names its section outright, which mold's ObjectFile::resolve_symbol
reads; staging now does the Mach-O search once per nlist, in parallel,
and the rounds, weak-definition coalescing and the duplicate check
read the result from ObjectFile::sym_subsecs.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
defined_classes followed each class list entry to the class's ro
record, its metaclass and the metaclass's ro record one subsection at
a time: four pointer reads a class through relocations, about 2 ms on
iTerm2's 2400 classes. The list subsections are now read in parallel
with an ordered collect, then a class listed twice becomes one in a
serial walk, as mold-rust's per-file passes gather and then merge
(merge_objc_categories on all cores: 2.0-2.3 -> 1.5 ms on ASan).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
merge_objc_categories found the class and category list subsections by
reading the section name of every subsection of the link, twice and
serially: 1.46M subsections for 2000 list ones on iTerm2's ASan dylib,
11 of the pass's 12 ms. Now the objects' section headers name the
list sections, a file at a time in parallel as in mold-rust's per-file
passes, and only their subsections, found by address, are visited:
12 -> 4 ms single-threaded, 12 -> 2-3 ms on all cores.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Non-allocated strings were merged in a background Rayon task, but Rayon
has no task priorities: a thread waiting for a foreground parallel pass
could pick up a long piece of the merge, and the pass waited up to
200 ms for it. Resolve these sections in compute_section_sizes as before
2316c952a. Within noise on large debug builds (0.94-1.04).
Instruction pairs such as ADRP+ADD on ARM64 refer to the same string
with two consecutive relocations, and each got a dummy symbol of its own.
Reuse the previous relocation's dummy symbol if it refers to the same
section symbol with the same addend. Since both relocations of a pair now
refer to one symbol, ADRP+ADD relaxation applies to them too. On
chrome-release-arm64, reattach_section_pieces 39 -> 30 ms and peak RSS
5.86 -> 5.56 GB.
merge_objc_categories looked each rewritten method list up with a
linear find over __objc_methlist's lists and dropped a superseded one
with a retain over them, both per list: quadratic in the lists, about
1.1 of its 12 ms on iTerm2's ASan dylib. The lists are in subsection
order, so a lookup is a binary search, and the dropped ones, already
dead, leave in one retain after every class has merged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Range extension thunk creation and removal lay out input sections in a
serial loop that looked up each member through its file, costing a few
dependent cache misses per section. Read sizes and alignments into an
array in parallel first, and publish the offsets in parallel. On
chrome-release-arm64, compute_section_sizes 34 -> 27 ms and
remove_redundant_thunks 25 -> 15 ms; on chrome-debug-arm64, 87 -> 81 ms
and 72 -> 50 ms.
CIE deduplication, which both ICF and .eh_frame layout run, compared each
CIE with every distinct CIE found so far in a serial loop. Compute their
SipHash digests in parallel with a random key and number the classes
through a hash table instead, assuming that digests never collide as ICF
does. FDE offsets are now assigned in parallel too. uniquify_cies 9 -> 2
ms on chrome-release-arm64; .eh_frame layout 15 -> 8 ms on
chrome-debug-x86_64.
set_visibility() is only called on exclusively owned symbols, but it
updated the bits with a compare-and-swap loop. Take &mut self and write
them directly.
merged_resolve_members visited every file on one thread. Fold the
members of each file in parallel and append the per-thread lists in
file order. About 5 ms on chrome-release-arm64.
gather reserved each shard for every recorded occurrence of a name, about
three times the number of distinct names, so the sparse table touched far
more pages than it used. Estimate the distinct names with HyperLogLog
instead. chrome-debug-arm64 peak RSS 26.76 -> 25.91 GB.
Pattern::matches called memchr::memmem::find for the literal after each
star, which builds a searcher on every call. Build them when compiling
the pattern instead. apply_version_script on tensorflow-debug 260 -> 220
ms.
compute_import_export collected updates in parallel and applied them on
one thread because Symbol's attribute bits needed &mut. Make the bits an
AtomicU16 so that file-parallel passes can set them through shared
references, testing a bit before writing it as mark() does.
parse_symbol_version now finds versioned symbols in parallel; it still
applies them in input order because one may read a version that another
has just set. compute_import_export on tensorflow-debug 167 -> 9 ms.
bare_code_records put every record's start (and end, without
subsections) into two SipHash maps of vectors, serially: 17 of
unwind_encode's 23 ms on iTerm2's debug dylib single-threaded. A
subsection already names its records, a range of ctx.unwind_records
(sold's get_unwind_records), so the parallel walk reads them there (4
ms single-threaded, 1 ms on all cores). lay_out_eh_frame now refreshes
the ranges after dropping folded copies' records, as dead_strip does.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A class reference is an __objc_classrefs slot holding the class's
address, which dyld binds at launch and the runtime reads at load, so a
delay-init dylib's class can't be reached through one. From macOS 15
on the loads of a slot (adrp/ldr, movq) become GOT loads of the class,
as ld-prime makes them: delay-init routes those through a helper that
dlopen()s the dylib first, and a class the image defines relaxes to
adrp/add or lea; a slot whose address is taken becomes the GOT entry.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An input section of non-lazy symbol pointers (S_NON_LAZY_SYMBOL_POINTERS,
e.g. an object's own __got) keeps its entries in the indirect symbol
table: each 8-byte slot names the symbol its pointer relocation binds,
if any. The slots were matched by searching the section's relocations
once per slot, so the time grew with the square of the section's size.
The relocations are now walked once, the first at a slot's offset naming
it as before.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
runtime_method_lists gave each subsection of a list section a finder
of its own, three allocations for every class list entry, which made
the pass 2 ms slower than the serial one on one thread (iTerm2's ASan
debug link). Blocks of 4096 subsections now share a finder, as
assign_input_sections's blocks share a group list, and the finder's
loop body returns to MethodListFinder as visit_list_section; the first
visit of a list still counts, so the output stays the same.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
convert_objc_method_lists made three serial passes: over every
subsection for the list sections and for the selector references, and
over the method lists it read. Each now runs in parallel, collected in
input order and merged serially, as mold-rust's per-file passes are:
the first visit of a list and the first reference to a selector count,
and the selectors get references and the lists subsections in list
order, so the output stays the same.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
assign_input_sections was one serial loop over the subsection arena.
It now follows mold-rust's create_output_sections (46bbf3df, with
WorkerLocal from 4980d26a): workers find each block's output sections
through a cache each and a shared table, and each section's groups are
concatenated in input order. Blocks of the arena stand in for mold's
files, groups are per output section as renames merge keys, and the
sections are made in the order of their first members, ld-prime's.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
objc_pointer_reloc scanned a subsection's relocations for the one at
the field's offset, and the method-list rewrite asks for every field of
a list in turn, so a long list cost the square of its length: half of
convert_objc_method_lists's 24 ms on iTerm2's debug link. The
relocations of a subsection are sorted by offset (read_relocations), so
a partition_point finds the run at the offset, as the arm64 hint
lookup does; the pass now takes 12 ms there, with identical output.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Object staging and six passes that synthesize a subsection - a common
symbol's zero-fill, a data word, a DOF, a C string, a -sectcreate
section and a placed Objective-C record - each spelled out all fourteen
fields of InputSection, most of them the same zeros and sentinels, and
how a slice becomes the contents address. InputSection::new takes the
file, section, alignment, size and bytes, and each caller names only
what differs: its flags, input address, relocations or output place.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
add_object_fixups walked the entries with a hand-advanced index and an
inner loop that skipped the rest of a subsection's run of record
entries. It now visits every entry and takes the fixups of a subsection
at the first entry of its run, the one whose predecessor stands for
another subsection, which says outright what the skipping did.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Synth::target_sym's comment promised an addend besides the symbol,
which the function stopped returning, symbol_stabs' comment broke its
last sentence in the middle of a line, and make_mergeable's module doc
pointed at mergeable.rs, which is a directory now. Each comment now
says what the code there does, and where the format is described.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
mergeable.rs had the record's format and reader in its first 770 lines
and the 1050-line synthesizer of the object a merging link takes after
them. mergeable/mod.rs now keeps the format, its encoders and the
reader - the counterpart of make_mergeable/write.rs - and
mergeable/object.rs the object, with the module doc's paragraph on
merging, so each side of the record is a file of its own.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
record_range walked the load commands with its own offset arithmetic,
which input_files now does once in load_commands for its readers,
is_mergeable among them. mergeable.rs takes the command from that
iterator too, so the check that a dylib is mergeable and the read of
its record find LC_ATOM_INFO the same way.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
delay_init.rs had copies of lazy_load.rs's parallel scan for the
relocations that reach an import, differing only in the predicate, and
of its naming of GOT load helpers, written out twice in each file for
a helper with and without a site of its own. lazy_load::import_uses
and load_helper_name now serve both passes, which keep only what
differs: the predicate, the errors, and the infix of the names.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The locals were LocalEnt, while global_entry, import_entry and
par_push_entries spelled the same (name, nlist, symbol) tuple out each
time, and several items wrote crate::symbol::SymbolId though the module
imports SymbolId. NamedEntry is now the one name for the triple every
entry builder returns, which shows that the locals, the defined globals
and the imports are made the same way.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Synth::write laid out the object's sections, relocations and symbol
tables and then wrote the Mach header and every load command in the
same 120 lines. The load commands now come from write_load_commands,
given a FileLayout of where the parts went, as relocatable.rs writes a
-r output's; write keeps the layout of the body, and the segment's
protections are spelled VM_PROT_* instead of 7.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
cityhash64 was one 111-line function with libc++'s helpers as closures
and its length cases inlined, which hid that it is a port of a public
algorithm. It is now make_mergeable/cityhash.rs, with libc++'s helpers
under their own names (hash_len_0_to_16, hash_len_17_to_32,
hash_len_33_to_64, weak_hash_len_32_with_seeds, hash_len_16, shift_mix)
and the long-string loop over 64-byte chunks, so each part can be
checked against __murmur2_or_cityhash<size_t, 64>.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The synthesized object's sections and the writer's content pool each
spelled out the first offset that is an entry's modulus past a multiple
of its alignment, and mergeable.rs kept a copy of sign_extend: both are
in util already, align_to_mod with the same meaning that input_sections
uses to place a subsection where it was in its object.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The writer's OutEntry repeated the reader's Entry field for field, with
its own flags() encoding the word the reader decoded inline, and the 40
bytes were laid out once in each direction at offsets spelled apart.
Entry<C, F> now serves both, differing only in where the bytes and the
fixups are, and Entry::write sits next to Reader::entry, which reads
what it writes, so an entry's layout and flags word are in one place.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The reader had its Fixup with numbered entries and the second
instruction's distance; the writer had OutFixup, numbered its targets
into a tuple beside it, and encoded the two last words in a function of
its own in write.rs. Now Fixup<T> serves both - entries by index, or by
the writer's To until it numbers them - and Fixup::read and Fixup::write
sit together in mergeable.rs, so the bit layout of a fixup is in one place.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Some twenty passes skip the subsections that don't reach the output as
themselves - dead ones, and the literals, weak definitions and ICF
twins replaced by an identical copy - each by testing is_alive() and
replacement == NO_REPLACEMENT, or the negation, in its own spelling.
InputSection::is_emitted says it once, the callers read as what they
mean, and one pass's local closure for the test goes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
is_unnamed_objc_list, has_unnamed_subsecs and is_class_or_protocol_ref
sat in passes.rs among the layout passes, though they classify an
object's sections as is_record_list, is_literal_section and
record_size beside them in input_files.rs do, and staging is where the
first two are used. They move next to those; the first two become
private, and passes.rs and output_sections.rs name the third's module.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An object whose LC_LINKER_OPTION holds other than one or two strings
fails the link. The message named the object twice, "in 'a.o' in
'a.o'", as ld-prime's does; its wording need not match, so it now
names the file once, in front, as mold's other errors about an
object's load commands do.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The input loop adds a dylib with parse_dylib for a .tbd stub and with
parse_dylib_binary for a Mach-O dylib, so the name of the first read
as if it were the general case. It is now parse_tbd, with a doc
comment saying when it returns None, and the call site reads as the
choice between the two formats that it is.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
passes.rs says it holds the passes in the order the driver runs them,
but its first half had LTO before the input version checks that guard
it, and the Swift metadata removal, initializer offsets and common
symbols in no order. The blocks move, unchanged, into the driver's
order: resolution, version checks, LTO, removing what isn't live, the
Swift reflection metadata, initializers, then common symbols.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
read_input_files went from finding the inputs straight into a filter
chain of stubs and then a loop over the -possible-l libraries not found,
with nothing to say what either is for. Each gets a line: the first
parses the inputs' stubs ahead in parallel for the serial loop that
loads them, the second notes the hints that weren't found, which are
reported only if symbols stay undefined.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
reader.rs kept the order its functions had in passes.rs, where they had
grown by accretion: find_input and the SDK checks sat 700 lines from the
other lookups, the namings of a library after the collecting that uses
them, and read_input_files in the middle. The file now goes top-down
from read_input_files: search paths, finding files, the namings, its
other helpers, collecting files, parsing objects, then auto-linking.
The functions are moved, not changed.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
collect_file refused a dylib that doesn't allow this link as a client
from a match guard, refuses_client, that reported the refusal as a side
effect before the arm was even chosen, and refused a dylib without an
LC_UUID in an arm of its own. Both are the first things load_dylib does
now, so collect_file's match chooses by the file type and the naming
alone, and what loading a dylib checks is in one place.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
error.rs interleaved strerror and the byte-string printing (Raw, RawBuf,
RawPath and the marks render puts back) with the reporting functions, so
the settings, emit, fatal, error and warn came after 120 lines of the
former. The file now runs in mold-rust's order - settings, emit, fatal,
error, warn, the held warnings next to warn, notice, strerror,
checkpoint, exit_after_cleanup - and the byte-string support follows as
a section of its own; two of its comments say what it does.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Since reading the inputs moved out, passes.rs opened with
merged_providers, dylib_ranks and the other helpers of claiming dylib
exports, ahead of create_internal_file and resolve_symbols, which run
first and call them only through claim_dylib_exports. They move, as they
are, to follow claim_dylib_exports, so the file reads in the order the
driver runs its passes, as its first line says.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The driver timed some passes with the timed! macro, defined halfway down,
and others with a timer it started and dropped by hand; the macro now
comes first and times every pass that runs alone. Its phases get the
one-line comments mold-rust's driver::link gives them. The checks for
errors at the head of do_lto and write_merged_bitcode repeated the one
the driver makes right before calling either; that one now says why.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
subsec_label_index found the labels at a subsection's start and handed
them to pick_label, its one caller, which looked the subsection and its
object up again to choose among them; the choice now follows the search
in one function. isec_addr's comment began with a paragraph about a
cached address field that no longer exists, contradicting the paragraph
after it and the code; only the description of what it computes stays.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
collect_file, 122 lines, carried what loading a dylib and queueing an
archive's members take inline in its match; they are load_dylib (with
name_dylib, which it calls, now beside it) and collect_archive_members,
and the match reads as a dispatch on the file type, as mold-rust's
read_file does, with the type read once. find_inputs, a one-line map
with one caller, folds into read_input_files, and load_pending
concatenates the per-object name lists with concat.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
resolve_symbols called do_resolve and clear_claims, which are mold-rust's
resolve_symbols_pass and clear_symbols, and now take those names. The
command-line symbols (-u, the entry point, -alias bases) were spelled out
in three places; command_line_symbols lists them once. claim_definitions
mixed the race check with what a claim sets in a 95-line closure; the
claim is claim_definition now, and both claim loops share SymbolSlots in
place of a raw-pointer wrapper each.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
passes.rs opened with 1500 lines on finding and loading inputs - search
paths, library lookups, the namings of a library, collecting files into
the parse queue and acting on auto-link options - before its first pass.
That is what mold-rust keeps in reader.rs (read_input_files, find_library,
read_file), so it moves there unchanged, and passes.rs starts at symbol
resolution. Callers and comments elsewhere now name reader:: for these.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The driver ran the resolve/auto-link loop twice, before and after LTO,
dispatching on an Autolinked enum that said what a round of auto-linking
added. load_autolink_deps now owns that loop: it reads the new objects'
options, loads what they name (load_autolinked_libraries), and resolves
again or lets new dylibs claim what is undefined until nothing new comes.
do_lto ends by redoing resolution as mold-rust's do_lto does, behind
has_lto_obj, so the driver reads like mold-rust's resolve/LTO sequence.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The reader read its tables at 0x60, 0x68, ... and the writer wrote them
at the same numbers, each side spelling them out, so checking that the
two agree meant comparing hex offsets. mergeable::header names each
field once and both sides use the names; the reader's table() returns
an offset and count the way the writer's table() stores them, and the
writer imports the record's sizes at the top instead of in each function.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The reader matched a content type to its standard section, and the
writer found a section's content type by trying every number below 82
against that match, with its own CT_ constants for the types it names
in mod.rs and objc.rs. Now STANDARD_SECTIONS is the one table, looked
up both ways next to each other in mergeable.rs, and mergeable::ctype
lists every content type either side names, as scope::LOCAL names the
scope the writer spelled 0.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The comment on the n_desc bits a -r output keeps told of a crash
reporter's template instantiations staying exported once, and the one
on remapping the FDE indices of folded copies of an encoder crash. Both
now state the rule the code keeps: a .weak_def_can_be_hidden marker
must survive for the final link to hide the symbol, and no unwind
record may point past the shortened FDE table.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The starts table and write_fixup_chains each cut a segment's fixups
out of the sorted list with the same two binary searches, and
write_fixup_chains encoded a rebase, with its out-of-range diagnostic,
in the middle of its chain loop. segment_fixups and rebase_word now do
those, and the loop says only how each fixup links to the next and
which word it gets.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
create_dyld_info_cmd repeated, for each of the rebase, bind, weak-bind,
lazy-bind and export tables, the same four lines: an empty table
leaves its offset and size 0, any other records its file offset and
size. A closure states the rule once and the command is built from its
results in one struct literal.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The paragraph of the thunks module comment on the entries mold trims
and this port keeps had a line broken after a few words, a leftover of
an edit, which left a ragged gap in the middle of the explanation. It
is rewrapped to the file's width, with no change to its words.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
auto_hide_weak_defs and weak_def_losers both picked an object's weak
definitions out of its global nlists with the same four-clause
condition - no stab, external, in a section, N_WEAK_DEF - spelled out
inline. The test is now is_weak_def, so both loops say what they walk
over in their first line.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
set_osec_offsets spelled out the same three lines twice - lay out the
segments, then again while finish_unwind_info says the encoding needs
more room - once before and once after creating range-extension thunks
the first placement shows a need for. The loop is now
lay_out_segments_with_unwind_info, so set_osec_offsets reads as place,
add thunks if needed and place again, settle the chain starts, then
build __LINKEDIT, like mold-rust's set_osec_offsets loop.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
lazy_load.rs took bind_dyld_lazy_load along at its end, where it sat in
passes.rs, though the driver binds __dyld_lazy_load before dropping the
unused dylibs and makes the helpers only afterwards. It now comes first
in the module, so the file reads in the order its passes run, as
passes.rs and delay_init.rs do.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
create_thunks decided whether the code after a section is within reach
by counting the batches still to come with an iterator of successive
batch ends, counted and less one, inside a closure in the middle of the
cursor loop. The count is now batches_left, a loop over batch_end
named for what it returns, so the reach test reads as the sum of the
room the code ahead takes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Assemblers emit a call to a static function in another section as a
branch to a section symbol plus an offset, but a range extension thunk
jumps to the start of a symbol. On RELA targets, give such a branch a
symbol at its destination and a zero addend, as we do for references to
section fragments. This fixes Chromium's ARM64 debug build, which
9b9162146 made an error and 2.42.1 linked into calls to wrong addresses.
stage_object ran the steps that read an object - load commands,
symbols, sections, relocations - and then, inline, the half-dozen that
read its unwind info: the compact unwind records, the __eh_frame's CIEs
and FDEs, the records whose FDE never turned up, and the grouping of
each subsection's records. StagedObject::parse_unwind_info now holds
those, as mold-rust reads an object's .eh_frame in a parse_ehframe of
its own, and stage_object reads as the list of steps.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
integrate_object, ensure_lto_plugin, foreign_bitcode_arch,
read_uleb_at, next_dylib_ordinal and PlatformVersion::of_triple were
public, as were the libLTO symbol attribute constants, though nothing
outside input_files.rs or lto.rs names them. Private, they tell a
reader that the module's other functions are all their callers.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
InputSection::set_placed has no caller: the passes that place a
subsection by hand build it with flags_placed. trace_name only called
without_fat_arch, under a second name, for the -t trace and one
diagnostic; they now call without_fat_arch, which says what is done to
the file's name.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
set_platform, which read_platform_version and read_macos_version_min
call, sat among the target triple helpers, and add_linker_option
between the parse state's struct and ArgCursor. Each now follows the
option readers that use it, so the readers of the platform options and
of the auto-link and dylib options stay together. Only placement
changes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Every other predicate of Args says in a comment what kind of image it
picks out and why that matters to the link; is_kext had none. It now
names ld64's kKextBundle and MH_KEXT_BUNDLE, as is_dylinker names
kDyld and MH_DYLINKER, and points to resolve_kext for what a kext's
link takes for granted.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
resolve_fixups erased ParseState::fixup_chains for a kext or a -kernel
image, which ld-prime takes neither -fixup_chains nor -no_fixup_chains
for, and read the field back six times; nothing after it reads the
field. The option as it applies to the image is now a local, so the
function only reads the parse state, as the other resolvers do, and
takes it by shared reference.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
parse_bitcode built the nlist of each of a module's symbols inline in
its loop and spelled out all twenty fields of the placeholder object,
as ObjectFile::internal did for the linker's own object. bitcode_nlist
now makes a symbol's nlist, and ObjectFile::new gives an object with
no sections or symbols, from which both name only what they set:
parse_bitcode drops from 90 lines to 62.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
integrate_objects was 160 lines: the prefix sums of each object's
places in the arenas, the mapping of its nlists to symbols, the unwind
ranges, the local symbols' initialization and a nested helper that
moved vectors into the arenas through raw pointers. The steps are now
arena_bases, StagedObject::symbol_ids and set_unwind_ranges, and
append_in_parallel, which fills Vec::spare_capacity_mut ranges, as
mold-rust's ParallelSymbolAllocator does; integrate_object shares the
unwind ranges. The integrate timer of a large debug link is unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
parse read a YAML stub in a 100-line loop of its own after handing
JSON stubs to parse_json, whose four closures held the real work. Now
parse only picks the format; parse_yaml selects each document's
target and parse_yaml_document reads its fields, and on the JSON side
parse_json_library, json_targets, json_applies and add_json_symbols
are plain functions. memchr_from calls the memchr crate instead of
libc's memchr through unsafe code, with no change in speed.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
InputSection's doc comment had slid onto the NO_REPLACEMENT constant
above it and named a header field it no longer has; data() spoke of a
data_ptr field that is now contents; an #[inline] split align_offset's
doc comment from the function; an old summary of the dylib binary
reader headed is_public_location's; and parse_cached's comment began
with a sentence meant for parse. Each item now has its own comment.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Eleven readers in input_files.rs walked a Mach-O file's load commands
with the same offset arithmetic, three of them its segments' section
headers too, and the -bundle_loader reader and the dylib binary reader
each read the symbol table's defined externals the same way.
load_commands, section_headers and lc_string now do the walking, and
defined_externals the symbol table, so each reader says only which
commands it wants: is_mergeable and has_uuid become one line each.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
MappedFile::is_lto_output marked an object LTO compiled, so that
diagnostics could name it as ld-prime does. Every constructor set it,
the LTO pass set it to true, and nothing has read it since diagnostics
name files by their paths; the field goes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A link refuses a dylib built only for other platforms, naming them; a
firmware link takes it with a warning. For a binary, the message named
its first platform, or "zippered(macOS/Catalyst)" for one built for
both, which is ld-prime's wording. It now lists the platforms the
binary's load commands name, as the message for a .tbd file does, and
check_dylib_platforms makes the names itself for both.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The stub reader and the dylib binary reader each loaded the libraries
their dylib re-exports, added the dylibs that moved exports bind to and
registered the dylib, in two copies of the same 40 lines; add_library
now does it once. The re-export walk works on the DylibFile itself
rather than on three export sets and a result struct, and its 140-line
loop is split by where a library is found: in the link, in a file or
inlined in a stub (ReexportWalk::load, load_file and load_inlined).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A dylib of the link has some thirty fields, most of them flags that
only the command-line options set later. The stub reader, the binary
reader, the -bundle_loader reader, the dylibs that moved exports bind
to and a merged dylib's dependencies each spelled out all of them.
DylibFile::new now gives the defaults, and each reader names only the
fields its file decides, so what differs between them is what shows.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A comment saying a GOT load of a local symbol needs no slot sat in the
parallel classification, above the line that classifies every
relocation, where nothing treats a GOT load apart; the code it explains
is the GotLoad arm of the serial loop that gives symbols their slots.
The comment now sits on that arm.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
subsec_named read a symbol's subsection, then tested whether the symbol
was a local it skips, and only then looked at the subsection it had
read, splitting one eligibility test in two around an unrelated
binding. The tests are now one condition, and the absolute symbol's
case, a subsection in no segment, follows with a comment saying so.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
dead_strip_dylibs's doc comment named auto-linked dylibs as the ones
the link drops without -dead_strip_dylibs, leaving out those loaded as
another dylib's public re-export, which its strippable test counts
too. It now names both. check_libsystem_linked's doc comment had a
line broken mid-sentence; it is rewrapped.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The second half of passes.rs spelled out crate::symbol::SymbolId,
crate::error::raw, crate::input_files:: and
crate::input_sections::NO_REPLACEMENT beside the same names imported at
the top and used bare elsewhere in the file, which lengthened the
filters and signatures they appear in. They now use the imported
names, with NO_REPLACEMENT and NO_IDX added to the imports.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The passes that make the helpers and slots through which an image
reaches its lazy dylibs - create_lazy_loads, the uses, slots and helpers
it builds, and bind_dyld_lazy_load - took 240 lines of passes.rs, apart
from the chunks that encode them, while the delay-init dylibs they
parallel have delay_init.rs. They are now lazy_load.rs, whose module
comment says what a lazy dylib is; passes.rs keeps the stub and binding
helpers both modules call, and comments elsewhere name the new path.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
passes::real_path returned a file's canonical path and a fat file's
architecture for its one caller, mapfile's trace_path, which used the
path alone. trace_path now strips the slice's architecture and
canonicalizes the path itself, and the helper, whose second half no one
read, is gone from passes.rs.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
set_osec_offsets collected the output sections that have thunks into a
list only to hand it to gather_thunk_addresses, which walked that list
in output order. gather_thunk_addresses now walks the output sections
in ctx.chunks itself, a section without thunks contributing nothing,
and takes only the context, like mold-rust's gather_thunk_addresses;
its comment loses a note about a past commit.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The -u symbols, the entry point and the -alias bases were spelled out
as the same chain of options three times - in dead_strip's
initial_undefines, in mark_live_references beside it, and in passes.rs
for the undefined-symbol errors and the references they count. The
two copies now call initial_undefines, which says what the names are
for, much as mold-rust gathers --undefined and --require-defined in
one place before gc_sections.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
print_dependencies skipped a symbol an object defines itself by
comparing raw pointers to the two objects, std::ptr::eq on a
&raw const of the defining one, where the loop could simply know which
object it is in. It now walks the live objects with their indices and
skips a provider whose index is the referencing object's, as the other
passes over ctx.objs do.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
file_display wrapped obj.mf.name.raw() under a name that hid what it
printed - an archive member's name is already its archive(member)
form - and its five callers in passes.rs, dead_strip.rs and
symbol_moves.rs sat among dozens of diagnostics that print the
object's name directly. The callers now print obj.mf.name.raw() as the
rest do, and the wrapper is gone.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
add_synthetic_symbols defined the mach header's symbols, the -alias
names and the layout-boundary symbols in one function, with
__mh_execute_header written out as a copy of define_header_alias that
differed only in being external. The header symbols are now one match
over the output type and define_header_symbol, and the aliases and the
boundary symbols add_aliases and claim_boundary_symbols, so the pass
lists what it defines as mold-rust's add_synthetic_symbols does.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
coalesce_weak_defs kept its per-object scan as a forty-line closure and
carried each candidate's offsets to a serial loop that compared them,
and merge_literals deduplicated each shard in an inline closure, both
with comments about the loops they replaced. The scan is now
weak_def_losers, which also checks the offsets, and the shard's
deduplication merge_shard, so each pass reads as find in parallel, then
apply serially, as mold-rust's sharded passes do.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
dead_strip_dylibs decided which dylibs stay and which load weakly, then
rebuilt the dylib list and remapped every symbol's and moved export's
dylib index inline, before giving the survivors their ordinals. The
removal and remapping are now remove_unused_dylibs, so the pass reads
as deciding, removing and numbering, like mold-rust's
remove_unreachable_dsos, and its doc comment says all three.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Four passes gave an import a stub with the same five lines - a stub,
then dyld_stub_binder with lazy binding or a GOT slot without - and two
looked up the symbols the linker's own code calls with the same pair of
functions, bind_linker_import or else look_up_linker_import, which
differed only in the dylib they bound to. The stub is now
add_import_stub, which delay_init uses too, and the binding one
bind_linker_import that picks the exporting dylib or dynamic lookup.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
layout_segment held a twenty-line table of the __LINKEDIT tables'
alignments in the middle of its chunk loop, encoded __unwind_info with
the same six lines as finish_unwind_info, and aligned the cursor for
the code signature inside the size computation. The alignments are now
chunk_p2align, the encoding encode_unwind_info, which both callers
share, and each chunk is placed before it is sized, so the loop reads
as place, size, record, as mold-rust's set_file_offsets does.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
build_linkedit_tables nested a closure sorting the globals, an enum
local to the function and the chained-or-classic fixup encoding inside
a four-level rayon::join, with comments repeating one another. The
name-sorted globals are now sorted_globals and the fixup encoding
build_fixups, which returns a Fixups whose classic variant names its
streams, so the task group reads as the list of tables it builds;
mold-rust likewise sizes each chunk with its own update_shdr.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
copy_chunks carried three stacked doc comments, two of them about
functions long gone, and computed the UUID and the page hashes for the
code signature inline after the copy. The UUID and its hashes are now
compute_uuid, which returns the hashes the signature is made of, as
mold-rust writes its build ID in write_build_id once the chunks are
copied; copy_chunks keeps one doc comment and reads as the sequence of
copy, fixups, symbol table, header, UUID and signature.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
report_undef_errors interleaved two decisions in one loop, turning the
symbols dyld may look up into flat-namespace imports and reporting the
rest, and built the map from symbol to referencing object behind a
closure that cached it in an Option on the first error. It now splits
the referenced undefined symbols into imports and errors first, and
builds the map (first_referencers) only when there are errors, as
mold-rust's report_undef_errors reports from a list made beforehand.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Eight __LINKEDIT tables (the dyld info streams, the chained fixups, the
export trie, function starts, split info) and the -sectcreate sections
each had a copy_buf of its own whose only job was to copy the bytes
built during layout into the chunk. copy_buf in chunks/mod.rs now
names copy_contents for those chunks, so the dispatch shows which
chunks are prebuilt bytes and which write themselves at copy time.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
copy_buf copied the members in parallel by indexing the section's
buffer through a raw pointer wrapper, with a SAFETY argument that the
members' ranges are disjoint. for_each_member, ported from mold-rust,
splits the buffer at the members' offsets instead and hands each
member its own slice, so copy_buf needs no unsafe code; the thunks,
which lie between the members, are written after them as mold does.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
data_seg, objc_refs_are_const and static_name in output_sections.rs
and create_load_commands in chunks/mod.rs were exported to the crate,
or beyond it, though nothing outside their own file calls them. They
are private now, so a reader sees at once that only their file depends
on them.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
isec_entries checked whether the lazy-load and delay-init site maps
were empty before each lookup, through then/flatten chains, to save the
hash of a relocation's site. hashbrown's get already returns None on
an empty map before hashing, so the lookups are now plain gets, and
the code says only that a site rewritten to a helper refers to it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
link placed the relocations through three matches over the section
kinds, one per use (file offsets, headers, copying), kept the merged
sections' offsets in a vector and the synthetic ones' in a field, and
wrote the whole file inline. Each section's relocations are now one
list in output order, FileLayout::new places them with the tables after
them, and write_object builds the file, so link reads like mold's
combine_objects: lay out, build the tables, write, report.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
encode_export_trie was 211 lines holding the export list, two nested
functions numbering the nodes, the sizing, the placement and the
parallel writer, which reached the nodes and the buffer through three
pointer wrappers and wrote each node's offset back through a raw
pointer. exports, number_nodes, place_nodes, write_nodes and
write_node are now passes of their own; the numbered nodes are shared
references, place_nodes returns the file order with the offsets, and
write_nodes cuts the buffer into the nodes' slices in that order.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
build_chained_fixups chose the import format, wrote the header, every
segment's starts table, the import entries with their ordinals
computed in a nested closure, and the names, all in 163 lines; its doc
comment had drifted onto the ChainedFixups alias. It now writes the
header and the names and calls write_starts_in_image and write_imports
for the two tables, with import_format and import_ordinal named for
what they decide, and the doc comments are back on their items.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
encode_unwind_info ran every step of the encoding in one 195-line
body: collecting and sorting the records, numbering the
personalities, merging equal neighbors, cutting the pages and writing
the table, with the pages as bare tuples. Each step is now a function
named for what it does (table_records, assign_personalities,
merge_records, split_pages, write_table, encode_page for one
second-level page), the pages are a Page struct, and
encode_unwind_info reads as the sequence of the steps.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The two option pairs shared an arm each, which matched the option's
name a second time only to pick the list or the vector it fills. Each
option now has an arm of its own that names its list or vector
directly, as the options with nothing else in common with their pair
do; the arms are no longer than the shared ones were.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The executable, the two target crates and the driver each spelled out
Arc<[Cow<'static, OsStr>]> for the expanded command line and
Result<i32, &'static str> for a link's outcome, five signatures in all.
driver::Cmdline and driver::LinkResult, mold-rust's names for the same
two types, say what is passed and returned, and link_for_target fits
a line again.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
resolve_defaults gave LC_SOURCE_VERSION -source_version's number or 0,
and resolve_env_source_version, two steps later, overwrote the 0 with
$RC_ProjectSourceVersion's. The number is now chosen in one match
where the command is decided: the option's, else the environment's
unless -no_source_version, else 0. check_arch_options moves ahead of
resolve_defaults, which keeps the environment's warning after its error.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The parent waited for the child's byte with libc::read on the raw
descriptor and the child sent it with libc::write in an unsafe block of
its own. mold-rust's subprocess turns each end of the pipe into a File
and uses read_exact and write_all, which say what is read and written
without buffers or casts; this port of its fork_child and
notify_parent does the same.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
write_stubs computed a stub's displacement to its pointer for the
range check, then computed it again to write it into the jmp. The jmp
now takes the checked value, so what is written is visibly what was
checked.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The lazy and delay-init load helpers both end by loading the GOT slot
into the register the call site loaded, and each computed the REX
prefix and the ModRM byte for that register itself. movq_rip encodes
the instruction up to its displacement, with a comment saying which
bits carry the register, and both helpers use it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
symbols_json spelled out the same five fields of a linked dylib's entry
twice, once for the dylibs with load commands and once for those dyld
loads lazily, which differ only in their attributes. dylib_entry makes
an entry from a dylib and its attributes, so the lazy ones are one line
and the others show just what sets them apart: the attributes they
collect and the exports of a re-exported one.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Nfa::matches collected the values of the accepted states with
states.iter().enumerate().take(num_words), but the state vector is a
clone of initial_states and so has exactly num_words words: the take
limited nothing and made a reader look for a case where it would. The
loop now reads as mold-rust's Nfa::matches does.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
write_sdk_imports walked the symbol table for the used imports of each
dylib with the same filters as dylib_imports, which the trace files
already use for the same lists; it now takes those lists and keeps the
names the API list allows. The report's glob import of crate::macho
already covers the names it imported again inside the function.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
get_macho_target, get_macho_filetype and get_file_type each checked the
data's length and magic before looking at the mach header; macho_header
does that once, so the first two are one line each and get_file_type
asks it for the file type. The universal-file check compares the bytes
with FAT_MAGIC, big-endian as such a header is, instead of swapping the
magic of a mach header read from them.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
parse_hex and parse_decimal were only ever called on cur.next_text of
the option they parse, eleven times; ArgCursor::next_hex and
next_decimal take the argument and parse it in one call, as next_text,
next_path and next_bytes already do for theirs, so the arms of
parse_args that read numbers say what they read once. A few of them
now fit a line.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
-objc_stubs_small was read before the shared-region check, which
refuses it on either architecture, and only cut down to arm64 at the
far end of resolve_options; the two lines now sit around that check.
The __oslogstring page of an encryptable image moves into
resolve_encryptable, and check_options tests -segment_order's length
itself instead of through a four-line helper without a comment.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
check_dylib_use asked shared_region_eligible again whether the image is
bound for the shared region, though resolve_shared_region had already
stored the answer in Args::shared_region and nothing it depends on
changes in between. With the stored flag tested there, the predicate
has one caller left and moves into resolve_shared_region, whose comment
already lists what makes an image eligible.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
resolve_lazy_load and resolve_delay_init each listed their libraries,
checked for macOS 27 or 15 and up, and warned of every library the
image's target is too old for, in two copies that differed only in the
library kind, the version and the word in the warning. dyld_supports
does that once and says whether there was a library at all, which
leaves each resolver the one line that is its own.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The comment on what a -preload image leaves out sat above the common
symbol alignment, which has its own one-line comment, with the block it
describes after both. The alignment now comes first, and the paragraph
heads the block that clears the code tables, versions, signature and
run paths of a -preload image.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
After the option loop, parse_args settles the target, the defaults, the
options that depend on one another and the checks, in that order; the
functions for those steps now follow it in the same order, each with
the helpers only it uses after it, instead of scattered among the
option readers (resolve_lazy_load, apply_target_triple, trace_env,
check_fixup_sections ...). Args's two impl blocks become one, after the
struct. Nothing but the placement changes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Every list add_patterns fills gives its names and patterns the value 0,
so the parameter goes; symbol_move, whose names take 1 and patterns 0,
adds a pattern to its builder directly instead of going through
add_patterns with a list of one. read_alias_list takes its lines from
symbol_list rather than repeating its comment and space stripping.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
-pagezero_size goes into ParseState as an Option, as -segalign,
-headerpad and -stack_size do, and resolve_pagezero_size settles
__PAGEZERO's size from it in one match: none outside a main
executable, the option's size, none in a -kernel image, else 4 GiB.
Args loses explicit_pagezero, a parse-time flag that only told the
resolver whether the default had been overwritten.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Args derives Default now, and initial_args, which parse_args starts
from, lists just the dozen options whose defaults aren't zero, false or
empty, next to what the environment variables say. The 200-line manual
Default gave every field a value, most of them placeholders that
resolve_defaults and resolve_options overwrite (pie, data_const,
headerpad, ...), which read as defaults they are not. The unread
-dynamic field goes: like ld64, the option is a no-op.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The DWARF reader's u8, u16, u32 and u64 each checked the bounds and
advanced the position by hand, skip did so a fifth way, and cstr_at
repeated Reader::cstr's search for the terminator. As in mold-rust's
gdb_index Reader, take(n) returns the next n bytes and moves past them,
the fixed-size reads and skip are built on it, and cstr_at reads with a
Reader at the string's offset.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Symbol::new and is_defined sat in an impl block of their own after
SymAux, far from the struct and from name(), file() and the other
accessors of the fields new() initializes. They join the first impl
block, the constructor first, as mold-rust's Symbol has it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
relative_methods and read_method_list each parsed a method list's
entsize and count and checked that its 24-byte entries filled the
subsection, before their own tests of the flags. classic_list_header
does the common part, so each says only what it requires of the
entsize field: no relative flag and 24-byte entries, or exactly 24.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
cputype_name matched CPU_TYPE_ARM64 and CPU_TYPE_X86_64 and returned
"arm64" and "x86_64" as literals, a second copy of what each target's
CPUTYPE and NAME say, which canonical_name right above it already uses.
It now matches Arm64::CPUTYPE and X86_64::CPUTYPE and returns their
NAMEs.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
merge_objc_categories's description had a sentence run past the
margin, and branch_shims's module comment a line break in the middle
of "not a -static or -preload one". Both paragraphs are refilled,
word for word as they were.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The accessor tested for an undefined external nlist, but every reader
of undefined symbols goes through the symbol table or tests n_type()
itself, so it had no caller left. is_common, which tests the same
fields plus a nonzero size, stays.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
arm64.rs and x86_64.rs each defined the same write32 and write64, and
arm64.rs a read32, for the instructions and relocated fields they
patch. They now live in target/mod.rs, private to the target modules,
as mold-rust's targets share one set of endian helpers.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
DigestMap::new spelled out three zeroed atomics per slot in a nested
struct literal, and the map's description sat on DigestSlot. As in
mold-rust's icf.rs, DigestSlot derives Default, so new() is one line,
and the comment describes DigestMap, whose definition comes first.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
SymbolTable::get built a probe Key by transmuting the name to
'static, with a comment calling the unsafe trick "safety-free". A
Query borrowing the name, hashing by the same precomputed hash and
compared through hashbrown's Equivalent, is how mold-rust's symbol
table looks names up without that, and so does ours now.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
coalesce_objc_refs declared its key types, a closure resolving a
pointer's target and the per-section key computation inside its one
loop, 118 lines whose local enum Target hid the Target trait. The key
types are now RefTarget and RefKey at module level, ref_target and
ref_key compute them, and coalesce_objc_refs only groups the records
by key and folds the later ones (23 lines). objc_pointer_at also reads
the relocation objc_pointer_reloc finds instead of searching anew.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
compile_unit_name collected the attribute specifications of every
abbreviation it walked past into a vector, only to keep the matching
one's. find_abbrev, as mold's gdb_index names it, skips the others and
returns the matching abbreviation's tag and a reader at its
specifications, which compile_unit_name then reads in step with the
DIE's values. compile_unit_name goes from 93 lines to 76.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
apply_relocs had four arms for the adrp and the ldr of GOT and TLV
loads, two copies of the same relaxation that differed only in which
symbols relax (a TLV load ignores branch_shims' 4 GiB distance) and in
a GOT load's taking a 64-bit add as relaxed already. relaxes_load now
decides both kinds by the relocation, so one arm handles the adrps and
one the ldrs, and the two differences show where they apply.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Both targets' read_relocs looked up the section a non-extern record
names with nonextern_target_section, failed the link the same way when
there was none, and made the address relative to it, in eight lines
each. target::section_target does all three and returns the target and
offset, so each read_relocs only says where its target's address comes
from: the field itself, or on x86-64 a pcrel field's end plus it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
write_lazy_helpers built its adrp, add and branch encodings as closures,
the same ones DelayInsn provided for the delay-init stubs and helpers.
HelperInsn now serves both, with ldr, b and bl spelled out instead of
raw opcodes and a ret_or_back for the load helpers' last instruction,
and write_code writes each helper's instruction list. The lazy helpers
lose 17 lines (98 -> 81) and the two copies of the encodings are one.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Both targets rewrote a GOT load of a lazy or delay-init dylib's symbol
into a call of its helper with two near-identical branches each, one
per helper table, and found the helper through site_helper on one
target and the sites map on the other. target::load_helper now looks
the site up in either table and says whether the helper is the site's
own, so arm64 has one match arm and x86-64 one block for both kinds.
(A dylib can't be both lazy and delay-init, so the order is moot.)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The test prints each class's bundle path relative to the app, replacing
NSBundle.mainBundle's path with @app. The main bundle's path is the
executable's real path, while a framework's bundle path is the one dyld
loaded it by, so under /tmp (a symlink to /private/tmp) the two spelled
the app's directory differently and nothing was replaced. Both paths are
now compared with their symlinks resolved, so the test passes wherever
its output directory is.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
mold needs Rust 1.95 for std::hint::cold_path and the atomic update
methods, while its dependencies need only 1.85. Declaring it makes Cargo
reject older toolchains with a clear message before building, and lets
the resolver avoid dependency versions that need a newer rustc.
This reverts 6f8dbb64b. mimalloc uses 64-bit atomics, which GCC
implements as calls into libatomic on hosts such as 32-bit PowerPC.
The need comes from linking mimalloc statically into mold, so the
executable is the right place to add libatomic. Our mimalloc_rust fork
is pinned to the commit before its libatomic change, so the fork
differs from upstream only by the bundled mimalloc version.
copy_mach_header built the header, then patched its flags with seventy
lines of conditions through a rebinding of the header, then copied the
load commands. mach_header_flags now computes the flags, as mold's
write_ehdr takes e_flags from E::eflags, and binds_to_weak names the
long MH_BINDS_TO_WEAK condition; copy_mach_header is left to fill the
header and lay out the commands.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
create_function_starts_cmd and create_code_signature_cmd each made a
linkedit data command the way the split info, data in code and the
other tables call create_linkedit_data_cmd directly, and the public
output_install_name only returned Args::output_install_name, which
every other caller uses. create_load_commands and create_id_dylib_cmd
now make those calls themselves.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The dylib, LC_ID_DYLIB and single-string load commands each appended
their string and then patched their cmdsize with the same three lines.
append_string now pads the command, sets its cmdsize and hands it back,
so each builder ends with the one call that says what the command
carries.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
assign_input_sections inlined the naming, creation and caching of
output sections in a five-level loop, and record_section repeated the
naming and creation. destination now names where an input section
goes, add_output_section_for makes the section, and OutputSectionTable
caches them by key as mold's create_output_sections does, noting each
key's zero-fill and TLV kind once; the loop keeps only the run cache,
now keyed by (file, shndx) instead of the old header pointer.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The lazy-bind stream spelled out the encoding of its records' opcodes
(the segment and offset, the three forms of a dylib ordinal, the
symbol) a second time, and the weak-bind stream wrote its overriding
symbols' opcodes by hand into a prefix buffer that encode then had to
take. bind_info.rs now has encode_op for one opcode, which encode, the
lazy-bind records and the weak-bind overrides (now Op::Symbol ops ahead
of the binds) all go through.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The rebase, bind and weak-bind streams and the chained fixups each
walked every live subsection's relocations with the same five-term test
for an 8-byte absolute pointer and computed its address the same way.
rebase_info.rs now has is_pointer_reloc for the test and pointer_relocs
for the walk, which yields each pointer with its address, so every
stream states only what it does with a pointer; the kext's external
relocations use the same test.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A mergeable record's fixups say what the objects' relocations said, and
ld-prime has kinds both for one instruction (ARM64_ADRP, ARM64_LO12,
ARM64_ADRP_GOT, ARM64_LD12_GOT) and for an ADRP fused with the add or
load of its page offset, which it writes for its objects' pairs. Taking
the pairs apart changes nothing: ld-prime merges a record of single
kinds into a byte-identical image, GOT loads of local symbols relaxed
to adrp/add all the same, and both linkers' merges run alike.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A provider's DOF lists its probe sites for the kernel to patch when a
probe is enabled. ld-prime makes the DOFs before dead stripping and
makes each a root that refers to every site, so -dead_strip keeps a
function nothing calls, and all it calls, for probes that can never
fire. The DOFs are now made once dead stripping is done, of the sites
in live code; identical code folding, which runs later, still leaves
the functions with sites apart.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The hook mold links into an image for merged libraries' classes has an
initializer in __mod_init_func that installs an Objective-C image-name
hook, so that NSBundle's bundleForClass: finds a class's framework
bundle. It was named __ZL11constructorv, ld-prime's hook's C++ name,
which only its static-initializer diagnostics print; the C function now
keeps its own (_install) and the tests find either linker's hook by its
table of classes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A GOT load of a delay-init dylib's symbol becomes a branch to a helper
in __TEXT,__delay_helper that has the dylib dlopen()ed and then loads
the symbol's GOT slot into the load's register: one per symbol and
register, or per load in arm64 code that keeps its link register live.
ld-prime lays the helpers out by symbol name, which only orders the
section, so they now go in the order of their first uses and each
rewritten site keeps the index of its helper as made.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A call to a delay-init dylib's symbol goes to a $delayInitStub that has
the dylib dlopen()ed once and then jumps through a __got slot bound to
the symbol at launch, as a GOT load's helper reads one. ld-prime gives
the stub a second slot of the same symbol when GOT loads already have
one; both are bound to the same address, so the stub now uses the
symbol's slot, which it makes if nothing else has.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A delay-init dylib's dlopen helper keeps a flag word in __DATA,__data,
set once it has dlopen()ed the dylib, and the lazy-binding stub helper
passes the address of its own word there, __dyld_private, to dyld. Both
are words of the linker's own after the inputs' data; ld-prime puts the
flag words ahead of __dyld_private, which only orders the section's
contents, so each word now stays where it was made.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Each delay-init dylib's dlopen helper passes its install name to
dlopen() from a C string the link adds to __TEXT,__cstring. ld-prime
merges an input's __cstring literal of the same bytes into that string,
as it merges literals in general; mold only merges the inputs' literals
among themselves, so the image may hold one more copy of the name, which
changes nothing but its size.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A delay-init dylib is bound at launch but initialized only when the
image first calls into it through a stub or helper, so a pointer to its
symbol in data, which dyld binds before any initializer runs, is
refused; so is an __objc_classrefs slot, a pointer to a class. Each
such reference now reads as a use of its symbol in its subsection,
without ld-prime's own wording for classes ("cannot be delayed when
targeting an older OS versions") or "anon" for a record list.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A DOF lists a provider's probes, each with an instance per function it
has sites in, and each instance's slots of probe sites and is-enabled
tests; the kernel's fasttrap registers them all whatever the order.
ld-prime has its probes by name, all probe sites before the tests, and
a probe's newest instance first, and it gives each site of a function
whose name is over 127 bytes an instance of its own, which the kernel
then drops. Now all go in site order, one instance per cut name.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A DOF lists each probe's argument types as strings, which the kernel
only bounds in length; libdtrace compiles a string when a D script uses
args[n], falling back to an untyped argument if it can't. ld-prime's
DOF has the names libdtrace's D compiler gives the types (unions and
enums as structs, pointers to D's typedefs renamed). The DOF now keeps
dtrace -h's spelling from the symbol, but for each of the provider's own
typedefs, which only the D script knew: those are ints.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime has libdtrace rebuild each provider's D script from the
___dtrace_* symbols and compile it, failing with libdtrace's messages
when a stability or typedefs symbol is missing or doubled, a stability
string is malformed, a probe has only is-enabled tests, or code calls
a symbol that is no probe - none of which dtrace -h writes. The DOF now
takes a provider's first stability and typedefs symbols (D's default
attributes without one) and refuses only a probe encoding but v1.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A DOF registers each probe site under the function it is in, which the
kernel shows as the probe's function field: the label of the site's
subsection, less its leading underscore. dtrace.rs found the labels by a
scan of its own that ranked each object's symbols as
Context::subsec_label does; probe sites are few, so it now asks
subsec_label for each.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A dtrace -h header has code call ___dtrace_probe$... functions that
nothing defines; a final link turns each such 4-byte call into a nop
(an is-enabled test's into zeroing its result) and lists its place in
the provider's DOF so the kernel can patch it when the probe is on.
ld-prime only looks for sites in sections of pure instructions and
leaves a call from any other section a branch to address 0. Every such
branch is now a site, wherever it is.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A mergeable record lists the dylib's imports as entries, and a merging
link binds each as an import of its own image. ld-prime adds every
initial undefine that resolved to a dylib (-u, and the _dlopen that
-delay-l forces) whether or not any merged code refers to it, so each
merging image imports it too. Now only what fixups reach is recorded,
as a link of the objects would import, plus dyld_stub_binder and the
_objc_msgSend the objc stubs call.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A mergeable record's header flags word carries the objects' Swift
versions and image info bits (26, 28, 29), which ld-prime's merge reads
and both linkers rebuild __objc_imageinfo from, and bit 31 for classes,
which gates mold's hook for merged libraries' classes. Flipping bit 30
(Swift or Objective-C metadata) or bit 24 (subsections via symbols) on
a record changes nothing ld-prime merges, so bit 30 is left clear and
bit 24 says only whether every object had MH_SUBSECTIONS_VIA_SYMBOLS.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The objects' __LD,__compact_unwind records go into a mergeable record
as hidden entries of no name, whose pointer fixups tie each one to its
function; a merging link makes __unwind_info of them as of an object's.
A record that started at a label (the assembler's ltmpN at the section
start) was recorded as a regular entry under that label's name, as
ld-prime does, which only names a temporary label: both linkers merge
the same image either way.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A mergeable record's fixups name their targets by entry index, and
both ld-prime and mold rebuild the objects from the entries whatever
their order: a fully reversed record merges and runs the same. So the
entries now go object by object in subsection address order, each
alias where its subsection's symbols are, then the names the objects
import or leave to the linker as first referred to, then the linker's
own, numbered by plain offsets instead of ld-prime's sorted order.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
C++ code passes ___dso_handle, the image's own Mach-O header, to
__cxa_atexit for its static destructors, and every final link defines
it. A mergeable record lists such linker-defined names as UNDEFINE
entries, which the merging link resolves afresh. ld-prime instead makes
___dso_handle an alias of an undefine of segment$start$__TEXT; both
forms resolve to the merging image's header, and ld-prime merges either
into the same image. The reader keeps taking ld-prime's form.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A mergeable record ties each entry to the debug notes (N_SO, N_OSO) of
the object it came from, and a merging link writes N_FUN, N_GSYM or
N_STSYM notes for the entries of each object. ld-prime ties none of the
subsections it splits by content (literals, Objective-C metadata) to
an object; the record now ties every named entry but an assembler
label, as the symbol table already notes them, and the reader notes
every entry its record ties. dsymutil ignores names no DWARF describes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The relative method lists and the records category merging writes are
the link's own subsections, so a mergeable record has entries of no
symbol for them. Naming them after a symbol that moved there or a
label (__OBJC_$_CLASS_METHODS_..., __OBJC_CLASS_RO_$_...) only puts
those locals back in a merging image: fixups refer to entries by
index, and ld-prime's merge still fills a merged class's list slots
with the merging image's categories when the entries are anonymous.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Every entry of a mergeable record that has a place in the object it
stands for gets a symbol, its name or a private label, but for the
__eh_frame records, which only other __eh_frame records refer to; an
import or a linker-defined name gets an undefined one. A fixup whose
target has none can only come from a corrupt record, which may make
the linker panic like any other broken input.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A mergeable dylib's record turns back into an object file whose
sections the merging link lays out like any input's. Where its entries
go inside an output section is not something dyld, the runtimes or a
debugger look at, so the object now has one section per name, flags and
alignment, filled in record order: no reopened sections that mimic the
objects' interleaving of __StaticInit with __text or of the literal
pools with __const, and no method lists put back in the dylib's order.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
create_dof_sections records, for each DTrace provider, the sites its
DOF section refers to, which identical code folding keeps apart, and
the DOF's own subsection, which -why_live read to credit the DOF to
the first object referring to it as ld-prime does. -why_live now
follows the subsections' parents alone, so nothing reads the field
and it goes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
-remove_swift_reflection_metadata_sections drops Swift's field
descriptors and the like, so a live reference to one (a type
descriptor's) has no target address and fails the link. ld-prime finds
the first by address as it writes the output; mold picked that one and
checked it after the map. Now each live reference is reported right
after dead stripping (in a -r link, after the drop itself), as a plain
error naming the referring subsection.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A pointer needing a fixup in a segment mapped without write permission
(a text relocation) and an x86-64 32-bit pointer dyld can neither slide
nor bind fail the link. ld-prime lists the text relocations per output
section from each subsection's last back (with rebase opcodes the first
section's only) and reports one 32-bit pointer chosen by a chain-order
rule, or none when there are text relocations. Now both kinds are
reported in full, sorted by address.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
-segaddr pins can make segments overlap, which is refused. ld-prime
checks before it places the floating segments, putting each right
after the one before it, so a floating segment that runs into the next
pin is refused though placement would move it clear. Now the check
looks at the segments where place_segments put them: only pins that
conflict (or an -image_base inside __PAGEZERO) fail the link.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A layout the image can't have - segments out of order, a section past
its segment's 32-bit file end, a broken thread-local template - fails
the link. ld-prime prints its "final section layout" once or twice per
such error, with __LINKEDIT unsized, after writing -dependency_info, and
mold kept the first layout error aside to match. Now these are plain
errors and the link stops at the next checkpoint; error.rs loses its
layout-error state, and the reports are written only for a link that
gets as far as writing its output.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
DylibFile::load_order held each dylib's place in ld-prime's order of
load commands. Load commands now follow the order the dylibs are
first named, and the lazy-load tables follow first use, so the field
was always u32::MAX and nothing read it; it goes with its initializers.
The output is unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime orders LC_LOAD_DYLIB commands by groups: the command line's
libraries in naming order with lazily named ones last, then auto-linked
and implicit ones together by install name, then the libraries
$ld$previous moved exports to, by source library and first export.
Now one key orders them: where a library is first named - by the
command line, an auto-link option, or the dylib that re-exports it or
whose exports moved to it, whose priority is taken before its re-exports
load. Command-line libraries keep their order; the dylib set is equal.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A relocation that can't be applied fails the link. ld-prime names the
fixup's kind after the instructions it makes, a subsection by its own
anon-N numbering of an object (or of its stubs-got-file or inits-file),
the object by its leaf name, and reports only the first error in the
file. Now each error says "path: subsection+0xoff: message", naming a
subsection by its symbol or section and offset and a stub as "stub for
<sym>"; every error is reported, and the same links fail.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The Swift compiler keeps an overlay loaded with a weak reference to its
__swift_FORCE_LOAD_$_<overlay> marker, stored in a hidden __DATA,__const
word that nothing reads. ld-prime keeps the dylib as a (weak) dependency
but writes no fixup for the word, which stays zero; mold skipped the
bind and the text-relocation check for it. The word now binds as any
weak import does - to the marker, or to zero without it - and the load
command is unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Tentative definitions no definition replaced become zero-fill symbols
in __DATA,__common, each as large as its largest declaration. ld-prime
lays them out in the order of the objects' symbol tables, each at the
place of the first largest declaration, which mold matched by comparing
each nlist's size with the merged one. The order means nothing, so each
now goes where its first declaration is; the sizes are unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An undefined -u name, entry point or -alias base fails the link even
under -undefined dynamic_lookup. ld-prime names the file wanting it by
its internal files: "<initial-undefines>", or the alias in its
"command-line-aliases-file" unless -dead_strip strips the alias, which
mold worked out by testing the alias for liveness. Now such a symbol is
wanted by "the command line"; which names must resolve is unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
dyld finds a stub's or GOT slot's symbol through the indirect symbol
table and the bind records, so the order of the entries means nothing.
ld-prime sorts stubs (and with them lazy pointers, helper entries and
indirect symbols) by name, and GOT slots by what fills them - the image,
the bundle loader, each dylib by ordinal, weak and flat lookups - then
by name. Now both keep the order scan_relocations first reached them in;
finish_stubs only works out which stubs bind lazily.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime sorts the macOS 27 lazy-load tables by name: __lazy_load_got
slots by symbol within each dylib, the _lazyLoadFlag$ words by install
name ahead of __dyld_private, the __lazy_helpers by helper name, and it
writes the LC_LAZY_LOAD_DYLIB_INFO records and their load commands in
reverse load order. dyld finds each record by its load command, each
dylib's slot chain by its record, and each helper by its call site, so
mold now lays all of them out in the order the image first uses them.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime makes __dyld_lazy_load an initial undefine of every final
image that names a lazy dylib (macOS 27 on) and keeps it alive from an
empty subsection it appends to __text, which gets an __unwind_info entry
of its own and a mergeable-record entry. Only the helpers that stand
for calls and GOT loads of a lazy dylib's symbols call it, through its
stub, so mold now binds it (like dyld_stub_binder, before unused dylibs
are dropped) only when the image uses such a symbol; a link that does
and finds no dylib exporting it fails as for any missing import.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A library an auto-link option names that doesn't let this link name it
directly (its allowable clients) is left out. ld-prime opens such a
library only for a symbol still undefined, so it warns only of one
that would provide one, which mold worked out by reading the library's
exports again. Now the warning joins the auto-link misses, given only
if symbols stay undefined, as a library or framework not found is.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An input that can't be opened or mapped, or a dylib without LC_UUID,
stops the link. ld-prime names the file in the error's own words and
again after them ("... path=X in 'X'"), and names a dylib missing its
LC_UUID twice too; mold kept a wrapper adding the second name. Now the
error is unreadable_file's "cannot open X: <strerror>" (or "cannot
map" for a directory) alone, and the LC_UUID error names its file once.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A library, framework or archive the command line names that isn't
found stops the link. ld-prime looks the frameworks up only after all
the libraries, so a missing library is reported over a framework named
before it, and lists -possible_framework's misses after the libraries'.
Now one loop goes in command-line order: the first missing input of
either kind is the one reported, and the possible ones' misses keep
their order too; the same links fail.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An image that loads no dylib is refused once dead_strip_dylibs has
decided which dylibs stay ("must link with libSystem"). Making the
first lazy stub binds dyld_stub_binder earlier, and ld-prime checks
for libSystem before it fails to find that symbol, so mold ran the
libSystem check there too. Either error refuses the link; the early
check only chose which message comes first, and is gone.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A lazily bound stub's helper enters dyld through dyld_stub_binder,
which ensure_stub_binder binds when the first stub is made. ld-prime
makes the symbol an initial undefine of every lazy-binding image, stubs
or not, so the library exporting it counts as used (the commit adding
it noted only that -map then lists libdyld). Nothing refers to the
symbol without stubs, so it is no longer bound up front; legacy
LINKEDIT's dyld_stub_binding_helper lookup stays.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A segment holding only zero-fill sections has filesize 0, so nothing
maps its file bytes and its fileoff is never read. ld-prime records
fileoff 0 for one, as it does for the zero-fill sections themselves;
mold special-cased the segment to match. Now it gets the running file
offset like any other segment, as one of empty regular sections does,
and the test checks only that such a segment takes no file bytes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
-assert-weak-l loads a dylib weakly but leaves its imports as the
references make them, and refuses the link if one is strong. ld-prime
lists, after the files referring to each symbol, its internal file of
GOT slots ("stubs-got-file", or "deferred-dylib-file" for a
delay-init dylib) and words a lazy dylib's refusal otherwise, naming
no files. The refusal now has one form: the dylib, then each symbol
with the files that refer to it strongly.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
-no_weak_imports and -weak_reference_mismatches error go through each
object's imports and name the weak ones, or the object referencing an
import otherwise than those before it. mold walked an object's
subsections section by section from the last address back, each one's
relocations reversed, to report in ld-prime's fixup order; the order
only orders messages, so subsections and relocations now go as stored.
Which object counts as mismatching is unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime names an input in its diagnostics by its realpath(3), an
archive member as /abs/libfoo.a[2](foo.o) by its place among the
archive's entries, a fat archive's with the slice's architecture too,
and by the leaf name in undefined-symbol and -assert-weak-l reports.
Every diagnostic now names a file by the path it was found at, an
archive member as libfoo.a(foo.o) and a fat slice with its
architecture; a ThinLTO module's ID is that name and its index.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime makes arm64 branch islands only among the code sections of
__TEXT, so a branch from or to code of another segment (a kext's
__TEXT_EXEC, a custom code segment) out of a b/bl's 128 MiB reach is a
fixup error. mold's thunks serve every executable section, as in mold.
The room bounds that let a branch skip a thunk hold within one segment
only, since segment placement may move the others apart, and a branch
to code 4 GiB away goes through its target's shim stub, not a thunk.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime places arm64 branch islands in clusters at most 124 MiB of
code apart, each island a b to the next, so no branch can cross a code
subsection of that size; it warns of each such subsection and fails a
link that must branch across one. mold's thunks are adrp/add/br
sequences that reach 4 GiB, placed before the subsection, so the link
succeeds and the warning describes a limit mold doesn't have.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A symbol a -move_to_rw_segment or -move_to_ro_segment list names that
its option may not move (code for rw, data written at run time for ro)
draws a warning; the subsection stays where it is either way. ld-prime
orders these by its own walk over objects and the files it makes for
TLV descriptors, relative method lists and -alias names, and calls each
section by its internal kind name. Warn as the symbols are met, say
"code" or "data", and name the object by its path (an alias by -alias).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
-commons error refuses, and -warn_commons warns of, a tentative
definition a dylib defines too; a re-export list naming what a
re-exported library exports already draws a warning. ld-prime fails
-commons error only once it has found no duplicate symbol, names a
dylib's definition by the real path of the private library it merged
or the stub it would find, and groups the warnings by file. Now the
check reports each conflict directly, files by their paths, and the
re-export warnings come by symbol; the merged files go unrecorded.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
-poison_symbol fails a link with a live reference to a named symbol
and lists, under each symbol, where it is referenced. ld-prime lists
every reference, a function twice for two, counting an arm64 adrp and
the add or load completing it as one, which mold matched by pairing
relocations. Now each referencing subsection is listed once per
symbol, so no pairing rule is needed; a SUBTRACTOR's subtracted symbol
still counts as no reference.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
-trace_implicit_libraries lists the auto-link hints of the objects and
the libraries dylibs re-export. ld-prime prints the command line
objects' hints, then what it loads for them as it goes, filtered by
whether the parent was loaded directly, then the hints again, and a
stub's re-export once per platform it has. Now one pass after
resolution prints each live object's hints in option order and each
library re-exported (merged privately, or an implicit dylib) once.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
-why_load names the symbol that pulled each archive member in, or the
option that loads its archive whole. ld-prime reports as it resolves,
round by round (again after LTO), and the members -force_load or -ObjC
loads as it parses each archive, last member first; mold kept the
state to replay that. Now one call after the last resolution round
prints each loaded member in input order with its reason, naming it by
its path, archive(member).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
-t prints the files the link read. ld-prime's listing has rules of its
own: an object as often as it is named, everything else once, and a
library loaded as another's re-export but named later by a symlink
under the naming's path alone, so mold took back the first line. Now
the files gathered as read are printed once each, the re-export's file
and the naming's both, which is what was read.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
-why_live modeled ld-prime's own reference graph to print its report:
every chain to a matching node, symbols in objects without subsections
as nodes of their own, the mach header and DOFs as boundary-file and
dtrace-file nodes, and imports under the real path of the library a
dylib merged them from. The walk now records only the subsection that
first reached each one (breadth first, from the same roots as without
the option) and prints, per live symbol, that chain down to the root and
its reason, naming files by their paths and subsections by their symbol.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Two bitcode files defining one symbol strongly can't be merged by
libLTO. ld-prime notes such duplicates before LTO but reports them
after it, compiling the ThinLTO modules regardless, skipping the merge
on a clash, listing its merged object too and ordering each list by
file kind. Now the bitcode-bitcode duplicates fail the link before
LTO; the merge is unconditional, and a bitcode-vs-Mach-O duplicate is
still found after LTO in the compiled object, files in input order.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An object's LC_LINKER_OPTIONs are read as a command line of library
options; unknown words, options missing their argument and the weak,
re-exported or upward forms are dropped with a warning. ld-prime reads
them twice - as it loads an object from the start, after the object's
other warnings, and for every loaded object after resolution - and
warns each time. Now load_autolink_deps warns as it first reads an
object's options, in the order met, and load_pending warns of nothing.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime checks dylibs and objects in one pass in input order,
interleaving each dylib's diagnostics by the input that named it,
warns of a newer dylib once per naming and defers a foreign platform
error to that point. None of that changes which links fail. Now a
dylib for another platform is refused (warned of in a firmware link)
as it is read, each named dylib built for a newer OS is warned of
once, and objects are checked in one loop over the live ones.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A mergeable dylib merged into the image that was built for a newer
macOS than the link's gets the warning of a dylib where its object is
checked with the inputs. ld-prime gives the same warning again as it
merges the dylib's code, once every input is checked; the repeat adds
nothing, so warn_merged_library_versions and its driver call go and
check_object_version's warning is the only one.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
-allow_sub_type_mismatches links an x86_64h object (or bitcode) into
an x86_64 link with a warning, given as the file is read. ld-prime
warns a second time once resolution has used the file, and twice more
for the object LTO compiles from such bitcode; the extra lines say
nothing new, so the warning is_foreign and parse_bitcode give is the
only one, and the driver's calls before and after LTO are gone.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A stub without a target on the link's architecture, a fat file without
its slice, or a dylib in a link that takes none is ignored with a
warning. ld-prime reads its command line as one input per naming and
warns again for each; that repeats the same fact and changes nothing
else. visited_files alone now decides: a file named again is neither
read nor warned of again, while an object still loads per naming.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Context::literals was meant to map a literal's section type and bytes
to the subsection that survives merging, but merge_literals keeps its
own local table per output section and nothing ever filled or read
this one. It is no ld-prime rule, only a leftover from an earlier
literal merge; the literals still merge as before, most-aligned copy
winning, and the output is unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
-sdk_imports_api_list names a JSON file whose "version" goes into the
-sdk_imports report and whose "apis" are the only imports the report
lists. Its reader was a module of its own, of 40 lines, though the
report in mapfile.rs is its only user. The reader now sits next to the
report, and the option still reads the file while the command line is
parsed, so a bad list fails before the link starts.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Category merging folds the categories on a class the image defines
into the class's own lists, so the runtime has nothing to attach at
load. ld-prime also merges the categories on a class another image
defines into the first of them, with lists named after the class and
the categories; the runtime attaches one category instead of several,
with the same method precedence, so mold leaves those categories be.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Category merging leaves a class's categories alone when its lists or
class_ro_t records are not in the shape it rewrites; the categories
then stay in __objc_catlist and the runtime attaches them at load, as
without merging. An environment variable printed which classes were
skipped and why, a debugging aid with no option behind it; it goes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An _objc_msgSend$<selector> stub loads its selector from a slot of
the __objc_selrefs tail, which points at the name in the
__objc_methname tail. ld-prime has that slot take over every input
reference to the same selector and points it at an input's copy of the
name; the runtime uniques selectors by their names, so separate slots
and strings read the same, and mold keeps the inputs' references.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An _objc_msgSend$<selector> stub loads its selector and jumps to
_objc_msgSend through a GOT slot. ld-prime gives the stubs a slot of
their own, so an image whose code also loads _objc_msgSend from the
GOT binds it twice, to the same address; mold gives the stubs the
symbol's one slot, as any GOT load of it would.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A symbol dyld binds by weak lookup (library ordinal -3), so that every
image shares one coalesced definition, has an ordinary GOT slot: the
bind says what to look up, not the section. ld-prime puts such slots
of an image bound for the shared region in a __DATA_CONST,__weak_got of
their own, but nothing reads that section by name - the shared cache
builder scans __got, __auth_got and __auth_ptr and leaves weak-lookup
slots unoptimized wherever they are - so mold keeps one __got, as ld64.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An object may carry a __DATA,__got of its own, slots its code loads
through. ld-prime makes each slot an entry of its own __got; dyld,
though, reads such a section as any other: typed as non-lazy pointers,
its slots are named in the indirect symbol table and bound or slid as
the table and the fixups say; typed otherwise, its pointers are data.
mold now places it as the input section it is, next to its own GOT.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A class reference is an 8-byte __objc_classrefs slot holding the
class's address, which dyld fixes up and code loads through. From a
macOS 15 target ld-prime folds the slots into __got, relaxing the
loads of classes the image defines, under rules of its own (each adrp
paired with one load, section-relative slots named); the runtime reads
the same classes either way, so mold links the slots as data and
coalesces them per class, as below macOS 15.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A firmware link takes a library stub of another platform with a
warning naming the platforms its .tbd file has targets for. ld-prime
names a zippered library's macOS and Mac Catalyst pair once more as
"zippered(macOS/Catalyst)"; the warning's wording need not match, so
mold lists the platforms alone.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A GOT load from a -lazy-l dylib calls a helper that loads the dylib
and returns the slot's value in the instruction's destination
register, so each helper is named after its register
(_sym$lazyGOT$loadHelper_rax). ld-prime names both %rsp and %rbp
"xxx"; a local label's name means nothing to any reader, so mold uses
the registers' real names, as for the other fourteen.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An x86-64 __stub_helper entry is a push of the lazy-bind offset and a
jmp to the helper's header, 10 bytes, padded to 12 so that the entries
stay 4-byte aligned; each lazy pointer starts out at its entry. ld-prime
leaves the last entry's 2 bytes of padding off, making the section 2
bytes shorter; nothing reads past the jmp, so mold gives every entry
its full 12 bytes, and legacy LINKEDIT's entries are the same size.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An ADRP's signed 21-bit immediate counts 4 KiB pages, so it reaches
from the page exactly 4 GiB below up to the one 4 KiB short of 4 GiB
above. ld-prime takes the reach as less than 4 GiB either way and
refuses the page 4 GiB below, such as address 0 from an executable's
__TEXT at 0x100000000; the instruction encodes it, so mold links it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A symbol of a dylib dyld loads at its first use (-lazy-l) or
initializes then (-delay-l) can be reached only by calls and GOT
loads, which the linker points at helpers that load the dylib first;
a pointer or a difference would need the address at launch. mold
refuses those as ld-prime does, but says "use of" rather than giving
ld-prime's internal fixup kind (ptr64, diff32, arm64_lo12, ...).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A section-relative (non-extern) relocation names its target section by
ordinal in r_symbolnum and holds the target's address in the place it
relocates. The ordinal alone tells apart sections that share an
address, so mold binds the target to that section whatever the
address; ld-prime also warns when the address lies outside the
section, a malformed-input diagnostic that changes nothing.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
From macOS 12 on, an image's initializers are 32-bit offsets from its
mach header in __TEXT,__init_offsets, which dyld adds to the address
it loaded the image at. An absolute symbol has no place in the image,
so it has no such offset, as an import has none: mold refuses either
as an initializer. ld-prime writes the low 32 bits of an absolute
symbol's value, which dyld would call as code in the image.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
macOS 10.14 brought LC_BUILD_VERSION; the loaders of older releases
read the deployment target from the legacy LC_VERSION_MIN_MACOSX
{version, sdk}, so an x86-64 image for them gets that command. ld-prime
also writes it for 10.14.4-10.14.x and 10.15.4-10.15.x, an artifact of
its version-to-year mapping (Platform.cpp's yearForTenMinorVersion);
every loader since 10.14 reads LC_BUILD_VERSION, so mold doesn't.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Classic dyld info (before macOS 12 or under -no_fixup_chains) lists
rebases and binds as opcode programs dyld interprets. mold now writes
runs of adjacent pointers as DO_REBASE_*_TIMES, small steps as
ADD_ADDR_IMM_SCALED and the rest as ULEBs, ending the rebase stream
with DONE; ld64's packing of a lone rebase or bind with the next step,
and of runs of equal steps into *_ULEB_TIMES_SKIPPING_ULEB, only made
the streams smaller. dyld applies the same rebases and binds.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A -static -pie image or a kext is slid by its local relocations: one
non-extern UNSIGNED record per pointer, its r_address a signed 32-bit
offset from the first segment (x86-64: the first writable one). The
kernel and kmutil apply them in any order, so mold lists them by
address, not ld-prime's subsection order, and fails a link only where
r_address can't reach a pointer - not ld-prime's arm64 rule counting
from -image_base plus __PAGEZERO, which refused links mold encodes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
LC_SEGMENT_SPLIT_INFO lists every reference the shared cache builder
must adjust when it moves sections apart. A 32-bit displacement from
one place to another in the same section, such as an FDE's pointer to
its CIE, never changes, so mold leaves it out as for code, where
ld-prime records it outside code; and a GOT slot holding the mach
header's address is recorded against section 0, the header itself.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
libunwind finds a function's entry by binary search, the last one at
or below its address, and reads a DWARF-mode function's personality
and LSDA from its FDE. So mold now merges any consecutive equal
entries but x86-64 STACK_IND ones, writes compressed pages with
page-local encodings and an index of just the pages, lists every LSDA,
and gives encoding 0 to code no record starts (in a section without
subsections, to the code past each record's length), not ld-prime's.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
dyld walks a fixup chain word by word, so the pointers it fixes up
should be 8-aligned and an arm64 image's chain pages (its -segalign)
4 KiB or 16 KiB. ld-prime also warns of small-aligned subsections as it
reads objects, picks which unaligned pointer to name by its parallel
section order, and reports page and alignment errors only after the
relocations; mold now checks the fixups once, in address order, as it
builds them, and numbers imports as the address-sorted binds name them.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Under -fixup_chains_section a -static or -preload image lists where
its fixup chains start in __TEXT,__chain_starts (a
dyld_chained_starts_offsets: the pointer format, a count and the
offsets) for its own loader to walk. ld-prime makes room for as many
starts as there would be if every section chained apart and leaves the
unused entries zero; the loader reads only `count` of them, so mold
now sizes the section by the starts it has.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Each dylib dyld loads at its first use has an LC_LAZY_LOAD_DYLIB_INFO
command pointing at its record in __LINKEDIT: the install name, the
flag word dyld sets once the dylib is loaded, and the __lazy_load_got
chain to bind. dyld reads each record on its own, so the commands'
order carries nothing; ld-prime's reverse order comes from walking a
hash table of install names. mold now writes them in load order.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
SG_NORELOC tells a loader that nothing in the segment is relocated or
points into it. ld-prime sets it on a segment of nothing but
-sectcreate contents and -add_empty_section anchors, but dyld, the
kernel, codesign and the shared cache builder ignore the flag: a
segment slides as a whole whatever it says. mold now leaves segment
flags to what loaders act on (SG_READ_ONLY on __DATA_CONST), and the
-sectcreate sections no longer remember which option made them.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The space between a final image's load commands and its first section
is for tools that add or grow commands in place: codesign adds
LC_CODE_SIGNATURE (hence at least 32 bytes in an image dyld loads) and
install_name_tool rewrites dylib names (hence 1024 bytes per dylib
command under -headerpad_max_install_names). ld-prime places sections
after an estimate of the commands that over-counts a few dozen bytes;
nothing reads the exact amount, so mold now leaves just the padding.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The arm64 assembler puts an ltmpN label at the start of every section,
which in an object without subsections names a subsection, keeping an
empty section in the link. ld-prime still drops an empty section of
fixed-size records with one, its label being at the section's end;
since that rule went, such a section reaches a -r output, empty, as
the test now checks, while the label stays out of the symbol table.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
-t lists the files the link reads. A dylib loaded as another's
re-export (Foundation's libobjc.A.tbd) and then found by a library
option at another path (-lobjc's libobjc.tbd, a symlink to it) is one
library; ld-prime lists it by the naming's file alone, so mold took
back the first line (untrace_file). Now -t lists both files, as each
was read.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
-trace_implicit_libraries prints a line for each library a dylib the
link loads re-exports. ld-prime prints a stub's lines once for each
platform the stub has for the target, so a zippered one's (macOS and
Mac Catalyst alike) twice; ReexportParent carried that count. Now each
re-export is noted once, a stub's as a binary's.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A C-string section may end in bytes without a NUL, which no compiler
writes. ld-prime takes them for one more string, completes it with a
NUL (past the section's end) and merges it with no other string, not
even an identical one. Now the bytes are a string of their own as they
are: no NUL is added, and since they differ from any NUL-terminated
string, merging them goes without a rule (is_unterminated_string).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime unwinds __TEXT,__text, __StaticInit and other sections with
pure_instructions unless it knows them for data (by type or one of 30
names); it refuses an FDE of a function in a section of data, and warns
of unwind info outside code only after an object's other warnings.
Now code is a section with instructions or __TEXT,__text; any other
function's FDE is carried with no __unwind_info entry, and the warning
comes as the object is read (worker messages are sorted).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime leaves out an input section with no bytes that no symbol
names a subsection in, and takes a label at the end of a section of
fixed-size records (literals, pointers) for no subsection's name, so
an empty such section with a label goes too. The ltmpN rule stays: the
arm64 assembler puts one at the start of every section, so the rule
decides whether clang's empty sections reach the output. The record
end rule goes: an empty record section with a label is now kept.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A dylib's "$ld$previous$...", "$ld$install_name$os<v>$..." and like
names rename it, or move a symbol to another library, for links that
target older OSes. ld-prime reads their numbers as strtoul does (an
overflow the largest value), "1..2" as 1.0.2 but "1." as nothing, five
numbers of which the last two must be 0, and skips empty ones in an OS
version. Now each version is X[.Y[.Z]] in 16.8.8 bits, and a directive
whose fields don't parse is ignored, as before.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime reads the API list with NSJSONSerialization, which skips a
byte order mark and allows a comma before a closing bracket, turns it
into dyld's JSON nodes, where a null anywhere fails the read, and takes
the version as atoi() of the value's text (a number's integral part, a
boolean as 1 or 0, clamped as strtol clamps). Now serde_json reads it:
the version an integer or a string of one, the APIs the strings of
"apis".
Co-Authored-By: Claude Opus 5.5 <[email protected]>
When libLTO's thinlto_codegen_process produces no object, ld-prime
fails quoting the library's error message and its version, for which
mold looked up lto_get_version alongside the entry points it calls.
The version tells nothing about the failure that the -lto_library
path, or the toolchain's, doesn't; the message now gives libLTO's error
alone, and the entry point goes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld64 warns of the libraries the command line repeats under the same
kind of option (-lz twice, not -lz and -weak-lz), and of an archive's
path given twice. ld-prime spells each as the option that named it
("'-weak-lz'", "'-weak_library /p'"), for which LibraryKind::option
held the spelling of thirteen kinds of option three ways. Now a repeat
is a (kind, name) pair seen before, and the warning names the library
or its path.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A lazy-loaded dylib's import must be reached through a stub or a GOT
load; a delay-init one's likewise, and its ObjC classes only from macOS
15. ld-prime refuses the uses that aren't in one error, a line each,
with a trailing newline when there are several (error::errors_together).
Now each use is an error!() of its own, as mold-rust reports errors,
and checkpoint() stops the link once all are given.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime reads the input files in parallel and gives the errors it
finds in them together, as one line ("multiple errors: A; B"), in
whatever order its threads found them; a fatal error among them is one
more. Now each is an error!() or fatal!() as found, as mold-rust's are:
those from worker threads still come out sorted (error::PARALLEL), the
same in every run, and checkpoint() stops the link after the reading.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime checks with access(2) that an existing output is writable
("can't write output file"), unlinks it but for a character device,
creates the file with O_CREAT|O_EXCL, so that a dangling symbolic link
fails, and writes in place one it couldn't unlink. Now the old file (or
link) goes but a device, and the output opens with create and truncate:
a read-only output is replaced, as mold-rust's rename replaces it, and
only a file this link made is removed on failure.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime words a failed open or read as ld64 did, "errno=2 (No such
file or directory)", in messages such as "-filelist file 'X' could not
be opened, errno=2 (...)" (with a blank line after), "file cannot be
open()ed, errno=2 (...) path=X" and "open() failed, errno=17 (...) for
'X'". Now error::strerror, mold-rust's, gives the reason, in messages
that say what could not be opened or written: "cannot open X: reason".
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime gives each warning as a warning under -fatal_warnings, links to
the end and then fails with "fatal warning(s) induced error", leaving
its output in place; it counts the warnings -w hid, but two it leaves
out under -w. Now -w drops a warning, and -fatal_warnings (or
$LD_TREAT_WARNINGS_AS_ERRORS) gives it as an error that fails the link
at the next checkpoint, before any output is written (a -r one too).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime deprecates -force_symbols_[not_]weak_list but for a /usr/lib
dylib, warns "ignoring -e" only without -w and deprecates -dylib_file
once; it warns that REFERENCED_DYNAMICALLY is deprecated on each
exported definition of every object read (archive members never loaded
too), and of a dylib binary's "$ld$" directives of unknown kinds. The
one-line warnings about options ignored or changed now go without those
conditions; the other two go.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A dylib bound for the dyld shared cache may not bind flat, look symbols
up dynamically (-undefined dynamic_lookup, -U) or add run paths. The
test checked which of several refusals ld-prime gives first and that
the warnings of the checks after a refusal are dropped; with the
options now checked in no promised order, each case gives one refusal,
and a run path's warning comes with the -U refusal.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
With no -dirty_data_list, ld-prime reads an OS dylib's list of the
data it dirties, to move to __DATA_DIRTY, from AppleInternal/
DirtyDataFiles/<install name's leaf>.dirty under the -syslibroot; with
no -order_file, a -final_output image's order from AppleInternal/
OrderFiles/<name>.order. Both files exist only in Apple's internal
SDKs, for its OS builds, which mold doesn't serve; -dirty_data_list
and -order_file give the same where wanted.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime loads another libLTO by running itself again with the
library's directory first in the dynamic loader's search path, so it
refuses any -lto_library not named libLTO.dylib and, for a file that
doesn't exist, warns and keeps its own. mold dlopen()s the library, so
the name is no limit: the last -lto_library is the libLTO a link with
bitcode loads, and one that can't be loaded fails that link.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
-no_allow_dylib_sub_type_mismatches and $LD_DYLIB_CPU_SUBTYPES_MUST_MATCH
list architectures; one of the link's CPU type makes a fat dylib's
slice of another subtype (arm64e for arm64) not do. ld-prime knows 34
arm64, ARM, x86 and PowerPC names, warns of any other, and takes "1"
for none. Now arm64* but arm64_32 is arm64's, x86_64* is x86_64's, and
any other name is of neither, without a warning.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
$LD_DYLIB_ARCH_FALLBACK="<arch>:<other>" had ld64 take a dylib (thin,
or a fat file's slice) of another architecture where the link's was
missing, its means to link armv7k dylibs into arm64_32 links. For the
arm64 and x86_64 links mold makes it could only bring in a dylib of
another CPU type, which dyld won't load. Its names of 32-bit ARM, i386
and PowerPC CPUs went to it and to warnings; such a file is now of an
"unknown" architecture, and still ignored with the warning.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime warns that a -lazy-l or -delay-l dylib's deferred loading is
ignored below its macOS version frameworks first, then other libraries,
and on firmware or under -preload adds a second warning per option
("-lazy_framework cannot be used on firmware ..."). The decisions stay
(the macOS 27 lazy load and its __dyld_lazy_load import, _dlopen for a
delay-init dylib); one pass over the inputs now warns once per library.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime checks the options in a fixed order, resolving some defaults
on the way, and a fatal error drops the warnings of the checks after it;
it refuses -threaded_starts_section in two wordings. Now parse_args
resolves the options in the order their values need (resolve_options),
then refuses and warns of what can't go together (check_options, which
takes in the separate last checks), and says nothing of which of several
errors comes first.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime collects the options it doesn't know and fails with "unknown
options: a b " only once it has read every known one and given its
warnings; it warns that most obsolete options are obsolete once all are
read, but -s, -Si, -Sn and -ld_classic as it reads them. Now the first
unknown option fails the link as mold-rust's do, and each obsolete
option, still accepted and ignored, draws its warning as it is read.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime gives some warnings as it reads an option, which only a -w
before that option silences (-fatal_warnings still counting them),
prints a bare notice for a renamed option (-macosx_version_min), and
gives the warnings it has before a later option's fatal error. Now
warn! holds back every warning while the options are read, as the
driver may read them once per speculated target, and gives them once
the parse is for the target, -w and -fatal_warnings applying to all.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime splits a response file at spaces, tabs, CRs and LFs only, ends
it at a NUL byte, closes an open quote or a trailing backslash with the
file, knows each file by its real path and calls any second naming of
one "recursively loading", and warns of a file it can't open, keeping
the "@" argument. Now mold-rust's read_response_file reads them: its
tokenizer, a nesting limit, errors for a NUL byte, a premature end or a
missing file; and still no response file in an @rpath-like dylib path.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
LC_SOURCE_VERSION packs a.b.c.d.e into 64 bits, 24 for a and 10 for
each other number. ld-prime reads each number modulo 2^32 before its
range check, takes an empty number for 0, ignores what follows the
fifth and calls only a trailing dot malformed. Now the version is at
most five decimal numbers between dots, each within its bits, else the
option (or $RC_ProjectSourceVersion) is malformed, as before.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime reads ld64's hexadecimal arguments and the ThinLTO cache's
decimal ones with strtoull(): after blanks and a sign, a negative
number wrapping around, an overflow giving the largest value, "0x" a
prefix only before a digit; it rounds a -pagezero_size or -image_base
past the top to 0, and takes -segalign 0 for pages of no size. Now a
number is an optional 0x and hex digits, or a decimal Rust parses (so
-prune_interval_lto -1 still never prunes), and -segalign 0 is refused.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld64 takes one way to choose an image's exports: an export list, an
unexport list or -no_exported_symbols. ld-prime words its refusal of
an option of another way after the option, three ways ("-exported_symbol
cannot be used with -unexported_symbol*" and the like). The refusal
stays; its message now names the option that comes second and the three
choices, whichever it is.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime gives the same failure three wordings: "unsupported platform"
for one of the thirty platforms it numbers (Apple's OSes, simulators,
exclaves, kernel kits), "-platform_version unknown platform" for any
other name, and "unsupported target", "unknown -arch name" or "linking
for i386 is no longer supported" by architecture. mold links for macOS
and firmware, arm64 and x86_64: anything else now fails in one message
per option, and -ios_version_min and the like fail as they are read.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime names what each option lacks in its manual page's usage words
("-e missing <name>", "-sectalign needs <segname> <sectname> <align>",
"obsolete option -Y requires 1 arguments"), and reads all of a
multi-operand option's operands before it checks any. Only whether the
link fails matters, so a missing or empty argument now fails with
mold-rust's "option -X: argument missing", each operand read in turn.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An x86-64 compact unwind encoding in UNWIND_X86_64_MODE_STACK_IND mode
(0x03000000, used for frames over 2040 bytes without a frame pointer)
doesn't hold the stack size itself: its UNWIND_X86_64_FRAMELESS_STACK_SIZE
field is the offset, from the function start the __unwind_info entry
names, of the `sub rsp, imm32` whose immediate is the size. Merging two
adjacent functions' identical encodings into one entry made the unwinder
read the second function's size from the first one's code, so an
exception thrown through the second never reached its handler. ld64
excludes this mode from merging (encodingCannotBeMerged), as we do now.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Some comments still described what the earlier changes removed: the
symbol table "in ld-prime's order" and its opening N_SO, a personality
found through the relocations' symbols, renamed locals' notes naming
them by the made-up name. They now say what the code does: the notes
of a renamed local keep its own name, and a personality is among the
undefined symbols its object lists.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A -r output's sections go in creation order, but zero-fill ones were
put last, as the contents used to be packed after ld-prime's fashion,
which broke if a file-backed section followed a zero-fill one. Each
section is now placed at its own aligned offset and a zero-fill one
takes no file space wherever it is, so the sections simply keep their
order; a later link orders them by its own rules.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
-deduplicate leaves each folded function's name at the function it
folded into, and icf noted for each such name whether ld-prime drops
it from the symbol table (the first of its scope to fold into a
survivor of that name stays). Neither the symbol table nor -map goes by
that any more, so the set of the names is all icf keeps: mergeable
libraries' records still make an alias of each.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime lists an import while live code refers to it, and also,
unbound, those only bitcode or its own unasked LTO dead stripping
referred to, softloaded runtime routines, -commons use_dylibs targets
and a merged mergeable dylib's imports. Only an import the image points
at by index must be listed: a stub's, GOT slot's or N_INDR's target.
Now those are, and every import live code uses (is_used, which dead
stripping refreshes), as mold-rust lists the imports it uses.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An -alias of an imported symbol is an N_INDR entry whose n_value is
the string table offset of the name it stands for. ld-prime writes
that name again right after the alias's own, "_alias\0_target", and
points n_value at the copy. The import has an entry of its own whose
string is the same name, so n_value now points at that one.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Without -pie nothing slides a -static image, so its __mh_execute_header
is absolute, N_ABS. ld-prime still writes the section number of the
mach header's section, 1, into the entry, though n_sect means nothing
for an N_ABS symbol and readers take NO_SECT for it. The entry now has
n_sect 0, as N_ABS says.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime lists an image's locals by address across all objects, ranks
the names at one address (private externals, locals, weak ones, each
by descending name), puts a subsection's own name after its aliases
and the absolute symbols last by value. A reader looks a local up by
address, not by its place in the table, so each object's locals now
come in its symbol table's order, then the demoted private externals,
then the linker's own names, as mold-rust writes each file's locals.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
When -deduplicate folds identical functions, a folded copy's local
name stays in the symbol table, naming the function it folded into.
ld-prime lists such a name only if it is the first of its scope (local
or private external) to fold into a survivor of that name, so that a
static helper copied into several objects shows once. Another local of
one name at one address is harmless, so every folded copy's name is
listed now, as mold-rust's ICF leaves folded symbols at the leader.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A method list rewritten in the relative form is a subsection ld-prime
makes itself, so it lists the input list's name as a plain local,
where a demoted private external, as Swift names its protocol method
lists, keeps N_PEXT (and N_WEAK_DEF) everywhere else. The bits change
only what nm -m prints, so such a name is now demoted like any other.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime lists no local naming an entry of the ObjC class and category
lists (but the aliases left once it picks the entry's name), none in
the sections it splits by content (CFStrings, references, ObjC and
UTF-16 literals) and no plain local in __objc_protolist. They name
data no reader looks up by name, and an extra local name changes
nothing, so every local of a live subsection now goes in, as mold-rust
lists every local but temporaries (l and L names here).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime writes an object's N_SO/N_OSO run only if some of its symbols
are left after dead stripping, and drops a -r input's copied unit none
of whose notes survive. A run with no notes in it costs a few entries:
dsymutil opens the object and maps nothing from it. Every live object
with DWARF now gets its run and every copied unit goes through, which
needs no bookkeeping of what each unit has kept.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime writes a common symbol's N_GSYM, the note that tells dsymutil
a unit describes the variable, only in the first live object with
notes that declares it. Each declaring unit's DWARF describes the
variable, and dsymutil takes several notes of one symbol as it does
an inline or ODR entity's, so every such object now notes it as long
as no real definition replaced it, with no table of owners.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime notes no symbol in the sections it splits by content
(literals, init and term pointers, exception tables, ObjC metadata,
relative method lists), and notes an absolute symbol by name or value.
dsymutil maps a unit's DWARF by the notes' names and ignores a note no
DWARF describes, and an absolute symbol has no address to map. Every
listed symbol of a live subsection now gets its note, N_FUN in code,
N_GSYM or N_STSYM in data; a sectionless one gets none but a common.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime writes the string table as " \0", the external symbols'
names, the locals' names, then the debug notes'. A string's place
means nothing to a reader, which follows each entry's n_strx, so the
names now go in the order of their entries, as mold-rust lays out
.strtab, in one parallel pass over all of them; the notes' follow.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A debug note naming a symbol (N_FUN, N_STSYM, N_GSYM) points at that
symbol's own string, so a -g link writes each name once. ld-prime
copies the string again for the first local's notes, and in a -r
output for a renamed private external's, whose notes keep its own
name. The first local's copy changes nothing a reader sees, so the
notes now share the string wherever the name is the same, and a local
-x or a strip list renames keeps its own name in its notes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime opens an image's (and a -r output's) stabs with an empty N_SO
of its own, and points a nameless ThinLTO object's N_OSO at the string
table's first byte, a space, where other nameless notes get the empty
string after it. An empty N_SO only closes a unit, and none is open
before the first; nm shows both strings as nothing, and dsymutil finds
no object by either. The stabs now start with the first unit's N_SO
pair, and every nameless note gets offset 1.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime -r rewrites its inputs' LC_LINKER_OPTIONs: one per library,
libraries then frameworks, each sorted by name, a -force_load path as
-l<path>, -weak_framework as -framework, a framework without its
",suffix". The final link then loses the force-loading, the weak
linking and the suffix. A -r output now carries -add_linker_option's
and its inputs' commands as they are, in that order and each only
once, which is what a link of the inputs would act on.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld64 -r writes LC_LINKER_OPTIMIZATION_HINT, the arm64 hints a final
link applies, whenever an input had a hint it took, even if every one
went with a coalesced-away weak copy, leaving the command empty. An
empty command tells the next link nothing, so a -r output now has one
only when some hint survives.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime starts a -r output's contents -headerpad (32) bytes after an
estimate of the load commands, pads a section in the file only where
its offset isn't aligned already, and rounds the segment's size up to
8 bytes, counting the padding in its file size too. Nothing reads where
an object's contents lie but the section offsets, and no tool adds
load commands to an object, so the contents now start right after the
commands, each section aligned as in the address space.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime -r writes 4 into every arm64 4-byte pcrel GOT reference
(ARM64_RELOC_POINTER_TO_GOT: a CIE's personality, an LSDA's type info),
whatever bytes the assembler left there, and into an x86-64 CIE's
personality cell. The arm64 field carries no addend for the next link
to read, and an x86-64 one holds the addend the object gave it, which
the next link reads as it would the object's, so a -r output now keeps
the object's bytes under them, as under every other relocation.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A __compact_unwind record is 32 bytes whose pointer fields a later
link reads from the section's start. ld-prime carries each record with
its input section's alignment, so the output takes the largest (a 2^0
input gives 2^0), and keeps a 4-byte relocation an x86-64 object set a
field with 4 bytes. Neither changes what the next link reads: mold
writes each 8-byte field whole, so an 8-byte relocation gives the same
value, and the section is now aligned for its pointers, 2^3.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime lists each subsection's relocations by descending offset, and
__objc_clsrolist's, which it cuts one subsection per record, record by
record. A section's relocation entries may come in any order: only a
pair must stay together, a SUBTRACTOR just before the UNSIGNED it
modifies and an arm64 ADDEND just before its PAGE21 or PAGEOFF12. The
reader keeps each pair in order and push_reloc puts an ADDEND before
its relocation, so the entries now go out as read.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime -r sorts the externals and undefined symbols by name and keeps
an undefined one only if a relocation, an unwind personality or -u
names it (and every ___dtrace_ one, which the final link builds DOF
from). An object has no LC_DYSYMTAB to sort for, and a final link
ignores an undefined symbol nothing relocates, so the output now lists
every undefined and tentative symbol its inputs do, as a link of the
inputs would see them, and all of them in symbol order.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime lists a -r output's locals object by object in section and
address order, ranks the names at one address, gives absolute ones no
n_desc and numbers the names it makes up for hidden locals (-x, the
strip lists) in that order. Nothing reads an object's local order, so
each object's locals now come as its symbol table has them - labels,
absolute symbols and demoted private externals in one loop, as
mold-rust writes a file's locals - and hidden ones are numbered as met.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime -r marks the names in an initializer or terminator list
N_NO_DEAD_STRIP, all but a subsection's aliases, and in an object
without MH_SUBSECTIONS_VIA_SYMBOLS marks every symbol N_NO_DEAD_STRIP
and clears N_ALT_ENTRY. A later link reads neither: dead stripping
keeps the lists by their section type, and keeps every section of an
output that lacks the flag, as it does when an input lacks it. Only a
no_dead_strip section's mark is still added.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime -r merges literals by content, names each one left itself
(LC<n>, l<nnn>), drops the labels of ObjC list entries and the ltmpN
labels other names shadow, and points section-relative relocations at
a symbol it derives from the address. A later link sees no difference:
it merges the literals again and finds a section-relative target by
address. mold's -r now merges no literals and keeps every label of a
live section, and __compact_unwind's fields stay section-relative.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime gives the Objective-C sections of a final image's __DATA extra
bits: no-dead-strip on the class and category lists and class refs,
the coalesced type on protocol lists and refs, literal pointers on
__objc_selrefs (but plain data when constant), and __eh_frame keeps the
compiler's coalesced/live-support flags. The runtime and unwinder find
those sections by name and nothing re-strips a final image, so they
are now plain data; only the types dyld and debuggers act on and the
instruction attributes remain. A -r output still copies input flags.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
-rename_section can put zero-fill and file-backed input sections into
one output section. ld-prime gives that section the type of its first
member in its own input order, counting a common symbol as the object
declaring the largest size, so the result may be zero fill and silently
lose the other members' contents. The section is now always file-backed
- zero-fill members are just zeros in the file, nothing is lost - with
a one-line warning naming the section.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A section$start$ or section$end$ symbol naming a section no input or
linker content makes gets an empty section of that name. ld-prime types
it by its table of standard sections, but makes an initializer or
terminator list plain data and its -merge_zero_fill_sections __zerofill
zero fill even without the option. An empty section's type is read by
nothing, so it now takes the table's flags or none, the same table that
decides which section the name maps to under -merge_zero_fill_sections.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime gives each x86-64 stub its own alignment: 1 byte when the stub
jumps through a lazy pointer the binder fills in, 2 bytes otherwise
(chained fixups, -bind_at_load, a weak-lookup symbol), the section
taking the largest; its x86-64 __objc_stubs are byte-aligned. A
section's alignment moves no stub relative to another and the CPU does
not require it, so __stubs now keeps its fixed 4-byte alignment and
__objc_stubs its 32-byte one on both targets (arm64's small stubs stay
word-aligned).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime sorts a final image's thread-local zero fill by subsection
size, smallest first, counting each subsection to the next one in its
object, and ignores -order_file for it. dyld only needs the template's
zero fill to follow its initial values and to be zeroed per thread;
where each variable sits inside it is reached through the descriptors'
offsets. The members now keep input order, with -order_file applied as
for every other section.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Compilers before ~2010 put weak code and data in __TEXT,__textcoal_nt,
__const_coal and __datacoal_nt. ld-prime renames those to __text,
__const and __data in final images, -r outputs and boundary symbols, in
place of a -rename_section. The name does not change what the bytes
mean, and the coalesced type is already dropped from a final image's
flags, so the sections now keep their own names like any other, and
-rename_section treats them like any other section.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A final image folds __TEXT,__StaticInit into __text and the fixed-size
literal pools __literal4/8/16 into __TEXT,__const, as ld64 always has.
ld-prime does it only for a section of the type the name implies, so a
__literal8 assembled as regular data stays a section of its own. Where
the bytes go changes nothing for the program, and mold-rust folds by
name alone in its output_name, so the type check goes; a section that
-rename_section gives one of those names still stays apart.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
When the linker rewrites Objective-C method lists into the relative
form, ld64 lays them out in __TEXT,__objc_methlist sorted by the name
of the symbol on each list. The lists are reached only through the
pointers in class_ro_t and category records, so their order within the
section means nothing to the runtime. They now keep the order in which
convert_objc_method_lists and category merging made them, and the pass
over every symbol to find their names is gone.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime copies each object's CIEs and FDEs in their input order, keeps
CIEs no FDE points at (with a GOT slot for their personality) unless it
dead-strips, and when -deduplicate folds same-named copies that a -r
link put in one object, keeps every copy's FDE with an __unwind_info
entry at the survivor. The unwinder reads only CIEs that FDEs point at,
through a backward offset. Like mold's EhFrameSection, a final image now
has the live CIEs and then the FDEs of unfolded functions; a -r output
puts each CIE just before its first FDE, and drops lone CIEs too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime reads each -sectcreate or -add_empty_section option as a file
of its own at the option's place among the inputs (the first option's
after every file), so its data joins a same-named input section among
the members in file order, the -map credits it to that file, and a -r
output interleaves the options' sections with the inputs'. The data's
place within its section means nothing to a reader. The options now
add their data in command-line order after the inputs' contents, the
map credits it to the linker, and -r lists sections in creation order
with zero fill last.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime ranks a segment's sections by a fixed table of standard names
(stubs, lazy pointers, init lists, ObjC lists, GOT...) read off each
section's first member before renames, and breaks ties by where it first
met a kept subsection, with LTO code placed at its bitcode files and
__common at the first object with a common symbol. No loader depends on
that order. As in mold's sort_output_sections, a section now ranks by
its kind - code, data, then zero fill - and keeps its creation order;
the thread-local template stays one block and -encryptable's
__oslogstring stays outside the encrypted range.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime makes one output section per name: a section the linker
synthesizes (the GOT, stubs, lazy pointers, ObjC image info) whose name
an input or -sectcreate section also has is appended to it and takes
its flags, so a joined GOT or stub section loses its type. Nothing reads
sections by name in a way that needs this: the Objective-C names that
must share a section already use output-section tails. Each synthesized
chunk now keeps its own header, which removes the joined-chunk plumbing
from layout, copying, thunks and __unwind_info.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A __compact_unwind record is 32 bytes: the function at offset 0, the
code length and encoding at 8 and 12, the personality at 16 and the
LSDA at 24, the pointers set by relocations. A section that is no
multiple of 32 bytes is refused as any section of fixed-size records,
and a relocation on the length or encoding fails with mold's short
error again instead of ld-prime's words.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An __eh_frame record is a length and an ID, zero for a CIE and for an
FDE the distance back to its CIE; the FDE's function and LSDA pointers
follow in the CIE's encodings. mold now fails only where it can't
rewrite them: an FDE pointing at no CIE, a CIE version but 1 or 3, a
pointer but the pc-relative 0x10 or 0x1b, and a personality but the
4-byte GOT reference 0x9b, which it took in any size and encoding, as
`.cfi_personality 0x10` makes, and wrote wrong. Broken records may panic.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A section of fixed-size records (literals, initializer pointers,
CFStrings, Objective-C lists) is split into a subsection per record, so
mold refuses one whose size is no multiple of the record's, and an
initializer pointer no relocation names. So are lazy and non-lazy
pointers in an object with indirect symbols, whose table names their
targets: mold would leave them null, as ld-prime does lazy pointers not
named __la_symbol_ptr. ld-prime's other refusals and warnings go.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A Mach-O relocation record's type, length, pcrel and extern fields
together say what it patches; mold keeps a table of the forms each type
takes and refuses any other, which is what an assembler writes for
`.short sym`, `.quad sym@GOT` or an arm64 `.long sym`. On arm64 it also
refuses a GOT load's page offset on anything but an add or 8-byte load,
and a TLV load's on anything but a load, whose relaxation would compute
something else, and a relocation into a section dropped as empty.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime's map numbering and rows needed facts gathered across the
link: the sequence of auto-linked archives and of each dylib naming,
the dylibs -dead_strip_dylibs dropped, the install names of merged
private libraries, the labels at __compact_unwind records, the mach
header names only dead code used, and the property lists and class_ro_t
fields category merging made. The map no longer lists any of that, so
the passes stop recording it; -why_live keeps the merged libraries'
files.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld64's map lists the files (number 0 the linker), the sections and the
symbols: address, size, file and name, then under -dead_strip the
removed subsections as <<dead>>. ld-prime fills that format with its
own internals - load-order file numbering, label ranking, a row per
record, literal names, stub and GOT rows credited to dylibs, a complete
list of coalesced subsections. mold now numbers the live objects then
the dylibs, gives each subsection a row per listed symbol (anon before
the first) and each linker-made section one row, in final and -r maps.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A final image whose objects bring no code (a dylib of only data) needs
no __TEXT,__text: dyld, codesign and the unwinder find nothing there,
and LC_FUNCTION_STARTS just lists no function. ld-prime writes an empty,
byte-aligned one anyway (left with the mach header when -rename_segment
moves __TEXT's sections) and lists a placeholder row for it in -map;
mold now makes none, and the dylinker's 4 KiB __text alignment applies
only when there is one.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An -order_file line names symbols, optionally qualified by object; each
subsection gets the rank of the first line naming one of its symbols.
ld-prime matches only the names its -map gives subsections (a C
string's label orders nothing) and its -order_file_statistics warns of
ambiguous and repeated lines; mold now matches any live symbol of the
name, and the statistics list the lines that named nothing and how many
did.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
-trace_symbol_layout tells where each symbol's subsection went. ld-prime
walks its own subsection order and prints one line per naming step - a
symbol move, -merge_zero_fill_sections, -text_exec, -data_const, the
renames - and lines for the content it makes itself, which mold mirrored
with a traced copy of the section naming and its own walk. mold now
prints the final output section of every symbol the map lists, "symbol
'_x', mapped to SEG/SECT", once the synthesized sections exist.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Xcode's build system reads -dependency_info's 0x10 (input), 0x11
(missing) and 0x40 (output) records to decide when to relink; what it
needs is each file once, by a path it can stat. ld-prime resolves a
relative path with realpath(3) - symlinks and the file system's case -
and lists a library loaded as another's re-export twice; mold now makes
a relative path absolute as spelled (std::path::absolute, no syscalls)
and lists every file once, the missing ones sorted and deduplicated as
bytes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A mergeable dylib's record (LC_ATOM_INFO) is binary data ld-prime's
-make_mergeable wrote: a file version 3 header, then tables found by
offset and count in it, of entries (directly after the header), fixups,
large addends, custom sections, symbol names, dylib identities and debug
notes. It is now read directly, without the header's version and
largest-kind checks or bounds checks: a broken record may panic.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A stub is read for what the link uses: the documents and groups whose
targets apply, the install name and versions (of a version 5 file, each
list's first entry), the symbols by kind, clients and re-exports; keys
it doesn't know are ignored. TAPI's per-version schemas, its value
formats and its diagnostics are not checked or reproduced any more: a
malformed stub fails simply or is read as far as it goes. Every .tbd of
Xcode and the Command Line Tools SDKs parses as before.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
TAPI has written five versions of the stub format: YAML versions 1
(untagged, "---\narchs:", which ld-prime only takes as a stub if it ends
in "..."), 2 and 3 give architectures and a platform, version 4 targets,
and version 5 is JSON. Each spells weak definitions, allowed clients and
re-exports its own way, and from version 3 on a re-exported library is
inlined as a document of its own. The test links one dylib through each.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime prints what a diagnostic names from an input as the bytes it
read: a path, an archive member's name, an install name, an auto-link
option, a libLTO message, and so does -t. mold went through
Path::display and lossy conversions, which make each byte that isn't
UTF-8 a U+FFFD. Paths now print through RawPath::raw and names through
error::raw, and a message put together before it is printed is bytes
(error::Message, RawBuf).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime takes a symbol an option or a list file names as the bytes
given: -e, -u, -U, -init, -alias, -why_live, the export, strip and
weakness lists, -alias_list and -order_file all match names bytewise,
UTF-8 or not, and the order file's statistics print them as they are.
mold refused such an argument and took a list file it couldn't decode
for one it couldn't open. Names and lists are now bytes, the lines
trimmed of the white space isspace() knows, as ld64 reads them.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A symbol name is the bytes of a string table entry up to its NUL, and
ld-prime takes any: it resolves and binds by them, writes them back as
they came and prints them byte for byte in its diagnostics. mold checked
each string table as UTF-8 and cut it at the first byte that wasn't, so
such a name lost its tail. Names are now byte strings throughout, as
mold's are: the symbol table, dylib and stub exports, $ld$ directives,
LTO, DTrace and -map; the JSON reports, being text, spell them lossily.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
libLTO's legacy API has no word for a preempted copy: a module whose
copy of a weak definition lost keeps it unless the winner is preserved.
A ThinLTO module's losing copy already counted as a reference to the
winner; now a merged module's does too, so that with a ThinLTO copy
winning both keep the symbol and coalesce, and a C++ inline function's
static local stays one variable (ld-prime splits it, in either order).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An arm64 adrp or page offset, or an x86-64 RIP-relative reference
other than a call or a GOT load, needs its target's address, which an
import has none of in the image: ld-prime fails the link with "target
does not have address". mold wrote the import's zero address instead,
silently in a dylib (whose page zero is in reach) and with an
out-of-range error in an executable, or none for a lone page offset.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime links a -make_mergeable dylib that names a lazy-load or
delay-init dylib but uses nothing of it as any other: _dlopen and
__dyld_lazy_load are imports its record keeps as initial undefines,
and the empty subsection keeping __dyld_lazy_load alive is an entry
of no size with keep-alive fixups to ___dso_handle and it. Only code
that reaches such a dylib gets rewritten helpers no merge could take.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime's mergeable record has an entry for each import the link
names as an initial undefine (-u), whether or not a fixup refers to
it, and an image that merges the dylib imports it in its place, as
the dylib does. mold recorded only the imports the objects' fixups
reach, so a merging link lost the others.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
At one address ld-prime's -map lists the label naming a subsection
with bytes, then those naming empty subsections in layout order, then
every other label by its subsection's layout order (end labels of the
one before first). An object without subsections has one subsection
per section, whose size it splits among the labels only when nothing
comes between them; otherwise the first label keeps it all.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime reads an arm64 adrp and the add, ldr or str right after it in
the relocation table as one fixup when both are of one kind (GOT load
or not), target and addend and the second's base register is the
adrp's destination, even with instructions between them; so
-poison_symbol lists such a pair once, as it lists a TLV pair twice. A
SUBTRACTOR's symbol it does not count as referenced at all.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A real definition in a live file beats a tentative one whatever the
sizes, but from the archives ld-prime, like ld64, loads for a live
tentative definition only a member that defines the symbol as data
(outside S_ATTR_PURE_INSTRUCTIONS sections), skipping code and the
dylibs but under -commons use_dylibs. It indexes members' tentative
definitions too, which ar leaves out of the table of contents, so a
reference loads a member for one, and its data overrider in turn.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
-sdk_imports writes one JSON object, pretty-printed, and -trace_file,
-trace_file_shared_cache and -trace_symbols_file (or $LD_TRACE_*) each
append one object a line, which Apple's build system reads as JSON: only
the content matters, not ld-prime's spacing or key order. The values are
built with json! and written by serde_json, keys sorted; a trace record
still goes out in a single write, as other links may append to the same
file. The tests check the content with jq instead of matching the text.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A version 5 .tbd is JSON, which TAPI parses with LLVM's JSON reader: of
a key given twice the last counts, and a number with no fractional part
is an integer even if written with one (5.0), where serde_json reads a
float. The parsed tree is leaked like the input files, so that the names
a TbdFile lists borrow from it, and the schema checks of check_json run
over it as before; a malformed file reports serde_json's message.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime reads the API list with NSJSONSerialization and walks dyld's
JSON nodes over the result: a number or a boolean is a value spelled as
a string, an empty string, map or array is an empty node, a null fails
the read, and of a key given twice the last counts. NSJSONSerialization
also skips a byte order mark and takes a comma before a closing bracket,
which serde_json refuses, so those commas are dropped first. The version
is atoi of its value, clamped to a long as strtol does (-1e20 reads as 0).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime checks an object's load commands, segment, sections and the
tables at its end before it reads the object, and refuses a corrupt
or truncated one in words of its own; mold did the same in
src/malformed.rs. Compilers and linkers write no such objects, so the
checks go, and mold may fail on such input. What well-formed inputs
meet stays: an LC_LINKER_OPTION of other than one or two strings (as
the assembler writes them) and an image without LC_UUID (-no_uuid).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
As it reads an object's symbols ld-prime refuses one naming a section
the object lacks, ignores one named outside the string table, takes a
relocation to a debug note to be out of range and warns of a
data-in-code entry no subsection holds; no tool writes any of these,
and mold no longer checks them. It still ignores, as ld-prime does, an
indirect symbol (ld -r -alias writes one) and one an assembler sets
past its section's end, which a new test now covers.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Random byte flips in valid objects had made mold fail on LC_DYSYMTAB
symbol ranges that disagree with the symbols' N_EXT bits, an
__eh_frame relocation naming no symbol, a common symbol of 2^63 bytes
or more, a debug note among a -r input's globals, and addresses that
wrap; ld-prime checks none of these and crashes on some. No compiler,
assembler or linker writes such objects, and they are out of scope
now, so mold reads the symbol table and those values as written.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A section header's alignment field is a power of two; compilers and
assemblers write at most 2^15 or so, and an alignment of 2^32 or more
is no input a toolchain makes. ld-prime shifts a 32-bit 1 by it, so
such a value wraps around, and mold had copied that at every place an
alignment is used. Inputs that broken are now out of scope (mold may
fail on them), so the alignment is taken as written, in 64 bits.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime traces an archive member's auto-link hints as it loads the
member, so the serial auto-link step gathered every newly loaded
member's hints - its options parsed again, sorted, each filtered by
the trace options - only to drop them all when neither
-trace_implicit_libraries nor -trace_implicit_library is given, as in
every normal link. The lines are now gathered only for those options.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Most of a .tbd stub is its export lists, a quoted symbol a line behind
~25 blanks of indentation, and parsing one went mostly to str::split
and str::trim_start over them: a Unicode whitespace test per blank.
The items are now split with memchr and their ASCII blanks skipped
byte by byte, falling back to str's trims at a non-ASCII character,
so they read the same; Foundation's 5.6 MB stub parses in 1.25 ms,
not 2.4, and iTerm2's 209 stubs in 18 ms, not 31.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ICF had a SipHash13-128 of its own whose update() was only #[inline],
so a function's base digest - a dozen updates of one to eight bytes
per relocation - called it and buffered each odd field with a
memmove. mold-rust's util::siphash, ported here as is, forces update()
inline and has update_u64 for the digests the rounds hash; with Swift
functions folding even when their address is taken, iTerm2's debug
dylib hashes 132k functions, and ICF's rounds take 15% less CPU.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The searches note every file they look for and don't find, thousands
in an app's link (3,600 in iTerm2's), which -dependency_info lists
once each. A PathBuf sorts and compares by its components, ~3 ms of
serial work there; a path that has no empty or "." component past
the first and no trailing slash spells its components exactly, so
such paths now sort and deduplicate as their bytes, as fast as memcmp.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A stub library's re-exports are loaded with it, and theirs in turn: an
SDK umbrella's tree goes several levels deep (Cocoa, AppKit, its
private frameworks...). The parse cache was warmed for the named
stubs and the ones they re-export only, so the next levels were parsed
in the serial input loop, ~4 ms of Transmission's link; the waves of
re-exported stubs now go on until one finds no stub not yet parsed.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
-dependency_info names an input by its real path, which macOS's
realpath(3) finds with a getattrlist call per component, giving each
name in the case the file system has it; 600 relative objects cost
~7 ms. Each directory's real path is now found once, and a file that
its listing names exactly and that is no symbolic link is that path
joined with its name; any other, or one a long listing doesn't reach,
is resolved alone as before, so the paths written are the same.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime folds a Swift function even when its address is taken, if
the label it picks to name the subsection is Swift's; finding that
label ranked every label at the start of every address-taken
subsection, most of them Swift metadata in data sections, after
looking up the symbol of every nlist. Now only __TEXT,__text functions
are wanted, an undefined symbol's nlist is passed over before its
symbol is looked at, and a label is ranked once its subsection is.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Auto-linked libraries load one at a time in the options' sorted order,
which fixes their ordinals and the misses -dependency_info lists, and
their .tbd stubs were parsed there too: ~20 ms of iTerm2's serial
auto-link step. A quiet search (one that notes no miss and warns of
nothing) now finds them first, and they are parsed on all cores with
the stubs they re-export, as read_input_files does for the command
line's; the serial loop's own search then finds every parse done.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Every Swift object of a module carries much the same LC_LINKER_OPTION
list (-lswiftCore, -framework Foundation and so on), so the options of
all the objects are tens of thousands of copies of a few hundred. They
were cloned, sorted and only then deduplicated, which cost ~15 ms of
the serial auto-link step of iTerm2's debug dylib; a hash set of
references now drops the copies first, and only the distinct options
not acted on yet are cloned and sorted into ld-prime's order.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The two hundred and forty arms of parse_args's match had come to stand
in the order they were added, -sectalign between -non_global_symbols_*
and -alias. They are now grouped, each group under a comment: the
output and its kind, the target, the inputs and libraries, symbols,
dead stripping and ordering, segments and sections, fixups and dyld,
load commands, Objective-C, LTO, diagnostics, obsolete options, and the
arguments no arm names, which stay last. No arm changes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
parse_args began with a struct literal of the Args fields that
environment variables set before any option is read: ZERO_AR_DATE,
LD_WARN_COMMONS, LD_TREAT_WARNINGS_AS_ERRORS, RC_UUID_SALT and the
others ld-prime reads in place of options. They now come from
env_defaults, so that parse_args opens with the loop over the options
and the variables are listed in one place with what they stand for.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The options with a dozen or more lines of their own now read through
helpers that take the cursor: -platform_version and the *_version_min
options, -bundle_loader, -dylib_file, the segment and section options
(-segprot, -segment_order, -seg_page_size, -section_order, -sectcreate,
-add_empty_section, -sectalign), -max_default_common_align and
-alias_list, and the joined options and input files of the last arms.
Each keeps its arm's comment as its doc comment and reads as before.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Each of the dozen options that names a symbol list file read it with
read_symbol_list and handed the entries on as an iterator of &str.
ArgCursor::next_symbol_list now reads the file an option names, and
add_patterns and add_initial_undefines take any entries that are
strings, so that each option's arm says which list it fills and
nothing more. A stray run of doc comments before ExportChoice, left
from functions that moved, goes, and symbol_list gets its own.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
After its loop over the options parse_args ran some 330 lines of
resolution and checks in ld-prime's order. They are now named phases,
called in the same order: finish_options (-lto_library, unknown
options, the environment), set_output_kind, exit_without_inputs,
resolve_target, resolve_defaults, SymbolLists::build, check_arch_options,
check_options (with resolve_fixups) and check_last, so that the order
of ld-prime's diagnostics reads off parse_args's last lines.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
parse_args kept some fifty locals for what the options say that it can
resolve into Args only once it has read them all: the output kind, the
switches whose defaults depend on the target (None for one not given),
the symbol lists it compiles into matchers, and the warnings ld-prime
gives late. They are now fields of ParseState, the symbol lists of its
SymbolLists, so that the option helpers and the phases after the loop
can take them as one argument.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
parse_args read each option's arguments through closures over the
command line and a bare index, and the options that look ahead (-rpath,
-segprot, -seg_page_size, -section_order, -oso_prefix, -commons and
-sectorder) indexed the command line themselves. They now all go
through ArgCursor, as mold-rust's parse_args does, so that the option
families can move into helpers that take the cursor. ld-prime's rules
for a missing or empty argument stay with the methods that read one.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime takes the segment and section names of -sectcreate,
-add_empty_section, -segprot, -sectalign, -segaddr, -seg_page_size,
-segment_order, -section_order, -rename_section, -rename_segment and
-move_to_rw/ro_segment as the bytes given, UTF-8 or not, and cuts a
name past 16 bytes there even inside a UTF-8 character. mold refused
such an argument ("expected a UTF-8 argument") and cut a long name at
the character before. The options now read their names as bytes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A segment or section name is 16 bytes padded with NULs, and to
ld-prime any bytes but NUL: it copies a name it reads from an object
into the output's headers as it came, in a final image and a -r output
alike, prints it as it is in -map, -trace_symbol_layout and its
diagnostics, and pads it by bytes in the layout it prints on an error.
mold read names as UTF-8 and took one that wasn't for an empty name.
Names are now byte strings throughout, as mold's ELF names are.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime prints the names it was given byte for byte, UTF-8 or not: a
-segprot protection letter it doesn't know comes out as the very byte,
\377 or each byte of a UTF-8 letter alone. Diagnostics here are now
rendered to bytes rather than text: error::raw wraps a byte string,
writing each byte that isn't UTF-8 as a U+FFFD at an address of its own,
and the renderer, which knows those copies, writes the byte back. Held
messages keep the bytes until they are printed.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime checks each image it reads - a dylib to link with, the
executable -bundle_loader names - as dyld does before it loads one,
and refuses one with no LC_UUID ("missing LC_UUID load command") or
more than one ("too many LC_UUID load commands"), naming the file by
its path, a fat file's without the slice's architecture, twice as it
does a malformed object. mold read such an image as any other.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime shifts a 32-bit 1 by an object section's alignment field, so
2^40 is 256 bytes and 2^64 one, but it takes any alignment of 32 or
more to exceed the segment's and reduces it with its warning, naming
the wrapped value ("from 0x100 to 0x4000"). mold shifted a 64-bit 1 and
overflowed at 64. An input section now keeps such an alignment below
64 with its remainder by 32, and the output section's wraps to 32 bits
once capped.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime doesn't check that a symbol table's ranges hold what its
LC_DYSYMTAB says, that an __eh_frame relocation names a symbol there is
(it reports "r_symbolnum=N out of range"), nor sizes and addends that
overflow; mold ran into each corrupting valid objects at random. It now
takes the symbols' own N_EXT bits when the ranges disagree, refuses the
relocation, aligns a common of an absurd size to the most, passes over
a debug note among a -r input's globals, and lets addresses wrap.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
As it reads an object's symbols ld-prime refuses one in a section the
object lacks ("n_sect=N for symbol 'x' out of bounds"), and ignores one
outside its section, with a warning, or named outside the string table,
or indirect; a relocation to such a symbol or a debug note is invalid,
out of range past the last symbol kept. It checks targets in table
order, __compact_unwind's too, stopping at the first bad one, and warns
of a data-in-code entry no subsection holds.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime walks an object's load commands first, numbering them from 0,
each within the load commands and of a size a multiple of 4, then what
it holds (sizes, strings, commands only an image has); then the ones an
image may have one of, the platforms, the segment and its sections, and
last the tables after the sections' content, which must lie apart in
the file. An archive member it refuses in the walk is no object to it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime's undefined-symbol report says where a symbol is referenced
from by the file's leaf name: "foo.o" for a fat object's slice too, an
archive member as "libfoo.a[2](foo.o)", a fat archive's as
"libfoo.a[arm64][2](foo.o)", the object LTO compiled as "lto.o". Its
-why_live, redundant re-export and unused bundle loader messages name a
fat dylib by its real path alone, without the slice's architecture.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime's -map numbers a dylib once for each input that names it, by
the path that input gives, used or stripped: libgcc_s.1.tbd, libc.tbd
and libm.tbd, the SDK's aliases of libSystem.tbd (the clang driver
links -lgcc_s.1 for old x86_64 targets), and the same file named again
as well; the symbols bound to the library are credited to its first
naming. Only a repeated -l option is ignored, with its warning.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime checks a bitcode file's architecture, from its target triple
(a Thumb one named by its ARM architecture), as it checks a Mach-O
object's: one the link doesn't take is ignored with "found architecture
'x', required architecture 'y'" (an error under -arch_errors_fatal), an
archive member needed or not, and -allow_sub_type_mismatches lets in
x86_64h bitcode, warning of it and of its LTO output twice each. It
knows bitcode only in Apple's wrapper; raw bitcode is an unknown type.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime always makes a final image's __TEXT,__text; when no input
subsection reaches it (a dylib of only data, whose objects' empty
__text sections have no subsection unless an arm64 assembler's ltmpN
labels one in an object without subsections) its -map lists the
section's placeholder, the linker's and of no size, named
"__TEXT,__text", as for an empty section a boundary symbol makes. The
labels of an empty output section come before the next section's.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime's -map ranks the labels at one place alike with or without
subsections (exported before local, then the greater name, ltmpN last),
but in an object without them the last, not the best, has the size, the
others listed before it. Of a C string it makes a subsection of each
label at its start: the best is the string's own row, each other
linker-private label a copy merged into it, each other label an alias,
which -dead_strip lists as dead when nothing live refers to it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime looks for linked libraries nothing binds to (-warn_unused_dylibs,
on by default for a shared-cache dylib) once the undefined, weak-import
and duplicate-symbol checks have passed, and before it warns of
redundant explicit re-exports and of weak exports, so its "linking with
... but not using any symbols" lines come first and still appear when
-no_weak_exports then fails the link. mold warned only when it stripped
the dylibs, after all those.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime adds each name -reexported_symbols_list spells without
wildcards to the initial undefines, as -u does, so one nothing defines
shows up in the ordinary undefined-symbol report, sorted with the
others and wanted by "<initial-undefines>" (or by the object that
references it), even under -undefined dynamic_lookup. mold already
forced those names undefined; drop its own earlier error for them.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime drops the FDE of a function -deduplicate folded unless it
folded into one of its own object with the same name (two copies of a
local function a -r link put together). mold compared the two names
with subsec_label, which scans all of the object's symbols for those at
a subsection's start, twice per folded FDE on the main thread: 29 ms in
iTerm2's debug dylib link with ICF. One pass per object, on all cores,
now finds the labels of all its folded functions and their leaders.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ThinLTO gives a static that another module imports global scope under
the name <name>.llvm.<module hash>, and ld-prime writes such a symbol,
local again in the output, into the symbol table cut at ".llvm.".
str::find builds a two-way searcher for every name, three times per
local symbol as the table is sized and written, which was 40 ms of CPU
in iTerm2's debug dylib link; a memmem Finder built once does the same.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
As it reads an object ld-prime warns of each subsection aligned below 8
bytes that holds a pointer (an 8-byte absolute relocation) outside
__DWARF, __LLVM and the unwind sections, and refuses a __DATA,__cfstring
record without its two fixups. Both checks ran for each subsection and
turned the 16-byte names into strs, a NUL search and a UTF-8 check each;
segname_is and sectname_is compare the bytes, and the cheap tests go
first. That was 30 ms of CPU in iTerm2's debug dylib link.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime warns of unwind info in a section of data and fails on an FDE
for one, naming the object as resolved_file_name does: by its real
path, and an archive member by its 1-based place among the archive's
entries, which member_index finds by walking the archive from the
start. check_unwind_sections computed that name for every object, a
realpath each and a walk quadratic in an archive's members: 56 ms on
the main thread of iTerm2's debug dylib link. It runs on all cores too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A stub says an export moved from an older library with an
$ld$previous$<install name>$<version>$<platform>$<lo>$<hi>$<sym>$ name,
and of the directives for one symbol that apply ld-prime takes the
first install name; mold searched its list of moved exports linearly
for each. SwiftUICore moves some 15,000 exports for a macOS 13 target,
which cost 85 ms on the main thread of iTerm2's debug dylib link (and
reading a directive's versions no longer collects them into a Vec).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime reads a regular __TEXT,__stub_helper, __objc_stubs,
__lazy_helpers, __delay_stubs or __delay_helper section as code only it
makes: in an object split at symbols, one a symbol is in (an arm64
ltmpN too) fails with "unknown symboled section type"; else it is data.
A regular __DATA,__lazy_load_got of 8-byte slots fails as lazy-load-GOT
content, and its size, like a weak or signed GOT's, is checked first.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime names the method and protocol lists it builds merging a
class's categories after the class and the categories, such as
__OBJC_$_INSTANCE_METHODS_A(Cat|Cat2), and -trace_symbol_layout reports
them with the rest it makes: after the -alias names and before the
method lists it rewrites, in the order the classes merged, mapped to
__TEXT/__objc_methlist or, for classic lists, __DATA/__objc_data. mold
left them out.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime makes one section per name whatever filled it, so a
-sectcreate or -add_empty_section section takes in the linker's own
contents of its name - the stubs, the GOT's slots, the lazy pointers,
or __dyld_private's word in a __data no input has - after the option's
bytes, keeping the option's flags (S_REGULAR: the stubs lose their
type even beside an empty section). The input files' contents still
come first, the options' next, the linker's records then its sections.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime leaves the Objective-C of a -static or -preload image or a
kext as the compiler wrote it, since no runtime sets it up there: it
converts no method list to the relative form (even under an explicit
-objc_relative_method_lists), merges no category into its class (so
__objc_catlist stays) and folds no class reference into the GOT from
macOS 15 on. Its __objc_stubs it still makes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime reads each class an object's __objc_classlist names, in list
order, and fails on the first whose data field (the class_ro_t pointer
32 bytes in) has no relocation, whatever its bytes: "null objc class
data for '<class>' in '<file>'", the file by the path it was given (an
archive member's as archive(member)). It reads every object so, the
archive members it does not load too, in any link, -r included.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime merges each object's __objc_imageinfo flags into the image's
as it checks the object, after its deployment target: an object whose
category-class-properties bit differs from the flags merged so far
draws "mixed ObjC ABI, <file> compiled with|without category class
properties" (the bit then clears), the Swift ABI mismatch is reported
in that order too, and the image keeps the oldest Swift language
version given, where mold took the newest.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime reads only __DATA,__objc_imageinfo as an object's image info,
the record whose flags it merges into the image's and checks the size
of. An __objc_imageinfo in __DATA_CONST, __TEXT or any other segment is
an ordinary section: copied as is (into a final image's section of its
name, which the linker's record then joins), stripped by -dead_strip
when unreferenced, and kept in place by -r. mold took any segment's.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime makes one output section per name: when the GOT, the stubs,
the lazy pointers or another section it synthesizes has (after
renames) an input section's name, such as an input __DATA_CONST,__got,
it joins that section after the input's contents, which keeps its
flags. So a GOT or stubs there are no longer typed as such: the
indirect symbol table skips their slots, and each stub in a code
section gets an __unwind_info entry, as code without unwind info does.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime reads -rpath's argument even when it starts with '-', but
takes it for a missing path: it warns "-rpath missing <path>" and adds
no LC_RPATH, as it does for an empty one; the argument is consumed
all the same, so the option it names is not applied. mold added a run
path of that name. (Found where a response file expanded under -rpath
put an option after it.)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime ignores an empty argument where it would take an input
file: one from an empty variable in a build system's command line, or
from a '' or "" in a response file. mold took it for a file named by
the empty path and failed to open it. Options' arguments are another
matter: each option has its own rule for an empty value, such as
-rpath's warning.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime takes any argument starting with '@' for a response file, an
option's too, but dylib paths starting @rpath, @loader_path or
@executable_path; mold expanded one only if the file existed. One it
can't open draws a warning, the argument staying; one it can't read is
an error, as is naming a file twice (by real path, nested or not).
Arguments split at spaces, tabs, CRs and newlines only, and a NUL, an
open quote or a trailing backslash ends the file without an error.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The test links a dylib of more than 128 MiB of code to check that a
call out of reach goes through a range-extension thunk, and it found
that call by disassembling the whole __text with otool -tv, which took
about 20 seconds and timed the test out on a loaded machine.
objdump --disassemble-symbols disassembles the one function it looks
at, so the test now runs in under a second with either linker.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A mergeable dylib's record header names the largest entry kind (byte
12), content type (13), generic fixup kind (u16 at 14) and target fixup
kind (u16 at 16) it uses. ld-prime refuses a record past what it knows
- kind 19, content type 81, fixup kinds 14, 0x92 (arm64) and 0x109
(x86-64), the target's in group 0x80 or 0x100 - with "atom file uses
unknown ... kind (n). Max supported is m"; mold now does, in its words.
mold's own errors for entries it can't place no longer say "atom".
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An object's own __DATA,__got becomes one entry per slot in a mergeable
dylib's record, and a merging link places the entries apart. Hand-
written arm64 code may reach a slot as another's label plus an offset:
an assembler-local label is referenced as the section start (ltmpN)
plus an ARM64_RELOC_ADDEND. ld-prime records that reference as the
first slot plus the offset, which reads the wrong slot once merged;
mold now records the slot the address falls in, with the offset there.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The DTrace symbols a dtrace -h header names (___dtrace_probe$...,
___dtrace_isenabled$..., a provider's stability and typedefs) are never
defined: a final link turns the call sites into nops and drops the
symbols. A pointer to one in data is no call site; ld-prime leaves it
to dyld as a bind with ordinal -2 (BIND_SPECIAL_DYLIB_FLAT_LOOKUP),
addend and all, chained or in the bind opcodes, while the symbol stays
out of the symbol table and gets no stub or GOT slot.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A link reaches a lazy-load (-lazy-l, macOS 27 on) or delay-init (-delay-l)
dylib's symbols through helpers: a GOT load becomes a call to a helper
that loads or initializes the dylib first. A mergeable dylib's record
keeps the rewritten instructions under the compiler's GOT fixup, so a
merging link finds no ADRP where the fixup says and fails. ld-prime
aborts with no message on the first and makes a dylib nothing can merge
of the second; mold refuses both, worded as ld-prime's other conflicts.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime checks a mergeable dylib merged into the image (-merge_framework,
-merge-l, -merge_library) as the dylib it is: one built for a newer OS
version than the link's gets the dylib warning naming its install name,
in its place among the inputs, and -deployment_target_mismatches, which
governs objects, has no say. It checks it again as it merges the dylib's
code, after resolving symbols and checking every input, LTO's output
too, so each such dylib's warning appears a second time, in input order.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime reads the contents of -sectcreate and -add_empty_section as
input files, in the options' places among the inputs, and makes its
own content (the selector names of arm64's objc_msgSend$ stubs, the
lazy binder's __dyld_private word) only after all inputs. Since a
section takes its place among those of its rank when its first content
is seen, a section of only synthesized content, such as __objc_methname
when the stubs absorb every input name, follows the options' sections.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
LC_DYLD_INFO came with macOS 10.6; ld-prime links an x86-64 image for
an older macOS without it. dyld binds GOT slots and lazy pointers by the
indirect symbol table, data pointers to imports and coalescable weak
definitions by external relocations (the slot keeps the addend), and
slides a PIE, dylib or bundle by local relocations, followed by an export
trie no command names. Stub helper entries jump to crt1.o's (dylib1.o's,
bundle1.o's) dyld_stub_binding_helper; no dyld_stub_binder is bound.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld64 made an x86-64 main executable position independent by default
only from macOS 10.6 on: for an older macOS one gets MH_PIE only with
-pie, and the kernel maps it where its segments say, so nothing in it
needs sliding. ld-prime keeps that default for a 10.4 or 10.5 target,
where mold made every dynamic executable PIE unless -no_pie said
otherwise. An arm64 executable is always PIE (arm64 macOS starts at 11).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The tests' comments now describe what ld-prime does in the linker's own
terms: it splits sections into subsections (more finely than mold where
it says so), fixed-size pieces are records, and a mergeable dylib
carries a mergeable record. Six tests whose names used ld64's word are
renamed after what they check (anon-subsection-names, map-subsections,
init-func-records, ...); the strings they compare with ld-prime's
diagnostics stay as ld-prime prints them.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
-make_mergeable makes ld-prime write a record of a dylib's contents in
__LINKEDIT, which LC_ATOM_INFO points at: a header, then a table of
40-byte entries - one for each live subsection, alias, tentative
definition, import, linker-defined symbol, initializer offset and
unwind record - their fixups, names and content pools. mergeable.rs and
make_mergeable now call these entries (Entry, OutEntry, To::Entry,
ENTRY_SIZE) and the whole payload the record.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime walks a graph for -why_live whose nodes are subsections, the
labels inside a section of an object without
MH_SUBSECTIONS_VIA_SYMBOLS, imports, its boundary symbols, DOFs and
DTrace symbols; dead_strip.rs calls such a node a Node. The
-move_to_*_segment and -dirty_data_list options move the subsection a
symbol names to ld-prime, which symbol_moves.rs calls a Subsec, found
by for_each_subsec_symbol and subsec_named.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime merges literals, selector and class references and CFStrings
by content, so their labels vanish from a -r output; arm64 relocations
must name what they refer to, so ld64 names each such literal itself
(LC<n> for a C string, l<nnn> for the others), and a label on one
stands for that literal. relocatable.rs now calls those pieces literals
and the sections it synthesizes records (NamedLiterals, literal_labels,
fixed_literal_names, and extern_target for a relocation's new symbol).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The output writers follow ld-prime's order and names piece by piece: a
chained fixup chain, a rebase stream or a bind table goes subsection by
subsection, -map and -trace_symbol_layout list a subsection under the
name ld-prime gives it, and a -r output's own unwind and image info are
records. Their comments and names now say so (traced_subsec_symbols,
subsec_mapping, subsec_location, unsplit_bare_subsecs, GoneSubsecs),
and a DTrace site goes by its subsection's name.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime cuts an object's sections at more places than mold's
subsections: each __compact_unwind entry becomes a 32-byte record of its
own, and a label that names no subsection starts a zero-size one, which
counts in the "anon-N" names of its diagnostics. Context::subsec_ordinal
and the object reader now say so in mold's terms, subsections and
records; a whole section's names are unweakened by
unweaken_whole_section_names, and diagnostics take a subsection's name.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This reverts bd97ebc90. mimalloc's page map still doesn't cover the
sign-extended high half of the sparc64 address space, but mimalloc maps
its arenas at hinted addresses in the low half, and since v3.5.0 a page
outside the map fails to register instead of corrupting memory. On an
UltraSPARC T5 running Linux, mold with mimalloc 3.5.3 passes the native
test suite.
ld64 and ld-prime move code and data in units they name with a word of
their own; mold-macho, like mold's other ports, calls an input section
split at its symbols a subsection (an InputSection). The functions and
fields that more than one module uses now say so: Context::subsec_name,
subsec_label and subsec_ordinal name a subsection as ld-prime's
diagnostics do, a mergeable dylib's LC_ATOM_INFO payload is a
MergeableRecord, and a -r output's own unwind and image info pieces are
RelocatableRecords. Only the -print_statistics timer label changes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Xcode keeps a mergeable framework's bundle in the app where an image
merges or re-exports its code (-merge_*, -no_merge_*). ld-prime adds an
initializer that, in an app, has objc_setHook_getImageName name
<app>/Contents/Frameworks/<leaf>.framework/<leaf> the image of a merged
library's listed classes, a re-exported one's exported classes and,
with -add_mergeable_debug_hook, a dylib's unexported ones. mold embeds
a C hook of its own, first among the objects, over a table it builds.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An N_OSO stab's n_value holds the object file's modification time, which
dsymutil checks before reading the DWARF there. -reproducible (or
ZERO_AR_DATE in the environment) makes ld-prime write 0 instead, and it
does so for the N_OSO entries a -r input carries from its own link as
well as for those it writes itself; mold copied those as they were
(Transmission's prelinked b64 and deflate objects).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime merges the categories on a class another image defines into
the first of them and drops the others from the category list that held
them. An object's __objc_catlist is one subsection (clang labels its
start); mold rewrote one that held a merged entry as a new record at the
end of the output __objc_catlist, after the lists of every later object,
which changes the order in which the runtime attaches categories. The
rewritten list now replaces the old one where it was (Transmission's
NSString(NSStringAdditions) moved behind five other categories).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime orders GOT slots by what fills them: the image's own addresses,
then each library's imports, then the weak definitions, then flat
lookups, by name within each group. A weak definition of the image's
own that the link hid, such as a C++ type_info an exception table
refers to through the GOT, goes with those bound by weak lookup rather
than with the image's other addresses; mold put it first (Transmission's
libgtest: testing::AssertionException's type_info).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A -r output passes each tentative definition on as a common symbol, with
an N_GSYM for it among the debug notes it carries. When a final link
merges such definitions from several objects, ld-prime notes the symbol
once, in the first object with notes that declares it, as it does for
objects with DWARF; mold copied the N_GSYM of every -r input
(____asan_globals_registered seven times in iTerm2's ASan
iTermSwiftPackages).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
-alias gives a defined symbol a second name at its address. ld-prime's
-map lists the new name as a symbol of no size of the linker's own, as
it does the aliases of folded functions: credited to file 0 and listed
after the symbol it aliases. mold credited it to the object that
defines the original (Xcode's ___debug_main_executable_dylib_entry_point
alias of _main in a debug dylib).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Swift promises a function no address of its own, so ld-prime folds a
function named by a Swift-mangled symbol (_$s...) even where its address
is taken or it is exported. An @objc thunk, whose mangled name ends in
To and whose address an Objective-C method list takes, is not such a
function to ld-prime: it folds one only when it is hidden and its
address is not taken. mold folded SwiftyMarkdown's thunk for an
unimplemented init() into the identical init() it stands for, and an
ivar destroyer's thunk in OpenEmuSystem into a one-instruction getter.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The entry point (-e, _main by default) is a root of an executable's
link: the archive member that defines it is loaded, and the symbol
counts as referenced. A dylib or a bundle has no entry point, and
ld-prime takes _main for nothing there; mold rooted it in every link,
so Hammerspoon's LuaSkin framework pulled Lua's command-line
interpreter, lua.o, out of liblua.a with its 74 symbols and its code.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A symbol a dylib re-exports binds to the library that defines it when
that library is in a public location, which then gets a load command of
its own. ld64 and ld-prime take a path under /System/Library/Frameworks/
for one only if it ends in the name before the first dot after that
prefix (Foo for Foo.framework/Versions/A/Foo), so a library inside a
framework is not public: OpenGL re-exports Libraries/libGL.dylib, and
glClear binds to OpenGL. mold took any path in a top-level framework,
and gave IINA a load command for libGL.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime notes no symbol whose name begins with l or L, a global's no
more than a local's (which the symbol table drops). Swift objects define
l_OBJC_PROTOCOL_SYMREF_$_<protocol> as weak private externals in
__TEXT,__const, which a -r output keeps as locals of that name: ld-prime
writes no N_GSYM for them, in a -r output or a final image, but mold did
(CotEditor's ControlUI.o, NetNewsWire's Account.o and RSCore.o).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A -r link allocates no tentative definitions: it passes each on as an
undefined external with its size in n_value and its alignment in
n_desc. ld-prime keeps N_PEXT on one that is a private external,
-keep_private_externs or not, as there is nothing to demote; mold
dropped it, so the final link made the symbol visible (ASan's
____asan_globals_registered in iTerm2's prelinked Swift packages).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime merges the tentative definitions (common symbols) of one name
into the largest, which wins whole: its size, its alignment, whatever
the others ask, and whether it is a private external; of those of one
size the first wins. mold took the largest size, the strictest alignment
and the visibility of the definition first claimed, so `.comm _v,16,2`
and `.comm _v,8,5` got 32-byte alignment, and a hidden 8-byte common
named after a visible 4-byte one stayed external.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime splits a section of fixed-size records into one subsection per
record and writes each subsection's relocations by descending offset, so
the relocations of __objc_clsrolist, the list of Swift generic classes'
class_ro_t records, come by ascending pointer. mold keeps that list as
one subsection (its records must stay for the method lists to be
rewritten) and so wrote them all by descending offset; it now sorts a
subsection's relocations by record first.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A final image drops the local symbols -x or -non_global_symbols_strip_list
or -non_global_symbols_no_strip_list strips, but a -r output may have
relocations that need them: ld-prime keeps each by a name it makes up,
l<nnn>, numbered in symbol table order by the counter that names its
literals (LC1, l002, ...), absolute symbols and demoted private externals
too. Under -x, which strip -x passes, it writes no debug notes or N_AST
paths; under a list the notes name a renamed local by its new name and a
demoted private external by its own. mold kept every name and note.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An N_AST stab names a Swift module for the debugger to load, and Xcode
passes -add_ast_path to the -r links that prelink Swift packages as well
as to final ones. ld-prime lists the paths in a -r output too, as in a
final image: after the local symbols, before the debug notes' opening
N_SO, in the order given, whether or not any object has DWARF. Under -S,
which drops the debugger's notes, it lists none in either. mold wrote
them in final images only, -S or not (each of Amethyst's packages).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime takes a command-line input whose path ends in .a for a library
option's, as if -force_load's path without the forcing: it looks an
absolute one up under each -syslibroot first (a .tbd beside it too),
reports a missing one as "library '<path>' not found" in command-line
order with -l, before any framework or object file, and merges what the
other options naming the found file say (-load_hidden hides it). Paths in
a -filelist stay files. Ghostty's replayed link with its products gone
named ghostty-internal.a where mold named the framework Sparkle first.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
mold makes an _objc_msgSend$<selector> stub for every such symbol a
live object references, before -dead_strip runs; ld-prime makes them
for the references that survive it, so a selector only dead code sends
gets no stub, no selector reference and no __objc_methname string. A
test bundle linking Nimble's prelinked object had one stub too many
(defaultCenter, sent only from dead default-argument functions).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An $ld$previous directive that names no version moves an export to the
older library at its own library's version, so exports of two libraries
can move to one at two versions (DeveloperToolsSupport's and
SwiftUICore's both to SwiftUI for macOS before 14 or 15). ld-prime gives
it one load command, at the version of the first of those libraries
whose moved exports bind; mold kept the first library's version even
when only the other's exports bound (SwiftUI 24.0.24 for 8.0.84).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A library that a dylib re-exports privately but that the link also
loads itself takes the symbols the dylib provides through it. An
auto-linked one with an @rpath install name does not: ld-prime takes it
for the library the dylib loaded already and binds its symbols to the
dylib, giving it no load command - UI test bundles auto-link
XCUIAutomation, which XCTest re-exports, and bind its symbols to XCTest.
A named one, or one with any other install name, still takes them.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime takes -reproducible, which Xcode passes to every link, as
ZERO_AR_DATE: the N_OSO entries of its debug map, final and -r outputs
alike, carry 0 where they otherwise hold each object's (or archive
member's) modification time for dsymutil and lldb to check. mold took
the option as having nothing to switch on, so every Xcode-built debug
image differed from ld-prime's in the N_OSO values (417 in Amethyst).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A -r link can put several copies of one local function, each with its
FDE, in one object under one name. When -deduplicate folds them, ld-prime
keeps every copy's FDE and CIE and its unwind record at the survivor's
address, __unwind_info listing one DWARF-mode entry for each, all of them
pointing at the last of those FDEs - as if it found a copy's unwind info
by its name in its object. Copies of other names, or in other objects,
lose theirs, as mold dropped every folded copy's (3 FDEs in Amethyst).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A chained fixup's 12-bit next field reaches 4095 4-byte strides, which
a 4 KiB or 16 KiB page never exceeds; in the larger pages a -segalign
makes, two fixups of one page can be farther apart, and ld-prime, which
finds that as it chains them, fails the link on it rather than on the
page size: "distance between fixups (N) is not encodable in chain for
fixup at __DATA+0x8, " - the first fixup's segment and offset there,
and a trailing comma - before the layout it prints.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime writes the classic weak-bind opcodes with the bind stream's
state machine: the type set once, the address moved by ADD_ADDR_ULEB
within a segment (backwards too) or not at all when the next slot
follows, and a bind followed by a step folded into DO_BIND_ADD_ADDR_IMM_
SCALED, a run of equal steps into DO_BIND_ULEB_TIMES_SKIPPING_ULEB. mold
reset the type and the segment and offset for every slot, which made
Transmission's tests' weak-bind info 14448 bytes where ld-prime's is 13480.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime writes the debug notes a -r output carries as notes of what
survives of its subsections, so a unit with none left - its code all
dead, or a Swift file with no code at all - loses its N_SO and N_OSO
entries too, and the notes of sections whose symbols it notes in no
object go: a method list the -r link left in __objc_const and this link
rewrote in the relative form in __TEXT,__objc_methlist. mold copied
every unit and every note of a live subsection (54 empty units in
Amethyst's packages).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime's -map lists the symbols at one address with the subsection's
name, which carries its size, first, and the other labels there, each
of no size, after it in the order the symbol table lists them (see the
previous commit): object by object, then the aliases of folded
functions. mold sorted the names at an address by size, the aliases
first, in no particular order among themselves. The linker's own
zero-size symbols (section boundaries, ___dso_handle) still come first.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime names a subsection by the best label at its start that is not
an alternate entry point (N_ALT_ENTRY), and the symbol table lists the
other labels there first, object by object in input order (each
object's by rank and descending name, its alternate entry points last),
then the aliases of functions -deduplicate folded into the subsection,
in input order, and the subsection's name last. mold took the greatest
label for the name, alternate entries too, and ignored objects.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime gives a function deduplication folds an alias of no size,
named as the function's subsection was, and -map credits that alias to
file 0, the linker; the function's other labels at its start stay their
own file's. The subsection's name is the best label at its start that
is not an alternate entry point (N_ALT_ENTRY), which names none: a
Swift function's _x with .alt_entry _y is the alias _x. mold credited
every label of a folded function to the linker, and let an alternate
entry with a greater name name the subsection.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime gives a function deduplication folds an alias of its own,
named as the function was, but drops one named as the function it
folded into unless it is the first function of its scope, local or
private extern, to fold into that one (and of another scope). So when a
local of another name folds in first, no local copy of the survivor's
name is listed at all: Amethyst's prelinked packages each fold
__swift_destroy_boxed_opaque_existential_0 and _1 into the app's _1,
and ld-prime lists no local _1.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime searches the libraries the command line names, in input order
with the archives, and only after all of them - a later archive member
included - the public libraries they re-export, as dylibs of their own:
level by level, nearest first (a private library in between counts as a
level), a level ordered by the install name of the library re-exporting
each and then by its own. Auto-linked libraries and their re-exports
follow. mold walked each dylib's re-exports depth first in its place,
binding NSURLSession to CFNetwork (Carbon > CoreServices) over Foundation.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A .tbd file inlines the libraries its library re-exports as further
documents, but ld-prime treats a document as re-exported only where the
main document or another re-exported one lists it in its re-exported
libraries; one that none lists contributes nothing, and its symbols stay
undefined, in a YAML (v1-v4) and a JSON (v5) stub alike. mold made every
inlined document a re-export of the main library, so a library nested
two re-exports deep counted as one re-exported directly.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An arm64 ADRP's signed 21-bit immediate counts 4 KiB pages, so it
could reach from the page 4 GiB below up to 4 KiB short of 4 GiB above.
ld-prime takes its reach as less than 4 GiB either way, and reports the
page exactly 4 GiB below as out of range too: an executable's __TEXT at
0x100000000 can't reach address 0 by ADRP (an absolute symbol there, or
a DTrace probe symbol a GOT load refers to); mold linked it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A dtrace -h header has each USDT probe site call an undefined
___dtrace_probe$<provider>$<probe>$v1$<hex types> function. ld-prime
turns each call into a nop (an is-enabled test's into zeroing its
result) and has the OS's libdtrace rebuild the provider's D script from
the symbols into a DOF, put in __TEXT,__dof_<provider> with each site's
distance from it, for dyld to register with the kernel. We make the same
DOF ourselves, before dead stripping, which keeps the sites as roots.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A dtrace -h header has each probe site call an undefined
___dtrace_probe$... function and names the provider's stability and
typedefs symbols only by an N_NO_DEAD_STRIP .reference, which no
relocation carries. ld-prime keeps every undefined symbol whose name
starts with ___dtrace_ in a -r output, with n_desc 0, as the final link
builds the provider's DOF from them; mold dropped the unrelocated ones,
and ld-prime then failed "Must have a valid dtrace stability entry".
Co-Authored-By: Claude Opus 5.5 <[email protected]>
-trace_symbol_layout tells of the step that put a section where it is
(a symbol move, -data_const, -merge_zero_fill_sections) and then of the
renames, as one step. Where -rename_section takes the section to another
segment and -rename_segment then moves that segment on, ld-prime gives
the first line the segment -rename_section named but the section's old
name ("__BAR/__data" for __FOO,__data renamed __BAR,__d2 and __BAR moved
to __QUX): it renames the segment in place before it writes that line.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
$LD_DYLIB_ARCH_FALLBACK gives "<arch>:<other>" (ld64 used it to let an
arm64_32 link take armv7k slices). In a link for <arch>, ld-prime takes
a dylib of <other> in place of one of its own architecture: a thin one,
or a fat file's slice when the file has none of the link's. Objects,
archives and stubs of <other> stay out with the usual warning; a value
for another link's architecture, or naming none it knows, is ignored.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A library search looks for a dylib's stub (.tbd) and the dylib itself
together and takes the stub - but where both turn up in an SDK (a path
with ".sdk/" in it), ld-prime warns "text-based stub file X and library
file Y unexpectedly found. Falling back to library file for linking."
(not in Apple's internal SDK) and links the dylib, unless the build
system sets $LD_PREFER_TAPI_FILE, which keeps the stub without a word.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The second byte of an object's __objc_imageinfo flags is the Swift ABI
version it was compiled for. ld-prime stops at the first object whose
version differs from the first one seen ("not all .o files built with
the same Swift ABI version. Started with (4.0), now found (4.1/4.2) in
<path>", the versions by their release names); $LD_WARN_ON_SWIFT_ABI_
VERSION_MISMATCHES makes that a warning for each such object instead.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Apple's build system sets $RC_UUID_SALT (the build train) so that two
builds of the same sources get images of different UUIDs; ld-prime
feeds the salt into the digest it makes the content-hash LC_UUID of,
before the output's contents. An empty salt adds nothing. mold's UUID
is a digest of the page hashes of its own, so it now takes the salt's
bytes first: one salt gives one UUID, another salt another.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime reads $LD_APPLICATION_EXTENSION_SAFE and $LD_NO_ENCRYPT (ld64's
iOS variable, which it took to mean the same "for now") before the
command line: either, set to anything, makes -application_extension the
default, so a dylib gets MH_APP_EXTENSION_SAFE unless a later
-no_application_extension says otherwise. LD_NO_ENCRYPT's own meaning,
no LC_ENCRYPTION_INFO, is for iOS apps, which macOS images never have.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
MH_APP_EXTENSION_SAFE tells a later link that the dylib uses only APIs
an app extension may use; ld-prime sets it under -application_extension
on a dylib alone. An executable or a bundle linked with the option gets
no such flag (nothing links against them, and dyld does not look at
it), where mold set it on any image dyld loads.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime reads $LD_TREAT_WARNINGS_AS_ERRORS before the command line and
makes every warning fatal, as -fatal_warnings does, when the variable is
set to anything but "0" - an empty value included. The link then gives
each warning as a warning, goes on to the end, and fails with "fatal
warning(s) induced error (-fatal_warnings)"; -w hides the warnings but
the link still fails.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime reads -trace_file's file from $LD_TRACE_FILE when no option
names one and $LD_TRACE_DEPENDENTS is set, whatever its value; and with
a -trace_file either way, it writes -trace_symbols_file's record, where
no option names its file, to one of its own in $LD_TRACE_SYMBOLS_DIR:
<parent pid>.<pid>.<arch>.<microseconds>.json, making the directory as
need be (mkpath_np) and failing the link if it can't.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime writes the -sdk_imports report as indented JSON, two spaces a
level, but with "platform", "deploymentVersion", "sdkVersion" and
"inputs" on one line, and an empty line inside an empty "inputs" list;
it lists the libraries in the order of the image's load commands. It
lists a symbol once for each reference (stub, GOT slot, code), which
says no more than once does: mold lists each symbol once, by name.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime takes a path on the command line whose name ends in .a for a
library's, as if an option named it: an absolute one is looked for
under each -syslibroot first (with a .tbd of the name, which it notes
as missing in -dependency_info), and one not found anywhere stops the
link with "library '<path>' not found". Any other path, an archive's
without the .a too, and every path a -filelist gives, is used as given.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime records in -dependency_info (opcode 0x11) every file it looked
for while finding the inputs and did not find, each once, so that Xcode
links again when one appears: per -L directory a library's stub, dylib,
.so and archive until one is there (the dylib even next to a stub it
found); per -F directory a framework's stub and binary; and for a
re-export, a path relative to its loader, then its leaf in each -L (a
framework's tail in each -F) directory, then its install name in the SDK.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime keeps the -sectcreate and -add_empty_section contents in a -r
output, each named by a non-external, no-dead-strip local symbol
"l<sect-create>SEG,SECT" (the name as the option spelled it, whatever
-rename_section did), after the objects' locals in command-line order.
A section only the options make is regular and byte-aligned, ranks as
an unknown section of its segment whatever its name, and comes with
the file of its first contents; __DATA,__interpose stays in __DATA.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime makes each -sectcreate and -add_empty_section option a file
of one section in the option's place among the inputs, but for the
first option's section, which it takes for its own (file 0) and places
after every file's. Options naming one section make one section of
their contents in file order; one naming an input section's output
section (__DATA,__data, __TEXT,__text) joins it among the subsections.
-map lists each such file where it stands ("(null)" for an empty one).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime lays out a -r output's section contents one after another: a
zero-fill section takes no file space, and each other section follows
the contents before it with the padding its address has from the end of
the previous section, zero-fill or not - unless the running file offset
is aligned for the section already, when it adds none. Past a __bss the
next section thus starts where the data before it ended, unaligned, and
a section whose file offset happens to be aligned can start early.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
mimalloc_rust now links libatomic itself on targets without 64-bit
atomics, so cli/build.rs, which did that for 32-bit PowerPC hosts, is
no longer needed.
A mergeable dylib with more than 128 MiB of code on arm64 reaches far
functions through range-extension thunks (ld-prime's branch islands).
The thunks are no subsections of the objects, so the record has no
entries for them: a call's fixup names the function called, and the
bytes the record points at hold the bl to the thunk, whose immediate a
merging link (either linker) rewrites to a thunk of its own if needed.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A weak definition's copies share one symbol, which names the winner's
subsection; the losing copies are coalesced away and ld-prime records
none of them. When the winner was itself folded into an identical
function, the mergeable record also gave each losing copy an alias of
the kept function, so the name appeared twice. Only the subsection the
symbol names gets the folded function's alias now.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An object's own __DATA,__got (pointers its code loads) moves into the
image's GOT, and the mergeable record took each moved slot for a class
reference: content type objc-class-ref, hidden and merged by content,
after the imports. ld-prime's merging link then asserted ("unsupported
references to class-ref"). ld-prime records each slot in its place, of
content type got (22), tu and named by its label if any, its bytes the
GOT slot's; a merging link reads type 22 as __DATA,__got, as objects
have it (a section of non-lazy pointers is refused).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Bit 30 of the mergeable record's header flags (offset 0x28) isn't the
Objective-C image info's presence, which mold took it for: ld-prime sets
it when an object has a __TEXT section named __swift* (any, even
__swift_foo; not one in __DATA) or Objective-C classes or categories
(__objc_classlist, __objc_catlist and their non-lazy lists), with or
without an image info. Selectors, protocols, CFStrings or an image info
alone leave it clear; bit 31 (classes) still wants the image info.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A mergeable dylib's record lists the dylibs it links (by install name
and versions), and a merging link gives each a load command. When the
image merges two mergeable dylibs and one links the other, ld-prime
gives the linked one none: the other's merged code defines what the
first imports from it. mold loaded it anyway, and dyld failed to find
it at launch. A dependency is compared with the install name of each
merged dylib's own file info (the record header's offset 0xa8).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
readelf before binutils 2.39 can't decode version 9 indices and exits
with an error, so gdb-index-dwarf5-types ignores its exit status as
check_index already does. arch-x86_64-eh-frame-hdr-order links without
the C runtime, whose objects may have FDEs of their own.
mold handles .ARM.exidx and other users of SHF_LINK_ORDER directly
rather than implementing the flag in general. This reverts 49129fbd5,
which kept SHF_LINK_ORDER sections alive with their linked sections, and
restores the ARM-specific GC edge to .ARM.exidx. 9aacb62c0 now sets
sh_link only in relocatable output, which needs it to preserve the flag.
A mergeable record marks an entry "don't dead strip" by its section's
S_ATTR_NO_DEAD_STRIP, but for __objc_classrefs, whose entries ld-prime
coalesces by content as the classes they point at. mold exempted
__objc_superrefs too, but ld-prime marks those (the l_OBJC_CLASSLIST_
SUP_REFS_$_ records of messages to super, of a class and of its
metaclass) like any other no_dead_strip section, on arm64 and x86-64.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A function deduplication folded into an identical one has no bytes of
its own, and the mergeable record left it out: a merging link could not
find an exported Swift function folded so. ld-prime -make_mergeable
-deduplicate records it as an alias entry (weakDefAlias for a weak
definition, no flags) of the kept function's entry, by the name its own
subsection had, placed after the imports; fixups name the alias, and
the folded function's other labels stay aliases of it in its place.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
As ld64 did, ld-prime takes an object with a section whose name starts
with __llvm_prf_ (in any segment, of any type: the counters, data and
names clang's -fprofile-instr-generate emits) to mean the image is
built for profiling, whose runtime registers itself with initializers:
it then neither warns about an OS dylib's static initializers nor
fails -no_inits.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The Objective-C runtime calls the first +load of a class's method list
alone, so merging a +load category into a class that has one of its
own, or with another +load category, loses all but one. ld-prime then
leaves every category of that class in __objc_catlist (and the +load
ones in __objc_nlcatlist) for the runtime to attach; mold merged them
and dropped the class's own +load.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
With no -order_file, ld-prime orders an image linked with -final_output
by the first -syslibroot's AppleInternal/OrderFiles/<final name>.order
if there is one - the name pasted in as given, so a -final_output path
finds none - as ld64 did, letting an internal SDK carry the order files
of the OS's own images.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime warns "OS dylibs should not use @rpath for -install_name. Use
absolute path instead" for a dylib bound for the shared cache - one
-add_split_seg_info makes eligible, as an install path in the cache's
directories already is absolute - whose -install_name starts with
@rpath, after its warning about -rpath, which shares the reason: the
cache builder resolves an OS image's dependencies by absolute path.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime still checks that the objects' __objc_imageinfo records agree
on their Swift version in a -static or -preload image or a kext, but
it writes none into such an image: no Objective-C runtime reads the
flags there, as none registers the image. mold wrote its merged
record into __DATA (or __DATA_CONST) of those images too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime turns initializer pointers into 32-bit __init_offsets on its
own only in an image dyld loads with chained fixups; the kernel runs a
kext's __mod_init_func itself, as it does a -static kernel's, so a
kext keeps the pointers (and the local relocations that slide them)
unless -init_offsets asks otherwise, whatever -fixup_chains says.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime takes a malformed pattern such as _x[ in any symbol list
without a word, matching nothing. Its bracket expression ends at the
first ], knows no negation ([!a] is ! or a) and no escapes, makes the
- after a range start the next one ([a-b-d] is a-b and --d), and is
malformed with a - first or last. An entry whose wildcards a backslash
all escapes names the symbol it spells (_a\* is _a*).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
With no -dirty_data_list, ld-prime looks for the list of a dylib bound
for the shared cache, installed in its paths, in the first
-syslibroot's AppleInternal/DirtyDataFiles/<install name leaf>.dirty
and moves the data it names to __DATA_DIRTY. The file's lines are
names alone: wildcards match only themselves. A dylib kept out of the
cache, or another image, reads none.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime defines each -alias name as an atom of its own
command-line-aliases-file, after every object's, that stands for its
base's: a -move_to_*_segment or -dirty_data_list entry naming either
the alias or its base moves the base's atom (its own list first), and
a warning about the alias names that file - "command-line-aliases-file:"
also qualifies a list entry for it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime walks the atoms file by file - an object's section atoms,
then its common symbols' and its absolute symbols' - and after the
objects the files it makes itself, each in the order it made their
atoms: the relative method lists of its objc-file, the thread-local
descriptors of its tlv-file. Its symbol-move warnings and trace lines
follow that walk; mold put commons and absolutes after every input.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime moves a record it rewrote in place of an input subsection as
it would the input's: the class_ro_t category merging rebuilt goes to
the list's segment, and the relative method lists are code its own
objc-file holds, so -move_to_rw_segment leaves them with a warning
naming "(objc-file)" and "(is objc-method-list)", and
-move_to_ro_segment lays them out in an __objc_methlist there.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime lays out an image no dyld loads (-static, -preload) with
__TEXT and then __DATA first and every other segment in the order its
first section appears in the inputs - __DATA_CONST too, which such an
image knows only from -data_const, the shared region or the inputs'
own, and which therefore has no place of its own there. A kext, which
kmutil loads, keeps its __DATA_CONST right after __DATA.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime places an output section by its first member's standard name
as it knew it in the input's __TEXT or __DATA (after its own moves to
__DATA_CONST and __TEXT_EXEC), which the section keeps through
-rename_segment, -rename_section and symbol moves: a renamed __DATA or
__TEXT keeps its order. Its own sections rank so too; an input's
__DATA_CONST sections and -sectcreate's rank by their type alone.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime knows a section of type S_MOD_INIT_FUNC_POINTERS or
S_MOD_TERM_FUNC_POINTERS by its type, not its name: like non-lazy
symbol pointers, such a __DATA section of any name moves to
__DATA_CONST in an image with that segment, before -rename_section
renames it there, and keeps its type. A section named __mod_init_func
or __mod_term_func but of another type is data and stays in __DATA.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime ranks a final image's sections by one order for every segment:
code, crt1's tables, lazy pointers, initializer and terminator lists,
__const, __cfstring, the ObjC lists, class data and references, the
lazy-load GOT, unknown sections, thread-local descriptors, __got,
__auth_ptr, thread-local data - so -no_data_const keeps that order in
__DATA. Init, term and TLV lists rank by type in any segment; a -static
or -preload image gives init/term, __const, __cfstring, __auth_ptr none.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A mergeable dylib's record lists each live subsection as an atom pointing
at its linked bytes, its relocations as ld-prime fixups (fused adrp pairs,
diffs, GOT and TLV kinds), aliases, absolutes, commons, imports by dylib,
CFI and compact unwind records, and the link's own ObjC metadata (relative
method lists, merged records, selector refs). ld-prime's merge needs one
atom per list record and a placeholder at each null class_ro/category list
pointer; names hash with libc++'s CityHash64. Hints stay unapplied.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime records a mergeable dylib's atoms in LC_ATOM_INFO ("nldprecr",
AtomFile v3): 40-byte atoms (scope, kind, content type, custom section,
alignment, debug unit), 16-byte fixups in its own kinds, names, and the
dylibs it links; contents are the dylib's linked bytes, so fixups apply
again. mold reads them back into the object they stand for (sections by
content type, symbols, relocations, -r style stabs), restoring relaxed
GOT/TLV loads, and loads the recorded dylibs by name without reading them.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime's map lists the names it gives the mach header - ___dso_handle,
which C++ passes when it registers a static destructor, and a dylib's
or bundle's __mh_*_header - once code refers to them; under -dead_strip
those only stripped code referred to go last among the dead, of size 0
and file 0, behind segment$start$__TEXT, the atom they name, unless the
header is a root (an executable's) or another of its names is kept.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime counts each -alias base among the initial undefines, the dead
stripping roots -why_live calls "initial-undef", so the base survives
-dead_strip (and the stripping of an LTO link) in an executable that
exports nothing, where mold stripped it. The alias itself is an atom
of its own that goes unless something exports or refers to it: an
executable's -alias vanishes but under -export_dynamic.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime resolves bitcode modules' references to dylib exports before
LTO, and the import symbols stay in the output's symbol table - unbound,
no fixup refers to them, their dylibs kept - even where the code LTO
compiled no longer uses them (an unused function it dropped), in
ThinLTO and merged links alike; only -dead_strip drops them, with the
other imports no live code uses. A symbol no dylib defined goes unseen.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A linkonce_odr unnamed_addr definition - a C++ inline function - is
LTO_SYMBOL_SCOPE_DEFAULT_CAN_BE_HIDDEN, which the bitcode placeholder
now marks N_WEAK_DEF|N_WEAK_REF as .weak_def_can_be_hidden does: an
image auto-hides it, so ld-prime counts it no export to preserve (nor a
dead-strip root), and LTO inlines it like a static. A ThinLTO module's
copy of a weak definition another module's replaced counts as a
reference to that one; a merged module's doesn't.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime makes each output section where its first atom comes, and it
takes the atoms of an object LTO compiled in the order it lays them out
- each where the file it is credited to was named - not in the
object's own order. So a ThinLTO dylib's __TEXT,__const, whose class
type name goes to the bitcode file, precedes its __gcc_except_tab,
whose unnamed exception tables stay the compiled object's.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime credits each symbol of an object LTO compiled to a bitcode
file - in the map, and in the order it lays the atoms out: an external
one to the file whose definition symbol resolution kept (the first
copy of a weak one, an inline function or template that several
modules define), and only a name no external symbol has to the one
module that defined it. The map's list of dead-stripped atoms credits
them the same way.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime notes the symbols two bitcode files define strongly before
LTO but reports them after it: it compiles the ThinLTO modules
regardless, and the merged ones unless two of those clash (libLTO could
not merge them), parses the objects (warning about their versions),
and then lists the bitcode files - plus /tmp/lto.o when the merged
object defines the symbol, as it does for a ThinLTO/merged clash.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An object ThinLTO compiled in memory has no name, and ld-prime gives
its N_OSO string offset 0 - the space a Mach-O string table starts
with - where every other nameless debug note (the closing N_SO, the
N_FUN holding a size, N_BNSYM) gets offset 1, the empty string after
it. nm shows either as nothing; dsymutil finds no object by either.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ThinLTO promotes a static some other module refers to to a hidden
global named <name>.llvm.<module hash>. ld-prime writes every symbol
that ends up non-external - a local, or a demoted private extern, of
any object - cut at its first ".llvm." in the symbol table and the
debug notes, so the debugger sees the source's name; a global keeps
its whole name, and the map shows every name whole. (ld-prime -r cuts
the globals' names too, which breaks references to them.)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime writes an object's debug-note run - the N_SO pair, the N_OSO
naming the object, the N_FUN/N_STSYM/N_GSYM entries and the closing
N_SO - from the atoms it keeps, so an object all of whose symbols were
stripped gets none at all; mold wrote the opening and closing entries
for it regardless. A ThinLTO module whose code was all inlined into
others' is the common case, now that LTO links strip unasked.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime walks a link with bitcode as dead stripping does before LTO,
each bitcode file's "internal" atom a root referring to all its module
refers to, and tells libLTO to preserve only what live code reaches;
then it strips any final image - executable, dylib or bundle, not -r -
as -dead_strip would, except that imports only stripped code used stay
in the symbol table, unbound (keeping their dylibs), and the map lists
nothing stripped. Code nothing reaches may refer to undefined symbols.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime's must-preserve list names the entry point only when a bitcode
module defines it, like every other symbol on it; a Mach-O main stays
off the list. Telling libLTO to keep a symbol no module defines does
nothing to the objects it compiles, so this only makes the list the
one ld-prime passes, as traced through a logging libLTO given as
-lto_library.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime orders a final image's atoms by the file each is credited to,
and credits an atom LTO compiled to the bitcode file whose module
defined its name, as the map does (see lto::origins). So the merged
object's code, and each ThinLTO object's, lands between the inputs
named before and after its bitcode file, and only what no one file
defined (a static two modules share, a literal) goes after every
input. A -r output keeps the compiled objects after the inputs.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime's -flto-codegen-only sends every bitcode module, ThinLTO or
not, through the ThinLTO code generator with
thinlto_codegen_set_codegen_only, which compiles each module as it is,
unoptimized, into an object of its own: the modules are named and
numbered as ThinLTO ones are, no merged /tmp/lto.o is made, and a -r
link of merged-LTO bitcode alone is compiled rather than written out.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime's -save-temps has ThinLTO keep each module's bitcode at every
stage (N.0.original.bc through N.4.opt.bc, and index.bc) in
<output>.thinlto.bcs/, which it creates owner-only if it isn't a
directory (warning when it can't, after which libLTO aborts), and
writes each object as <output>.<index>.thinlto.o, warning "unable to
write temporary ThinLTO output" for one it can't - all beside the
merged modules' <output>.lto.bc, .lto.opt.bc and .lto.o.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
With ThinLTO modules in the link, ld-prime makes the -cache_path_lto
directory if it isn't one (mkdir 0700, one level; on failure it warns
"unable to create ThinLTO cache directory: <path> (<errno>)" and goes
without) and passes it to thinlto_codegen_set_cache_dir, then the prune
interval if given and the entry expiration and relative size, 0 meaning
libLTO's default. Each number is strtoul's, cut to 32 bits first.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime reports input files by their real paths (an archive member as
archive[index](member)), but an object LTO compiled by the name it got:
/tmp/lto.o, the empty name of a ThinLTO object kept in memory, or the
-object_path_lto path as given - relative, symlinks and all - for the
merged object, and libLTO's <dir>/<index>.<arch>.thinlto.o for a
ThinLTO one. Duplicate-symbol lists and newer-version warnings show it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime sends the modules lto_module_is_thinlto reports through a
thinlto codegen first - named realpath+index (archive[N](member)+index),
told the shared preserve set and each module's undefined symbols as
cross-referenced - and gets one object per module; the rest merge as
before. A reference counts toward preservation unless both ends merge;
exported means listed, else visible. Objects are nameless, or in the
-object_path_lto directory; -r then compiles instead of merging.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime hands every -mllvm option, in order, to libLTO, which parses
them as LLVM's own command line: the merged-module code generator gets
them through lto_codegen_debug_options_array right after its PIC model
is set, and parses them when it optimizes, so an unknown option ends
the link with libLTO's "Unknown command line argument" message. clang
itself passes one (-enable-linkonceodr-outlining). A link without
bitcode never loads libLTO and so ignores them.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
When -object_path_lto names an existing directory, ld-prime writes the
merged LTO object into it as lto.o, appending "/lto.o" to the path as
given (so dir/ yields dir//lto.o), and names the object so in the map,
diagnostics and N_OSO stab; ThinLTO links pass a directory for their
per-module objects, which the merged object then shares. A path that
can't be written is no error: the object keeps the name all the same.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime groups its arm64 branch islands into clusters spaced by
-branch_island_region_size, a hexadecimal size it reads as strtoull()
does (leading white space and a sign allowed, nothing after the
digits): "missing <size>" without one, "must specify a hexadecimal
size" for anything else. mold places its range-extension thunks by
each branch's reach instead, so island placement differs anyway; the
size is checked and unused.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime reads -sdk_imports_api_list's file as it reads the option, with
NSJSONSerialization into dyld's JSON nodes: an object whose "version"
(read by atoi, into an int) the report records as apiListVersion and
whose "apis" are the only imports it lists. A fault stops the link with
NSJSONSerialization's or dyld's words; with nothing left to report, the
report has no input. The reader keeps ld-prime's diagnostics.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
-arch_variant names a variant of an architecture, such as arm64e.x1 of
arm64e's pointer authentication ABI, which ld-prime takes only for some
arm64e links. A name it doesn't know is a fatal "unknown -arch name" as
it reads the option; for an arm64 or x86_64 link, the targets mold
links for, it refuses the option once it has read the others, -r or
not: "-arch_variant is not supported with -arch arm64".
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime takes a fat dylib's slice of another subtype of the link's CPU
type (arm64e for arm64, x86_64h for x86_64) if it has none of the link's
own, unless -no_allow_dylib_sub_type_mismatches (else the variable
$LD_DYLIB_CPU_SUBTYPES_MUST_MATCH) lists, ':'-separated, an arch of that
CPU type: then it ignores the dylib as one missing the arch. Of a name
it doesn't know it warns "unknown architecture name '<name>' in
LD_DYLIB_CPU_SUBTYPES_MUST_MATCH" once the options are read.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A fixed-size literal (4, 8 or 16 bytes) of an object with subsections
is one atom whatever labels it, and ld-prime's -map names it by the
label best to name an atom (a global before a local, then the later
name) unless that label is linker-private (lCPI0_0), which leaves the
literal known by its size. Every other label, linker-private or not,
gets a row of no size right after the atom's, the better first.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
When category merging gives a class (or, for categories on a class of
another image, the first category) a method, protocol or property list
of a kind it had none of, ld-prime's -map lists under -dead_strip one
dead pointer-sized atom of the class's file per list pointer so set -
two for a protocol list, which the metaclass shares - after that file's
other dead atoms. The merged property list, which no symbol names, is a
live anonymous atom of file 0.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Category merging writes a class's and its metaclass's class_ro_t anew,
each in place of its input record, as ld-prime does. When those were
all that remained of the image's __objc_const (the categories' lists
merged away, as for a class that had no methods of its own), no output
section existed to place them in, and they went to a tail in the order
written: the class's before the metaclass's. Now the section is made.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An indirect symbol (N_INDR: an -alias of an import, or a re-export an
export list makes) has in n_value the string-table offset of the name
it stands for. ld-prime writes that name as a string of its own right
after the entry's name, rather than pointing at the import's string, so
-alias _puts _my_puts gives "_my_puts\0_puts\0" and a second "_puts" for
the import; an entry standing for its own name points at that name.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime ignores an $ld$ directive it can't read without a warning. An
$ld$previous symbol needs no final '$', and with no symbol field the
directive renames the whole library; a version it can't read (1.2.3.1,
10., 70000) or an empty install name voids it. OS versions compare by
their first three numbers. A stub may give $ld$compatibility_version
too; a dylib binary can't, and for kinds it doesn't know ld-prime warns
"unknown link constraint kind: <kind>" once per directive name.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
TAPI reads each YAML scalar as its key's type: a library version is at
most three numbers, the first below 65536 and the rest below 256; a
Swift ABI version is a byte (or 1.0-3.0 before version 4); flags,
objc-constraint and tbd-version have their own sets; a sequence in a
scalar's place fails at its first token. A JSON .tbd it reads section by
section, refusing the first it can't read ("invalid <key> section"), and
takes the install name and versions from each list's first entry.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
TAPI reads a YAML .tbd by the schema of the version its document's tag
names (none or !tapi-tbd-v1, -v2, -v3, !tapi-tbd for 4), and ld-prime
refuses a key that schema lacks: objc-eh-types before version 3,
swift-version from 3 on, weak-def-symbols in 4. Its reader reports the
first fault: a key repeated in a mapping, then in schema order a missing
required key, a scalar or flow item where a sequence or mapping belongs,
and last the unknown keys of the mapping, the first by name.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A .tbd's "undefineds" sections list the symbols the library imports, so
ld-prime links nothing against them; mold read their symbols as exports.
TAPI's YAML reader takes a sequence in block style ("- _foo" a line) as
well as in flow style ("[ _foo ]"), and quoted keys as plain ones, and
version 1 names the clients an exports section allows "allowed-clients"
rather than "allowable-clients". mold read none of these. Full-line
comments are now skipped rather than read as keys.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A symbol an export list or -reexported_symbols_list names that binds to
a library the image re-exports whole (LC_REEXPORT_DYLIB) is exported
already: ld-prime adds no N_INDR entry or re-export trie node for it and
warns "explicit re-export for symbol '_foo' is redundant because it is
already re-exported from dylib '<file>'", the file by its real path and,
for a private library merged into the re-exported one, that library's.
The warnings come after the undefined and duplicate symbol checks.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime makes the sections of -sectcreate and -add_empty_section as it
walks the command line, one list for both options, so that
"-add_empty_section __FOO __a -sectcreate __FOO __b d" gives __a before
__b. Like other sections, they follow their segment's input sections,
and a segment only they make takes its place among the other such
segments by its first section's option. mold made all -sectcreate
sections first, then the empty ones; now Args keeps one list.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime appends a line of JSON to each after writing a final image,
naming it by leaf name and UUID: the loaded dylibs' real paths (or
install names, for the shared cache), by kind and in load-command
order, and the archives a member loads from; the symbols file adds a
dylib's exports, each dylib's imported (and re-exported) symbols, the
loaded members' globals and the archives with an unloaded member.
-no_uuid leaves the first two out; a file it can't open fails the link.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime reports each atom-naming symbol of a final link, in the
order it reaches them - by input subsection, last-defined first at one
place - and then its own atoms: Objective-C stubs, relative method
lists, __dyld_private and TLV descriptors. A symbol gets "use default
mapping to S/s" or a line per step that moved it: a symbol move,
-merge_zero_fill_sections, -text_exec or -data_const, then the renames
as one step, -rename_segment's if it applied. A -r link reports none.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime prints the auto-link hints of the command line's objects as
it parses them and again as it acts on them, frameworks first, and in
between the libraries each directly loaded dylib re-exports, found or
not, unless loaded directly too: a stub's once per platform it has for
the target, so twice for a zippered SDK stub (macOS and Mac Catalyst),
and an archive member's hints as it loads. -trace_implicit_library
picks the lines about libraries whose names contain its argument.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime matches an order file line against the names atoms have in the
map, so a C string's label orders nothing, and gives each atom the first
line naming it. -order_file_statistics (or LD_PRINT_ORDER_FILE_STATISTICS)
then warns of each atom whose name lines repeat unqualified, of an
unqualified name several objects define, of each line that ordered no
live atom (a repeated object-qualified line is silently dropped), and
of how many of the lines did.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime's version entry is its -v banner, newline included, and it
sorts the entries by opcode and path, keeping duplicates. Its inputs are
every file the command line names - archives whether or not a member
loads, dylibs -dead_strip_dylibs drops, the -bundle_loader, the
-filelist and -sectcreate files - once per spelling, and twice each a
library loaded only as another's re-export; its outputs the image, -map
and -sdk_imports. A relative path is resolved to the real path, if any.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime writes a map for a -r link too, as for an image: the output's
sections at the addresses they have from zero, and the atoms in them.
It makes atoms of the records of the sections it writes itself, each
its object's: a __compact_unwind record is named by the label at it
(an arm64 assembler's ltmpN at the section's start) or "anon", an
__eh_frame CIE "CFI" and an FDE "FDE for: " and its function's name.
It writes the map and the dependency info before the output.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime writes the dependency info and the map once the sections are
laid out, before it sizes __LINKEDIT and writes the output, so a link
that then fails - on a fixup out of range or without a target (Swift
metadata -remove_swift_reflection_metadata_sections dropped), thread
data outside the template, chain pages of a bad size - still leaves
both. An error found before that, a segment out of order or a section
past its segment's end, fails the link after the dependency info alone.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime ranks a final image's sections by the content of their atoms:
code (a section of pure instructions) comes first in any segment, not
only in __TEXT - in __DATA ahead of the lazy pointers and the
Objective-C data, in a custom segment ahead of its data, in input
order among the code sections, as __text has no rank of its own there.
A section a symbol move made thus ranks as in __DATA, code first.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
dyld copies a thread's initial values from one block: the sections of
type S_THREAD_LOCAL_REGULAR and S_THREAD_LOCAL_ZEROFILL. ld-prime checks
in its walk over the segments that each such section directly follows
the one before in load command order (across segments too), failing
with "TLV sections must be contiguous, but A - B aren't" and printing
the layout once, when a -rename_section or a symbol move parts them.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime's -trace_symbol_layout prints "symbol 'S', <step> mapped it to
SEG/SECT" on stdout for each step that changes a symbol's section: the
move (-move_to_rw_segment, -move_to_ro_segment, -dirty_data_list), then
-rename_section (legacy names included) and -rename_segment on the new
name; -trace_symbol_layout_file writes the lines into a file instead,
warning if it can't. mold reports the moved symbols' lines only, not
the default mapping and __DATA_CONST lines ld-prime gives every other.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime moves the atom of a symbol a -move_to_rw_segment or
-move_to_ro_segment list names (name, file:name or pattern; an option's
first matching list wins) to its section's name in the list's segment,
and -dirty_data_list's from __DATA to __DATA_DIRTY (after __DATA). rw
keeps code, strings and literals, ro writable data, warning per named
symbol; rw beats ro, -r refuses both. ld-prime leaves a segment of
moved code rw- (a Bus error); mold deliberately makes it r-x.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime gives each function deduplication folds away an alias atom of
its own named as the function's atom was - the label it ranks first at
the function's start - but of the name of the atom it folded into it
lists one of each scope, local or private extern, in the symbol table
and -map. Identical local functions that share a name across objects,
as an inline function's .cold.1 part does, so leave the name there
once; mold listed every copy.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
With a -segalign other than 4KB or 16KB, an arm64 image's fixup chain
pages are of a size dyld does not read, and ld-prime fails the link on
the first segment with fixups. It builds the starts record of every such
segment before it reports that, though, so the __LINKEDIT size in the
layout it prints counts the records; mold stopped at the first, making
its __LINKEDIT 0x20 bytes smaller (a one-page record and its padding)
for an executable with fixups in __DATA alone.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A -static image has no LC_DYSYMTAB and so no indirect symbol table to
name a __got slot's symbol, and ld-prime types its __got S_REGULAR, as it
does a kext's, in a -preload image too; only a -static -pie image, whose
LC_DYSYMTAB indexes the slots, keeps S_NON_LAZY_SYMBOL_POINTERS. mold
typed them all as non-lazy pointers, with a reserved1 index into a table
the image does not have, which otool reports as running past its end.
Such a slot holds an absolute symbol's value, or a target 4 GiB away.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime sends an arm64 branch between two segments 4 GiB apart through
a shim, the target's __stubs entry jumping through a __got slot dyld
rebases (shared with a GOT load's), in any image dyld loads, and keeps
a GOT load between them unrelaxed. It judges the distance before
layout: a segment -segaddr pins (to a nonzero address) is at its pin,
every other one a page past the image's base - the end of __PAGEZERO
in an executable, else 0 - so an unpinned segment is far from a
__TEXT pinned 8 GiB up even when it lands right after it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime checks -segaddr pins for overlaps on a first layout that puts
each floating segment right after the one before it in segment order,
pinned or not, and only then places the floating ones in the lowest gap
that fits, so a segment after one pin that runs into the next is an
overlap error even where the final placement would have avoided it. A
dylib's or bundle's pinned __TEXT, the preferred address that ld-prime
honors without chained fixups, is the base the others float from; only
a PIE's or a chained image's is not, letting them follow a lower pin.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime orders __DATA's zero-fill sections by where it first sees each:
for __common that is an object's own __common input section (where clang
puts a C++ zero-initialized global, which is no common symbol) or the
first object with a common symbol, whichever comes first. mold took the
first common symbol's object alone, so a link whose early C++ objects
have __common sections and whose commons come later from archive members
(transmission's tests) put __bss before __common.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Swift promises no function an address of its own, and ld-prime's
deduplication (-O1 and up, or -deduplicate) acts on that: a function of
__TEXT,__text whose atom is named by a Swift-mangled symbol ("_$s...",
not "_$S", "_$e" or "_T0") is a candidate even if its address is taken
or it is exported, so a coroutine's resume function and a value witness
fold. The atom's name is the label ld-prime ranks first at its start (an
exported one before a local, then the greatest name), so a non-Swift
label that outranks the Swift one keeps it apart.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
collect_file had grown by the checks of a file named again (-force_load
of an archive, the renaming of a dylib), of an object cut short and of
a file no input kind takes, and load_pending by the warnings ld-prime
gives of each object once it has its atoms. Each is now a function of
its own (name_again, refuses_malformed, refuse_file,
warn_about_objects), so the two read as the steps they take again.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime knows the segments of arm64e's signed pointers as standard
ones: in an image dyld or kmutil loads, __AUTH_CONST and then __AUTH
come after __DATA_CONST and before __DATA (a kext's __DATA_CONST, after
__DATA, too), whatever order the inputs name them in; only a -static or
-preload image takes them as any other segment, in the order first
seen. mold put them after __DATA with the non-standard segments.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime checks an object's layout before it reads it: a file with a
64-bit Mach-O magic number but short of a 32-bit header is "buffer too
small", load commands past the end "mh.sizeofcmds extends beyond buffer
size", and a symbol table or its strings past it "LINKEDIT content
'symbol table' (or 'symbol strings') extends beyond end of segment".
Each is one of the input errors it gives together; mold panicked.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
With lazy binding the stub helper enters dyld through dyld_stub_binder,
which libSystem exports. In an image linked without it that may look
symbols up dynamically (-undefined dynamic_lookup, or -U naming it),
ld-prime binds the symbol with a flat lookup as any other it can't
find, where mold failed the link ("lazy binding needs dyld_stub_binder,
which no loaded dylib exports").
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime reads each 32-byte record of a __DATA,__cfstring section as a
CFString constant, which must have just two relocations: the class
pointer's at offset 0 and the string's at 16. It refuses an object with
another as it reads it ("cfstring constant does not have two fixups",
"... isa not at offset 0 ...", "... string-data not at offset 16 ..."),
an archive member the link doesn't use and a -r input too; mold took it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Below 4 KiB, a -segalign gives arm64 chain pages dyld can't read, and
ld-prime fails the link with "chained fixups, page_size not 4KB or 16KB
in segment #N" as it writes the chains - after it has applied the
relocations, so one it can't apply (an LDR from an address the reduced
alignment left unaligned) fails the link first, with its own error.
mold reported the page size as it laid the chains out, before.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime's -why_live walk starts from its initial undefines in the
order it lists them: the -u symbols as the command line gives them,
then the entry point, then the other root atoms in input order. So a
chain from a -u root is printed before one from _main. mold walked
from the entry point first (and its test said so).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld64 drops the -syslibroots when the last one is /, as configure
scripts pass it, and ld-prime does so for an option naming a library
by an absolute path too (-weak_library, -force_load...): it looks the
file up as the path spells it only, and fails with "library ... not
found" where mold still found it under an earlier root.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime checks the OS version of the dylib each input names: a dylib
given twice by path, by path and by a library option, or another file
with its install name, draws the "built for newer version" warning
once per input, at each one's place among the objects' warnings. Only
the library options naming one library (-lfoo -needed-lfoo) make one
input. mold loads the dylib once and warned once.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime reads an object's LC_LINKER_OPTION commands twice, warning
both times of the options it ignores: as it reads an object the link
loads from the start (one on the command line or a member -force_load
loads), after the object's other warnings, then once it has resolved
the symbols and checked the inputs' versions, for every object it
loads, in input order. -add_linker_option's it reads once, first.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A section-relative relocation names its target by the section's
ordinal and an address, which may lie before the section's start. ld
-r re-targets it as any other at the symbol of the section's first
atom, with a negative addend in the field ("_d-8"), where mold kept it
section-relative: the same address, but no longer tied to the section
once the final link moves the sections apart.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime takes a 4-byte pointer where a pointer would be a text
relocation for one, and lists it so. Any other in an image dyld loads
fails the link once the text relocations are listed: with chained
fixups in place of their error, naming the last section's (its first
atom's last pointer), as the chain builder finds unaligned pointers;
with classic dyld info only if there are no text relocations, naming
the first. mold gave an error for each, before the list.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime checks the options it has read in a fixed order, warning and
failing as it goes, so a fatal error drops the later checks' warnings:
-segaddr repeats, the output kind's options, -no_pie, mergeable dylibs,
the shared region, -segalign, -seg_page_size, -pagezero_size, the stack,
segment pins and orders, -image_base, -headerpad, -read_only_relocs, -U,
then the obsolete options and an unused -e. Under -undefined
dynamic_lookup -U is ignored, and /usr/lib dylibs may force weakness.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Under -allow_sub_type_mismatches ld-prime takes the slice of a fat file
for another subtype of the link's CPU type, x86_64h in an x86_64 link,
when there is none for its architecture, and warns of it as of a thin
file, naming it by the fat file's path ('fat.o', 'libfat.a(m.o)'),
where mold ignored the file for lacking the architecture. Its other
diagnostics name a fat object by the fat file's real path too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime prints its -why_load lines as resolution loads each archive
member, a -r link's too, then checks the inputs' platforms and
versions, then warns a second time of each object of another CPU
subtype -allow_sub_type_mismatches let in that the link uses. mold
printed the -why_load lines much later and not at all for -r, and gave
the subtype warnings before the version warnings.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A library named twice is read once, as its first naming says, but
ld-prime applies -force_load to an archive whatever named it before:
every member is loaded, and -why_load says "-force_load caused load
of" each. mold skipped the second naming, so with "lib.a -force_load
lib.a" a member nothing references was left out of the output.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime checks the platform of the dylibs as of the objects in one
pass after it has read all the inputs, in input order: a dylib or an
object built for another platform than the link's stops the link
there (a firmware link takes such a dylib with a warning), so an input
it could not read fails the link first, and only the first foreign
input is named. mold refused a dylib as it read it, and listed every
foreign object.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime warns "alignment (N) of atom X is too small and may result in
unaligned pointers" as it reads each object, in parallel: for every
object it reads whole (an archive member the link doesn't load too, a
weak copy that loses, an atom dead stripping drops), before any
symbol is resolved - so before an undefined-symbol error, and of a
pointer to another object's absolute symbol too - then warns of the
object's unwind info. An object it gives up on gets no section warning
past the bad section.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime reads the input files in parallel and collects the errors it
finds in them - a file it can't open, an empty file, one it can't link,
a section or relocation it refuses, a file of another architecture
under -arch_errors_fatal - to give them at the end in one line, "ld:
multiple errors: A; B", in whatever order its threads found them. mold
gave a line each. Those found reading the files in turn now come first
in that order, then those of the parallel parse, sorted.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime checks the pointers of an arm64 image with chained fixups
section by section in parallel: it stops a section at its first
unaligned pointer (the last of the first atom in address order that
has one, as an assembler lists relocations from the last), and the
error it reports is the one of the last output section that has any.
mold reported the first section's, naming '_r3'+0x9 in __DATA_CONST
where ld-prime names '_r1'+0x8 in __DATA.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An image bound for the dyld shared cache may not carry interposing
tuples: ld-prime looks for a section named __interpose in a segment
whose name starts with __DATA or __AUTH, as dyld does, and when there
are several it names the last one in the image's final section order
(in two non-standard segments, the one first seen later), where mold
named the first.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime coalesces __objc_classrefs slots by content and makes each an
atom of its own that no label names, whatever labels the object puts
on it: below macOS 15, where the slot is not folded into the GOT, a
text relocation to it names "anon-N", N its kept copy's ordinal among
its object's atoms. As after a literal, an arm64 assembler's ltmpN at
the slot is numbered after the slot's atom in an object without
subsections. (At macOS 15, ld-prime asserts on such an object.)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
GCC emits no pubnames for type units, so the index listed a type only
for the compilation units referring to it by signature. GDB 18 doesn't
follow such a reference when it looks a type up through the index, so
ptype failed for types defined in type units. Read the name of the type
each type unit defines from its DIEs, resolving their section offsets
with input relocations, and add it to the index for the type unit.
The dead-stripped list's per-object pass grew three jobs: reading the
object's symbol table for the labels of its gone atoms (and counting
each C string's labels), turning them and the unnamed stretches into
rows, and listing the tentative definitions the output lacks. The
first and the last now have functions of their own, gone_labels and
dead_commons, and a label is a DeadLabel rather than a five-tuple.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime's -map lists the members it loads from an auto-linked archive
where the archive's option comes in the sorted options, not with the
command line's objects. A library an $ld$previous directive gives a
dylib's install name, libswift_Builtin_float before macOS 15, is listed
where its own option names it (else after the auto-linked files), and
the dylib only if a symbol of its own binds; a library whose bound
exports all moved away is listed where it was named.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime gives the libraries that per-symbol $ld$previous directives
move exports to their load commands after all the others, auto-linked
ones included: by the library the exports moved from, in load-command
order (one left with no load command keeps its place), then by the
first export that moved. A Swift program for macOS 11 thus lists
libswiftFoundation, where Foundation's Swift exports move, last.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime never shows the "(for architecture X)" mold appended to a fat
file's slice name. Its -map lists a fat archive's member by the path
given and the member, "libfat.a(f.o)", and its diagnostics (duplicate
symbols, -why_live) name a fat object by its real path and a fat
archive's member as "/abs/libfat.a[arm64][2](f.o)": the slice's
architecture between the path and the member's place in the archive.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime's -map lists each symbol it defines that something refers
to, as a sizeless atom of its own: ___dso_handle (C++ static
destructors and the macOS 27 lazy-load helpers pass it on), a dylib's
or bundle's header name, and section$start/end bounds (not segment
ones). A -sectcreate or -add_empty_section section is an atom named
"l<sect-create>SEG,SECT", one made for a boundary symbol "SEG,SECT".
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Under -dead_strip, ld-prime's -map lists every atom of the loaded
objects that the output lacks, whatever took it out: dead stripping,
coalescing (a literal or ObjC reference equal to another file's, a
losing weak definition, a folded function, each tentative definition
but the one kept, after its file's other atoms) or an ObjC rewrite. A
C string is an atom per label at its start, the extras merged away;
an empty section's ltmpN label and a consumed section have no atoms.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime names an atom with subsections after the best label at its
start (exported, private extern, local, weak, ltmpN; then the greatest
name), which has the atom's size; the others alias it. Each record of a
fixed-size record section (an Objective-C list's pointer) is an atom;
a category list rebuilt by category merging keeps its categories'
files, and a merged +load's __objc_nlclslist entry and a folded
function's alias are ld-prime's own. A common symbol a real definition
replaced is the defining file's.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
In an object without subsections, ld-prime gives each label an atom
of its own for __unwind_info and goes by the labels alone, so a label
inside a function that a compact unwind record or FDE spans by its
length gets encoding 0 too, and the code past it can't be unwound
(a backtrace or exception stops there). Such a label now gets no
entry, leaving the record its whole range; ld-prime's output differs.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
lay_out_eh_frame drops the FDEs of folded copies, decides which CIEs
live (those a kept FDE points at, and lone ones unless -dead_strip),
then gives every kept record its offset. The last step, which walks
the CIE and FDE lists together to lay each object's records out in
input order as ld-prime does, is now assign_eh_frame_offsets, which
returns the section's size, keeping lay_out_eh_frame to its decisions.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A DWARF-mode __unwind_info entry holds its FDE's offset in __eh_frame
in 24 bits. For an FDE past them ld-prime writes offset 0, which makes
the unwinder search the whole section, where mold kept the low bits,
pointing it at a wrong record. Like ld64 it neither merges DWARF-mode
entries nor makes their encoding a common one. A final image's link
warns "__eh_frame section too large (max 16MB) ...", but not a -r one,
and -no_warn_eh_frame_too_large silences it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A CIE no FDE of its object points at is an atom like any other to
ld-prime: a final image keeps it in its place in __eh_frame unless
-dead_strip drops it, with a GOT slot for its personality to point at
and an __unwind_info even if it is the only unwind info, and a -r
output keeps it too. A CIE whose FDEs all go, as those of functions
with compact unwind records do in a final image, still goes with them.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime copies the CIEs and FDEs it keeps into __eh_frame as its
inputs have them: object by object in load order, and in each the
order of its own __eh_frame, so an object's CIE follows the FDEs of
the object before it, whatever -order_file does to the functions.
mold put every CIE ahead of every FDE, as mold does for ELF, which
moved each FDE and the DWARF-mode __unwind_info entries naming it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An FDE-only function gets one __unwind_info entry in DWARF mode, whose
low 24 bits give its FDE's offset in __eh_frame. When an object has
two or more FDEs for one function, ld-prime carries every one of them
into __eh_frame but gives the function a single entry, pointing at the
last; mold made an entry per FDE. The record made for the first FDE
now takes each later one's, as an object's own DWARF-mode record does.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime reads the word after a record's length for its ID whatever
the length says, so after a zero-length record it takes the next
record's length for one: zero makes an empty CIE, anything else an FDE
whose CIE pointer it checks before the size, failing with "FDE points
to CIE outside __eh_frame section" or "CIE ID is not zero" when that
points nowhere sensible. One pointing at a real CIE is still refused
as truncated, where ld-prime reads the fields from the next record.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime sorts the entries of one function address by their encoding,
not by input order: two records for one function, a record at one
section's end and the next section's first function, or an empty
atom with a record and the function after it. The unwinder's binary
search settles on the last entry of an address, so the greatest
encoding wins; an empty atom's encoding 0 still comes first.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime looks for the function of a __compact_unwind record in the
section its relocation names (a symbol's n_sect, or the section of a
section-relative one), where a label goes: an address at the section's
end is its last atom's, or that of a label there, so a record there
links instead of failing, and is not the next section's first atom's
at the same address. Without subsections, the last atom takes it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Without MH_SUBSECTIONS_VIA_SYMBOLS a section stays whole, but ld-prime
still cuts its atoms at every label for __unwind_info (an alt entry's
too; extra labels at one place, or at the end, make empty atoms), and
each atom with no record of its own gets encoding 0, so a function
between two records no longer unwinds by its neighbor's. A record
anywhere in an atom is the atom's and leaves the atom's start without
an entry, with subsections too (where an alt entry's is its own).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime needs no -lto_library: it links @rpath/libLTO.dylib, its rpath
being @executable_path/../lib, so it takes the libLTO of the toolchain
it is installed in, as ld64 did by its own real path and as clang finds
the one it passes. mold run directly now does the same, with libLTO.so
on other hosts, through the portable current_exe; failing that, the
dynamic loader's search for the plain name, as before.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime links its own libLTO through @rpath and loads another by
running itself again with that library's directory first in the dynamic
loader's search path, so it checks -lto_library before the rest of the
command line: any one not named libLTO.dylib fails the link ahead of
unknown options; the last one counts, and if no such file exists it is
ignored with a warning, given before the other option warnings.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Resolution reruns after auto-linking and after LTO, but a file live
after one round stays live, so clearing the -why_load record each round
lost the reasons of the archive members loaded earlier, which then read
as loaded by -ObjC. ld-prime reports each member once, for the symbol
that pulled it in, bitcode members LTO compiled included; mold now keeps
the first reason and lists those members too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
LTO code may come to call runtime routines no input called before, so
once bitcode is in a -static or -preload link, or any link with
-lto_softload_runtime_symbols (the last of it and its -no_ form wins),
ld-prime looks up ___divsi3, ___gtdf2, ___ltdf2, ___muldi3, ___udivdi3,
___udivsi3, _memset and _strcpy in the libraries as if referenced: the
first archive or dylib providing one loads or binds it, the latter an
unbound import in the symbol table unless -dead_strip; none is missing.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
By default (-use_lto_filenames_in_order_file_matching) ld-prime takes a
symbol of the compiled /tmp/lto.o as the bitcode file's it came from, by
the same name-based credit its -map gives, so "b.o:_f" orders the _f of
bitcode b.o; a symbol no single module defined stays lto.o's. With
-no_use_lto_filenames_in_order_file_matching (the last of the two wins)
only the object's own leaf name, lto.o, matches.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An order-file line "file:symbol" narrows the match to the symbols of
the object whose leaf name is "file": m.o, or lib.a(m.o) for an archive
member. ld-prime compares nothing else, so a qualifier with a directory
in it, relative or absolute, matches no file and the line orders
nothing; mold also took any suffix of the object's path, which ordered
symbols ld-prime leaves in input order.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime's map numbers each bitcode file where it was named, like any
object, and the compiled /tmp/lto.o after every other file. A symbol of
the compiled object is credited to the one bitcode file whose module
defined a symbol of that name, internal ones included; a name two
modules defined (a static in each) or none did (literals, a static LTO
renamed to keep it apart) stays the compiled object's.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime checks for duplicates among bitcode definitions before LTO,
whose module merge would fail on them, and lists every file defining the
symbol, Mach-O objects first. One between bitcode and a Mach-O object
waits for the compiled object, which keeps the definition: the error
lists the objects, the bitcode files it came from and /tmp/lto.o, in
that order (ld-prime orders each group at random; mold by input).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime checks a bitcode file as it checks a Mach-O object, by the OS
and version in its target triple (arm64-apple-macosx27.0.0), in input
order: a newer one draws the "built for newer" warning under the file's
own name, another platform an error that stops the link before LTO. The
compiled object, which carries the merged module's newest version, and
whatever it pulls in are checked afterwards, so that warning comes last.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime calls the object libLTO compiles /tmp/lto.o although it never
writes such a file: warnings, duplicate-symbol errors, -why_live, the
-map file list and the N_OSO debug stab all name it so, the stab with
modification time 0 to skip dsymutil's check. With -object_path_lto
the object is named after the file written, as given, and the stab
carries that file's mtime. -dependency_info lists neither.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A weak bitcode definition loses to a strong one in a Mach-O object, but
libLTO only knows that if the linker says so: left unpreserved, LTO
internalized the weak function and inlined it into its bitcode callers,
so the program ran the wrong one. ld64 tells libLTO to keep the LLVM
atoms it coalesced away for Mach-O ones; doing the same also keeps a
strong duplicate in the compiled object, where the duplicate check sees
it, as ld-prime reports one between bitcode and a native object.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A -r link whose inputs are all bitcode writes no Mach-O: ld-prime adds
the modules to libLTO in input order, marks every symbol they define
(private externs and weak ones too) must-preserve, and writes the merged
module with lto_codegen_write_merged_modules, wrapper header and all, so
the output is byte-identical. One native object among the inputs makes
it compile the modules with the same roots instead, and the object joins
the -r output; an export list leaves the symbols it omits internalized.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A -dependency_info, -map or -sdk_imports file ld-prime can't create is
not an error: it warns "Could not open or create -dependency_info file:
P", "could not write map file: P" or "can't open SDK imports file for
writing at 'P'" and links on, writing the reports in that order once the
layout is done. mold failed the link with "cannot open P", and wrote the
map before the dependency info.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
arm64 ld-prime -r names each record of __literal4/8/16 itself, l<nnn>,
in place of the record's name: of the record's labels, the greatest
name, an assembler ltmpN label only where it is the only one. The
record's other labels stay as plain local aliases (n_desc 0, the record
being an atom of its own), relocations keep naming them, and an ltmpN at
a section start survives in an object without subsections. mold let
every label of the record stand for l<nnn>, dropping e.g. ltmp1 next to
an l-label; a C string's labels all still vanish into its LC<n>.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime knows __objc_selrefs by name, but makes its atoms by content -
merging the entries that name one selector and naming none of them -
only where the section has the literal-pointer type clang and swiftc
give it. A regular or coalesced __objc_selrefs (hand-written assembly)
is plain data: every object's entries stay, and their labels are kept,
a local one as a local of the final image and by its own name in a -r
output on both arches. mold merged them and dropped or renamed labels.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Below macOS 27 ld-prime links a dylib named by -lazy-l, -lazy_library or
-lazy_framework as usual (with the "lazy-load will be ignored" warning),
but it gives the lazily named ones their LC_LOAD_DYLIBs, and so their
ordinals, after those of every other library the command line names,
in their own naming order - a dylib named both ways included - and
before the libraries loaded implicitly or by auto-link options. mold
kept command-line order, which changed the bind ordinals too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime's ad-hoc code directory identifier is the leaf name of the
image's install name - -install_name's, an executable's too, else
-final_output's, else the output path - taking whatever follows the
last slash, so a name ending in one gives an empty identifier. mold took
the output file's name, so a dylib written as m.dylib with install name
/usr/lib/libfoo.dylib had a signature 8 bytes smaller than ld-prime's,
the identifier being part of the code directory's size.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
In a -static image without -pie nothing slides the image, and ld-prime
makes __mh_execute_header an absolute symbol (N_ABS | N_EXT,
REFERENCED_DYNAMICALLY), but its nlist still has n_sect 1, the section
the mach header's atom belongs to, as in every other image where the
symbol is N_SECT. mold wrote n_sect 0 for the absolute form.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime caps a section's alignment at -segalign, with the "reducing
alignment of section" warning, only in an image dyld maps. A kext, which
kmutil links into the kernel, is treated like a -static or -preload
image: its sections keep their alignment, no warning is given, and the
segment a section opens starts on that alignment (__DATA of an x86-64
kext at 0x260, not right after __TEXT's 0x251, under -segalign 0x1).
mold capped a kext's sections as it does a dylib's.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Under a -segalign below 8, __LINKEDIT starts wherever the previous
segment's file contents end, at any byte. ld-prime then places its
tables by file offset: the export trie, function starts, data in code,
split info, symbol and string tables on 8 bytes, the indirect symbol
table on 4 and the code signature on 16, while the dyld opcodes, the
chained fixups and the local relocations simply follow the table before
them, the first of them at the segment's unaligned start (their sizes
are multiples of 8). mold aligned them relative to the segment start.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
For an x86-64 macOS deployment target ld-prime writes the legacy
LC_VERSION_MIN_MACOSX {version, sdk} not only below 10.14, which
brought LC_BUILD_VERSION, but also from 10.14.4 up to 10.15 and from
10.15.4 up to 10.16, in final images and -r output alike and whatever
the SDK version: 10.14.0-10.14.3, 10.15.0-10.15.3 and 10.16 on get
LC_BUILD_VERSION. arm64 gets LC_BUILD_VERSION at any version. mold used
the legacy command below 10.14 only.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A kext and a -static -pie image carry their relocations in LC_DYSYMTAB
(locreloff/nlocrel for the pointers that slide, extreloff/nextrel for a
kext's imports). ld-prime fills in a table's offset only when it has
entries: with none, both the offset and the count are 0, as otool shows
for a kext that imports nothing or has no local pointers. mold wrote the
offset of the empty table's place in __LINKEDIT.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime's MH_OBJECT segment command gives the sections' span rounded up
to 8 bytes as vmsize (0x9 -> 0x10, 0x5 -> 0x8, whatever the sections'
own alignment), on arm64 and x86-64 alike, and its filesize is that less
the zero-fill sections' span, i.e. the end of the contents plus the same
padding: past a trailing __bss it reaches a few bytes into the
relocations that follow. mold wrote the exact end for both.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime writes a -r output's relocation entries right after the section
contents, one block per section in the order of the section headers,
the sections it writes afresh among the others: x86-64's re-synthesized
__TEXT,__eh_frame, whose CIE personality pointers carry relocations,
comes between __gcc_except_tab and __data. mold put the merged sections'
blocks first and the synthetic sections' (__compact_unwind, __eh_frame)
after them all; only the file offsets differed.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime checks that an existing output is writable ("can't write output
file"), unlinks it unless it is a character device, and then creates it
with O_CREAT|O_EXCL and mode 0777 (an object: 0644) less the umask; a
file that is still there (/dev/null, or one in an unwritable directory)
is opened O_WRONLY and written in place, keeping its mode, and nothing
is chmod'ed afterwards. It sizes the file with ftruncate, which fails on
a pipe, and reports open/ftruncate/write failures as "open() failed,
errno=N (text) for 'path'". mold now follows that, so -o /dev/null works.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld64's dylib ordinals give a library the image re-exports from a
location that isn't public (by install name, all of them under
-no_implicit_dylibs) BIND_SPECIAL_DYLIB_SELF when there are two or
more of them, and ld-prime does likewise: its imports bind to ordinal
0 in the chained or classic binds, n_desc and export trie, sort with
the image's own GOT slots, and dyld finds them through the re-exports.
The load commands stay; Xcode's -no_merge_* debug builds hit this.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime links its merged-libraries hook (bundleForClassHook.o, with
_relinkableLibraryClasses binding each class) into an image when a
library a -no_merge_* option names exports a class itself, by name:
_OBJC_CLASS_$_*, _OBJC_METACLASS_$_* or a Swift _$s...CN, after $ld$hide
and $ld$add, from a dylib or a stub; not a re-exported library's. It does
so even if another naming loaded the library; without the hook the
option links as -reexport_* does, so mold now refuses only that case.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A .tbd stub names an Objective-C class for its class and metaclass
objects (objc-classes, or objc_class in version 5) and for its
exception type (objc-eh-types, objc_eh_type). tapi turns a class listed
for its exception type alone into all three symbols, so ld-prime binds
_OBJC_CLASS_$_Foo and _OBJC_METACLASS_$_Foo to such a stub as well as
_OBJC_EHTYPE_$_Foo; mold exported only the last. SDK stubs list such
classes under both keys, so only hand-written stubs differed.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A non-PIC object referring to an absolute or undefined weak symbol with
a PC-relative relocation, such as AArch64's ADRP, can't be linked into
a position-independent output, but recompiling it with -fPIC fixes that
by going through the GOT. The error message lost that hint when it was
reworded, which broke abs-error on aarch64_be.
od -w is a GNU extension, so arch-x86_64-eh-frame-hdr-order failed on
FreeBSD. head and grep -q exit before reading all of their input, so
under pipefail the producer may die of SIGPIPE and fail the pipeline,
which happened to mergeable-entsize-order on FreeBSD.
The option drops Swift's field descriptors (__swift5_fieldmd),
associated type records (__swift5_assocty) and the names they give
(__swift5_reflstr), in any segment, from a final image and a -r output
alike, as ld-prime drops their atoms on reading them. Every Swift
type's descriptor refers to its field descriptor, so such a link
fails: ld-prime reports the first reference by address as a fixup
error (and crashes on a pointer or in -r, where mold errs instead).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
-fixup_chains_section chains a -static or -preload image's fixups and
lists the chains' starts in __TEXT,__chain_starts (pointer format,
count, offsets from the image) instead of an LC_DYLD_CHAINED_FIXUPS.
A chain runs on through a segment while its 12-bit stride reaches, the
section has room for a chain per section, and reserved1 is 1, or 2
with _vm. -rebase_section is for 32-bit images; kexts and -kernel
images ignore both, and no image dyld loads may have either.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld64's -sectorder <segment> <section> <path> named an order file for
one section, an order of strings for __TEXT,__cstring; ld-prime 27037
takes any for an -order_file of symbols, whatever (even empty) segment
and section it names, and warns the same way about a file it can't
open. An empty path, or fewer than three arguments, is an error that
names all three.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The option makes each segment's zero-fill sections one __zerofill, the
objects' in input order (each in section order) and then the common
symbols, in a final image and in a -r output (without commons). The
merge comes before -rename_section, which then matches __zerofill. A
boundary symbol's empty __zerofill is zero-fill and leads the others
even without the option. ld-prime merges __thread_bss too and crashes
laying out the TLV template; mold leaves thread-local sections alone.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Zero-fill sections follow a segment's file-backed ones, and in
ld-prime 27037 __DATA's go in the order an input section of each was
first seen, by object and then section, with __thread_bss alone kept
first for the thread-local template and the synthesized __common
counting from the first object with a common symbol. mold put __bss
and __common ahead of every other zero-fill section, such as one an
assembler's .zerofill makes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime reads -add_linker_option's options as an object's
LC_LINKER_OPTION strings, all in a row before any object's, warning
"in command line" about those it drops; it splits one at its first
space only if the first word contains "framework". A final link loads
what they name, a -r output carries them. -ignore_auto_link acts on no
object's nor the command line's; -force_load_swift_libs force-loads
an auto-linked archive whose file name starts with libswift.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
-no_dwarf_unwind drops every CFI record of the inputs' __eh_frame
(ld64 did it for sepOS binaries, which have no unwinder), so neither a
final image nor a -r output gets an __eh_frame. The compact unwind
records stay: a function compact unwind can't describe keeps its DWARF-
mode entry in __unwind_info, whose FDE offset is then 0 (0x04000000 on
x86-64, 0x03000000 on arm64), and a -r output keeps the record in
__compact_unwind as it came. Personalities only a CIE named go too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A call to ___dtrace_probe$<provider>$<probe>$... (or a test of
___dtrace_isenabled$...) marks a USDT probe site: ld-prime turns each
into nops and describes them in DOF, a __TEXT,__dof_<provider> section
the kernel reads at load time. -no_dtrace_dof skips the DOF, and
ld-prime then fails to link the sites at all; without probe sites it
changes nothing. ld64 took a D script with -dtrace; ld-prime 27037
doesn't even open the file, in a -r link either. mold makes no DOF yet.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
These switched off ld64 passes that ld-prime no longer has: the ".eh"
labels a -r output gave each FDE of __eh_frame, the automatic ordering
of initializer functions within __text, and x86-64's "huge" pass for
zero-fill sections beyond the reach of 32-bit displacements. ld-prime
27037 takes all three silently and writes the same final image and -r
object with them as without, so mold just accepts them.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
With __DATA_CONST in use, ld-prime moves __objc_selrefs into it by
default only in an image bound for the dyld shared cache, at any
deployment target (ld64 required macOS 13 too). -const_selrefs moves it
in any image that has __DATA_CONST (not with -no_data_const), and
-no_const_selrefs keeps it in __DATA; the last one given wins. The
segment stays SG_READ_ONLY and the runtime registers the selectors all
the same.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
-text_exec (implied by an arm64 kext) gives an image's code a segment
of its own, __TEXT_EXEC, and leaves __TEXT read-only. Besides the input
code, which becomes __TEXT_EXEC,__text, ld-prime moves the code it
synthesizes there under its own section names: __stubs, and the
_objc_msgSend$<selector> stubs' __objc_stubs, which mold left behind in
the non-executable __TEXT.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An arm64 _objc_msgSend$<selector> stub loads its selector reference
into x1, then reaches _objc_msgSend. A -objc_stubs_fast (default) stub
branches through a GOT slot of its own: 32 bytes padded with brk,
32-byte aligned. A -objc_stubs_small one is a plain b to _objc_msgSend
(its __stubs entry if imported, which shares the usual GOT slot): 12
bytes, word-aligned. The last counts; x86-64 has one form, and a
shared-cache image may not have small stubs on either architecture.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
For an image bound for the dyld shared cache, ld-prime checks the
options the cache rules out in a fixed order once it has read them:
-flat_namespace is refused first, then the run paths get their "OS
dylibs should not add rpaths" warning, and only then are -undefined
dynamic_lookup and -U refused. mold refused -U before it warned, so
the warning went missing from a link that failed on both.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An x86-64 _objc_msgSend$<selector> stub is a movq of the selector
reference into %rsi and a jmpq through _objc_msgSend's GOT slot: 7 + 6
= 13 bytes. ld-prime 27037 lays them out back to back in a byte-aligned
__TEXT,__objc_stubs, with no int3 padding (arm64's stay 32 bytes, 32-
byte aligned, their tail filled with brk). mold padded each one to 16
bytes, which moved every later stub and section.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An encryptable image (iOS apps the App Store encrypts; the last of the
two options says) has 16 KiB pages for 4 KiB ones, and its code starts
a page of its own after the header and load commands, which stay in
the clear, as does __oslogstring, moved to __TEXT's end on a page of
its own. LC_ENCRYPTION_INFO_64, after LC_MAIN or LC_UNIXTHREAD, names
the file range from the first section to the page end of the last but
__oslogstring, with cryptid 0. ld-prime gives a -r or -preload output
none (the latter still gets the pages) and crashes on a kext.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A framework or library may come in variants (_debug, _profile) next to
the plain binary. -framework Name,suffix links the variant of the file
Name.framework/Name resolves to (Versions/A/Name) with the suffix
appended, looking in every framework directory before falling back to
the framework itself. -image_suffix has -l and -framework try each
suffixed variant, of any extension, before the plain one, directory by
directory; a library named by path is taken as it is.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Both re-export a library the command line loads anyway (LC_REEXPORT_DYLIB,
in any image): -sub_umbrella a framework a -framework option names so,
-sub_library a library whose file is so named less its extension
(libfoo.A for libfoo.A.dylib, whatever its install name), a framework's
too with a deprecation warning. A weak naming yields to them with
"re-exported dylibs cannot be weak-linked"; a name matching nothing is
ignored. mold took -sub_library foo for -reexport-lfoo.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
dyld interposing (an inserted library's __DATA,__interpose tuples)
redirects binds only, so an image's direct calls to its own exports
escape it. -interposable makes every export of a dylib, bundle or main
executable interposable, and -interposable_list (wildcards allowed,
winning over -interposable) those it names: as for a -flat_namespace
dylib's exports, the image calls them through stubs and binds its GOT
slots and data pointers to them, here to itself (library ordinal 0).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime checks that the pointers dyld fixes up are 8-aligned, as fixup
chains need. By default it warns of unaligned ones where the image has
chained fixups or its deployment target would; -unaligned_pointers
warning (or warn) does so anywhere, error fails on the first, suppress
keeps quiet - though x86-64 still gives chained fixups up, saying so.
An arm64 image with chained fixups, or bound for the shared region,
fails whatever the option says, with a warning that it is fatal if it
says warning.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The option tells ld-prime what to do when objects disagree on whether
their class_ro_t pointers are signed (the __objc_imageinfo flag
SignedClassRO, 1 << 4), which only arm64e objects can be: warning (or
warn) or error, read as the other treatments are. After the options,
ld-prime reads $LD_OBJC_CLASS_RO_SIGNING_MISMATCH the same way, failing
on an empty or unknown treatment. Without arm64e mold has nothing to
check.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime warns of each object built for a newer OS version than the
link targets ("object file (...) was built for newer 'macOS' version
(13.0) than being linked (12.0)"), an object file's link included.
-deployment_target_mismatches error makes the first such object fail
the link with the same words, and suppress silences them; neither
touches the warning about a dylib built for a newer version.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime fails a link, an object file's included, that keeps a
reference to a symbol -poison_symbol or -poison_symbols_list names
(wildcards allowed), defined in the link or not; a reference -dead_strip
removes, or a -u, is no use. Once it has found no duplicate symbol it
prints "Use of poisoned symbols:", then for each such symbol each
reference, as the function holding it and its file's leaf name (an
archive member as "lib.a[N](member.o)"), and a blank line.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime reports a symbol live objects define strongly more than once
as "duplicate symbol '<name>' in:" followed by the real path of each
file defining it (an archive member as "lib.a[N](member.o)"), then
fails with "<N> duplicate symbols". Under -dead_strip it lists only the
symbols whose kept definition is live but counts them all, and
-allow_dead_duplicates lets one stay whose other definitions are dead.
-duplicate_symbols warning|error is read and has nothing to act on.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime reads the treatment options (-read_only_relocs,
-unaligned_pointers, -deployment_target_mismatches, -duplicate_symbols,
-objc_class_ro_signing_mismatch) with one parser, which takes "warn" as
well as "warning", besides "error" and, for the options that have it,
"suppress", and names the treatments it takes when it refuses one.
mold refused "-read_only_relocs warn"; the parser is now shared.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The two name functions (wildcards allowed in the list, and a malformed
pattern taken without a word) that function deduplication leaves alone:
ld-prime neither folds such a function into an identical one nor folds
others into it, whether the symbol naming it is global or local. They
mean nothing when deduplication is off - which in ld-prime it is unless
an -O option above -O0 or -deduplicate turns it on.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The two lists (wildcards allowed) make a final image's exported
definitions weak or not whatever their objects say, the weak list
winning: dyld then coalesces a forced-weak one with other images'
copies, so the image calls it through a stub and binds its pointers by
weak lookup, as for any weak definition; resolution and the duplicate
check still go by the objects. ld-prime warns of a hidden one it can't
change, deprecates both options, and names the weak list for a missing
argument to either; a -r output is left alone.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime's error about a symbol list it can't open (-exported_symbols_list,
-unexported_symbols_list, -reexported_symbols_list, the local strip lists
and the like) or about a -filelist file ends with an empty line: the
message "<option> file '<path>' could not be opened, errno=N (...)" has
a newline of its own before the one every diagnostic ends with. The
warnings about an -order_file or -alias_list have none.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A common symbol with no alignment of its own is aligned to its size
rounded up to a power of two, at most 2^15 - but 2^8 in a -preload
image, not the 2^3 ld's manual says. -max_default_common_align lowers
the cap: ld-prime reads a hexadecimal number as strtoull does, refuses
one above 0x8000, takes 0 for 1 and any other number for the power of
two below it, both with a warning. It means nothing in a -r output,
whose common symbols stay common.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime places the common symbols no definition replaced in
__DATA,__common by the order of the objects and of their symbol tables,
each symbol where the tentative definition that wins is - the first of
the largest, as the merged size is the largest any object asks for.
mold went by its global symbol table, whose order follows nothing in
the inputs.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Apple's build system passes a project's source version to ld-prime in
$RC_ProjectSourceVersion, which stands in for -source_version when the
command line gives none. ld-prime reads it, once it has read the
options, unless the last of -add_source_version and -no_source_version
says there is no LC_SOURCE_VERSION, even for an image that has none
anyway (a -r or -preload output); a malformed one is 0, with a warning
naming the variable in place of the option.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A tentative definition (a common symbol, which C's -fcommon makes of an
uninitialized global) wins over a dylib's definition of the name by
default (-commons ignore_dylibs); use_dylibs makes it an import instead,
listed in the symbol table whether referenced or not, and error fails
the link on the first such name once no duplicate symbol is found.
-warn_commons (or $LD_WARN_COMMONS) warns of each as symbols resolve,
naming the object and the file defining it (a merged sub-library's).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A symbol an object declares weak_import (N_WEAK_REF) binds with
BIND_SYMBOL_FLAGS_WEAK_IMPORT, so dyld leaves it NULL if missing. When
references disagree, ld-prime's default makes the import strong;
-weak_reference_mismatches weak makes it weak, and error reports each
object whose reference differs from those of the objects before it.
-no_weak_imports names every weak reference to an import. Both go by
object, then by the object's relocations as listed (each import once).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A weak definition an image exports, or a definition overriding a
dylib's weak one, makes dyld coalesce the symbol across images at launch
(the image is MH_WEAK_DEFINES). Once it has found no undefined or
duplicate symbol, ld-prime names each such symbol under
-warn_weak_exports, sorted by name ("weak external symbol: _x", or
"overrides weak external symbol: _x"), and fails a final image that has
any under -no_weak_exports; a -r output is only warned about.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
-no_shared_cache_eligible turns the image's shared-region eligibility off
as -not_for_dyld_shared_cache does (no split info, no flat-namespace or
rpath restrictions), and also writes ld64's cache-ineligible marker: an
LC_SEGMENT_SPLIT_INFO of no data. ld-prime gives every final image but a
-preload one the marker, a main executable, a kext and a -kernel image
included, even where -add_split_seg_info would have asked for real split
info; a -r output gets none.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime marks a dylib or a dynamic main executable linked with
-no_dynamic_access with the old prebinding flag MH_NOFIXPREBINDING
(0x400), which dyld now reads as "no dynamic access": it refuses to
dlopen() the image and finds none of its symbols with dlsym(). For any
other output - a bundle, an object file, a static or -preload image, a
kext or dyld - it ignores the option with a warning once it has read the
command line.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime derives LC_UUID from a hash of the output's contents unless
-random_uuid asks for a random one (version 4, RFC 4122 variant), which
spares hashing a large output in a debug build; the last of -no_uuid
and -random_uuid decides whether there is a UUID at all. The random one
goes into the header before the pages are hashed, so an ad-hoc code
signature needs no second pass over the header. Random bytes come from
the getrandom crate, as mold's --build-id=uuid takes them.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
LC_DYLD_ENVIRONMENT (0x27) is a string load command holding a
DYLD_xxx=value setting that dyld applies when it launches a main
executable, such as DYLD_LIBRARY_PATH=@executable_path/lib. ld-prime
writes one per -dyld_env, in order and repeats included, after the run
paths; it refuses an argument not starting with DYLD_ or lacking '=' as
it reads it, and the option for any output but a dynamic main
executable once it has read them all.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime collects the references it can't make lazy (or delay) and
reports them as one error, a line each - every line ending with a
newline when there are several, so the message closes with an empty
line - rather than as an error per reference. errors_together reports
a check's errors that way; the lazy-load refusals now use it as the
delay-init ones do.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A delay-init dylib (macOS 15 on) loads and binds at launch, but runs
its initializers only when the image dlopen()s it: its load command is
a dylib_use_command with DYLIB_USE_DELAYED_INIT, calls go to a
__delay_stubs stub and GOT loads to __delay_helper helpers that test the
_dlopenHelperFlag$ word, have _dlopenHelper$ dlopen() the dylib once,
then go through the __got slot. ld-prime's x86-64 dlopen helper loses
xmm0-7 (float arguments): this one saves them.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime loads a dylib an -assert_weak option names weakly
(LC_LOAD_WEAK_DYLIB, or a dylib_use_command flag beside re-export or
upward, which -weak-l can't join), but leaves its imports as weak as
the references make them, unlike -weak-l, and fails the link if one
isn't: it names each, the files referring to it strongly by leaf name
and "stubs-got-file" for a GOT slot (a lazy dylib's symbols alone).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime takes a library a -possible option alone names as it takes one
an auto-link option names: a hint it looks at after the command line's
inputs and the auto-linked libraries, so an archive named later wins;
it gets a load command only if something binds to it, ordered by install
name with the auto-linked ones; and if it isn't found, ld-prime says so
only when symbols stay undefined, libraries before frameworks.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime numbers the dylibs the command line names in naming order and
then the rest - those auto-link options name and those loaded as public
re-exports of another - together, by install name: an auto-linked
/AAA/libqbar comes before the CoreFoundation Foundation re-exports and
/zzz/libqfoo after it. mold put the auto-linked ones first, in the
sorted order of their options, which only -map follows.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime has the force-load and hidden kinds of library option in each
naming: -force-lfoo force-loads the library -lfoo finds (-why_load says
"-force_load"), and -hidden_framework Foo and -load_hidden path hide an
archive's definitions as -hidden-l does; a dylib named so links as any
other. Like the other library options, -force_load now merges with
those finding the same path: with -hidden-l the archive loads whole and
hidden, and with -l it still loads whole.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime's library options form a grid: each kind of library (weak,
re-exported, hidden, needed, upward, lazy, merged, force-loaded...) is
named in up to three ways - a name joined to the option (-weak-lfoo)
looked up in the library paths, a framework name, or a path - and the
options naming one library merge what they say. InputArg::Library holds
the two apart, so that a new kind is one variant and its spellings a table.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime ignores an object built for another subtype of the link's CPU
type, such as x86_64h (Haswell) code in an x86_64 link, as one of
another architecture. -allow_sub_type_mismatches has it link the object
with a warning, "linking x86_64h file '<file>' into x86_64 link", as it
reads the file and again once resolution has the link use it (so twice
for an object on the command line or an archive member it loads). An
arm64e object, its pointers signed, stays out of an arm64 link.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
kmutil links a kext into the kernel by its relocations. An x86-64 kext
calls an import directly, each call carrying an external BRANCH
relocation for kmutil to fill in; -kexts_use_stubs has ld-prime route
the calls through a __TEXT,__stubs entry instead, a jmp through the
import's __got slot, which an external UNSIGNED relocation fills, as
for its data imports. An arm64 kext calls through stubs anyway, and
the option changes nothing there.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
As ld64 drives libLTO, -mcpu goes to lto_codegen_set_cpu, and the
modules are optimized and compiled in two calls so that -save-temps can
keep the merged bitcode before and after optimization and the object
beside the output (<output>.lto.bc, .lto.opt.bc, .lto.o, byte for byte
ld-prime's). The ThinLTO cache options (-cache_path_lto, -prune_*_lto,
-max_relative_cache_size_lto ...) get ld-prime's argument checks only:
mold compiles all bitcode as one module, ThinLTO's too, with no cache.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime reports on its branch islands, unmatched order file entries,
the libraries re-exports load, each symbol's section (-trace_symbol_layout
[_file]), the atom graph (-dot), the files and symbols used (-trace_file
and kin), a link snapshot (-debug_snapshot[=minimal], -snapshot_dir) and
a comparison with -reference_output; mold takes them with their
arguments and errors but gives no report. -max_code_deduplicate_passes
wants a decimal number, and -x86_64_layout_emulation warns off arm64.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime ends its deduplication, when it has folded any function, with
"code deduplicated functions N (size: S) out of total T (size: U)
(P% size reduction)" on stderr: the functions folded away and their
bytes, out of every function of __TEXT,__text left in the output after
dead stripping, counted before folding. (When it needs more than one
pass to fold callers of folded functions it also gives a line for each
pass, which mold, folding in one go, does not.)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The framework search path finds Foo through Foo.framework/Foo (or its
stub Foo.tbd), a symlink into Versions/Current that a sparse framework,
such as one only partly copied into an SDK, lacks.
-search_in_sparse_frameworks makes ld-prime look for
Foo.framework/Versions/Current/Foo too, in a second pass over the whole
search path once the first has found nothing, as ld64's findFramework
did; a complete framework later on the path wins.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime ignores an input that lacks the link's architecture with
"ignoring file '<file>': <why>": an object or archive member of another
architecture, a dylib of another CPU type, a fat file without the slice
or a stub without the target. -arch_errors_fatal makes each of these an
error in the same words, "<why> in '<file>'", and the link stops once
the inputs are loaded. (ld-prime folds several into one "multiple
errors: a; b" line, where mold gives each its own.)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
dyld lets a simulator process load a macOS dylib only if its header has
MH_SIM_SUPPORT (0x08000000), which says its LC_BUILD_VERSION may name
macOS, Mac Catalyst or a simulator platform. -simulator_support asks
for the flag; ld-prime sets it on a dylib and ignores the option for
every other kind of output.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A section in an image dyld maps can be aligned no more than its segment
(the page, or -segalign), so ld-prime lowers a bigger alignment, such as
an x86-64 .align 16's 64 KiB, to the segment's with a warning about it.
-no_warn_reduced_section_align, anywhere on the command line, silences
that warning and no other: -sectalign's own about lowering a section's
alignment stays. The layout is the same either way.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime takes -ios_version_min and -maccatalyst_version_min as it does
-macos_version_min: an argument that must be a version, under its old
names too (-iphoneos_version_min, and -iosmac_version_min and
-uikitformac_version_min for Mac Catalyst) with a bare note that the
option was renamed, its errors spelled with the new name. Neither
platform is one mold links for: it says so as it reads the option, as
it does for -platform_version ios, instead of not knowing the option.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld64 could link a static arm64e image with its rebases threaded through
its pointers from a __TEXT,__thread_starts list, and the fragile (version
1) Objective-C ABI of 32-bit macOS. ld-prime has chained fixups for the
one and knows the modern ABI alone: it refuses -threaded_starts_section
once it has read every option (after the -kernel and -bundle_loader
checks and the lazy-load warnings), naming -fixup_chains if given too,
and any -objc_abi_version but "2", spelled so, as it reads it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld64's -force_flat_namespace made a main executable look up every
image's imports in the flat namespace, its dylibs' too, by setting
MH_FORCE_FLAT in the header. ld-prime no longer sets the flag: it warns
that the option is no longer supported as it reads it (so only a -w
before it silences that) and takes it for -flat_namespace, the image
binding its own imports flat, which a later -twolevel_namespace undoes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Xcode 14 dropped bitcode bundles, and ld-prime ignores -bitcode_bundle,
-bitcode_hide_symbols, -bitcode_process_mode, -bitcode_symbol_map and
-bitcode_verify (none takes an argument any more) with "is no longer
supported and will be ignored", given with the obsolete options' once
every option is read. -ld_classic, which once picked ld64, draws the same
words as it is read; -ld_new picks ld-prime, and so does -ld_prime with a
deferred deprecation warning. All of them used to be taken for -l here.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime still accepts what ld64 and its predecessors took for
prebinding, two-level namespace hints, multiple modules, Objective-C
garbage collection, kext object directories and the classic ld's -X, -m,
-b and -Y, and ignores each with "<option> is obsolete": given once it
has read every option, in command-line order (so -w anywhere silences
it), but for -s, -Si and -Sn, which it warns about as it reads them.
Those that took an argument still take one, an empty one too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime reads -weak-l, -needed-l, -reexport-l, -hidden-l, -upward-l,
-lazy-l, -merge-l and -no_merge-l given alone as -l: the next argument
is the library's name, so `-weak-l foo` is `-weak-lfoo`, and only a
command line that ends there, or an empty argument, misses the name.
mold refused the option alone whatever followed it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime makes a dylib mergeable by recording its atoms, fixups and
dependencies in LC_ATOM_INFO; -add_mergeable_debug_hook gives a debug
build of one the hook merged images get, for the classes it doesn't
export. It takes either for a dylib only, checked before -r -dead_strip
and after it respectively, and refuses -flat_namespace and the hook
with -make_mergeable after the shared cache's flat-namespace check.
mold writes neither the atoms nor the hook yet, and refuses both.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A mergeable dylib (-make_mergeable) carries LC_ATOM_INFO, its atoms in
ld-prime's own serialized form, so that a link naming it with -merge_*
takes its content instead of a load command. ld-prime looks for the
dylib itself, never a stub (nor an archive for -merge-l), refuses one
without the command even where dylibs are ignored, and refuses a merge
next to a weak, upward, lazy or re-exported naming of the library.
mold reads no atoms yet, so it refuses to merge for now.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Xcode links a mergeable library into a debug build with -no_merge_*
where a release build merges it: ld-prime re-exports the library, as
-reexport_* would (-no_merge-l finds a dylib or stub only), and spells
the option as given in duplicate warnings but as -reexport-l in a
conflict. It also links its own bundleForClassHook.o into the image
for the library's Objective-C and Swift classes unless
-no_merged_libraries_hook; mold has no such hook and refuses for now.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime puts a stub in place of the dylib a library option's path
names only under a -syslibroot, where an SDK keeps .tbd files for the
OS's dylibs: -weak_library /usr/lib/libz.dylib links the SDK's
usr/lib/libz.tbd. The path as given is that file alone, even with a
stub next to it, so a dylib whose .tbd lies beside it links the dylib,
and a stub alone is no library: ld-prime reports it not found.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime refuses -flat_namespace in a dylib bound for the dyld shared
cache as it checks the options against the kind of output: after
-client_name and the -pie warning, but before -pagezero_size, the -e
warning, -stack_size and -seg_page_size, which mold checked first. The
eligibility test is now a function of its own, so the check can run
there while the rest of the shared-region settings, and the
-undefined dynamic_lookup refusal, stay where they were.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
REFERENCED_DYNAMICALLY (n_desc 0x10) marks a symbol dyld looks up by
name so that strip(1) keeps it, as crt1.o does for _NXArgc, _NXArgv,
_environ and ___progname. ld-prime warns "REFERENCED_DYNAMICALLY flag on
symbol '%s' is deprecated" for each exported non-weak definition in a
section (not a private extern, local, undefined, absolute, common or
weak one, judged before a weak atom name is unweakened) as it reads an
object, archive members it never loads included, yet still sets the
flag on the output's entry for an executable or a dylib; mold dropped it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A code signature's code directory holds a hash of every page; macOS
before 10.12 reads only a SHA-1 directory, so ld-prime puts one in the
code directory slot, with the SHA-256 directory as the first alternate,
for an image targeting such a release. It goes by the target alone and
does so for arm64 too, though no such release runs arm64 code; mold
added the SHA-1 directory only to x86-64 images (and to -static ones and
x86-64 firmware, unchanged).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
LC_SOURCE_VERSION holds the version of the sources an image was built
from as a.b.c.d.e packed in 64 bits, 24 for a and 10 for each other
number. -source_version sets it and asks for the command as
-add_source_version does. ld-prime reads up to five decimal numbers
apart by dots, an empty one 0, ignores what follows the fifth, and
refuses a trailing dot, any other character or a number too large for
its bits as a "malformed 64-bit a.b.c.d.e version number".
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A function with a compact unwind record needs no FDE in __eh_frame: the
record in __unwind_info describes its frame, and the unwinder looks there
first. Unwinders before macOS 10.9 still went to __eh_frame, though, so
ld64 kept every FDE for such a target unless -no_keep_dwarf_unwind said
otherwise. ld-prime keeps them by the target alone, for any kind of image,
and takes -keep_dwarf_unwind and -no_keep_dwarf_unwind as obsolete with a
warning; mold dropped the FDEs whatever the target and refused the options.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Since macOS 10.8 dyld calls an executable's main through LC_MAIN. An
older dyld jumped instead to the program counter of LC_UNIXTHREAD's
initial thread state, crt1.o's "start", which clang links for such a
target (-lcrt1.10.6.o) and which sets up argc, argv and environ before
calling main. ld-prime still starts an x86-64 executable for a macOS
before 10.8 so, with -stack_size's stack a __UNIXSTACK segment that
-stack_addr may place, as in a static executable; arm64 keeps LC_MAIN.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
LC_SOURCE_VERSION records the version of the sources an image was built
from (-current_version's packed A.B.C.D.E, zero by default), for crash
reports and the like; it came with macOS 10.8. ld-prime writes it into
an image for a macOS before 10.8 only under -add_source_version, of any
kind and on either architecture, and leaves it out of any image under
-no_source_version, the last of the two winning. Other platforms always
have it, and a -preload image never does.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
crt1.o, the C runtime startup clang links for a macOS before 10.8,
brings two tables of its own into __DATA: __program_vars, through which
it hands dyld argc, argv, environ and the mach header, and, before macOS
10.6, __dyld, where dyld's lazy binder and lookup function are found.
ld-prime places both at the start of __DATA, in input order and ahead
of the lazy pointers, whatever the target; mold left them among the
input sections after the lazy pointers.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Without -arch, ld-prime takes the target from the first object file on
the command line: a thin Mach-O object's CPU type, or the triple of a
bitcode file. Archives, dylibs, stubs and universal files don't count,
and with no such object the link stops with "Missing -arch option"
rather than falling back to the host. Looking for the target (or the
platform) it opens the files in order and stops at one it can't map,
naming the path alone, before the link proper names any input.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A dylib's LC_BUILD_VERSION (or a TBD v5 stub's min_deployment) names
the oldest OS it runs on. ld-prime warns of a library the link names
that needs a newer OS than the output's, naming it by install name, in
input order with the objects' "built for newer" warnings. It trusts
the SDK: a library a -l/-framework search found under a -syslibroot, as
the directory's path spells it, or a library's path found joined to a
root; nor does it check the libraries a dylib re-exports.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime writes everything -v prints, its version banner and then the
library and framework search paths, on stderr and leaves stdout alone;
-v with nothing to link prints the banner and exits 0, whatever else is
given. -version_details prints its JSON on stdout and, with nothing to
link, exits 0; with inputs it does not stop there but prints the search
paths as -v does (without the banner) and links. mold printed the banner
on stdout and exited at -version_details wherever it stood.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime orders a final image's thread-local zero fill by atom size,
smallest first, keeping input order among atoms of one size; the
command-line order and -order_file make no difference, and -r keeps
the input order. An atom's size is what the object gives it: the
distance to the next atom of its section, padding included, or to the
section's end for the last one. Since a smaller atom is usually less
aligned, the order also changes the padding and so the section's size.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A branch to an import, or to a definition dyld may interpose, goes to
the symbol's stub in __stubs, which ld-prime makes an unnamed atom of
its "stubs-got-file". When such a B/BL or call can't reach the stub,
ld-prime's fixup error names the target '', as it does a GOT slot,
while mold named the symbol. An objc_msgSend stub keeps its own symbol
name in both.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime reports one fixup error per link: the first in the output
file (segments and sections in load command order, then by address),
whatever its kind - B/BL, ADRP, rip32, ptr32, LDR alignment, a GOT or
stub or init-offset fixup - or the order of inputs and relocations. An
error in the layout, found earlier, wins over all of them. mold printed
every one, sorted by text; it now keeps one layout error, the first
reported or the fixup error at the lowest file offset, whatever thread
finds it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime stops at the first error in the layout. Before __LINKEDIT it
walks the segments in load command order: a slid segment must not be
below the one before it, then each section must end within its
segment's 32-bit file end, and a __thread_data or __thread_bss must be
typed thread-local (one layout dump for that, two for the others).
Thread-local data a rename moved out of the template comes next. mold
checked the types at section creation and reported every error.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A -r output keeps each subsection's relocations, rewritten against its
own symbol table, and its raw bytes, the fields of section-relative
relocations moved into the merged address space. Both are each
subsection's own, so they are now made on all cores as a final link
copies its sections, and so are the __compact_unwind entries; the name
of a section-relative target is looked up among its subsection's
places rather than by a search over every place.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A -r output's locals come object by object, each object's in the order
of its sections and addresses, so each object's are gathered on a core
of its own; only the labels of literal atoms, which can stand for
another object's merged copy, are attached across objects afterwards.
The externals, the referenced symbols and the symbol indices are made
on all cores too, sorted by name_sort_key, where serial passes with
SipHash maps took most of a big -g prelink's symbol table time.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A -r output's symbol table has a final image's layout: the locals, an
opening N_SO and each object's run of debug notes, then the externals
by name, the strings following the externals, locals and notes in that
order, a note sharing the string of the symbol it names. -r built all
its notes in memory and laid out one string table over every entry;
it now plans each object's run on all cores and writes it straight
into the output with the final link's writer, as mold-rust does.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A -r link returned before -print_statistics reported anything, and it
timed none of its own steps. It now reports the passes it shares with
a final link, then those of the -r output: the layout, the symbol
table (its locals, debug-note stabs, externals and strings), the
regenerated relocations, the copy into the output buffer and the
write, and the output's size, as a final link's statistics do.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The output is copied into an anonymous buffer and written by four
threads with pwrite in 8 MiB blocks, cut from each range's start and so
unrelated to page boundaries. When two writers store into different
bytes of one 16 KiB page that isn't cached yet, the kernel may zero-fill
the part of the page one write doesn't cover over the bytes the other
just stored: a 280 MiB branch-island test under load lost the first
word of the blocks at 0x80004d4 and 0x88004d4. Writers now get only
whole pages, and each range's partial first and last pages are written
one by one in finish(), after every writer has joined.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Context::segment_align only forwarded Args::segment_align, which
parse_args already resolves to its final value: -segalign's power of
two, else the target's page size, but 4 KiB for a -preload image on
any target. As mold-rust's Context has no such option accessors, its
callers in the layout passes, the section sorting and the chain page
size now read the field itself. No output changes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime rounds -pagezero_size and -image_base up to a page, caps the
__PAGEZERO of a chained-fixups executable at 4 GiB, checks the -segaddr
pins against them, and drops the base of an image dyld slides; all of
that reads only the options, so it now runs at the end of parse_args
instead of as passes on a fresh Context, leaving Args final once
parsed. page_align moves to util for both callers. No output changes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime warns about the linked dylibs nothing binds to by default only
in a dylib bound for the dyld shared cache, where each needless load
costs every process; -warn_unused_dylibs and -no_warn_unused_dylibs
override that. As the shared-region decision is made in parse_args, the
default is now settled right after it into a plain
Args::warn_unused_dylibs instead of in the pass. No output changes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime rewrites Objective-C method lists in the relative form from a
macOS 11 deployment target, in every arm64 image but on x86-64 only in
dylibs and bundles, unless -objc_relative_method_lists or its negative
form says otherwise. That depends on the options and the target alone,
so parse_args now settles Args::objc_relative_method_lists as a plain
bool, replacing objc::objc_relative_method_lists. No output changes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Whether imported functions go through lazy pointers bound on first use
(__la_symbol_ptr and __stub_helper) is settled by the options alone: an
image dyld loads gets them when it is not laid out for chained fixups,
as ld64 binds below the chained-fixups deployment targets, unless
-bind_at_load. parse_args now resolves Args::lazy_binding right after
the fixup format, replacing Context::lazy_binding. No output changes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Whether an image is laid out for chained fixups follows from the options,
the target and the deployment target alone (only the x86-64 fallback at
an unaligned pointer depends on the link), so parse_args now settles
Args::fixup_chains, and with it the __init_offsets conversion chained
fixups imply even under -undefined dynamic_lookup, instead of Context
recomputing both. -no_fixup_chains stays as a flag of its own for the
rebase and weak-bind opcodes it gives a -static image. No output changes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Whether the output is ad-hoc code signed depends on the options and the
target alone, so parse_args now settles it, as mold-rust settles its
option defaults, instead of Context::adhoc_codesign working it out at
each use: ld-prime signs arm64 macOS images unless told otherwise, and
leaves x86_64 ones, -static images, kexts, firmware and (whatever the
options say) -preload images unsigned. Args::adhoc_codesign is now a
plain bool and the Context method is gone; the output is unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime looks a library name that ends in .o up in the library search
path as it is, with no lib prefix and no other extension, whatever
the option that names it (-upward-l too), and loads the object where
the option is. clang names crt1.o that way, -lcrt1.10.6.o, for an x86-64
macOS older than 10.8, and bundle1.o and dylib1.o likewise. mold looked
for libcrt1.10.6.o.tbd and the like and failed every such link.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime takes lib<name>.so for a dylib in the library search path:
in each directory after lib<name>.tbd and lib<name>.dylib and before
lib<name>.a, so under -search_dylibs_first one anywhere beats an
archive anywhere, and -upward-l and -reexport-l, which take a dylib
only, find one too. An image that links no dylib still looks for the
archive alone. mold reported a library found only as a .so not found.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime drops a -L or -F argument that repeats an earlier one byte
for byte, before it looks anything up, so the directory is searched,
listed by -v and warned of once. Spelled otherwise (-L./x after -Lx,
/usr/lib/ after /usr/lib), or met again among the default directories,
it is searched once more. mold searched each repetition again, which
found nothing new but cost a stat of each file name tried there.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime searches a -L or -F directory, or a default one, only if it
is there and a directory. It warns "search path '%s' not found" of a
missing one the command line gave (a missing default goes without a
word), "search path '%s' is not a directory" of anything else, and
"-syslibroot and combined search path '%s' is not a directory" of such
a path under a root. mold kept a directory found under no root as it
was, there or not, and said nothing.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime looks each absolute -L or -F directory up under every
-syslibroot, but a -syslibroot of / anywhere (ld64 heeded only a last
one) leaves every search directory where it is; the roots still hold
the files -weak_library and the like name. A directory that climbs
with /.. is first resolved as realpath(3) does, and it joins a root
less one leading slash, so one that starts with // replaces the root.
mold took / as one more root, kept /.. and stripped every slash.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Once it has checked the options, ld-prime with -v gives its banner and
then "Library search paths:" and "Framework search paths:" on stderr,
a tab before each directory: the -L (-F) ones, then the default ones,
as they are looked up under the syslibroots. With nothing to link it
prints the banner alone. mold printed only its banner, as it read -v,
and twice if it parsed the options again for another target. The paths
are now settled once, before any input is read, not for each lookup.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A C++ or Objective-C function's unwind info names its personality
routine in a compact unwind record or in its FDE's CIE, not through a
relocation of the code. Only symbols a live relocation names were
looked up at run time under -undefined dynamic_lookup, so a link that
left ___gxx_personality_v0 to dyld (Hammerspoon's, with no libc++)
failed on it as an unresolved address; without the option it went
unreported among the undefined symbols. ld-prime binds it flat.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Clang gives the STT_TLS type to the labels of TLSDESC code sequences,
which are in .text. With --emit-relocs they are written to .symtab,
where their values were computed as offsets from the start of the TLS
segment, which underflowed. Write the address of an STT_TLS symbol
outside a TLS section as it is.
Assemblers emit a call to a static function as a relocation against a
section symbol plus an offset. A range extension thunk jumps to the
start of a symbol, so taking such a branch through a thunk landed at
the wrong address. Report it as an error instead.
Relocations in a relocatable output may refer to local symbols such as
RISC-V's .L labels, so removing them with -x, -X or -s left relocations
pointing to symbol 0. -r now ignores -x and -X, and -s strips only debug
info as -S does.
In the extreme code model, the pcalau12i with R_LARCH_TLS_GD_PC_HI20 or
R_LARCH_TLS_LD_PC_HI20 begins a 64-bit sequence whose lu32i.d and lu52i.d
supply the bits beyond ±2 GiB, so the GOT may be farther away than that.
The other *_PC_HI20 relocations were already applied without a range
check, as in GNU ld and lld, but these two rejected such a GOT with an
out-of-range error.
In the extreme code model, lu32i.d and lu52i.d follow pcalau12i by 8 and
12 bytes, and the upper 32 bits they set are relative to the pcalau12i's
page. We used page(P - 8) for both, which is the next page for lu52i.d if
the pcalau12i is the last word of a page. The result was wrong whenever
the borrow reached bit 52; e.g. a pcalau12i at 0x100000ffc materialized
0xfff0000080000000 instead of 0x80000000.
mold crashed at startup on sparc64 Linux, which maps user memory at
sign-extended high addresses. mimalloc v3 registers pages in a map that
covers only 48-bit addresses, so the first registration wrote out of
bounds. Until the bundled mimalloc handles that layout, sparc64 hosts use
the system allocator (https://github.com/rui314/mold/issues/1639).
mold -run looked for mold-wrapper.so in /usr/local/lib/mold unless it was
next to the executable or in ../lib/mold, and install-mold.sh always put
it in $PREFIX/lib/mold, so a package using lib64 or a multiarch libdir
could not make -run find it. The C++ version took the directory from
CMake's libdir. MOLD_LIBDIR now sets it at build time and install time.
util::prefetch assumed SSE on all x86 targets, but i586-unknown-linux-gnu
has none, so mold died with SIGILL on CPUs such as the Pentium, K6,
Vortex86 and Quark. The x86 prefetch is now compiled only when the target
has SSE. Such a target simply ignores the hint, as other targets without
a prefetch instruction do.
Versions of a symbol have the same name, so they tied in the unstable sort
of exported dynamic symbols. Rayon breaks ties with a pseudo-random
generator whose width follows usize, so .dynsym and its hash and version
tables differed between 32-bit and 64-bit hosts. The input order now breaks
the tie. .eh_frame_hdr had the same problem with FDEs for one function,
which are now ordered by their addresses.
On a 32-bit host, linking an output of 2 GiB or more panicked, because
the doubled mapping length wrapped around and sizes of 4 GiB or more were
truncated to usize. Each panic also left a multi-GiB temporary file. We
now check the size before creating the file and report a fatal error.
The limit is 2 GiB, as no Rust object can be larger than isize::MAX bytes.
A range extension thunk jumps to its destination with `br x16`, which
faults under BTI if the destination doesn't begin with a landing pad, as
is the case for a static function that is only called directly. For such
a function, we now put `bti c; b <function>` at the beginning of the first
thunk after the function's section, which is always within direct branch
range, and other thunks jump there instead, as lld's landing pad thunks do.
-z force-bti and -z pac-plt force the BTI and PAC bits of the output's
GNU_PROPERTY_AARCH64_FEATURE_1_AND, and -z pac-plt also makes PLT entries
authenticate .got.plt entries with autia1716 and sets DT_AARCH64_PAC_PLT.
-z gcs=implicit|never|always controls the GCS bit. -z bti-report and
-z gcs-report diagnose input files lacking the features, and as in lld,
-z force-bti and -z gcs=always imply the warning level.
We used to merge only x86 properties in .note.gnu.property, so an ARM64
output lost GNU_PROPERTY_AARCH64_FEATURE_1_AND even if all its inputs were
compiled with -mbranch-protection. We now AND-merge it as GNU ld and lld do.
If the output is BTI-enabled, the PLT header and the PLT entries that may be
reached by an indirect branch start with `bti c` as lld's do, which makes PLT
entries 24 bytes long, and DT_AARCH64_BTI_PLT is set.
R_RISCV_ALIGN has no symbol, and its addend is the size of the NOP
padding that a relaxing link shrinks to realign the code after it.
symidx_addend returned (0, 0) for any symbol not written to the output
symbol table, including the null symbol, so `mold -r` wrote R_RISCV_ALIGN
with addend 0 and a later link could no longer realign the code. We now
keep the addend of a relocation without a symbol, as GNU ld and lld do.
LoongArch's R_LARCH_ALIGN refers to a local symbol and was not affected.
GCC 14 uses R_LARCH_TLS_DESC64_PC_LO20 and R_LARCH_TLS_DESC64_PC_HI12
with -mcmodel=extreme -mtls-dialect=desc to extend the TLSDESC sequence's
GOT access to 64 bits, and we rejected them as unknown. We now apply them
like the other 64-bit PC-relative pairs and don't rewrite the sequence's
LO12 instruction with pcaddi. As GNU ld and lld do, we don't relax the
sequence to IE or LE, except in static executables, which can't use
TLSDESC and therefore get LE.
is_got_load_pair checks that a GOT load's PCREL_LO12_I refers to the
label at its AUIPC by comparing the label's value with the AUIPC's
r_offset. Symbol values are adjusted for removed bytes before relocations
are applied, but r_offsets are not, so a GOT load after a shrunk
instruction was no longer rewritten to AUIPC+ADDI. We now compare with
the label's st_value, as find_paired_reloc does.
R_RISCV_32_PCREL, R_RISCV_PLT32 and R_RISCV_GOT32_PCREL in allocated
sections were truncated to 32 bits without a range check, so a reference
to a symbol or a GOT slot more than 2 GiB away silently got a wrong
value. We now report an out-of-range error for them, as we already do
for R_RISCV_32_PCREL in .eh_frame and as lld does.
The .riscv.attributes parser knew the value types of only the three tags
mold uses, so the value of any other tag was read as a tag. For example,
an unknown tag 40 with value 5 was mistaken for Tag_RISCV_arch. As per
the psABI, an even tag has a ULEB128 value and an odd tag has a
NUL-terminated string, so we now skip other attributes by that rule.
They are still dropped from the output, as lld does for unknown tags.
The output's e_flags were the first input's plus EF_RISCV_RVC, so an
object compiled for the TSO memory model (-march=rv64gc_ztso) lost its
EF_RISCV_TSO bit if it came after an ordinary RVWMO object. RVWMO code
runs correctly on TSO hardware but not vice versa, so the output now has
the bit if any input has it. GNU ld does the same; lld still takes the
bit from the first file.
-Map wrote address zero as "0x0" where C++ mold wrote "0", and
--print-dependencies omitted the blank line that separated the comment
header from the dependency lines. Both outputs are now byte-identical
to those of C++ mold.
With MOLD_JOBS=1 and an empty XDG_RUNTIME_DIR, mold joined "" with
"mold-lock" and created the lock file in the current directory, so
processes in different directories did not serialize. Like other XDG
variables, an empty value now means unset, and mold falls back to
/tmp/mold-lock-$USER.
-rpath-link, -fuse-ld, -O, --sort-section and --max-cache-size are
accepted only for compatibility, and their values are never used. mold
nevertheless required the values to be UTF-8, so a non-UTF-8 directory
given to -rpath-link was a fatal error. Read the values as raw bytes.
The manual said -z separate-code was the default, but mold defaults to
-z noseparate-code and adds no padding between segments unless
--section-order is given, which implies -z separate-loadable-segments.
The manual documents --no-color-diagnostics, but mold rejected it as an
unknown option; accept it as lld does. Conversely, the manual and --help
listed -z nodefs and -z start_stop_visibility, which neither mold nor
GNU ld accepts; the options are -z undefs and -z start-stop-visibility.
--help also said --color-diagnostics means always, while it means auto.
--dynamic-list-data, --lto-pseudo-probe-for-profiling and a bare
--thinlto-index-only take no argument in GNU ld and lld, but mold read
the next command line argument as their value, silently dropping an
input file placed there. GNU ld's --package-metadata takes an optional
argument, so mold now accepts its value only after an equal sign.
Rust's io::Error appends " (os error N)" to the strerror text when it is
displayed, so mold printed "cannot open foo.o: No such file or directory
(os error 2)" where the C++ version printed just the text. Diagnostics
now format I/O errors with a strerror helper that returns the text alone
and falls back to the usual formatting for errors not from the OS.
scan_pcrel, which also handles GOT-relative relocations, told the user
to recompile with -fPIC whenever it couldn't resolve a relocation, even
though code using GOTOFF is PIC already. It now reports the reason: a
symbol at a fixed address, such as an absolute or undefined weak one, in
a position-independent output, or a preemptible symbol defined in the
shared object being created, which -Bsymbolic or hidden visibility fixes.
`-e 0x401000` was looked up only as a symbol name, so the link warned
that the entry symbol was undefined and set e_entry to 0. GNU ld and lld
fall back to parsing the argument as a number (decimal, 0x hex or octal)
when no symbol has that name, and so does mold now.
When a version script named the same symbol in two version nodes, each
exact-name match overwrote the previous one, so the last node won.
GNU ld and lld give the symbol the first node's version (lld warns about
the reassignment, and GNU ld rejects a name that is both local and
global). Apply only the first exact match. Wildcards still let the last
matching node win, as in both linkers.
A version script or --default-symver on a link that exports no dynamic
symbols, such as a static executable, made verdef::construct index the
empty .dynsym and panic. Symbol versions exist only for dynamic symbols,
so write no version sections in that case, as GNU ld does.
Undefined symbols are allowed by default with -shared or with
--unresolved-symbols=ignore-all, and a non-weak hidden one was then
silently resolved to 0. A hidden symbol can't be resolved at runtime,
so report it as an error regardless of those options, as GNU ld and lld
do. Weak undefined hidden symbols still resolve to 0.
--defsym=foo=bar with bar defined by a shared library copied bar's
library-relative value into an absolute foo, so calling foo jumped to a
bogus address. Such a symbol has no link-time address, so report it the
same way as an undefined one. GNU ld and lld reject it as well.
The first file on the command line owned a common symbol, so `int buf[4]`
in one file and `int buf[64]` in another gave a 16-byte buf, and writes
to it clobbered its neighbours. As GNU ld and lld do, let the largest
common own the symbol and align it to the largest alignment among them,
for .common, .tls_common and -r output alike.
--export-dynamic and --dynamic-list export symbols even from an
executable that links against no DSO. Such an executable has no
.dynamic, but the symbols were still added to .dynsym, and .dynstr
lacked its leading empty string, which only the creation of .dynamic
adds, so entry 0 of .dynsym got a symbol's name. Like GNU ld and lld,
emit no dynamic symbol table without .dynamic.
Mergeable sections that share a name, type and flags but differ in
entry size are merged into separate output sections. Their sort keys
tied, so their order depended on which one the parallel conversion of
input files happened to create first. Break the tie with the first
member's file priority and section index, as for output sections.
add_synthetic_symbols defined __start_ and __stop_ symbols for every
allocated chunk with a C identifier name, including the ELF and program
headers named EHDR and PHDR. fix_synthetic_symbols skips headers, so
the symbols were left in .symtab with value 0, and -z
start-stop-visibility=protected exported them. Exclude headers in
start_stop_name, which both functions use.
compute_address_significance marked exported symbols' sections as
address-taken by walking .dynsym, but .dynsym is not populated until
after ICF, so the loop did nothing and --icf=safe gave identical
exported functions of a DSO the same address. Walk each object file's
defined global symbols instead, whose is_exported bits are already set.
DT_INIT and DT_FINI refer to the --init and --fini functions, but only
the entry point was a GC root. If such a function was not exported
because it was hidden or made local by a version script, --gc-sections
discarded its section, DT_INIT and DT_FINI were emitted with value 0,
and the DSO crashed on load. Make them GC roots as GNU ld and lld do.
The $a, $t and $d symbols that mold creates for PLT entries and thunks
were STT_FUNC, copied from the symbols naming the entries, while AAELF
requires mapping symbols to be STT_NOTYPE. push_mapping_symbol now
builds the symbol from a section index and an address itself.
Only a final link converts big-endian ARM code to BE8, so relocatable
output keeps the input's BE32 code, but it was marked EF_ARM_BE8 anyway.
GNU ld rejected such an object as "already in final BE8 format", and
disassemblers decoded its code in the wrong byte order. -r also failed
with "--be32 is not supported" unless --be8 was given, which GCC and
Clang never pass with -r.
A 32-bit Thumb instruction is a pair of halfwords, each in the byte
order of the input's code, but NOP.W was written as a single 32-bit
word. On big-endian targets, that swapped its halfwords, so a Thumb call
to an undefined weak symbol and a TLSDESC call relaxed to local-exec
became garbage in BE8 output. NOP.W is now written a halfword at a time.
Clang assembles ADR and LDR (literal) referring to a symbol in another
section to R_ARM_ALU_PC_G0 and R_ARM_LDR_PC_G0 in ARM code and to
R_ARM_THM_ALU_PREL_11_0 and R_ARM_THM_PC12 in Thumb code, which failed
with "unknown relocation", as did 8- and 16-bit data. The offset's sign
picks ADD or SUB, or the U bit of LDR, and the Thumb forms are relative
to the PC aligned down to 4, producing the same bytes as GNU ld.
TBZ/TBNZ to a label in another section, .2byte/.4byte data referring to
a symbol, and GOT loads compiled with -mcmodel=tiny -fPIC failed with
"unknown relocation". TSTBR14 reaches ±32 KiB and is range-checked like
GNU ld and lld do, without thunks. ABS16 and ABS32 are too narrow for
dynamic relocations, so they must be link-time constants in PIC output.
GOT_LD_PREL19 is an LDR literal that loads the symbol's GOT slot.
Relocations copied by --emit-relocs refer to symbols in .symtab, which
--strip-all omits, so the combination crashed or produced relocation
sections pointing to a nonexistent symbol table. Report an error as
lld does. (GNU ld instead keeps the symbols the relocations need.) -r
sets --emit-relocs internally and keeps accepting --strip-all.
An SHF_LINK_ORDER section's sh_link refers to the section it describes,
but mold always wrote zero. Once passed through -r, such sections lost
their links, so relinking with --gc-sections dropped .ARM.exidx and
__patchable_function_entries, and C++ programs on ARM aborted when
throwing. Set sh_link from the members' linked-to sections. Since it
can name only one section, -r also separates SHF_LINK_ORDER sections
whose linked-to sections are in different output sections, as GNU ld
and lld do.
An SHF_LINK_ORDER section such as __patchable_function_entries holds
metadata for the section that its sh_link refers to, and usually
nothing else refers to it. --gc-sections therefore discarded all of
them except .ARM.exidx, for which there was an ARM-specific edge from
a section to its unwind index. Replace that edge with a generic one
from a section to the SHF_LINK_ORDER sections linked to it, as GNU ld
and lld do.
Allocated fragments of mergeable sections start out dead under
--gc-sections and are revived by the garbage collector, but mold -r
returns before running GC. As a result, `-r --gc-sections` discarded
every .rodata.str* and .rodata.cst* piece and left the relocations
referring to them with symbol index 0. Since mold doesn't collect
garbage for -r, ignore the option there. GNU ld and lld do collect
garbage with -r, taking roots only from -e and -u.
R_SPARC_WDISP16 is used by branch-on-register instructions such as brz,
whose 16-bit word displacement is split into d16hi at bits 21:20 and
d16lo at bits 13:0. We ORed the fields into the upper halfword of the
instruction instead, corrupting its opcode, and limited the displacement
to 64 KiB instead of 128 KiB. Place the fields as GNU ld does.
R_SPARC_OLO10 carries a second addend in the upper 24 bits of r_info,
which is added after the low 10 bits of S + A are taken. RelocSection
built its output records with ElfRel::new(), which zeroes that field, so
`ld [%g1 + %lo(var) + 8]` lost its +8 in -r and --emit-relocs output.
Build each record from a copy of the input one instead.
The 8- and 16-bit GOT and TLS relocations fill the displacement of a
(d8,An,Xn) or (d16,An) operand, which the CPU sign-extends, but we checked
them against unsigned ranges. A GOT offset of 32 KiB or more was thus
silently encoded as a negative displacement, and a local-exec TLS offset,
which is usually negative because the thread pointer is biased by 0x7000,
was rejected. Check them as signed as GNU ld does.
SH4 object files keep relocation addends in the relocated places even
though the relocations are RELA, and RelocSection already follows that
convention. EhFrameRelocSection, however, put adjusted addends only into
r_addend, which SH4 readers ignore, so after `ld -r` every FDE's pc_begin
pointed at the start of its output .text and C++ exceptions terminated.
Write the addends into .eh_frame as we do for REL targets.
This enables the three -r exception tests that were skipped on SH4.
On SH4, r2 carries the address of the buffer for a struct return value.
Our PLT header loaded the link map into r2 and set r0 to 0, which glibc's
resolver accepts, but the resolver then restores r2 to its entry value, so
a lazily bound function returning a struct overwrote the link map. The
header now passes the link map in r0 as GNU ld does, borrowing r2 only
until the jump's delay slot restores it from the stack.
A section folded by ICF has no output section, so with --emit-relocs a
relocation against its section symbol was emitted with symbol 0, and one
in .rela.eh_frame, such as an LSDA pointer, hit an expect(). Symbols
defined in a folded section are not in .symtab either, so references to
them lost their symbol or, in .rela.eh_frame, got a wrong one. Emit both
relative to the leader's output section, as the relocated contents and lld
do.
When relaxing TLSDESC to initial-exec, we write an AUIPC to the
TLSDESC_ADD_LO12's slot and a load to the TLSDESC_CALL's slot, but we
computed their GOT-relative offsets from the TLSDESC_HI20's address. The
two coincide only if the instructions in between are deleted, which is not
the case for objects lacking R_RISCV_RELAX on TLSDESC relocations, such as
clang 18's, so the load read a wrong GOT entry. Use the AUIPC's address,
as lld does.
With --emit-relocs, a TLSDESC_HI20 relaxed to IE or LE was rewritten to
R_NONE as soon as it was applied. The TLSDESC_LOAD_LO12, TLSDESC_ADD_LO12
and TLSDESC_CALL that follow find it with find_paired_reloc(), which
matches by type, so such links failed with "paired relocation is missing".
Rewrite TLSDESC_HI20s after all relocations have been applied. LoongArch is
unaffected because its TLSDESC relocations refer to the symbol directly.
shrink_sections adjusted each symbol by the r_deltas of its own input
section, but a section folded by ICF is never shrunk, so its symbols kept
their pre-relaxation offsets while being resolved against the shrunk
leader. With LoongArch GCC's leading R_LARCH_ALIGN nop, calls to a folded
function landed 4 bytes past the leader's entry. A folded section was
identical to its leader before relaxation, so use the leader's deltas for
both symbol values and st_size.
GCC calls _savegpr0_N, _restgpr0_N, _savefpr_N and similar functions
from -Os prologues and epilogues and expects the linker to provide them.
We synthesized them only for ELFv2, so ELFv1 programs built with -Os
failed to link with undefined symbols. ELFv1 calls them with plain
R_PPC64_REL24 relocations, not through function descriptors, and
expects the same code, so emit the same section in target byte order
and move its instruction table from ppc64v2.rs to the section's module.
R_PPC64_GOT16, _LO, _HI and _HA take the offset of a GOT slot from the
TOC base, but we subtracted the TOC base from the slot's offset within
.got rather than from its address, so the result was off by the address
of .got. GCC addresses data through .toc instead, so only hand-written
assembly using @got was affected.
With -fpic, PPC32 code loads a GOT slot with one instruction whose
R_PPC_GOT16 relocation is a signed 16-bit offset from the GOT, and
R_PPC_GOT_TLSGD16, R_PPC_GOT_TLSLD16 and R_PPC_GOT_TPREL16 are the
same. We truncated these offsets silently, so a program whose GOT grew
past 32 KiB linked but loaded the wrong slots. Report an error instead,
as the psABI requires overflow checks for these fields.
A static executable's startup code applies the R_IRELATIVE relocations
between __rela_iplt_start and __rela_iplt_end, and we placed the start
by counting only the GOT slots of IFUNC symbols. On PPC64, a data
reference to an IFUNC symbol gets its own R_IRELATIVE instead of the
canonical PLT address, so the range began too late and skipped a GOT
slot, and a call through that slot ran the resolver. Count them too.
A global symbol defined only in a member of a discarded COMDAT group has
no owning file, so it has nothing to resolve to. The relocation scan
reports a reference to it and skips the relocation, but --noinhibit-exec
downgraded the error to a warning and the relocation was applied anyway,
which panicked on a missing GOT or TLS entry on most targets. Report the
reference as a fatal error in that case.
ld-prime settles each section's alignment in output order, its own
sections (__stubs, __got, __unwind_info...) as well: first -sectalign,
which may lower it with a "-sectalign reduces alignment" warning, then
the cap at the segment's maximum alignment (the page unless -segalign),
with a "reducing alignment of section" warning outside -static images.
mold applied -sectalign to input-derived sections only, before the
others existed, so it printed all such warnings first and never
aligned a synthetic section as asked.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime cuts __TEXT,__ustring at its symbols, merges the UTF-16 strings
by content and names none of the atoms, so no label of theirs reaches
an output. But in an object without MH_SUBSECTIONS_VIA_SYMBOLS every
section is one atom, __ustring too, and ld-prime keeps its labels as
those of any other section: locals and demoted private externals in a
final image's symbol table and map, and all of them, marked
no-dead-strip, in a -r output. mold dropped them there as well.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Apple's $ld$add, $ld$hide, $ld$install_name and $ld$previous names are
linker directives that let a library change shape per deployment
target. A .tbd lists them among its exports; a dylib binary exports
them as absolute symbols, and ld-prime obeys them there as well, for a
linked library and a re-exported one alike: it adds or hides exports,
renames the library, or binds single exports to an older library. mold
read them from stubs only and let a binary export the directive names.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld64 appends /usr/lib and /usr/local/lib to the -L directories, and
/Library/Frameworks, then /System/Library/Frameworks, to the -F ones;
ld-prime adds /usr/lib/swift after /usr/lib, searched for any library.
Each is looked up under the syslibroots, but a default directory the
only SDK lacks is not searched at all (clang passes the SDK and adds
-L/usr/local/lib itself), and -Z drops them. mold searched /usr/lib
alone and the framework directories in the other order.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
dyld copies one template for each thread: the image's sections typed
S_THREAD_LOCAL_REGULAR (initial values) and S_THREAD_LOCAL_ZEROFILL
(zero fill), from the first to the last, which must be contiguous.
ld-prime places them by type whatever their names - the initial values
last among file-backed __DATA sections, after the __thread_vars
descriptors, the zero fill first among zero-fill ones - and gives all
of them, in final and -r outputs, the strictest of their alignments.
mold went by the names __thread_data and __thread_bss only.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A symbol in a thread-local zero-fill section (S_THREAD_LOCAL_ZEROFILL,
__thread_bss) names a thread-local variable's initial storage, which
only its own object's __thread_vars descriptor refers to; clang names it
x$tlv$init and keeps it local. ld-prime gives even an external one
translation-unit scope: another object's reference to its name is
undefined, another definition of it is no duplicate, and final and -r
outputs list it as a non-external symbol, neither private extern nor
weak. mold resolved, exported and demoted such symbols as any global.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
-x (ld64's kLocalSymbolsNone) keeps no local symbol in a final image:
not the non-external symbols of the objects and not the private
externals the link demotes to non-external either, whether they name a
section's atom or an absolute value, nor the linker's own names such as
objc_msgSend$ stubs; only the external symbols and the debug notes stay.
mold kept the demoted private externals, as N_PEXT locals.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An N_ABS symbol (`.set x, 5`, `x = 5`) has no section and never slides;
ld-prime keeps a non-external one in the symbol table as any other
local, its l/L-named ones too in a -r output. A final image lists the
absolute symbols after the symbols of the sections, by value, locals
before demoted private externals where values tie; a -r output lists
each object's after its section symbols, in the object's order, the
demoted private externals last. Either way n_desc is 0, although the
assembler marks every absolute symbol N_NO_DEAD_STRIP. mold dropped them.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime's deduplication (-O1 and up or -deduplicate) folds functions of
__TEXT,__text only, and only those whose address is insignificant: a
global .weak_def_can_be_hidden definition (N_WEAK_DEF | N_WEAK_REF, an
unnamed_addr inline function) that the link auto-hid, even if its address
is taken, and any other unexported one - local, private extern or hidden
by an export list - that only branches reach (mold's --icf=safe, with
the address-taken bit inferred from live non-branch relocations). mold
folded exported weak functions, breaking pointer identity, and skipped
debug-info objects; like ld-prime it now ignores unwind encodings too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime lays out the segments -segaddr leaves free from the image base,
each at the lowest address where it runs into no segment placed before
it, and counts the pinned ones as placed only from the first of them
on. In an image dyld slides, a pinned __TEXT sets no base, though: every
pin counts from the start, and a segment may follow one below the base,
so one at the base moves __DATA_CONST past it, out of order, where the
segments ahead of the first pin would otherwise overlap it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A section may be aligned no more than its segment: under a small
-segalign ld-prime lowers __stubs, __got and __unwind_info with a
warning as well, in output order. It checks a pointer's alignment in
its segment, and gives x86-64's fixup chains up on an unaligned GOT
slot too; arm64's chain pages must be 4KB or 16KB, which it reports
as a layout error, printing the layout, unless a pointer fails first.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A link that names a lazy dylib (macOS 27) calls __dyld_lazy_load to
load it; ld-prime wants the symbol from its "<lazy-load-undefs>" as an
ordinary reference, not as one of the command line's initial undefines
that must resolve, so a kext, which links no dylib, takes it dynamically
looked up as any undefined symbol. A lazy dylib has no load command, so
an image with no other one fails ld-prime's libSystem check.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime looks up the path of -weak_library, -needed_library,
-reexport_library, -upward_library, -lazy_library, -force_load and
-bundle_loader under each -syslibroot first, a .tbd in place of a dylib,
and takes whatever a search finds, failing on a directory. The last
-bundle_loader counts, and one that is no executable, like a file a
-dylib_file names, loads as any input: an object is linked in, and a
file it can't link it words as unknown or an unsupported Mach-O type.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime makes the atoms of UTF-16 literals (__TEXT,__ustring in an
object with subsections), CFStrings, selector and class references,
the class and category lists and the ObjC literal sections by content
and names none of them: an external symbol that labels one defines
nothing. The object's own relocations still reach the atom, but no
output lists the name, another object's reference to it is undefined,
another definition of it no duplicate, and ld -r names the atom lNNN.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime writes no stab for a symbol in a section it takes apart into
its own atoms (literals by section type, __TEXT,__ustring, init/term
pointers, the __DATA ObjC lists and references) or whose atom was
coalesced away, but notes an absolute one (N_GSYM, or a local's N_STSYM
of its value). A -r input's N_GSYM goes by its symbol: a demoted
private external's becomes an N_STSYM with its address. N_SO splits
the unit's full path at its last slash; N_OSO drops a fat slice's arch.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A stub's $ld$previous$<name>$<compat>$<platform>$<lo>$<hi>$<sym>$
says an export lived in <name> for targets from lo up to hi: ld-prime
binds it to a library of that install name at <compat>, or else at the
stub's version, and renames the whole stub if <sym> is empty. It lists
a library under the install names bound to it: one whose bound exports
all moved loses its load command, one with the name as its own speaks
for both, and a weakly loaded one makes its moved exports weak imports.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Warnings and errors that parallel passes find - bad relocations as
objects are read, alignment warnings - were printed in whatever order
the worker threads ran, so a link reporting several of them printed
them differently from run to run. A message given from a worker thread
now waits and comes out sorted before the next message of the link's
own thread, or as the link ends; the reports printed as lines of their
own (-why_live, -why_load, text relocations, the final layout) follow.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
relocatable.rs spelled out crate::symbol::SymbolId and the input
section types at every use, eighteen times for the symbol id alone,
which stretched the signatures of the symbol table's and relocations'
helpers over several lines. They are imported once at the top, as the
other passes do, and the code names them directly. The output is
unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A symbol's address in a -r output is its subsection's, its output
section's address plus the subsection's offset there, plus the symbol's
offset, and a non-external relocation's target is found the same way;
a literal merged away stands at its survivor's address. The -r code
computed that by hand in two places, following the literal-merge
redirect itself; Context::isec_addr does exactly that and is used
instead. The output is unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
LC_DATA_IN_CODE lists the ranges of code sections that hold data: the
live entries of the inputs, each placed by its subsection, sorted, 8
bytes each. A final image records each range by file offset, an object
by address, as its sections are laid out from zero; that is the only
difference between the two tables, so the -r output now builds and
writes them with the final link's functions, told which of the two a
section's place is. The output of either kind of link is unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The section renaming rules now live in output_sections.rs and the
symbol table's debug-note planning in chunks/symtab.rs; passes.rs kept
re-exports of the five items the -r output used through it. The -r
code names them where they live, and the re-exports go. A -r output
names its sections as a final image does, and carries the same notes
naming the input objects. The output is unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An FDE copied into an output's __eh_frame has three self-relative
fields to rewrite for its new place: the CIE pointer, how far back its
CIE is from the field; pc_begin, the function; and the LSDA pointer
past the augmentation length, each of the size its CIE's encoding
gives. The final link's __eh_frame and the one a -r output synthesizes
did that each in its own copy; they now share one function. The output
of either kind of link is unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A -r output's symbol table is its locals, the debug notes naming the
input objects, the defined externals and then the undefined and
tentative symbols, the last two sorted by name. The externals' entries
carry the n_desc flags the next link needs (N_ALT_ENTRY, the weak
markers), and an undefined symbol stays only if a relocation or -u
names it. Each of those two lists is now built by a function of its
own, and build_symtab concatenates the parts. The output is unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A -r output's locals are gathered in three passes - the literal atoms
ld64 names itself (LC<n>, l<nnn>), the objects' surviving labels and
the demoted private externals - then ordered by place and rank, with
the literal atoms numbered in that order. That is now a struct with a
method per pass, and one lookup maps a reference into a literal section
to its atom for gathering and relocating alike. The output is unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A -r output's local symbols are gathered before they are ordered: each
with its name (its own, or LC<n> or l<nnn> that ld64 makes for a literal
atom once the order is known), its entry, the input symbols it stands
for, its place and its rank among the names of the place. That type,
the ranks, now an ordered enum, and the address helper move to module
level, ahead of splitting build_symtab. The output is unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The -r output's file is its header and load commands, the sections'
contents from the end of the load commands (the one segment), then the
relocations, data in code, hints, symbols and strings. The load commands
now know their own count and size, which place the contents; a layout
records the offsets they refer to; and the header, the section contents
with their rewritten fields, and the tables are each written by a
function of their own. The output is unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Each synthetic section now knows what it holds - the __objc_imageinfo
record, the unwind records of __compact_unwind or the CIEs and FDEs of
__eh_frame at their offsets - and builds its contents from that once
addresses and symbols are known. __eh_frame's self-relative fields
(pc_begin, the LSDA pointer) are written as its records are copied,
since the section's address is final by then, rather than recorded as
patches applied later. The output is unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A -r output names a relocation's target by the symbol its table gives
it, by the atom ld64 names itself in a literal section, by the symbol
ld-prime re-derives from the address, or section-relatively; the
__compact_unwind fields and the CIEs' personality cells do the same.
Those lookups were closures in link and parameters threaded through the
relocation functions; they are now the methods of one struct built from
the symbol table. The output is unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A -r output's sections, merged and synthetic, are sorted into ld64's
order, given addresses from zero with zero fill in place, and given
file offsets that mirror the addresses less the zero-fill spans, from
the end of the load commands. A synthetic section now keeps a chunk
header like a merged one, so each of those steps is one loop over the
sections in its own function, and the load commands are sized before
the symbol table is built. The output is unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A -r output synthesizes three sections beside the merged ones: the
merged __objc_imageinfo, __LD,__compact_unwind and __TEXT,__eh_frame.
Their sizes are known before layout, so they are sorted and placed with
the merged sections, and their contents are built once addresses are
assigned. Their type and the functions that decide which records each
one carries and how large it is now live outside link, which only
collects them. The output is unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
-sdk_imports asks for a JSON report of the SDK symbols the output
imports, by library, which Xcode reads to check API use. A -r output
imports nothing - it is linked again - so ld-prime takes the option
with -r and writes no report. mold refused the combination with an
error ld-prime has no text for; it now writes no report either.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime maps every input file whole: an empty one it refuses ("file is
empty in '<path>'"), and a directory it can't map ("file cannot be
mmap()ed, errno=22 (Invalid argument) path=<path> in '<path>'"). A
-dylib_file whose file is not there leaves the re-exported library to
the usual search, but one that is fails the link so, or with open()'s
errno; mold searched on. An empty object on the command line likewise
fails the link, where mold took it for one with nothing in it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
From macOS 27, dyld loads a -lazy-l, -lazy_library or -lazy_framework
dylib at its first use; ld-prime links it as usual below that, warning
that lazy-load will be ignored. Firmware, which a -preload image is to
ld-prime whatever its platform, has no dyld at all: it links the dylib
as usual there too, and warns "-lazy_library cannot be used on
firmware, changing to regular link" (-lazy_framework: "... regular
-framework"), in both lists naming the frameworks before the libraries.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime looks up the libraries the command line names - by -l, by path
(-weak_library and the like) or by -force_load - in command-line order,
and the frameworks only after all of them, and fails the link at the
first it doesn't find, naming that one only. mold reported every one,
a library named twice twice, and a missing -force_load archive only as
it loaded the inputs, after the others.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime resolves every symbol and dead strips the image before it
checks for duplicate strong definitions of a name: it reports none if
some symbol is undefined, which it reports alone, and none of a symbol
dead stripping leaves no live definition of. mold reported duplicates
right after resolution, before undefined symbols. A -r link, which has
no undefined symbols to report and strips nothing, keeps the old order.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A Mach-O section's file offset is 32 bits, and ld-prime computes its
segment's file end in 32 bits too: it refuses a section that ends past
that end ("goes past the segment end"), before laying out __LINKEDIT,
printing the layout twice. A segment that ends at 4 GiB, which a
-segalign of 2 GiB gives the second one, ends at 0 so; and a -segalign
of 0, which ld-prime takes as it is, rounds every segment to size 0.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
-bundle_loader names the executable a bundle's undefined symbols bind
to, so ld-prime takes it only with -bundle. It checks that right after
reading the options - after incompatible platforms and -kernel without
-static, before any other option check, warning or input file - where
mold did so as it loaded the loader. Its check of -r with -dead_strip
likewise follows the lazy-load warnings.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
From macOS 12's chained fixups on, an image's initializers are 32-bit
offsets from its mach header in __TEXT,__init_offsets, which ld-prime
makes of __mod_init_func's pointers and of the -init function, first.
An initializer dyld binds has no offset, and fails the link; but for
an absolute symbol, ld-prime writes the symbol's value as the offset,
its low 32 bits. mold refused one as it refuses an import.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A class reference slot folded into the GOT (macOS 15 on) is its class's
GOT entry, an atom of ld-prime's stubs-got-file, which it numbers as it
meets the references, object by object in address order: two atoms for
each GOT entry, one for each stub after its entry's, and a lazy stub's
three after the stub helper's four. A text relocation to the slot names
that anon-N. Encoding rebase opcodes, ld-prime lists only the first
section's text relocations; with chained fixups, every section's.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime finds a segment out of order in an image dyld slides, and a
__thread_data or __thread_bss not typed as thread-local, before it lays
out __LINKEDIT: it stops there, printing __LINKEDIT with no size where
it had it - its -segaddr, or after the last segment as it first placed
them, each unpinned one above all before it - and the layout twice for
a segment out of order. An initializer dyld binds, which __init_offsets
can't hold, still lists its import in the symbol table.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An x86-64 jmp rel8 (a BRANCH relocation of r_length 0) reaches only
127 bytes either way, so it can only be to code near it in the image.
ld-prime makes stubs only for 32-bit branches: a one-byte branch to
an import stays bound to the dylib's symbol, and applying it is a
fixup error ("target ... does not have address"). mold gave it a stub
and a __got slot for one, which showed in the section layout it prints
with the error; the branch now takes neither.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An arm64 b/bl reaches +-128 MiB, and a branch farther than that goes
through a branch island. ld-prime makes islands in every final image
type - -static, -preload, -dylinker and -kernel ones included - but
only among the code sections of the __TEXT segment: a branch from or
to code in another segment, such as a kext's __TEXT_EXEC or a custom
code segment, gets none and is a fixup error if it is out of reach,
and no atom there gets the warning about oversized island clusters.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Layout errors - fixups that can't reach, segments out of order,
thread-local data out of place - come from passes that run in parallel,
so they were printed in whatever order the threads found them: a link
with several out-of-range branches printed them in a different order
from run to run. They are now held back and given sorted, before the
final section layout ld-prime prints with them, or as the link exits.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A segment's chain-starts record gives its offset from the image base.
A layout that failed - a PIE that ignored -segaddr __TEXT and so put
the other segments below __TEXT - still builds the tables before the
link reports its layout errors and stops, and a debug build panicked on
the negative offset there, losing the error. The table of such a link
is never written, so the offset now just wraps.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The table of standard sections' flags and the rules read from it -
canonical_section_flags, is_standard_section, input_section_flags -
move unchanged from among the boundary-section code to right after
output_section_flags, so that the module opens with all of ld-prime's
naming and flag rules and then reads as create_output_sections and its
steps.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The inlined-document branch repeated the on-disk stub's merge - the
exports by kind and the re-exports queued - and the dylib branch the fat
slice's. ReexportWalk now holds the walk's queue, the document pool and
the export sets, with merge_tbd and merge_binary for both pairs, and the
stub's detailed documentation, stranded above trace_file, is back on
load_reexports. Exports still merge in the same order.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
create_output_symtab (307 lines) keeps the table's layout and hands its
self-contained parts to functions: live_refs (what keeps an import
listed), classify_symbols, a global's and an import's entry, and the
N_INDR aliases. plan_local_symbols gathers object_locals and
linker_locals and reorders aliases apart, and plan_object_stabs splits
off copying a -r output's notes and opening a run (N_SO pair, N_OSO).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
create_output_symtab and its planning - the local symbols in ld-prime's
order, each object's debug notes (Stab, StabPlan, plan_object_stabs)
and which local names survive - move unchanged from passes.rs to the
symbol table's chunk, next to the writer that emits the plans, as the
export trie and fixup tables are built in their chunks. passes.rs drops
to 4,849 lines; the atom-naming rules -r shares stay there.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The 617-line function now reads as its list of steps: assign input
sections to output sections, settle alignments, order members
(-order_file, cold code), compute sizes, add the synthesized sections
(ObjC stubs and method lists, -sectcreate, the merged __objc_imageinfo,
__eh_frame), sort the chunks and group them into segments. The hot
assignment loop stays whole; the first member's flags and the section
creation it shared with the always-present __text become helpers.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
create_output_sections and the code only it uses move unchanged from
passes.rs into a module of their own, as the ObjC, dead-strip and ICF
passes did: ld-prime's rules for an input section's output section name
and flags (SectionMap, output_section_for, the standard-section table,
legacy and -rename_* names), the synthesized and boundary sections, and
the chunk and segment order. passes.rs drops from 7,802 to 5,838 lines;
it re-exports renamed for relocatable.rs, which is being reworked.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Sections of one rank follow the order in which the objects first have
them, and ld-prime counts only the atoms it keeps there: a string merged
into a more aligned copy in a later object places nothing, nor does the
losing copy of a weak definition. mold counted every input copy, so
NetNewsWire's __objc_methtype, whose first strings lose to 16-aligned
Swift copies, came before __objc_methname, and iTerm2's __DATA,__data
came early. A copy a synthesized record took over still counts.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A method list rewritten in the relative form is a new atom ld-prime
makes in __TEXT,__objc_methlist; the input list's symbol names it as a
plain local (N_SECT, n_desc 0), and no debug-map stab notes it. Swift
emits its protocol method lists as weak private externals, which mold
kept as such (N_PEXT with N_WEAK_DEF) - iTerm2's debug dylib had 228 -
and it noted every rewritten list with N_STSYM or N_GSYM.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The runtime of older systems writes the class, superclass and protocol
references; later ones leave them to dyld's fixups, so the linker may
make them read-only. ld-prime moves __objc_protorefs, __objc_classrefs
and __objc_superrefs to __DATA_CONST, in that order, from a deployment
target of macOS 14.4 on, though it folds class references into the GOT
only from macOS 15. mold moved them at 15 too, and ranked __objc_classrefs
of __DATA_CONST, left by the fold, after __objc_superrefs.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
From macOS 15 on, ld-prime folds __objc_classrefs into the GOT: a load of
a slot becomes a load of the class's GOT entry, relaxed to computing the
address of a class the image defines. An x86-64 slot points section-
relatively at a class only an L label names, and ld-prime folds that
too; mold folded only slots naming a symbol. Such a class now gets an
anonymous symbol for the fold. A slot pointing into the middle of an
atom stays: ld-prime folds it to the atom's start, which is wrong code.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime names no entry of __objc_classlist and the other class and
category lists, nor of __objc_clsrolist. Of the symbols at an entry it
takes one for its atom's name, which is lost: an external one (global
or private external) if any, else the greatest name, ltmpN aside. The
others survive as aliases, without N_NO_DEAD_STRIP in a -r output. So
clang's l_OBJC_LABEL_CLASS_$ stays where a private external names the
entry too, which goes; mold dropped every local and kept the latter.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime cuts __objc_superrefs and __objc_protorefs one atom per pointer.
One a symbol names stays apart and keeps its label, in an image's
symbol table too; the others merge by target, and an arm64 -r output
names each l<nnn> (N_PEXT), as it names literals. Of the literal-pointer
type, though, it takes them for class references: all of one target
merge, whatever labels them, no label survives, and the output has the
standard section's flags. mold dropped the labels and read the type away.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
__DATA,__objc_clsrolist is the compiler's list of the class_ro_t records
of Swift's generic classes, for the linker to rewrite their method lists;
no image keeps it. ld-prime cuts it into one atom per 8-byte entry and,
as for an initializer pointer, requires one fixup of each: its check
reads "__objc_clsrolist pointer has no target", though an assertion on
the same condition trips first. mold took a null entry silently; it now
reports the first such pointer of an object, entry by entry.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Of several copies of a weak definition only the one resolution keeps
reaches the output; the others are merged into it, and so are the GOT
slots their relocations would need. Swift's symbolic type references in
__swift5_typeref are weak: the defining object's copy refers to the
type descriptor directly, every other object's through a GOT slot
(ARM64_RELOC_POINTER_TO_GOT). mold scanned the losing copies too, and
NetNewsWire's debug dylib got 17 GOT slots ld-prime's doesn't have.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An atom keeps its object address modulo its section's alignment, and a
C string is an atom too. Of identical literals ld64 and ld-prime keep
the copy with the greatest alignment in those terms (an atom at 16 mod
32 is 16-aligned), the first of equals. Swift pads the strings of its
16-aligned __objc_methname, __objc_classname and __objc_methtype with
NULs so that many start at a multiple of 16; mold kept clang's unaligned
copy instead, and iTerm2's debug dylib lost ld-prime's NUL padding.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A CFString constant whose string isn't ASCII points at a UTF-16 copy in
__TEXT,__ustring, a regular section each object has its own of. ld64 and
ld-prime cut it at its symbols and merge identical atoms, whatever labels
them, in an image as in a -r output; CFStrings then merge too, as they
coalesce by the string they wrap. mold merged neither: iTerm2's ASan
debug dylib had __ustring 0xde0 and __cfstring 0x860 bytes (67 CFString
constants) larger than ld-prime's.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Of several weak definitions of a symbol, ld64 and ld-prime keep one that
can't be auto-hidden over a .weak_def_can_be_hidden one, then a global
over a private extern, then the most aligned, and the first loaded only
among equals. An atom's alignment is its section's with its object
address as the modulus, so a copy at 8 mod 16 counts as 8-aligned; mold
compared the sections' alignment alone. In iTerm2's ASan debug dylib
5128 symbols (Swift metadata, ASan string globals) took another copy,
which changed __DATA_CONST,__const, __TEXT,__const and __asan_globals.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A __mod_term_func section (S_MOD_TERM_FUNC_POINTERS) lists functions to
run when the image goes away, as __mod_init_func lists those to run when
it loads; nothing else refers to them. ld64 and ld-prime take every
atom of either type for a dead-strip root. mold kept initializers only,
so -dead_strip silently dropped every terminator and its function:
iTerm2's ASan debug dylib lost the 1626 module destructors ASan emits
(__DATA_CONST,__mod_term_func, 0x32d0 bytes in ld-prime's output).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
From macOS 27 a dylib -lazy-l and the like name has no LC_LOAD_DYLIB
but an LC_LAZY_LOAD_DYLIB_INFO record: its install name, a __data flag
word, and the symbols used with their __lazy_load_got slots, a bind
chain LC_DYLD_CHAINED_FIXUPS doesn't list. Calls and GOT loads go via
__lazy_helpers code that tests the flag, has __dyld_lazy_load load the
dylib and bind the chain, then uses the slot; other uses are refused.
(ld-prime's classic self-binds of the slots break dyld: left out.)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime names an unnamed atom anon-N for the object's Nth atom: by
section header order, then address; none in an empty section, the
dropped __DWARF/__LLVM/__LD sections, __eh_frame or __objc_imageinfo,
but one per __compact_unwind record. A label naming no atom is a
zero-size atom: a second one at a place, an alternate entry, any past a
section's start without subsections. On a literal a private L/l label
takes the bytes, naming nothing; arm64's ltmpN only counts, and last.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime fails the link on an error in the layout it made - a fixup
that doesn't fit as it writes the output, a thread-local section it
can't place in the template, a segment below the one before it - only
once the layout is done, printing it: each segment in load command
order with its address, size and file range, and each section's address,
size and file offset (0 for zero fill). mold now reports those errors as
layout errors, which let the layout finish, and prints its own layout
the same way before failing.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
__init_offsets holds each initializer as a 32-bit offset from the mach
header, which a function in a library, or one left to dynamic lookup,
does not have. ld-prime converts every __mod_init_func all the same and
fails the link as it writes the offsets, with an imageOffset32 fixup
error for the first such initializer; its synthetic "inits-file" names
the k-th offset anon-(2k+1). mold kept __mod_init_func for such an
object. The initializer's symbol still counts as referenced, so one
that is plain undefined is reported as that.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime makes every slot of an input __DATA,__got an entry of its
__got, a pointer with an addend or to a place in a section too: dyld
binds an imported symbol plus the addend (the indirect symbol table
names the symbol) and slides a local address (INDIRECT_SYMBOL_LOCAL).
mold kept such slots in a second, regular __got. They now follow the
symbols' slots in the GOT, each keeping its bytes and relocation, and
every __got slot is pointer-aligned whatever the input's type says.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime knows a section of non-lazy symbol pointers by its type,
whatever its name: in a final image it keeps the type, names each
8-byte slot's symbol in the indirect symbol table (INDIRECT_SYMBOL_LOCAL
for a pointer it fills in itself, a rebase), moves the section from
__DATA to __DATA_CONST like the GOT, and makes each slot an atom aligned
to a pointer. A kext's are plain data, and a __DATA,__auth_got,
__weak_got or __weak_auth_got of that type, pointers only it makes, is
refused as an unknown fixed-size section. mold made them regular data.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
With classic dyld info, a stub's lazy pointer first leads to its stub
helper entry, which pushes the symbol's lazy-bind offset and jumps to
dyld_stub_binder (exported by libSystem's re-exported libdyld) through
a GOT slot. ld-prime makes that name an initial undefine of any image
with lazy binding, stubs or not, one that may stay undefined, so the
library exporting it counts as used: -map lists libdyld even for an
image with no stubs. It gets no slot or import until a stub needs it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A common symbol is a tentative definition: each object declaring it
names only a size and alignment, and the linker allocates the storage
once, in __DATA,__common, for the largest declaration. ld-prime's map
credits that storage to the object whose declaration won, and a GOT
slot pointing at it too, as it credits every symbol's stub or slot to
the file defining the symbol. We credited the slot to the linker (file
0), since the storage itself is a subsection of our internal object.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
-reexported_symbols_list names imports a dylib publishes as its own
exports: each becomes an export-trie entry with the REEXPORT flag that
sends dyld on to the library that defines it. Only a dylib has clients
to look symbols up in it that way, so ld-prime refuses the list for an
executable, a bundle, a -static or -preload image or a -r link, even an
empty one, in its own words, after its other option checks. (Re-export
of a library whole, by -reexport_library, is still allowed there.)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An arm64 b/bl reaches +-128 MiB; a linker makes a branch that can't
reach its target go through an island (our range-extension thunk)
placed within reach. -no_branch_islands asks for none: ld-prime then
reports each out-of-range branch as a fixup error, "B/BL out of range
(displacement=..., max is +/-128MB)", and no longer warns about code
atoms too large for its island clusters, as there are no clusters.
The option is accepted and has no effect on x86-64 or in -r links.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
-init names a function the image runs before its other initializers.
ld64 recorded it in LC_ROUTINES_64 (its unslid init_address; the module
and reserved fields are relics of multi-module dylibs), which dyld runs
ahead of __mod_init_func. ld-prime makes it the first __init_offsets
entry instead, in executables and bundles too, and silently drops it
from an image that keeps __mod_init_func; there we use LC_ROUTINES_64.
The name is an initial undefine like -u's; an import has no offset.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An LC_LINKER_OPTION command carries a count and that many NUL
terminated strings: clang writes one command per auto-link option, the
option alone (-lfoo) or with its argument (-framework Foo). ld-prime
reads an object's commands that way and fails on one holding no string
or more than two, "LC_LINKER_OPTION has count=3, only 1 or 2 is valid
in 'd.o' in 'd.o'", in a -r link too. mold read any count.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A library named twice is loaded once, and ld64 warns of the repetition
once, listing each repeated option as spelled, sorted. ld-prime counts
the options naming a library by path too - -force_load, -weak_library,
-needed_library, -reexport_library, -upward_library, -lazy_library -
and an archive's bare path, while a dylib's bare path and the framework
options go unmentioned. mold listed the -l options only.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A weak import (N_WEAK_REF, and LC_LOAD_WEAK_DYLIB when every import
from a dylib is one) lets dyld leave a symbol NULL when the dylib or
the symbol is missing at run time; an archive's members are copied
into the image and have nothing to leave out. ld-prime links an
archive a -weak-l finds as usual, warning once for the library that
only dynamic libraries can be weak linked; -weak_library says nothing.
mold linked it silently.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
-lfoo searches each library directory for libfoo.tbd, libfoo.dylib
and libfoo.a in turn before the next directory, so an archive in an
earlier directory hides a dylib in a later one. A re-exported library
has to be a dylib, whose exports the re-exporting one lends its
clients through LC_REEXPORT_DYLIB; ld-prime's -reexport-l, like
-upward-l, looks for a dylib only, in every directory, and reports
the library not found if there is none. mold took the archive.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime walks the atom graph depth first from each dead-strip root in
turn (the entry point and -u symbols, then root atoms in input order,
the executable's header last) and prints a chain each time a reference
reaches a matching atom, or for a root its reason. An object without
subsections is one atom per section plus one per other symbol pointing
at it; initializer pointers read mod-init-ptr, dylib symbols name their
file. mold printed one spanning-tree chain per symbol.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An object's symbol table holds an undefined entry for each name the
assembler saw declared or used; the relocations refer to them by index.
ld-prime writes a -r output's undefined symbols from what its output
still refers to: those a relocation names (an unwind personality in
__compact_unwind or an __eh_frame CIE included) and the initial
undefines of -u. A stray .globl, or a weak, lazy or no-dead-strip
reference nothing uses, goes; mold kept every one.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime turns its command line into inputs, the options naming one
library making one, and reads each: a stub without a target on the
link's architecture, a fat file without its slice, a dylib in a link
that takes none, is ignored with its warning every time an input names
it, even by the same path. mold read each path once, so it warned
once; it now reads an ignored file again, while a library it loaded
still loads once.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A universal (fat) file holds one Mach-O slice per architecture behind
a fat header, and a link takes the slice for its own. ld-prime ignores
a fat file without one, with "fat file missing arch", wherever it
comes from: named on the command line, or found for a library a dylib
re-exports (by its leaf in the library path, its install name or
-dylib_file); the re-export is then left out without the missing
indirect library warning. mold failed the link for a re-export.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A static linker reads each object file on its command line as a unit
of its own, where an archive is searched and a dylib merely imported.
ld-prime so loads an object file as often as the command line names
it: twice over, its global definitions are duplicate symbols, its
local-only data is there twice, and -t lists it twice. mold loaded
each path once, as it does a library, which it still loads once.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A dylib's stub re-exports libraries by install name: Foundation's
brings /usr/lib/libobjc.A.dylib, which mold finds as the SDK's
libobjc.A.tbd. A later naming may find the same library at another
path, as -lobjc does libobjc.tbd, a symlink to it; the two are one
dylib by install name. ld-prime's -t lists such a dylib by the file
the naming found alone, whether the naming is on the command line or
an auto-link option; mold listed both files.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A -r link leaves the libraries an object's LC_LINKER_OPTION commands
name for the final link to load, and carries the commands into its
output. ld-prime writes them back from the libraries they name: one
command per library, the libraries by name then the frameworks by name.
The first naming says whether a library loads lazily and any whether
it is needed; -hidden-l and -force_load say nothing, so a -force_load
path comes out as -l<path>, and a framework loses its ",suffix".
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime gives some diagnostics as it reads an option, which only a -w
before the option silences: a deprecated -undefined treatment, a
-sectcreate name cut to 16 bytes, the note on -macosx_version_min.
mold holds them back until the options are known to be read for the
target, so that a parse redone for another architecture gives them
once; they were lost when a later option was fatal. The fatal error now
gives them first, and the two mold gave after parsing are held too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A dylib's LC_REEXPORT_DYLIB names a library by its install name, the
path dyld loads it from at run time; the linker has to find a file for
it to see the symbols it lends. -dylib_file install_name:file names
that file outright, ahead of the search by the name's leaf in the
library path; a file not there leaves the search to find one, and an
argument without a colon is malformed. ld-prime deprecates the option,
once, as it reads it. mold rejected it as unknown.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Segments start and end on the segment alignment in memory and in the
file, which is the page size unless -segalign says otherwise (rounded
down to a power of two, with a warning); it also caps section
alignment and the -pagezero_size, -stack_size and -seg_page_size
checks. Fixup chains are cut into pages counted from each segment's
start, which dyld takes to be 4 or 16 KiB: ld-prime uses the segment
alignment on arm64, refusing any other, and 4 KiB pages on x86-64.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A re-exported dylib (LC_REEXPORT_DYLIB) lends its exports to every
client of the re-exporting image, so it must be there whenever that
image is; a weak (LC_LOAD_WEAK_DYLIB) or lazily loaded dylib need not.
ld-prime therefore stops when the options naming one library make it
both, "'-weak-lfoo' and '-reexport-lfoo' cannot be used together", as
the option completing the pair spells the library (a path as -weak-l,
a framework as -weak_framework); a library not found before wins.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime reads every library option before it loads a file: it finds
each -l and -framework, merges what the options naming one framework,
or finding one file path, say (under -L., `-lfoo -upward-lfoo` and
`-lfoo -upward_library ./libfoo.dylib` load an upward libfoo), and then
loads each file once, as its first naming says. A bare file path, or a
path spelled another way, merges with nothing, so a later -upward_* or
-reexport_* for that file is ignored. mold merged every naming.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
MH_DEAD_STRIPPABLE_DYLIB was ld64's way for a dylib to ask the linker
of each client to drop its LC_LOAD_DYLIB whenever the client bound no
symbol from it, as -dead_strip_dylibs does for every dylib. ld-prime
has retired the mechanism on both sides: -mark_dead_strippable_dylib
sets nothing and draws "-mark_dead_strippable_dylib is obsolete" once
the options are read (so a -w anywhere hides it), and a flagged input
dylib stays loaded unless -dead_strip_dylibs is given. mold does both.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An arm64 b/bl reaches +-128 MiB. ld-prime places its branch islands in
clusters at most 124 MiB of code apart, each island a b to the next
one, so an atom of 124 MiB or more in a code section is one no chain of
islands can cross: it warns "atom '<name>' (<file>) is larger than the
max code size between branch island clusters, this may lead to
unreachable branches", and fails the link if a branch must cross it.
Our thunks load the target's address and reach 4 GiB, so we still link.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime seeds resolution with "initial undefines": the -u names, the
names an export list gives without wildcards, and the entry point. One
that stays undefined is reported as referenced from <initial-undefines>
even under -undefined dynamic_lookup, and -U on the entry point is an
error. An -alias base is wanted by its alias in command-line-aliases-file,
unless -dead_strip strips the alias (only an export root survives); it
too must resolve. The undefined symbols are reported sorted by name.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Only an image no dyld loads (-static, -preload) or firmware may order
its segments with -segment_order. In an image dyld loads, __DATA_CONST
is a standard segment, and a list naming __DATA without it gets
ld-prime's "lists __DATA, but not __DATA_CONST, assuming standard order"
warning; ld-prime decides that as soon as __DATA_CONST is settled,
before it refuses the option for an executable or dylib, so the warning
precedes the error. The two-segment minimum is checked before either.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld64 splits a section of C strings or of 4-, 8- or 16-byte literals
into one atom per literal and ignores the L and l labels there
(ignoreLabel), so the compiler's lCPI0_0 constant-pool labels and
l_.str string labels name nothing: -map lists such a literal as
"8-byte-literal" or by its contents, and diagnostics as anon-N. The
arm64 assembler's ltmpN section-start label is the weakest name an
atom may take: with subsections it names the atom only if nothing else
does, as in a __const or __mod_init_func that starts unlabeled.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Under -flat_namespace dyld looks every bind up in all loaded images in
load order, so an image loaded earlier can interpose a symbol. ld64 and
ld-prime extend this to a dylib's or bundle's references to what it
exports: calls go through stubs (lazily bound with classic dyld info),
GOT loads are not relaxed, and data pointers - __mod_init_func and ObjC
metadata included - are binds with ordinal -2 and the offset as addend
instead of rebases. A weak definition keeps its weak bind besides (with
chained fixups only that). Executables and private externs are as ever.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The LC_DYLD_INFO rebase stream is a small program for a state machine
whose address advances past every pointer it slides. ld64 builds it as
one SET_SEGMENT_AND_OFFSET_ULEB or ADD_ADDR_ULEB plus one rebase per
location, merges adjacent rebases into DO_REBASE_ULEB_TIMES, pairs a
lone rebase with the following step into DO_REBASE_ADD_ADDR_ULEB, folds
three or more pairs with one step into DO_REBASE_ULEB_TIMES_SKIPPING_ULEB
and writes small pointer-aligned steps as ADD_ADDR_IMM_SCALED. It ends
the stream with no DONE, only the zero padding to 8 bytes, which reads
as one; ld-prime emits the same bytes. A run also stops at its segment.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime cuts __mod_init_func and __mod_term_func into one atom per
8-byte pointer, whatever the labels: its diagnostics name the pointer's
own atom (anon-N, the object's Nth), a relocation straddling two
pointers is out of its atom's bounds, and a -r output lists the
relocations atom by atom. mold cut these sections at symbols only; it
now cuts them per pointer too, and reports a pointer with no relocation
only if the object's relocations were read, as ld-prime does.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime cuts __objc_superrefs and __objc_protorefs into one atom per
8-byte pointer and coalesces the atoms of one target that no symbol
names; a named one stays, even one the arm64 assembler's ltmp label of
the section's start names. The compiler labels every entry, but an
x86-64 -r output drops those l-prefixed labels, so categories calling
super on one class share a single entry after it. mold kept each entry.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An initializer pointer is an 8-byte slot of __mod_init_func that a
relocation fills with a function's address; with init offsets the linker
turns each into a 32-bit image offset in __TEXT,__init_offsets instead.
A slot the difference of two symbols makes carries a SUBTRACTOR and an
UNSIGNED relocation; ld-prime takes the UNSIGNED's target for the
initializer, but mold made one of each relocation, so it also ran the
subtracted function and listed it for -no_inits.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime's flags for the Objective-C runtime's sections - no-dead-strip
for the class and category lists, literal pointers for the selector
references, the reference lists' own flags and the protocol list's
coalesced type while in __DATA - hold for the sections of those names
in __DATA, where compilers put them. An input's __DATA_CONST or __FOO
section of such a name is data like any other, flags 0. mold decided by
the output name alone, so the first member's standard-ness now decides.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime reads an input __DATA,__got as non-lazy pointers whatever its
type and makes its slots entries of its own __got, each named in the
indirect symbol table. mold kept them as data in a second section of that
name, and split one typed as strings at its NULs. Each slot, now an atom
of its own, becomes the GOT entry of the symbol it points at, as folded
class references do. A -r output keeps __got as regular data with its
relocations; ld-prime's names the slots in the indirect table instead and
drops the relocations, making an object neither linker takes as input.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
dyld copies an image's thread-local sections, those typed as
thread-local data or zero fill, for each thread, and a variable's
descriptor holds its initial value's offset from the first of them. A
-rename_section can put __thread_data or __thread_bss in a section whose
first member makes it plain data, outside that template; ld-prime then
reports "thread-locals too large" for data before the template, whose
offset wraps. mold panicked on the subtraction; it now reports that for
any thread-local data outside the template (ld-prime crashes on some).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A literal section is split into atoms, one per string or fixed-size
record, and identical atoms coalesce. ld-prime coalesces C strings of a
section of any name, but 4/8/16-byte records only in __TEXT,__literal4/8/16
of their own types; mold merged the records of any literal-typed section,
and by their bytes alone, so two pointers to different targets, whose
bytes are the same addend, became one. A record or string that carries a
relocation no longer merges either (ld-prime merges __literal8's anyway).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Each kind of image may name its own mach header: an executable by
__mh_execute_header, which it exports (N_SECT, REFERENCED_DYNAMICALLY),
and a dylib, a bundle or dyld by __mh_dylib_header, __mh_bundle_header or
__mh_dylinker_header, linkage-unit symbols ld64 keeps out of the symbol
table as it does ___dso_handle. Code passes one to dyld APIs that take a
mach_header, and a pointer to it is a rebase to the image's start.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
-dylinker makes MH_DYLINKER, dyld itself. The kernel maps it anywhere and
starts it from LC_UNIXTHREAD at "start"; dyld slides itself by its chained
rebases (DYLD_CHAINED_PTR_64_OFFSET) before loading any image. So it has
no __PAGEZERO, links no dylib, binds nothing (weak-def pointers get rebases,
not ld-prime's weak-lookup binds), names itself /usr/lib/dyld in
LC_ID_DYLINKER and exports a trie; it is bound for the shared cache (split
info, SG_READ_ONLY __DATA_CONST), and its __text is 4 KiB-aligned.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A branch to a weak definition that dyld may coalesce with another
image's goes to the definition's stub, which dyld binds by weak lookup
to the first definition in load order, such as the executable's. A
thunk entry jumped to the definition itself, so a far call in a dylib
over 128 MiB ran the dylib's own copy while near calls ran the
executable's. The entry now jumps where the branch would, to
Context::branch_target_addr, as ld-prime's island does.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An arm64 BRANCH26 relocation can carry an addend, from the ADDEND
relocation paired before it (bl _far+4), but a thunk entry belongs to
a symbol and jumps to the symbol itself, so a far branch through one
landed on _far and silently skipped the addend. ld-prime's is no
better: it branches to its island plus the addend, past the island,
and the program crashes. A branch with an addend now never takes a
thunk entry, and one out of reach is a fixup error (arm64_b26_addend).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Our thunk for a batch of code went right after it, so a branch to a
subsection not yet placed, to a stub or to another code section took
an entry: 82,006 on a synthetic 200 MiB __text, where ld-prime makes
30,029 islands. mold places the thunk at D, as far ahead as it stays
in reach of the batch's start, so the subsections before D have final
offsets when the batch is scanned and the rest are 117 MiB away at
least; stubs and other code sections are judged by bounds on the code
span around the section. That link now gets 29,489 entries, as a thunk
reaches 4 GiB where an island reaches 128 MiB. As in mold, a
subsection wider than the reach no longer gets its thunk in front.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime makes a branch island only for a branch that can't reach its
target, so code spanning less than a bl's +-128 MiB gets none; we put
all code through the thunked layout once the code sections summed past
64 MiB, giving every stub call an entry (25,630 in iTerm2's 99 MB ASan
__text). Thunks are now decided once the sections are ordered, by a
bound on the span from the first code chunk to the end of the last,
stubs included; if a chunk in it is sized only as it is placed (a
shared-region image's __stubs follow __unwind_info) or the code
crosses segments, the placed span decides, placing the code again with
thunks if it is too wide.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime reads the Objective-C runtime's standard sections by name, as
it knows them from its table: a list typed as C strings or literals is
still 8-byte pointers, __objc_const typed as literals is data it never
merges, and a regular __objc_methname is C strings it merges. mold split
such sections by type, failing on relocations that straddled strings or
merging data into crashing images. The table's flags now replace theirs
as the object is read; __objc_selrefs keeps its type, but not a literal.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime knows __DATA,__objc_selrefs by name and writes it as literal
pointers (no-dead-strip) in a final image whatever the input's type.
Only the references typed literal pointers, which it coalesces, become
plain data once the shared region moves them to __DATA_CONST; a regular
or coalesced list keeps 0x10000005 there. Class references moved to
__DATA_CONST likewise drop the no-dead-strip attribute they carry in
__DATA, and neither kind of list is marked so in another segment.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime reads each input section into a kind of content and writes a
final image's section type from that kind: zero fill (S_GB_ZEROFILL
included, its file bytes dropped), strings, literals, initializer and
terminator lists, the thread-local kinds and bare DOF keep their types,
and literal pointers, stubs, interposing tuples or init offsets become
regular, as do instructions of a non-regular type. A literal pool it
folds into __TEXT,__const turns regular there; a typed __const does not.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
dyld sets up each thread's variables by copying the image's template,
the sections typed S_THREAD_LOCAL_REGULAR and S_THREAD_LOCAL_ZEROFILL,
and the variables' descriptors hold offsets from its start. ld-prime
takes an output section named __thread_data or __thread_bss, in any
segment and after renames, for part of it and fails the link with
"Missing TLV section flags" when its first member types it otherwise.
mold linked such a section as plain data, with broken offsets.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
GCC's .constructor directive, which it used for code built without dyld
(-static, -mkernel), puts pointers in a regular __TEXT,__constructor.
ld-prime reads that section as initializer pointers whatever its type:
they join __init_offsets in input order, a -static image or -r output
types the section S_MOD_INIT_FUNC_POINTERS, and __destructor stays data.
mold took it for plain data and never ran them; the flags are now
canonicalized as the object is read, as mold's canonicalize_type does.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime reads the whole command line before it complains about the
options it doesn't know: it collects them and, once every other option
is read and the warnings they drew are given, fails with "unknown
options: " and each one followed by a space. An error in an option it
knows still comes as it reads that option, first. mold stopped at the
first unknown option, in words of its own.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime keeps 32 bytes after the load commands of an image dyld loads
for a code signature's command added later, whatever -headerpad says,
warning that a smaller one "is too small"; a -static image or a kext
gets -headerpad as given. It places the sections after an estimate of
the load commands, and the estimate gives a static executable's
__UNIXSTACK the 80-byte header of a zero-fill __stack section that the
segment's command then goes without, so __text moves 80 bytes on.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime's map lists the __eh_frame it writes record by record, credited
to the object each came from: a CIE as "CFI", an FDE as "FDE for: " and
its function's atom; the branch islands as the symbol table names them;
and each dylib named, -dead_strip_dylibs or not, where it is named and
by the path given (auto-linked ones in option order), re-exported first
or not. It credits itself with the method lists it makes relative and
its ObjC stubs' selector names, spells C strings' bytes raw.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime takes an empty option argument for a missing one, reporting it
in the option's usage ("-e missing <name>", "-platform_version missing
arguments ..."), but for a dylib's current or compatibility version,
which it takes for 0.0.0, and the few that do without (-rpath,
-oso_prefix, -executable_path); a library option with the name joined
to it needs one too. An architecture it doesn't know is "unknown -arch
name", or "unknown architecture in target triple"; i386 is unsupported.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld64's numeric option arguments (-image_base, -segaddr, -pagezero_size,
-headerpad, -sectalign, -seg_page_size, -stack_size...) are hexadecimal,
read with strtoull(): white space, a sign and a 0x or 0X prefix may come
first, a number too big is the largest there is and a negative one wraps
around. Anything left over is "OPT: not a hexadecimal number: V"; sizes
and alignments must fit 32 bits. A base or __PAGEZERO rounded up to a
page past the top wraps to 0, which ld-prime takes as none.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An upward dylib depends on the image being linked in turn, so dyld
need not initialize it first: -upward-l (which looks for a dylib only),
-upward_library and -upward_framework give a dylib LC_LOAD_UPWARD_DYLIB,
or one weak or re-exported a dylib_use_command with its flags; anything
else loads the library as usual, with a warning. ld-prime loads a dylib
lazily (-lazy-l, -lazy_library, -lazy_framework) only from macOS 27, via
LC_LAZY_LOAD_DYLIB_INFO; before, it links the library as usual, warning.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
MH_PIE marks an executable dyld may load at any address; -pie and
-no_pie set it either way, and the last one given wins. As it reads
the options, ld-prime warns about one that turns the other around
("-no_pie overriding previous -pie", and the other way), whatever the
kind of output, though not about a repetition; being given as options
are read, the warning is silenced only by a -w that comes before.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime reads an object's LC_LINKER_OPTION strings in a row, as a
command line of library options. It links what -l, -needed-l, -lazy-l,
-hidden-l, -framework, -needed_framework, -lazy_framework, -force_load,
-needed_library and -lazy_library name, warns about unknown words and
options missing their argument, then about weak, re-exported and upward
forms ("unexpected"), and drops search paths silently, in -r links too.
A library not found is named only if symbols are left undefined.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld64 resolved a re-exported library's @executable_path install name at
link time against the output executable's directory, or the path given
by -executable_path. ld-prime expands no @executable_path: such a
library is looked up by its leaf in the library search path like any
other indirect library, and is otherwise missing. -executable_path
still takes its argument, but is ignored with a warning ("is
obsolete") that -w silences.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime prints "-macosx_version_min has been renamed to
-macos_version_min" bare on stderr for each use of the old spelling,
whatever -w or -fatal_warnings say, and reports on the option under its
new name. The last option naming the deployment target wins; as it
reads one, ld-prime warns about another minimum version for the same
platform and about firmware replacing macOS, and it refuses macOS
replacing firmware ("incompatible platforms") once all are read.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
-sectalign fixes an output section's alignment whatever its atoms ask
for, in -r links too: ld64 takes the largest power of two dividing the
value (1 for 0), warning at the option if that is not the value itself,
and the first -sectalign for a section wins. One below the atoms'
lowers the section, with a warning; atoms keep their offsets in the
section, so a fixup needing more alignment fails. A final image's page
limit on section alignment applies afterwards.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A version 1-3 .tbd gives its architectures in "archs" and its platform
in a key of its own, which TAPI's YAML reader requires, spelled as it
knows it: macosx, ios, tvos, watchos, bridgeos, iosmac or maccatalyst,
zippered or driverkit. ld-prime refuses a file with another value (the
modern names macos, xros or firmware too) or with none as malformed,
pointing at the value or the document. We took macos, and a missing
platform for macOS.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A Mach-O header names its CPU type and subtype, and a universal file
lists a slice per architecture. ld-prime ignores, with a warning, an
object for another architecture than exactly the link's (arm64e in an
arm64 link too; an archive member as well, needed or not), a dylib for
another CPU type (an arm64e one serves arm64), and a universal file
with no slice for the link. We refused an object or a universal file
and loaded a dylib of any architecture, binding symbols to it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A .tbd stub lists its library's targets, architecture-platform pairs.
ld-prime (through TAPI) ignores a stub that has none on the link's
architecture, warning "ignoring file ...: tapi error: missing required
architecture", whether a library named on the command line, one a
library re-exports, or one a -r link or a dyld-less image ignores
anyway, while a target of a platform TAPI doesn't know makes the file
malformed, reported at its line and column. We stopped the link.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime splits a section of fixed-size records (literals, init and
term pointers, CFStrings, Objective-C lists and references) into one
atom per record, so a label at the section's end names nothing: it
warns "ignoring extranenous label" and drops it, and a relocation to it
fails. Each init or term record needs a relocation to name its function.
An __objc_imageinfo is read only if it has 8 bytes (with a warning if
shorter but not empty, or longer); we read past an empty one and crashed.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
At layout ld-prime warns of atoms aligned below 8 that hold pointers
and turns an x86-64 image's chains into classic dyld info. Only after
applying relocations and listing the text relocations (in address order
where it encodes rebase opcodes, else each atom's from its last) does an
unaligned pointer of an arm64 chain fail the link, in place of the text
relocations' error, as no chain holds a read-only segment's pointer; the
classic info's warnings come only if nothing failed. We did both first.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An arm64 LDR or STR scales its 12-bit page offset by its access size, so
a PAGEOFF12 can only reach a target that size divides. ld-prime reports
one that doesn't as "fixup error (kind=...) at 'atom'+0xOFF from obj,
target 'name' not N-byte aligned, which is required by LDR/STR
instruction": arm64_lo12 names the symbol, arm64_was_ld12_tlv_load_got
the dylib TLV's unnamed __got slot (an ldr q of an 8-mod-16 slot). We
stopped with a fatal error of our own wording instead.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A non-extern relocation names its target by section ordinal and holds
its address. ld-prime's -r output re-targets one on either architecture
at the name of the place at or before the target, an alt entry's too,
with the distance as the addend, and a pc-relative x86-64 field keeps
its distance from the instruction's end. name_at already does so; this
checks it where compilers don't emit such relocations themselves (arm64
data pointers), and alongside x86-64 SIGNED fields past an immediate.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The LC_DYLD_CHAINED_FIXUPS payload is a header, a starts_in_image table
of per-segment offsets, a dyld_chained_starts_in_segment record per
segment with fixups, the import table and the name pool. ld-prime puts
each segment record on an 8-byte boundary but declares its size as just
its 22 bytes of fields plus a u16 per page, and aligns the imports only
as their entries need: 4 bytes, or 8 for 64-bit addends. We padded each
record and the imports to 8, so the payload could grow by 8 bytes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The runtime attaches each category_t in __objc_catlist to its class at
load. ld-prime merges the categories on a class another image defines
into the first one listed: that record stays, pointing at new lists of
each kind another category has (methods and protocols last category
first, named __OBJC_$_INSTANCE_METHODS_NSView(A|B)); the others go. A
class with a +load category keeps them all, as each +load must run.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Below macOS 15 the Objective-C references stay in writable __DATA, which
ld-prime lays out in a fixed order ahead of the program's own data:
__objc_const, __objc_selrefs, __objc_protorefs, __objc_classrefs,
__objc_superrefs, __objc_ivar, __objc_data. Protocol references had no
rank, so they sorted with the unknown sections after __data; a program
using @protocol() laid __DATA out differently from ld-prime's.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Cstring and fixed-size literal sections are split into one atom per
string or record, and identical atoms coalesce - but ld-prime only
coalesces atoms of the same section: "Foo" in __objc_classname and
"Foo" in __cstring stay two strings, as do identical records of
__literal8 and another 8-byte-literal section. Keying on content alone
moved ObjC class and selector names into __cstring. The shard lookup
now reuses the content hash rather than rehashing the bytes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A final image names no entry of the Objective-C list and reference
sections by a local label, but keeps a private external there: clang's
hidden __OBJC_LABEL_PROTOCOL_$_P in __objc_protolist and
__OBJC_PROTOCOL_REFERENCE_$_P in __objc_protorefs. An ld -r output
demotes private externals to locals that keep N_PEXT, and ld-prime
keeps those too; NetNewsWire's prelinked PLCrashReporter lost two.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A class_ro_t names the protocols its class adopts (baseProtocols). A
clang class and its metaclass share one protocol list, but Swift gives
the metaclass ro a copy of its own. When categories that adopt
protocols merge into the class, both ro records are rewritten to point
at the merged list, so ld-prime drops both superseded lists; keeping
the metaclass's left an orphaned list and its __PROTOCOLS_ symbol
behind (NetNewsWire's __objc_const was 0x58 bytes larger).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime makes the atoms of CFStrings, selector and class references,
UTF-16 literals and Objective-C constant literals (__objc_intobj,
__objc_arraydata, __objc_dictobj ...) by content, so none of their
labels reaches an output's symbol table. A -r output names such atoms
itself only where arm64 relocations need a symbol (l<nnn>, N_PEXT); on
x86-64 only __TEXT,__cstring literals get names (LC<n>), the others and
l-labeled class references are referred to section-relatively.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A Swift class whose superclass comes from a resilient module has no
static class_t; the runtime reaches it through a class stub, and the
categories of such a class go in __objc_catlist2 (Swift puts their
method lists in __objc_data). ld-prime rewrites those lists in relative
form like any other, giving selectors no code references a slot in the
__objc_selrefs tail. It places __objc_catlist2 right after
__objc_catlist and drops its entries' label, as for the other lists.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime takes __objc_classrefs one 8-byte class pointer at a time,
whatever the labels (an x86-64 -r output has none), and coalesces them
per class at any deployment target. From macOS 15 on it rewrites the
references to the coalesced slots into GOT references; what folds is
what is referenced, class or not, so a slot nothing refers to stays in
__objc_classrefs and its class gets no GOT entry. -dead_strip removes
such a slot although clang marks __objc_classrefs no-dead-strip.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime -r points a __compact_unwind function or LSDA field, and an
x86-64 section-relative relocation, at a symbol where one names the
address. Of several names at a place it takes the first: non-weak, then
global, private extern and local, by descending name, ltmpN last. Past
the place it names the first plus the offset, but in an object without
subsections, away from a section's start, each name is an atom, empty
but the last, which it names. We picked by hash order.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An arm64 b/bl reaches +-128 MiB, so a larger __text gets branch islands
that relay out-of-range branches. ld-prime lists each island as a local
N_SECT symbol in the branch's section, named after its target (a stub's
import too): "<target>.island" for the target's first, then
"<target>.island2" and so on, in address order. Like other locals they
go with -x and obey the -non_global_symbols lists; they have function
starts but no stabs. Each thunk entry of ours is now named that way.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Without MH_SUBSECTIONS_VIA_SYMBOLS an object's symbols are labels, not
atom boundaries, so ld64 models it as one huge atom: every atom of a
section it cuts at symbols is dontDeadStrip, a dead-strip root. So in
ld-prime 27037 a loaded object's unreferenced __text, __const, __data or
__bss survives, but what it cuts by content (literals, literal pointers,
CFStrings, __thread_vars descriptors) is stripped as usual. -why_live
now says why an atom asked about is a root: initial-undef and so on.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A __compact_unwind record's function, personality and LSDA fields are
8 bytes, set by UNSIGNED relocations; x86-64 objects may set one with a
4-byte relocation (r_length 2) instead, leaving the high half alone.
ld-prime's -r output keeps each such relocation 4 bytes, as it keeps
any fixup's size. mold wrote every field's relocation 8 bytes; it now
records the 4-byte fields of each object's records and keeps them.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A CIE names its personality routine through a 4-byte pcrel GOT
reference (X86_64_RELOC_GOT, ARM64_RELOC_POINTER_TO_GOT) whose cell
the next link overwrites. An x86-64 cell elsewhere holds its addend,
which ld-prime's -r output keeps, but in __eh_frame ld-prime writes 4,
what a compiler's own CIE holds, whatever the object had there; mold
kept the object's value, and a comment said ld-prime does too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A compact unwind encoding's UNWIND_HAS_LSDA bit tells the unwinder to
look the function up in __unwind_info's LSDA index, a table of
(function, LSDA) image offsets sorted by function. ld-prime adds a
__compact_unwind record's LSDA pointer to that index only if the
record's encoding has the bit; without it the pointer still keeps the
record's entry apart from a neighbor's of the same encoding, but the
LSDA goes unlisted. mold listed it whatever the encoding said.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime writes __TEXT,__unwind_info whenever the image has unwind
info, an FDE included, even when the only FDE describes a function in a
section that is not code, which gets no entry: the table then lists
just the code atoms, each with encoding 0 (no unwind info), so that the
unwinder finds every function of the image in it. mold wrote the table
only for compact unwind records, or FDEs of code, and so left it out.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime warns about a __compact_unwind record for a function in a
section that is not code, yet still gives it an __unwind_info entry at
its address, say in __DATA. __unwind_info is encoded as __TEXT is laid
out, before later segments have addresses, and mold re-encoded it only
for code or LSDAs outside __TEXT, so such an entry got a bogus offset.
It now re-encodes for any record's function there; LC_MAIN, sized while
the re-layout that may follow runs, no longer overflows its offset.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A Mach-O image's unwinder looks a function up in __TEXT,__unwind_info
first and falls back to an FDE in __eh_frame only for a function that
table sends there. -no_compact_unwind, which GCC's driver passes on
every link, leaves __unwind_info out: ld-prime then keeps every FDE, even
those of functions a __compact_unwind record covers, and drops the
records without turning them into FDEs, as for -static. -r ignores it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
-stack_addr moves the top of a static executable's __UNIXSTACK from
ld64's fixed one, so the segment lies at the address minus -stack_size
and the thread's stack pointer starts at it. ld-prime wants it a page
multiple (4 KiB in an object file) with a size to go with it, before
it checks the size; dyld places a dynamic executable's stack itself
("can't be used with modern executables"), the size must be below the
address, and a zero address has no effect, with a warning.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An arm64 ADRP reaches 4 GiB of pages either way and a B/BL 128 MiB,
an x86-64 rip-relative field 2 GiB; mold wrapped a farther target into
a wrong displacement silently. ld-prime reports it as a fixup error
named after what it makes of the reference (arm64_adrp_lo12, arm64_b26,
x86_64_rip1, ..._got_load_load_got), with the displacement and the
addresses from and to, naming a GOT slot ''. A stub is anon-2 (anon-6
with lazy binding) of its stubs-got-file, and a thunk too far is none.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime's map numbers every input file in load order: command line
files (dylibs too), then by install name the re-exported libraries
something binds to, including libSystem's private libsystem_c and kin,
credited with the symbols they define, then auto-linked dylibs. It
lists linker-made atoms (stubs, GOT slots, lazy pointers as sym.stub,
.got, .lazy_ptr of the defining file; "anon" helpers), literals by
content, unnamed atoms "anon", and dead atoms after a blank line.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
XNU's x86-64 kernel links with both. -seg_page_size SEG SIZE starts the
segment after SEG on a SIZE boundary counted from SEG's start, in memory
and in the file, while SEG keeps its page-rounded size (__LINKEDIT takes
the boundary as its size); SIZE rounds down to a power of two and may
not be below a page. -no_zero_fill_sections gives zero-fill sections
file bytes as regular sections, a thread-local one as
S_THREAD_LOCAL_REGULAR (ld-prime's S_REGULAR breaks TLVs).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
dyld reads a main thread's stack size from LC_MAIN, but a static
executable starts from LC_UNIXTHREAD, so ld64 reserves its stack as an
rw- segment of address space alone, __UNIXSTACK, last before __LINKEDIT
and pinned like a -segaddr below a fixed top (0x120000000;
0x7fff5c000000 on x86-64 macOS) where the thread's stack pointer starts.
ld-prime checks the size against 512MB (1TB on x86-64 macOS), then the
output kind and the page size; zero has no effect.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime rounds an -image_base up to the segment alignment, warning
"base address 0x... is not properly aligned. Changing it to 0x...",
before it does anything else with the base: 4 KiB in a -r link, whose
object is loaded nowhere, and the page otherwise (still 4 KiB for
-preload). The rounded base is what must match a -segaddr __TEXT, and
the warning comes even when a PIE executable or chained fixups then
make the base moot. mold rounded only a base it went on to use.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
LC_BUILD_VERSION packs an OS version X.Y.Z into 16.8.8 bits, and
ld-prime refuses one that does not fit, or that is not dot-separated
decimal numbers, naming the option in its complaint. It names a
platform in any case or by a number up to 30, and calls any other name
unknown; mold now does too, and only a platform ld-prime knows is
unsupported. A dylib's LC_ID_DYLIB versions share the packing but are
truncated with a warning instead, each number clamped, the rest dropped.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An image no dyld loads has nothing to load a dylib with: ld-prime makes
-l in a -static executable, a -preload image or a kext look for
lib<name>.a only, and ignores a dylib named outright with "ignoring
unexpected dylib '<real path>'". A relocatable object searches as a
dylib-linking image does, since -static never applies to it, and drops
every dylib it finds with the same warning. mold searched dylibs for a
kext and loaded them, and warned about -r in ld64's old words.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld64 keeps one output kind that -execute, -dylib, -bundle, -r, -preload
and -kext each overwrite, so the last wins: -r -dylib links a dylib and
-dylib -r an object. -static turns any kind but an object or a kext into
a static executable (no dyld, LC_UNIXTHREAD), which a later -execute
keeps and a later -dylib replaces; -kernel needs that kind, not
-preload. ld-prime agrees. mold kept independent flags, so -r always
won, -static -dylib made a static dylib and -execute was unknown.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Clang's __LD,__compact_unwind holds unwind records for the linker to
encode into __unwind_info, and carries S_ATTR_DEBUG so that no output
copies it. ld-prime's table of standard sections has the name, so a
section only a section$start$ or section$end$ symbol makes there - an
empty one in an __LD segment of its own - gets the debug attribute; a
boundary-made __DWARF section gets no flags, as the table lacks those.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A section of type S_CSTRING_LITERALS is split into its NUL-terminated
strings, which the linker merges by content across inputs. ld-prime
takes bytes after the last NUL for one more string, completed with the
NUL it lacks, in a final image and a -r output alike, and merges it
with no other string - not even an identical unterminated one or the
terminated copy. We refused such a section as malformed; the string is
now a leaked copy with the NUL, kept out of merging.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The linker consumes the __DWARF segment (debuggers read DWARF from the
objects that debug-note stabs name) and the __LD segment (compact
unwind records), and copies neither into any output. ld-prime tells
them by segment alone: a __DWARF or __LD section without S_ATTR_DEBUG
is dropped all the same, and one with S_ATTR_DEBUG in another segment
is copied like any other - into a final image without the attribute,
which only marks debug information for tools, into a -r output with it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Old compilers put weak code and data in sections of their own, which
ld-prime renames in final images and -r outputs: __TEXT,__textcoal_nt
to __text, __const_coal (in __TEXT, __DATA or __DATA_CONST) to __const,
__datacoal_nt (in __DATA or __DATA_DIRTY) to __data. The rename takes
the place of -rename_section (-rename_segment still applies), boundary
symbols follow it, and flags and the __DATA_CONST move follow the old
name; only ld-prime's own __text, under its new name, is code by fiat.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime takes an input section of a standard name for that standard
section only if it has the type its table gives the name, except for
the Objective-C runtime's sections (and __got), which it knows by name
alone: a regular __objc_methname gets S_CSTRING_LITERALS, a regular
__objc_protolist S_COALESCED. Only a section so taken moves to
__DATA_CONST or merges into __text or __const in a final image: a
regular __mod_init_func or __literal8, or a coalesced __DATA,__const,
stays put, as does a section -rename_section names __literal8.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A -r output section, like a final image's, takes its flags from its
first member, and ld-prime makes that member of any input section it
doesn't ignore: an empty section counts if a symbol names an atom
there - a label, or an arm64 assembler's ltmpN in an object without
.subsections_via_symbols. The sections it ignores (see bare_sections)
are dead from the start and never members, so skipping empty ones as
well let the next member's flags win where ld-prime's didn't.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime gives an output section the flags of its first member, final
image or -r output alike, so code after data in a section doesn't make
it code. Those flags come from its table of standard sections when the
member's name is there with the member's type: a __TEXT,__const or
__DATA,__data an assembler marked as code is data, a regular
__stub_helper code, a regular __cstring (no literals) keeps its own.
Being ld-prime's own, a final image's __TEXT,__text is always code.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
LC_MAIN's entryoff is the entry point's offset from __TEXT's start
(__TEXT maps from file offset 0). The layout sizes the load commands
before the entry point is resolved, and lays the segments out again
when __unwind_info - which covers code outside __TEXT, such as a
section of pure instructions in __DATA - grows on its second encoding;
__TEXT then already has its address while the entry point is still 0,
and the subtraction underflowed. Only the command's size matters then.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The dyld shared cache builder refuses a library with interposing
tuples, so ld-prime refuses them in any image bound for the shared
region - a dylib installed where the cache takes libraries from, an
-add_split_seg_info image, an arm64 kext. It finds them as dyld does:
an output section named __interpose, by its final name after the
__DATA_CONST move and renames, in a segment whose name starts with
__DATA or __AUTH. An empty one counts, -sectcreate's too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
From macOS 15 on ld-prime puts an image's interposing tuples in
__DATA_CONST, but not as one of its up-front moves (__DATA,__const's,
which -rename_section then matches as __DATA_CONST,__const): the move
happens where -rename_section applies, as the default when none names
__DATA,__interpose. A -rename_section of __DATA,__interpose thus keeps
the section out of __DATA_CONST, one of __DATA_CONST,__interpose never
matches, -rename_segment moves the section on, and a -sectcreate or
boundary-made section of that name moves as an input section does.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An x86-64 BRANCH record of length 0 patches the rel8 of a short jmp,
which reaches only 127 bytes either way and never a stub, so ld-prime
refuses one to a dylib's symbol and one out of range. Both are fixup
errors of kind x86_64_branch8 naming the atom, the offset of the byte
and the object's leaf name: "target 'sym' does not have address", and
"8-bit branch out of range (displacement=N, max is +/-127), from 0xP
to 0xT ('sym')" with P the byte's address.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A non-extern relocation names its target section by a 1-based ordinal
in r_symbolnum and holds the target's address (an UNSIGNED's field, or
a pcrel field plus the address after it). ld-prime binds it to that
section whatever the address says: one past its end is its last atom's,
and one before its start or beyond its end its first or last atom's at
a negative or overlong offset, with "address=0xADDR points before
section(N) start (or beyond section(N) end) and the target atom is
ambiguous". mold took the section holding the address instead.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A __compact_unwind record is 32 bytes: the function at offset 0, the
length and encoding at 8 and 12, the personality at 16 and the LSDA at
24. ld-prime reads a pointer field through a 4-byte UNSIGNED as well as
an 8-byte one (only x86-64 has 4-byte pointers), in -r and final links
alike. A relocation on any other offset in the record is an error:
"compact unwind fixup at offset of N but expected 16 or 24 in 'path'",
N counted from the record's start.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A pointer that dyld must slide or bind in a segment whose initprot
lacks write (__TEXT, or one -segprot makes read-only) is a text
relocation. ld-prime lists each under "Illegal text-relocations:", a
header per output section and each atom's from last to first, as
"text-relocation in 'atom'+0xOFF (path) to 'target'" (an unnamed atom
is anon-N, its object's Nth), then fails. It allows them by default in
an x86-64 kext or non-PIE executable; -read_only_relocs warning or
suppress allows them in firmware, -static and x86-64 kexts, and
elsewhere the option is ignored with a warning.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A lone 4-byte X86_64_RELOC_UNSIGNED (.long sym) asks for a 32-bit
absolute address, which dyld can neither slide (rebases are 8 bytes)
nor bind. ld-prime rejects one in an executable, dylib, bundle or kext
whatever its target, as "32-bit pointer used in 64-bit code in
'atom'+0xOFF (path)". Only a -static or -preload image, which nothing
relocates, may hold one, and there the address must fit: else "fixup
error (kind=ptr32) ... 32-bit pointer oveflow" (sic).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ARM64_RELOC_GOT_LOAD_PAGEOFF12 marks the ldr that reads a symbol's GOT
slot. For a symbol defined in the image ld-prime drops the slot and
rewrites the ldr into "add Xd, Xn, #pageoff"; an "add Xd" is taken as
that add already. Any other instruction is a "fixup error ... non-LDR
instruction": kind arm64_was_adrp_ldr_got_elide_got at the adrp when a
GOT adrp of the same symbol sets the load's base register, else
arm64_was_ld12_got_elide_got at the instruction. A TLVP load relaxes an
ldr of either width (arm64_was_ld12_tlv_elide_got otherwise).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
X86_64_RELOC_GOT_LOAD and TLV mark a RIP-relative load of a symbol's
GOT slot or TLV descriptor pointer. For a symbol defined in the image
ld-prime drops the slot: a movq (opcode 0x8b two bytes before the
field) becomes a leaq (0x8d) of the symbol and a leaq is taken as one
already; any other instruction is a "fixup error (kind=x86_64_was_rip_
got_load_elide_got)" naming the atom and the object's leaf name. mold
kept the slot for all but movq, so such a leaq got the slot's address.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A -r output names each selector reference and CFString constant as
ld64 does, with an l<n> local of its own (a record, not a symbol, so
the name has no SymbolId). An input's symbol at such a record makes
that name an alias, and deciding whether an alias in an initializer
list drops N_NO_DEAD_STRIP read the name's first symbol, which it
doesn't have: an index out of bounds. ld-prime drops such a symbol.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A .tbd file lists the targets, architecture-platform pairs, its library
is for; mold read the macOS one only. It now reads the link's, as
ld-prime does. A firmware link takes a library for any other platform
with a warning naming the stub's platforms in platform order (the SDK's
"macOS macCatalyst zippered(macOS/Catalyst)"); a macOS link refuses one
without a macOS target, as it refuses such a binary dylib.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
macOS before 10.12 checks a signature's SHA-1 page hashes only. So
ld-prime's ad-hoc signature of an x86-64 image for such a release or
for firmware, and of any -static image, holds two code directories: a
SHA-1 one in the CSSLOT_CODEDIRECTORY slot and the SHA-256 one, the one
newer loaders choose, as the first alternate (0x1000), packed one after
the other, the same fields but the hash type and size.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A fixup chain's links are 8-byte words, so ld-prime checks the pointers
dyld fixes up when the target defaults to chained fixups (or with
-fixup_chains): it warns of each atom aligned less than a pointer that
holds one, then of each unaligned pointer under classic dyld info. An
arm64 link fails at the first; an x86-64 image falls back to classic
dyld info, laid out as for chains. It also warns of a __DATA,__cfstring
not aligned to a pointer, in every object it reads.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime gives each atom of a fixed-size record section an alignment
with no modulus: a record lands on a multiple of it whatever its input
offset. Most keep their section's alignment (a 2^4 __objc_selrefs stays
2^4, also for the slot an _objc_msgSend$ stub takes over); literals get
their size, and initializer, terminator and pointer-auth slots and
CFStrings a pointer's. A thread-local descriptor gets a pointer's in an
image but at least one in -r, where the section's own counts too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime splits a section of fixed-size records into an atom per
record: literals, initializer, terminator and GOT pointers and
thread-local descriptors by the section's type, and __DATA's GOT,
Objective-C lists and (when regular) CFStrings and pointer-auth slots
and __LD,__compact_unwind by name. A size that is no multiple of the
record's fails the object, in -r and for an unused archive member too;
mold linked it, and a 9-byte __mod_init_func crashed an arm64 link.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A 32-bit object carried its GOT as S_NON_LAZY_SYMBOL_POINTERS and its
lazy pointers as S_LAZY_SYMBOL_POINTERS, each slot filled with the
symbol the indirect symbol table names for it. ld-prime refuses every
non-lazy pointer section of an object that has indirect symbols at all
(an empty one, or one the table names no slot of, too), after it has
split the object's sections, and a non-empty __DATA,__la_symbol_ptr of
the lazy type is a fixed-size section it has no record size for.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A rebase tells dyld to add the slide to a pointer when an image loads
elsewhere than its segments' addresses. A non-PIE executable (x86-64
-no_pie) has no MH_PIE flag, so the kernel maps it exactly where its
segments say and nothing ever slides it: ld-prime writes it an empty
rebase stream, or chains holding only binds, and every pointer keeps
its target's address. mold rebased its lazy pointers, initializers and
data pointers as if it could move.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A common symbol (N_UNDF | N_EXT with a nonzero n_value, C's tentative
definition) states its size in n_value and its alignment, as a power of
two, in bits 8-11 of n_desc; .comm without an alignment leaves those 0.
ld64 then aligns the symbol to its size rounded up to a power of two,
limited to 2^15, on every target; a __common section aligned beyond the
page is then reduced to it with a warning. mold capped the alignment at
16 bytes on x86-64 (and at the page on arm64, without the warning).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A zero-fill section (S_ZEROFILL, S_GB_ZEROFILL, S_THREAD_LOCAL_ZEROFILL)
takes address space but no file bytes, so its header's offset is 0, and
ld64 places those sections last in their segment, after the file-backed
ones. A segment made only of them (__DATA with just __bss and __common,
or a custom .zerofill segment) has filesize 0, and ld-prime records its
fileoff as 0 too, in any kind of image; a segment of empty regular
sections keeps the running offset. mold wrote the running offset.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
__PAGEZERO is a no-access segment that a 64-bit main executable maps
over the low 4 GiB so that NULL and truncated pointers fault. A -kernel
image is an MH_EXECUTE too, but ld-prime makes it position independent
for the kernel collection to slide and lays out its __TEXT from address
0 with no __PAGEZERO, PIE or -no_pie, unless -pagezero_size asks for
one. mold kept the 4 GiB default unless -pagezero_size 0 was given.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Every dylib_command carries a timestamp. It once held the dylib's build
time, and a prebound client recorded the time of the dylib it was bound
against so dyld could tell whether the prebinding was still valid. That
is long gone and linkers now write constants for reproducible output:
ld-prime writes 1 in a dylib's own LC_ID_DYLIB and 2 in the
LC_LOAD_*_DYLIB and LC_REEXPORT_DYLIB commands of its clients. mold
wrote 0 in LC_ID_DYLIB.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
__DATA_CONST holds the pointers that are constant once dyld has fixed
them up (GOT slots, const data with relocations), and dyld makes it
read-only after fixups. ld-prime never gives it to a non-PIE executable
and always to an image for the shared region or firmware; otherwise a
macOS image gets it from 10.15 (ld64's version2019Fall) on, except for
10.15.4 up to 10.16 and with -no_pie, which counts here even where it
is otherwise ignored (arm64, dylibs). mold ignored version and -no_pie.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Objects record their target OS in LC_BUILD_VERSION or LC_VERSION_MIN_*.
Without -platform_version, ld-prime links for the first object file on
the command line that has one (not archive members, universal files or
dylibs; a bitcode file's triple only if no Mach-O object has one), takes
an object with none for macOS with a warning, and fails a final link
with nothing to go by (or with no inputs at all). mold assumed macOS 0.0
for the defaults decided by version; parse_args now infers the target.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
In debug builds, ICF checks each folded subsection byte for byte
against its class's leader, relocation targets included. Two members
may call different candidates that were themselves folded together -
one C++ template instance calling another - so the check compared
candidate indices and panicked on a correct fold. It now compares the
classes those candidates belong to.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A subsection finds its unwind records as one run of the records array
(unwind_offset, nunwind), which the loader filled in the order of
__compact_unwind followed by the FDE-only functions'. A section without
subsections holds many functions, and an alt entry may have a record
of its own, so a subsection's records could lie apart: the batch loader
kept only the last run and the LTO path swallowed others'. -dead_strip
then lost an LSDA and pointed its __unwind_info row at the mach header.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
In a -r output ld-prime carries each __LD,__compact_unwind record as
an atom aligned as the section it came from, so the output section's
alignment is the largest of the surviving records' input sections'
(a coalesced-away weak copy's record doesn't count): 2^0 in gives 2^0
out, 2^4 gives 2^4. mold always wrote 2^3. Above 2^5 ld-prime also
pads between the 32-byte records, leaving zeroed records that name no
function; mold packs them.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An __unwind_info entry has no length: it covers the code up to the
next, so consecutive functions of one encoding may share an entry, and
ld-prime merges them, except for encoding 0 (no unwind info), which
every atom of an output section of pure instructions without a record
gets, one per atom; a section the assembler only marked as holding some
instructions gets none. Its common encodings table then ranks the merged
entries' encodings by use, ties by increasing encoding like ld64's
std::map walk. mold merged zeros and broke ties by first use.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A function whose frame compact unwind can't encode gets a DWARF-mode
__unwind_info entry whose low 24 bits are its FDE's offset in
__eh_frame; the FDE's CIE names its personality routine and the FDE
holds its LSDA. ld-prime still sets the entry's personality index and
UNWIND_HAS_LSDA, lists the personality in the personality array and
the function in the LSDA index, as for a compactly encoded function,
though libunwind takes both from the FDE; mold left all three out. It
also numbers personalities in address order, by first use in the table.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
LC_FUNCTION_STARTS, the ULEB128 deltas of function addresses from the
mach header, is how lldb and crash symbolication split stripped code
into functions. ld-prime lists the start of every non-empty atom of an
output section with S_ATTR_PURE_INSTRUCTIONS: __text including the
__StaticInit static initializers merged into it, any other code
section in any segment, unlabeled leading code, every label inside a
section without subsections, and its branch islands; stubs are not
such atoms, nor is a label at a section's end. mold listed only the
symbols of input __TEXT,__text sections, end labels included.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The relocation scan skips a relocation to an undefined symbol when that
is an error, because the link fails anyway. --noinhibit-exec reports the
error as a warning and writes the output, so relocations that needed a
GOT, PLT or TLS entry were applied without one, and mold panicked on the
missing index. Such relocations are now scanned as they are with
--warn-unresolved-symbols, so the symbol gets the entries it needs and
resolves to 0, as it does in GNU ld and lld.
Every target crashed on a -fPIC object reading an undefined variable,
an undefined TLS variable, or both.
GNU as emits R_RISCV_ALIGN and R_LARCH_ALIGN for an alignment directive
in an executable NOBITS section. With -r, RelocSection sliced the output
section's bytes at each relocation before calling write_addend, but a
NOBITS section has no bytes in the file, so the slice was out of range.
Since only REL targets and SH4 keep addends in section contents, take
the slice only on those targets.
GAS and Clang emit R_RISCV_64 and R_LARCH_64 for `.quad sym` even in
ELF32 objects. On RV32 and LA32, they are not the word-size absolute
relocation that the output section applies, so they reached
apply_reloc_alloc, which had no case for them and panicked. Handle them
like other non-word-size absolute relocations, as lld does: scan them
with scan_absrel and write S + A as a 64-bit value.
With a plugin that lacks get_symbols_v3, mold re-executes itself with
the same command line. The new process applies -C again, but it was
started in the directory the first one had already changed to, so a
relative -C failed with "chdir failed". Remember the directory before
-C and return to it before re-executing.
Input file names already have --chroot applied. Relative thin archive
members and relative names in linker scripts are resolved against such
a name, but the result was opened with --chroot applied again, so the
link failed. Open these paths as they are. Also write the --repro
archive next to the output in the chroot rather than outside of it.
--repro reopened each input by its recorded name. Response files and
--retain-symbols-file are opened before -C changes the directory, so a
relative name no longer found them and the link failed, and a name with
--chroot applied got the prefix twice. Archive the contents already in
memory instead: no input has been rewritten in place at this point.
An existing output file is reused by writing it in place. When that file
is also a memory-mapped input, as in `mold a.o -o a.o` with an a.o larger
than 32 KiB, its private mapping sees the truncation and the writes while
the input is still being read, so mold crashed with SIGBUS or produced a
broken output. Such a file is now replaced with a fresh one. Inputs are
matched by device and inode, which covers archives and thin members too.
An absolute path in a linker script is prefixed with the sysroot if
the script is in the sysroot. is_in_sysroot returned false for an
empty sysroot, so such a path fell through to the -L search, and an
incompatible file was skipped with a warning. C++ mold treats an empty
sysroot as containing every file and opens the path as is, reporting
an error if it cannot be used. GNU ld and lld don't search -L either.
--dynamic-list and --export-dynamic-symbol-list files were opened by
the driver after parse_args had already changed the directory for -C,
so a relative path was looked up in the new directory. C++ mold reads
them in the option loop, as we already do for --retain-symbols-file.
Open them there too and keep the mapped files in DynamicListSource;
the driver still parses them, since the parser needs a Context.
A panic aborts the process in release builds, skipping the cleanup that
a fatal error or a SIGSEGV/SIGBUS does, so a failed link left its
.<output>.<pid> file behind. Install a panic hook that removes the file
and then calls the default hook, so the panic message, backtrace and
abort stay as they were. The hook runs on the panicking thread, so it
also covers rayon workers and the extern "C" LTO plugin callbacks.
Arguments are passed on as C strings, so a NUL byte in the -plugin path,
a -plugin-opt or the -o name made loading the LTO plugin panic. A
response file is the only way to get a NUL byte into an argument, as
the OS passes arguments as C strings too, so reject one when reading a
response file instead of at each C string conversion.
mimalloc uses 64-bit atomics, which GCC implements as calls into
libatomic on hosts such as 32-bit PowerPC. The mimalloc crate links
libatomic only on ARM, so mold failed to link there, which CI's cargo
check did not catch. Link libatomic when the target lacks 64-bit
atomics, as the C++ version did. It is a link argument rather than a
library because rustc would place the latter before mimalloc, where
--as-needed drops it.
libz-sys declares the length parameter of adler32_combine() as
libc::off_t, which is 32 bits on 32-bit glibc hosts, but Debian and
Ubuntu now compile C code for armhf and powerpc with 64-bit off_t, so
the bundled zlib reads a 64-bit length there. Every zlib-compressed
debug section larger than one 1 MiB shard got a wrong checksum, and
readelf, objcopy and gdb rejected it. Compute the combination in Rust
instead; it is a few lines of modular arithmetic.
A section of type S_NON_LAZY_SYMBOL_POINTERS, as 32-bit code wrote
with .non_lazy_symbol_pointer, holds pointer slots whose targets the
object names not by relocations but by its indirect symbol table
(LC_DYSYMTAB, the slots indexed from the section's reserved1). We
ignored that table and so left the slots null, crashing at run time;
ld-prime rejects the section in any 64-bit link, final or -r, and so
do we now.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A dylib meant for DYLD_INSERT_LIBRARIES lists (replacement, replacee)
pointer pairs in __DATA,__interpose, which dyld reads at launch to
redirect every other image's binds, but never writes. From a macOS 15
deployment target on, ld-prime therefore makes the tuples read-only
after fixups in any image dyld loads (not a -static one or a kext):
the section goes to __DATA_CONST, even with -no_data_const, which then
yields a __DATA_CONST segment holding just the tuples.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A -r output copies each section's type and attributes from its first
input section, so a later link sees what the compiler asked for. Not
so a section of ld-prime's standard table, when the input's type is
the table's: it gets the table's flags, the ones boundary sections
get. The ObjC constant literals and __objc_imageinfo lose the
no_dead_strip clang gives them, and __data or __const the
some_instructions an assembler nop left; __text is always code.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A section only a section$start$ or section$end$ symbol makes has no
input to take flags from, so ld-prime gives it those its table of
standard sections holds for the name. Besides the ones we had, that
table marks the code it generates itself - __objc_clsstubs, the
delay-load and lazy-binding helpers, __resolver_help - as pure
instructions, __oslogstring as C strings and __la_resolver, the
resolver functions' pointers, as lazy symbol pointers.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The assembler marks any section it emitted an instruction into with
S_ATTR_SOME_INSTRUCTIONS, and a compiler's code sections carry
S_ATTR_PURE_INSTRUCTIONS as well. ld-prime's final image keeps the
pair on code only: a section of pure instructions gets both, and any
other - a __DATA or __TEXT,__const section a nop landed in - loses the
mark with the other linker-directing attributes, so it is data to
dyld and to the linker's own passes (range-extension thunks) alike.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A section$start$ or section$end$ symbol names a section as ld-prime
names it in the output. For a pointer section only the linker makes,
that is where it puts its own: __auth_got, __weak_got and
__weak_auth_got in __DATA_CONST, and in the shared region, where dyld
binds everything at load, __la_symbol_ptr and __lazy_load_got too (an
input section of such a name stays in __DATA). From macOS 15 on, class
references fold into __got and __objc_classrefs moves to __DATA_CONST.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
dyld makes __DATA_CONST read-only once it has applied an image's
fixups, so a final link moves there the __DATA sections nothing writes
after that. ld-prime decides by a fixed list of names: besides the ones
we had, __auth_ptr (the signed pointers clang emits for arm64e),
__objc_catlist2 (categories of classes with stubs, no-dead-strip like
the other ObjC lists), __objc_dateobj and __const_cfobj2 - but not
__objc_boolobj, which stays in __DATA. Boundary symbols follow suit.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An option's arguments follow it on the command line, and when they run
out ld-prime names the option and what it needs, in its usage's words:
"-o missing <path>", "-headerpad missing <size>", "-sectalign needs
<segname> <sectname> <align>". It takes all of several operands before
reading any, only warns about a missing -rpath path and ignores a
missing -oso_prefix. mold named the argument before the missing one.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Under -fatal_warnings ld-prime still reports each warning as a warning
and links to the end, output file and all, then fails with "fatal
warning(s) induced error (-fatal_warnings)". A warning -w hides counts
too, except the few ld-prime leaves out under -w altogether (duplicate
libraries, an -e a dylib ignores). mold turned each warning into an
error, stopped at the next checkpoint, and let -w excuse every warning.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Compilers write __eh_frame's pointers as SUBTRACTOR pairs, which the
linker folds into the contents, and a personality as a GOT-relative
reference. ld-prime checks each relocation's offset, type and size, in
that order and in its own words, but takes a SUBTRACTOR's next one for
the other half whatever it is, and adds a lone UNSIGNED's target to the
field. mold refused both, and panicked on a SUBTRACTOR ending the list
or a field past the section's end, where ld-prime crashes or reads on.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A CIE's 'L' augmentation gives how its FDEs encode the LSDA pointer, 'P'
the personality's encoding and pointer. As libunwind does, ld-prime reads
an LSDA only where the FDE's augmentation data is not empty and the
pointer not zero, which GCC writes for a function with none, and takes
only 0x10 and 0x1b; the personality it knows by the GOT-relative
relocation alone, and it refuses any other reference from a CIE. mold
took only 0x9b personalities and every LSDA pointer, failing GCC's C++.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A CIE's 'R' augmentation says how its FDEs encode the function's address
and size (DW_EH_PE_*), DW_EH_PE_absptr without one. clang writes 0x10,
eight bytes relative to the field; GCC 0x1b, four signed bytes, which
ld-prime reads and writes back in kind in a final and a -r link. It
refuses a format other than 8-byte or 4/8-byte signed, or a base other
than none or pc, as it reads the pointer, then any encoding but 0x10 and
0x1b once it has found the function. mold took every FDE for 0x10.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An FDE locates its function, and the CIE's 'L' augmentation its
language-specific data area, by a pointer the CIE's encoding describes;
compilers write it pc-relative, as a SUBTRACTOR pair against a label of
__eh_frame that the linker folds into the value. ld-prime looks the
resulting address up among the object's sections and, where it lands in
none, reports "address=0x... not in any section" with the address it
computed; mold now says the same instead of "FDE with an invalid function".
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Unwind info describes code: ld-prime takes __TEXT,__text and sections
with S_ATTR_PURE_INSTRUCTIONS it does not know for data as code, and
warns, once a section, about any other with compact or DWARF unwind
info. An FDE for a function in a section it knows for data, by type
(strings, literals, zero fill) or name (__DATA,__data, __TEXT,__const),
is an error; one elsewhere stays in __eh_frame but gets no __unwind_info
entry. mold made one, whose __DATA address overflowed the encoder.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A relocation record packs r_address, r_symbolnum, r_pcrel, r_length,
r_extern and r_type; ld-prime checks each as it reads an object (in -r,
unused archive members and __compact_unwind too), stopping at the first
bad one. The type fixes the other fields: on arm64 only an 8-byte
UNSIGNED may be section-relative, a 4-byte one must pair with a
SUBTRACTOR, and b/bl, adrp, add/ldr/str need a zero immediate unless an
ADDEND precedes them. A field must stay within its atom.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A relative method list names each method through a selector reference
(a __objc_selrefs slot) instead of pointing at the selector string, so
converting a classic list adds a slot for any selector that has none
yet. A list whose entry names something other than a whole selector
string stays classic, but the slots made for its earlier entries were
left behind, unused. The names are now all checked first. (ld-prime
converts such a list, dropping the name's offset - renaming a method.)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
clang links for arm64-apple-firmware with -target, as in -target
arm64-apple-firmware1.0.0, in place of -arch and -platform_version.
ld-prime takes the triple's architecture, platform and minimum version
over both options, given before or after it, and records no SDK
version; firmware may leave the version out but macOS may not, and a
triple of fewer than three parts, of an OS it doesn't know or with an
environment (clang's x86-64 firmware is a -simulator one) is an error.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
PLATFORM_FIRMWARE (13) marks code for no OS. ld-prime links it like any
image - one dyld loads unless -static or -preload says otherwise - but
takes it as newer than any OS release (chained fixups, init offsets at
1.0), signs nothing by default, leaves 128 bytes after the load commands
and writes LC_BUILD_VERSION, -r output too, only for -version_load_command.
It takes objects of any platform, warns of a foreign dylib, and allows
-segment_order, -section_order and -read_only_relocs (macOS: a warning).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
-section_order SEG A:B:C lays out a segment of an image no dyld loads
(-static or -preload): ld-prime puts the listed sections first, in the
list's order but behind __text unless the list names it, and the rest
after them as usual, skipping names nothing defines. A zero-fill section
has no file bytes and must come after every section with contents, so a
list that places one ahead of such a section is an error; so is a
segment listed twice, and the option in a link for dyld.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An MH_PRELOAD image is firmware: a loader copies its segments into ROM
and starts it at LC_UNIXTHREAD's pc, so the mach header, load commands
and symbol table lie in no segment. ld-prime writes the header alone in
the first 4 KiB pages, the segments from address 0 on 4 KiB pages (arm64
too), then the symbol table - no __PAGEZERO, __LINKEDIT, source version,
code tables, signature or __mh_execute_header - and otherwise links it
as -static, whose entry is "start" and whose sections may exceed a page.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime lists every atom of an instruction section in __unwind_info,
an empty one too: the empty __text of an arm64 assembler object of
only data, labeled ltmp0 and one atom without .subsections_via_symbols,
gets an encoding-0 entry at its address - at the end of the code, or
ahead of the function that starts there, in input order. mold skipped
atoms of size 0, so its table was an entry (8 bytes) short.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
With -text_exec (implied for an arm64 kext) ld-prime puts all code in
__TEXT_EXEC,__text: every input section with the pure-instructions
attribute, in any segment (__TEXT,__mycode and __DATA,__dcode alike),
and before -rename_section, which so cannot catch one by its input
name. A section with only some instructions stays put. mold moved just
__text and __StaticInit by name, leaving other code in __TEXT (r--).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
When a rename merges __common with file-backed sections, ld-prime
counts each common symbol's subsection as the tentative definition of
the object declaring the largest size, after that object's own
sections. It names that object in the conflicting-zerofill warning,
and when the object comes first, the merged section is zero-fill (the
others' contents are dropped). mold skipped its synthesized commons.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
segment$start$X and segment$end$X resolve to segment X's bounds. When
the image has no X - no input has one, or -rename_section emptied it -
ld-prime adds X as an empty segment: an LC_SEGMENT_64 with no sections,
vmsize and filesize 0, read-write, at __LINKEDIT's address and offset
just before it (several in their symbols' name order), so both
symbols have that address. mold failed with "no segment for boundary
symbol".
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A section$start$ symbol naming a section the image lacks makes an
empty one. ld-prime gives it the type and attributes a compiler marks
a section of the name in the symbol with - pure instructions for
__TEXT,__StaticInit, S_8BYTE_LITERALS for __literal8, non-lazy pointers
for __DATA,__got (moved to __DATA_CONST), no-dead-strip for the ObjC
lists, zero-fill for __bss - and none for other names; mold gave none.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A section header holds a 16-byte segment name and a 16-byte section
name. ld-prime cuts longer -sectcreate names to fit, warning
"-sectcreate segment name too long ('X'), will be truncated to 'Y'"
(and the same for the section name); mold panicked copying them into
the header. -add_empty_section's names are cut silently: ld-prime
fails an assertion on them, so there is no behavior to match.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
-text_exec gives code a segment of its own: __text and the stubs move
into __TEXT_EXEC, mapped r-x, and __TEXT keeps the headers, strings and
unwind info read-only, so no page of the image is both readable data
and executable. Kernels and kexts are laid out so (an arm64 kext
implies it); dyld refuses a macOS executable built this way. ld-prime
takes the option for any output, and mold's layout matches its.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
__unwind_info lists every function by its offset from the mach header,
and its size (pages split at 2^24-byte gaps, entries merged by
neighbor) depends on those offsets, so it is encoded as __TEXT is laid
out. A -rename_section or -rename_segment can move the code, and its
LSDAs, to a segment placed only later; mold then read addresses it had
not assigned and panicked. It now re-encodes the section once every
segment is placed, redoing the layout if the section needs more room.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A -static -pie image (a kernel) slides itself by its local relocations,
whose r_address counts from the first segment (on x86-64 the first
writable one) and is signed. XNU pins __HIB below the kernel with
-segaddr, so its pointers get negative addresses; mold subtracted
without wrapping and panicked. ld-prime writes the wrapped value, and
fails with "atom address cannot fit in a fixup" when a pointer is out
of the signed 32-bit range, on x86-64 as well as on arm64.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Segments normally follow one another from the image base, __TEXT's
address, which -segaddr __TEXT sets as -image_base does (and with it
__mh_execute_header). ld-prime puts a pinned segment at its -segaddr,
the ones -segment_order lists after it right behind it, and the rest
from the base, each at the lowest address clear of those placed before
it; pins count only from the first one on, so an earlier segment can
run into one. An image dyld slides must keep its segments ascending.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
__PAGEZERO is the no-access segment at address 0 that turns NULL
dereferences into faults, and __TEXT starts where it ends unless
-image_base says otherwise. Segments start on page boundaries, so
ld-prime rounds an unaligned -pagezero_size up to the page size (16 KiB
on arm64, 4 KiB on x86-64) with a warning, in static and dynamic links
alike; mold used the value raw and put arm64 __TEXT at 0x1000. An
executable with chained fixups also gets at most a 4 GiB __PAGEZERO.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime folds identical auto-hidden functions only when told to: by
an -O level above -O0, which clang passes on from its own command line,
or by -deduplicate, and -no_deduplicate turns it off again; the last of
the two options wins. mold ignores -O and folds by default, so
-deduplicate asks for what is already on, but it still undoes an
earlier -no_deduplicate. mold rejected it as an unknown option.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime checks -segprot's protection letters and its segment, and
notes an obsolete option, as it reads each option, so these warnings
come in command-line order, before those of the checks made once the
whole command line is read, and only a -w earlier on the command line
silences them. mold gave them after parsing, silenced by a -w anywhere;
they now go through the option-time warnings -alias_list's took, while
-no_pie's are still given, and silenced, after parsing as in ld-prime.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A lazily bound stub (the default before macOS 12 and without chained
fixups) enters dyld through the stub helper, which calls libSystem's
dyld_stub_binder, so mold binds that symbol while scanning relocations,
before dead_strip_dylibs decides which dylibs stay. An image loading no
dylib at all, such as one relying on -undefined dynamic_lookup alone,
then failed with mold's own dyld_stub_binder error; ld-prime reports it
as "dynamic executables or dylibs must link with libSystem.dylib".
Co-Authored-By: Claude Opus 5.5 <[email protected]>
-segprot takes a segment name and two protections, each a string of
the letters r, w and x ('-' for none), and ld-prime refuses an empty
or missing one of the three with "-segprot missing <seg> <max-prot>
<init-prot>"; mold took an empty protection for none. ld-prime reads
the protections as bytes, warning about each one that is no letter,
so a non-ASCII letter draws a warning per byte, and a protection need
not be UTF-8; mold warned per character and refused non-UTF-8.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime reads an -alias_list file as it reads the option and, when the
file can't be opened, warns in its order-file words and links on, so
only a -w earlier on the command line silences the warning, while
-fatal_warnings anywhere fails the link. mold warned while parsing
options, which it may do twice: parse_args runs for a speculated target
first and again for the actual one. Such warnings now wait until the
parse is known to be for the target, and a later -w leaves them alone.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
__eh_frame is a run of records, each a 4-byte length and a 4-byte ID
that is zero for a CIE and, for an FDE, the distance back from the ID
to its CIE. ld-prime checks that each record fits in the section ("CFI
at 0x... extends beyond end of section", also for a 64-bit extended
length), that an FDE's CIE is not before the section ("FDE points to
CIE outside __eh_frame section"), and, taking whatever is there for a
CIE, its length ("empty CIE"), ID and version; mold now does the same,
also checking that a record holds its fields where ld-prime reads on.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The x86_64 assembler leaves a difference of two labels to the linker
when a named label separates them, as a SUBTRACTOR/UNSIGNED pair, and
a label that no named label precedes in its section has no symbol for
the pair to name: that half is non-extern, names the section, and the
label's address goes into the contents. An FDE's CIE pointer is such a
difference once a named label starts the FDE. mold rejected the pair;
ld-prime takes it, though, like ld64, it reads the section ordinal as
a symbol index, right only when that symbol's value is 0.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
dyld makes __DATA_CONST read-only once it has applied an image's
fixups, so the linker moves the GOT, initializer pointers and other
data written only by fixups there. ld-prime does so for dylibs, bundles
and PIE executables, but a non-PIE executable keeps them in __DATA
unless -data_const asks, chained fixups or not, and even when bound for
the shared region. mold gave every dynamic image the segment.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The export trie, __unwind_info and __init_offsets hold offsets from the
image's mach header, where dyld and the unwinder add the address the
image was loaded at, as LC_FUNCTION_STARTS does. mold counted them from
the end of __PAGEZERO, which is where the header sits unless
-image_base moves it: a classic dylib or non-PIE executable given a
base got exports dyld_info calls too large and function offsets 2 GiB
off, so dlsym and exceptions failed. The link map's header too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An undefined symbol's n_desc carries a library ordinal in its high
byte, which dyld reads only in an MH_TWOLEVEL image: the dylib's
load-command number, 0xfe (DYNAMIC_LOOKUP_ORDINAL) for a symbol left to
-undefined dynamic_lookup or -U, 0xff for the -bundle_loader executable.
ld-prime writes 0 for every import of a -flat_namespace image, with
chained fixups or classic binds alike (whose ordinal stays -2, flat
lookup); mold wrote 0xfe there.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
-image_base (or -seg1addr) fixes where an image that dyld does not
slide is linked. ld-prime drops the base of any image dyld loads with
chained fixups, warning "prefered load addresses (-seg1addr) are
disabled with chained fixups": a dylib or bundle, and also a non-PIE
executable given -fixup_chains, which then sits right after
__PAGEZERO. (A PIE executable's base goes first, with "Linking with
PIE".) mold kept the base of such an executable.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Chained fixups became ld-prime's default from macOS 12 (x86_64
executables from 13), but only for an image dyld may slide: a non-PIE
executable, which only x86_64 still allows, keeps classic dyld info
whatever its deployment target, with lazy binding and __mod_init_func
rather than __init_offsets. An explicit -fixup_chains still gives it
chains. mold chained it by the deployment target alone.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime knows one -undefined treatment besides the default error:
dynamic_lookup, which leaves an unresolved symbol to a flat lookup at
run time. suppress selects it too; error, warning or any other word
changes nothing, so -undefined warning still fails and nothing undoes
an earlier dynamic_lookup. All but dynamic_lookup draw "-undefined <x>
is deprecated", and -U beside it (or in a kext) draws "-U option is
redundant". mold took error as a reset and warned per symbol.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A -static image (a kernel) has no dyld, so ld-prime gives it no fixups
by default, but an explicit -no_fixup_chains gets it classic
LC_DYLD_INFO_ONLY: a rebase for every pointer, and a weak bind for each
slot holding one of its own weak definitions, though its code reaches
them directly. It binds and exports nothing, and is PIE only with -pie,
where LC_DYSYMTAB then lists no local relocations. A kext or -kernel
image, which kmutil links or slides by relocations, takes neither flag.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An output section takes its first input section's type, so a rename
that merges __data and __bss leaves one kind in a section of the
other: file-backed contents vanish in a zero-fill section, or zeros
take file space in a regular one. ld-prime links on with "section S
has a conflicting zerofill flag defined in F but missing in:", naming
the first file with a zero-fill member and then, latest first, each
file with a file-backed one. A -r output gets no warning.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime applies -rename_section and -rename_segment to the sections
it synthesizes - stubs, GOT, lazy pointers, __init_offsets, __eh_frame,
the ObjC ones, -sectcreate's, a -r __objc_imageinfo - but not
__unwind_info. The empty __text of a final image moves with -text_exec
and follows a -rename_section, but -rename_segment __TEXT moves the
mach header (and it) only in a -static image, which must then list
that segment first in -segment_order.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime makes its own moves first - data needing no writes after
fixups to __DATA_CONST, a kext's code to __TEXT_EXEC - so
-rename_section matches __DATA_CONST,__const; then -rename_segment
applies even after a section rename, and only then does a literal pool
or __StaticInit still in __TEXT merge, under its renamed name. Flags
follow the pre-rename name. -r outputs and section$start$ symbols take
the same names (mold made an empty __DATA,__const); names cut at 16.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The locals, externals and imports were each appended with two
par_extends, one for the entries and one for their names, so every
external's and import's symbol was looked up twice, and the sorted
locals were first copied into a (name, entry, symbol) Vec. One helper
now writes an entry and its name together into both arrays' reserved
capacity, and the locals go in straight from plan_local_symbols' sorted
list.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The per-symbol loops that plan and write the stabs called sectname_is,
segname_is and SymtabBlock::push out of line - they live in other
modules, and without #[inline] they stay calls, and a constant name
stays a memcmp - and a function's N_BNSYM, N_FUN and N_ENSYM each
looked its address up again. The helpers are now marked #[inline], and
populate_symtab keeps the last symbol's address for the next note.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The debug notes, three quarters of a large -g link's symbol table, were
expanded into the entry, name and named-symbol arrays - 48 bytes each
- only to be laid out and copied into the output. Like mold-rust's
symtab (compute_symtab_size, populate_symtab over carved SymtabBlocks),
each object's notes now stay in their plan: the table and the string
table get one block per object, sized once the symbols' own strings are
laid out, and copy_symtab writes the blocks in parallel in place.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Every local symbol and every noted symbol checked its section by name,
and sectname() and segname() find the NUL and validate UTF-8 each time:
on a large -g link, half of the stab planning's time went there. The
new sectname_is and segname_is compare the 16-byte field with the name
up to its NUL, which is the same test (and strncmp's, which ld64 uses),
and the Objective-C list check looks at the ten names only for a
section whose name starts with __objc_.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Each object's stab plan held its entries themselves, 48 bytes each and
four per function, so a large -g link built and page-faulted in over
400MB of plans only to copy them into the symbol table. A plan now
keeps its fixed entries (the N_SO pair and N_OSO, or a -r input's
notes) and, for each symbol noted, just the symbol, its section and a
function's size; the entries are made from those as they are written
straight into the table, in the same parallel pass as before.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A local symbol that shares its address with others is listed after
them unless a strong external names the atom. To tell, mold collected
the address of every strong external - two million on a large link -
and sorted them, although few atoms have aliases at all. It now finds
the aliased addresses first, from the sorted locals in parallel, and
only if there are any marks those a strong external has in one
parallel pass over the externals, with no sort.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The entry and name arrays grew range by range: each par_extend and
the stabs' reserve reallocated and copied everything before it, and
on a large -g link that meant copying hundreds of megabytes and
faulting their pages in again. The locals, stabs and imports are now
all planned first, so both arrays are allocated once at their final
size and every range is written in place. The timers follow the new
steps: symtab-entries fills the arrays, symtab-strings lays out names.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The local symbols, the defined externals and the imports were pushed
onto the entry and name arrays one by one, and each symbol's output
index was then found by a serial scan over every entry. Each range is
now appended with par_extend, which writes in place, and the indices
reuse the per-symbol entry array the string layout builds, with the
imports' added in parallel: a local now gets its index too, which the
indirect symbol table never asks for.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
layout_strings built a length, an offset and a (offset, name) array
over all seven million entries of a large -g link, patched the shared
notes serially, and copy_symtab then copied the strings out of that
list. It now blanks the name of a note that shares its symbol's string
and assigns offsets per 64K-entry block from prefix-summed block sizes,
so an entry's name is exactly what goes at its n_strx, and copy_symtab
writes it in the same parallel pass that writes the entry.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime sorts a unit's notes by address, so mold gathered each symbol's
notes into a Vec of their own, sorted those by address and flattened
them: three million allocations and a sort on a large -g link, for an
order no reader uses (dsymutil and lldb map a unit's notes by name, and
an N_FUN pair stays together). The notes now go straight into the unit's
run in nlist order, and a -r input's notes are copied through as they
are. stabs-order.sh now checks dsymutil's debug map instead of the order.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The stabs' entries and names are copied out of the per-object plans in
parallel, each object into its prefix-summed range, but the symbol each
note names (the string table layout needs it to share that symbol's
string) was then gathered by a separate flat_map over every plan. It
now goes into its own array in the same parallel loop, which drops a
pass over five million notes on a large -g link.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld64 splits a section of fixed-size records into one atom per record
and gives each atom an alignment of its own, with no modulus, instead
of the section header's: a literal is aligned to its size, and an
S_MOD_INIT_FUNC_POINTERS or S_MOD_TERM_FUNC_POINTERS entry and a 32-byte
__DATA,__cfstring constant to a pointer. A __mod_init_func that says
2^0 (or 2^4) thus comes out 2^3 from ld-prime, in a -r output as in an
image, while we kept the header's alignment. The literal rule now
covers those pointer records too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
When several objects define one weak symbol, ld64 keeps a single copy
(the most aligned, the first at equal alignment) and discards the
others' atoms with their relocations, unwind records and data-in-code,
without comparing them: an inline function compiled at different
optimization levels differs in size from one object to the next and is
still one definition. We folded a losing copy only if it matched the
winner's size, so such a copy's bytes stayed behind as anonymous dead
weight in the final link, and in a -r output as a stray tail that the
next link glued onto the preceding atom.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
LC_DATA_IN_CODE marks the ranges of __text that hold data (jump tables,
literal pools), each an address in an object and a file offset in an
image. Every copy of a weak function carries its own entries, and when
the link keeps one copy, the others' entries must go with their bytes.
We redirected a dropped copy's entries to the copy that replaced it, so
both -r and the final link listed each range twice at the kept address;
ld-prime writes it once. The two builders now share one walk that
skips replaced subsections.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime checks the options against the kind of output it writes: only a
main executable has an MH_PIE flag, a main-thread stack in LC_MAIN, a
__PAGEZERO or an entry point, -client_name names a bundle or an
executable to the umbrella it links, and a -r object leaves the
__DATA_CONST split to the link that consumes it. We silently accepted
all of these with -r and most with a dylib; like ld-prime, they are now
errors (-e and a dylib's -pie only warnings), and a zero -pagezero_size
is accepted anywhere.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
do_resolve was a 269-line function that ran every step of a symbol resolution round inline, with the rank rule a closure in the middle. Each step is now a function, and do_resolve reads as mold's resolve_symbols_pass does. collect_references records which symbols the considered objects reference and whether any reference is strong (a symbol is a weak import only if every reference is weak), with -u, -e and the -alias bases counting as references. definition_rank is the rank rule: class << 40 | inverted alignment << 32 | priority, where the class orders a live strong definition, a live weak one, a lazy archive member and a live common symbol, and among weak definitions the most-aligned copy wins as in ld64. race_definitions takes each symbol's atomic minimum rank, claim_definitions lets the unique winner write the symbol, merge_common_symbols merges common sizes and alignments, and claim_dylib_exports binds what objects leave undefined to the first exporting dylib that outranks any lazy member. Interning the names the command line gives moves up to resolve_symbols, since only the first round of a call can create them, and claim_dylib_exports and claim_new_dylibs share import_from_dylib and walk the symbols with par_iter_mut instead of a raw pointer, since each writes only its own symbol. Resolution results and outputs are unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
icf_sections was a 287-line function whose steps were closures and inline blocks, so reading one step meant keeping the others' captured state in mind. It now has the shape of icf.rs in mold: weak_only_sections and is_candidate choose the candidates (live pure-instruction __TEXT subsections without debug info whose every non-temporary definition is weak, since only a weak-only subsection's address may be shared), edge_of names what a relocation points at (a candidate by index, anything else by identity, with a symbol's offset into a candidate moved into the addend), compute_digest hashes a candidate's content once, gather_edges builds the candidate-to-candidate graph in CSR form, propagate rehashes each vertex with its neighbors' digests, and count_num_classes counts distinct digests while electing each class's lowest-index leader; icf_sections runs them and folds members onto leaders. gather_edges now fills the edge array through rayon::iter::split at vertex boundaries, as mold does, instead of writing through a raw pointer, and verify_leaders is the debug-only equality check. The digests, classes, leaders and outputs are unchanged, and the -print_statistics phases (icf-prep, icf-rounds, icf-fold) cover the same work.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
dead_strip was one 297-line function that carried the edge enumeration twice, once in a closure for the serial walk and once inside a nested struct for the parallel one, so the rule for what a subsection keeps alive (relocation targets, plus the LSDAs and personality routine its compact-unwind records name, directly or through an FDE and its CIE) had to be kept in sync by hand. It now reads like gc_sections.rs in mold: collect_root_set marks the roots (should_keep's pinned sections, __init_offsets initializers, no-dead-strip and exported symbols, -u and the entry point) in a fixed order, mark runs the work-stealing parallel visit, walk is the serial walk that -why_live needs for a stable spanning tree, mark_live_support does ld64's single in-order live-support pass, and sweep consumes the visited bit and compacts the FDEs and unwind records. Both walks take their edges from the one for_each_edge. The parallel visit still reads all of a section's edges before recursing into any, unlike mold's mark closure: recursing midway through the relocations cost about 4% more CPU in dead_strip on a 2M-function link, as the relocations were evicted before the loop came back to them. The order of mark_visited calls is unchanged, and so is every output.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
dyld makes __DATA_CONST read-only once it has applied the fixups, and
ld-prime marks the segment SG_READ_ONLY (0x10) for it - but not in an
image bound for the shared region (-add_split_seg_info, -kernel, an
arm64 kext, a /usr/lib or /System/Library dylib), where the dyld shared
cache or kernel collection builder protects the segment itself. A plain
-static -data_const image keeps the flag, as in ld-prime.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
kmutil, not dyld, links a kext: undefined symbols are looked up (flat)
and LC_DYSYMTAB relocations replace dyld info. Extrel: 8-byte UNSIGNED
at import GOT slots and pointers, x86-64 BRANCH at calls (no stubs);
locrel for local pointers; from image base 0. No LC_BUILD_VERSION,
function starts or signature; 32 bytes of header pad; __got S_REGULAR.
arm64 (ld64: -text_exec -data_const -add_split_seg_info): r-- __TEXT,
code in __TEXT_EXEC, __DATA then __DATA_CONST, stubs via (__weak_)got.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
With -no_data_const (and in an x86-64 kext, which has no __DATA_CONST)
ld-prime's __DATA begins with __la_symbol_ptr and then __mod_init_func
and __mod_term_func, and puts __got after every input section, just
before the zero-fill ones. We had __got right after the lazy pointers.
The indirect symbol table follows the sections' order, so there the
lazy pointers' entries now precede the GOT's, as in ld-prime.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
XNU links itself with -static -pie -kernel -add_split_seg_info. In
ld-prime, -kernel (ld64's fKernel) requires -static ("-kernel must be
used with -static"), implies -pie, and makes the image shared-region
eligible: split info and __DATA_CONST, unless -not_for_dyld_shared_cache.
As in ld64, __TEXT_EXEC is r-x; no empty __TEXT,__text is added once
code went to another __text; function starts count from -image_base.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld64 sets fInitializersTreatment to a warning for a shared-region
eligible dylib: each target of an input __mod_init_func pointer, by
symbol name and real object path ("static initializer '<name>' found
in '<path>'. Use -no_inits to make this an error. Use -no_warn_inits
to suppress warning"). -no_warn_inits silences it; -no_inits makes any
initializer an error for every output, "Static initializers:" listing
"<name> in <path>" lines. Bundles and executables get no warning.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld64's fSharedRegionEligible: -add_split_seg_info, or a dylib installed
in /usr/lib or /System/Library, but not -not_for_dyld_shared_cache.
Such an image gets split info and __DATA_CONST (even -static), where
ld-prime also puts __la_symbol_ptr (first), __objc_selrefs (S_REGULAR),
__objc_const (if method lists are relative) and __weak_got, the
weak-lookup GOT slots; __stubs and ObjC names go past __unwind_info; a
dylib self-binds its exported classes; -U and -rpath are diagnosed.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Split info V2 lists each reference between sections as (from, to,
kind, offsets), section 0 being the header. ld-prime records a data
pointer (PTR64) or distance (DELTA64/32, at the positive term's atom)
wherever it points; adrp, ldr/add, bl (ADRP, OFF12, BR26) and x86-64
displacements (DELTA32) only across sections; nothing dyld binds.
Linker-made references count too: stubs, helper, lazy/GOT slots, objc
stubs, method lists, init offsets, unwind info (IMAGE_OFF32), eh_frame.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
LC_SEGMENT_SPLIT_INFO records every reference from one section to
another, so that the dyld shared cache or kernel collection builder
can slide segments apart. ld64 asks for it with -add_split_seg_info
(fSharedRegionEligible) and has no negative form; neither does
ld-prime 27037. Our -split_seg_info and -no_split_seg_info were never
ld64 options, so they go, and are now unknown options as in ld-prime.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld64 takes a dynamic image that would load no dylib at all for one
linked without libSystem by mistake and fails with "dynamic executables
or dylibs must link with libSystem.dylib": any executable but a -static
one, and a dylib or bundle even with -static. The count is of the
dylibs left after -dead_strip_dylibs, bundle loader included, and any
of them will do; ld-prime keeps ld64's exceptions for libsystem_kernel
and for links with an exit-asm.o. mold linked such images.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
-image_base (or -seg1addr) places __TEXT of an image that is not slid,
and ld-prime drops it for a PIE executable with "Linking with PIE,
-image_base will be ignored". A -r output is an object file, loaded
nowhere and not an executable at all, so ld-prime says nothing about
PIE for it; it only rounds a base that is not a multiple of 4 KiB up
and warns "base address ... is not properly aligned", then has no use
for it. mold, taking a -r link for a PIE executable, gave the warning.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Dead stripping decides what is live from the entry point, the exports
and the other roots, following every reference, and only a final link
sees all of them: a -r output is an object file whose atoms the final
link may still reach from other inputs. ld64 refuses the combination
with "-r and -dead_strip cannot be used together", as ld-prime does,
also for -no_dead_strip_inits_and_terms, which now means -dead_strip.
mold ignored -dead_strip in a -r link.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Given with -dead_strip, -no_dead_strip_inits_and_terms once kept the
initializers and terminators (__mod_init_func and __mod_term_func
entries) that nothing else referenced. Dead stripping always treats
them as roots now, so ld64 turns the option into -dead_strip, and
ld-prime does too, warning "option '-no_dead_strip_inits_and_terms' is
obsolete, use '-dead_strip' instead". mold ignored the option.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
__unwind_info's first-level index ends with a sentinel entry whose
function offset is one byte past the last code the table covers.
ld64 takes it from the last entry's atom, adding the atom's size plus
one to the atom's address, however much of the function the unwind
record behind the entry says it covers. mold took the record's own
length, so a DWARF FDE (or a compact unwind entry) shorter than its
function ended the table early; it now uses the end of the subsection.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A CIE's augmentation string lists what its augmentation data holds: 'z'
its length, then 'P' a personality, 'L' the LSDA encoding and 'R' the
FDE pointer encoding. Other letters are flags without data: 'S' marks a
signal frame and, on AArch64, 'B' return addresses signed with the
pointer-authentication B key and 'G' an MTE-tagged frame. ld64 reads
CIEs with libunwind's parser, which ignores letters it does not know, so
"zRG" or "zRQ" give the same unwind info as "zR"; mold rejected them.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A -segprot protection is a string of letters, r for VM_PROT_READ, w for
VM_PROT_WRITE and x for VM_PROT_EXECUTE, with '-' standing for none.
ld64 lowercases each letter first, so "RW" is 3, and ld-prime, rather
than failing on any other character as ld64 did, warns "unknown
-segprot letter" each time one appears and ignores it. The letters of a
-segprot for __LINKEDIT, which ld-prime refuses outright, are not read.
mold failed with "invalid protection" on either.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime silences a warning under -w and fails the link on one under
-fatal_warnings for the warnings its option checks give, too: -no_pie's
"ignored for arm64*" and deprecation ones, which it gives once the whole
command line is read, and -segprot's, given as the option is read and so
silenced by a -w before it. mold applied the two flags only after the
option parser returned, so these warnings always printed and never
failed a link; they now take effect before the checks that warn.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Method lists become relative from macOS 11 on, the class methods too:
they hang off the metaclass, which a class_t's first word (isa) points
at. The class_t of a Swift class lies 24 bytes into its full metadata,
past the value witness table and other prefix fields, and so into its
subsection; mold read the isa at offset 0, found no metaclass and left
Swift class methods as an absolute list in __objc_data. ld-prime
rewrites them.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The parallel rebasing step in integrate_objects returns, for each
object, the map from its nlists to symbol ids that becomes
ObjectFile:🔣 locals go to the slots the prefix sum reserved,
which the next step initializes, and globals to the ids interned for
the whole batch. Its comment said it reported "local symbol names ...
for the serial arena extension", which is neither what it returns nor
where it is used.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Now that stage_object fills a StagedObject phase by phase, a reader
meets its fields before the code that sets them, and several had no
comment: isecs holds the object's subsections in section order (the
index every other field refers to them by), subsecs sorts those indices
by address, sym_names is interned only at integration, and unwind,
cies and fdes are the per-function records and the __eh_frame entries
they point to. stage_object's doc also said the -r flag kept "a weak
symbol's flags"; it keeps them only for a .weak_def_can_be_hidden
symbol that names a whole section, and now says so.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A staged object reaches the global arenas either in a parallel batch
(integrate_objects, for the input files) or one at a time
(integrate_object, for the LTO output object). Both rebased the same
local indices - subsection ids in relocations and unwind records, CIE,
FDE and personality symbol references - and built the same ObjectFile,
in two copies of the code. That is now StagedObject::rebase and
StagedObject::into_object_file, and each path keeps only what differs:
how symbols are interned and how the arenas grow. integrate_object_with
took pre-interned symbol ids that no caller ever passed, so it is folded
into integrate_object; its doc comment had ended up on integrate_objects.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
parse_compact_unwind and parse_eh_frame were free functions taking the
object's pieces one by one (seven and ten arguments, with
too_many_arguments allowed), and each built a private copy of the
subsection address table to reimplement find_subsec as a closure. They
are now StagedObject methods, like mold's ObjectFile::parse_ehframe,
and look subsections up with find_subsec itself. parse_eh_frame reads
the section into CIEs and FDEs; the two self-contained decodings it
did inline are named functions, apply_subtractor_pairs (making the
records' pointers self-relative) and cie_lsda_size (validating a CIE's
augmentation); and add_fdes holds the policy that ties each FDE to its
function's unwind record, which a compact record wins unless it is a
DWARF-mode one that points at the FDE. A compact unwind entry's fields
are read by offset instead of through inline blocks, and its function
and LSDA pointers share one address computation. The records produced
are the same, in the same order.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
stage_object did all of an object's parsing in one 470-line body whose
locals were threaded from one step to the next. Like mold's
ObjectFile::parse, it now creates the StagedObject right after reading
the headers and fills it in with a method per phase:
unweaken_section_atom_names applies ld64's whole-section atom naming
rule, initialize_sections splits sections into subsections (at the
symbols from symbol_split_points, or per element with
literal_split_points), read_relocations reads each section's relocations
and hands them to its subsections (check_reloc_target reports the ones
ld-prime rejects, section_target rebases a section-relative target), and
read_symbol_names records names and their intern hashes. The per-section
subsection lists (a Vec<Vec<usize>>) became ranges of the isecs arena,
which is what they always were, and the literal element sizes are one
table instead of five copies of the same step_by. The resulting objects
are the same.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
stage_object opened with a 90-line loop that decoded every load command
into eight local variables, then read the symbol table through a pair of
mutable defaults. The loop is now LoadCommands::read, which returns the
decoded commands as one struct (section headers, LC_SYMTAB/LC_DYSYMTAB,
linker options, platform versions, data-in-code and LOH entries), and
read_symtab returns the nlists and the validated string table. The
decoding itself is unchanged. stage_object now starts with what it
reads rather than how, and a misplaced doc line that belonged to it
(and one that belonged to ObjectFile) is back on its item.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Every -O level must give the same output, which cmp already checks
byte for byte, so running each of the twelve x86_64 binaries under
Rosetta added nothing but time; under load the test hit the runner's
timeout. The program now runs once, from the link without -O.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The __objc_methlist chunk resolved each ObjcRef of a relative method
list with a closure of its own, a copy of objc_ref_addr, which the
__DATA writers and the rebase table already use for the same
references in synthesized records. It now calls objc_ref_addr too, so
where an Objective-C reference lands is decided in one place for every
writer. The outputs of every test and of the iTerm2 and NetNewsWire
corpus links are identical byte for byte.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
convert_objc_method_lists did four things in one 215-line body: map
the inputs' selector references, find every method list the runtime
would visit (a nested fn threading a closure, two sets and the list
through its recursion), convert each list while picking each entry's
selector reference inline, and move the lists' symbols. The function
is now the 30-line outline - find the lists, convert each, move the
symbols - over runtime_method_lists, which walks the list sections
with a small MethodListFinder (note a record's method list; visit a
class, then its metaclass); SelrefFinder, which holds the three places
an entry's selector reference can come from and picks among them in
ld64's order (an input's slot, an objc stub's, a new tail slot); and
relative_methods, which reads a classic list into relative entries or
declines it. The list walk now reads each list section's entries
through list_entries, as category merging does. The same lists are
found in the same order and the same slots chosen, a declined list
still keeping the tail slots its earlier entries added; the outputs of
every test and of the iTerm2 and NetNewsWire corpus links are
identical byte for byte.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
merge_objc_categories was one 690-line function: local structs, a
dozen closures (list readers, record writers, supersede, retire,
rewrite_ro, retarget) and the per-class loop all shared its scope, and
the closure that wrote records had to be threaded through another as
a `&mut dyn FnMut` (the NewBlob alias). The function is now the 80-line
outline of the pass, and each phase it names is a function of its own:
defined_classes and find_categories collect the classes the image
defines and the categories on them (each class now carries its
categories, replacing a map keyed by class); merge_lists reads a
class's lists and its categories' through read_method_list,
read_protocol_list and read_property_list and says whether they can be
merged; MergedListWriter writes the merged lists and names them;
drop_superseded_lists, rewrite_ro and retarget_class_data replace what
they supersede; rebuild_category_lists takes the merged categories out
of __objc_catlist. ListRefs names the five lists a category adds - and
a class has - once, at their category_t and class_ro_t offsets, where
the old code read each field separately in the gather and again in
the drop. Two simplifications fall out, neither changing what is
decided: supersede's `merged` argument was always None, so its
redirect-to-the-merged-list branch (and the comment promising it) was
dead; and the "ro rewritable" test `(A && meta.1 == 0 || B) && ... &&
B` reduces to both data pointers being rewritable and both records 72
bytes long, as it now reads. The outputs of every test, and of the
iTerm2 and NetNewsWire corpus links (whose 158 and 21 merged lists
exercise it), are identical byte for byte, as are the
MOLD_OBJC_DEBUG reports of the classes skipped.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Every record the Objective-C passes synthesize was a fifteen-field
InputSection literal pushed onto ctx.isecs, seven times over, and the
two kinds of subsection they make were told apart only by which fields
of the literal differed. They now go through named helpers:
add_placed_isec for a live subsection the pass lays out itself (a
relative method list, a data record), add_slot_stand_in for the dead
8-byte subsection standing for a GOT entry or an objc stub's selector
reference that input subsections are redirected to, add_data_blob for
a record in the tail of a __DATA section, and add_methlist_section and
add_relative_method_list for the relative method lists, which
convert_objc_method_lists and category merging now write the same way
(align to 4, place at the running offset, register with the
__objc_methlist chunk). Each call site now says what it makes. The
subsections, their order and the section headers are exactly as
before; the outputs of every test and of the iTerm2 and NetNewsWire
corpus links are identical byte for byte.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
objc.rs kept the order the code had in passes.rs, so ObjcRef and the
readers every pass relies on (objc_pointer_at, objc_ref_location,
objc_pointer_reloc, objc_class_ro) sat between fold_objc_classrefs and
convert_objc_method_lists, and the DataBlob records category merging
and the chunks share sat just before merge_objc_categories. The file
now opens with that shared vocabulary - what a synthesized record or
rewritten method list refers to, and how a pointer field is read
through its relocation - and then runs the passes in driver order, as
mold defines its helpers ahead of the passes in passes.cc; the module
comment's description of how the passes read the metadata is followed
directly by the functions it names. Only the order of the items
changes; the outputs of every test are identical byte for byte.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The passes that rewrite an image's Objective-C metadata - coalescing
selector and class references and CFStrings, the objc_msgSend$ stubs'
selector references, folding class references into the GOT, the
relative method lists and category merging - made up some 1,600 of
passes.rs's 6,700 lines, interleaved with unrelated passes, and the
data model they share (ObjcRef, the synthesized DataBlob records, the
readers that follow a metadata pointer through its relocation) was
reachable only by reading them in scattered order. They now live in
src/objc.rs, in the order the driver runs them, under a module comment
that names each pass and explains how they all read the metadata, the
way mold keeps a self-contained feature such as ICF or TLS in a module
of its own. The code moves unchanged; passes.rs keeps what the
output-section layout shares with it (objc_refs_are_const, add_got,
data_seg, redirect_symbols_to_replacements) and imports the record
types and cstring_of back. The outputs of every test are identical
byte for byte.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Without MH_SUBSECTIONS_VIA_SYMBOLS mold keeps a section whole, since
nothing may be moved or stripped apart from it, but ld64 still cuts its
atoms at every symbol - local, 'l'-prefixed or global - and only ties
them together. A linker optimization hint must name instructions of
one atom, so ld64 drops one that spans a symbol ("addresses are not in
same atom"); mold applied it. Such hints come only from hand-written
assembly.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
LC_LINKER_OPTIMIZATION_HINT points at ULEB128 records - kind, count,
then the addresses of an adrp sequence - zero-padded to 8 bytes. ld64
takes a hint whose one to three addresses are 4-byte aligned and lie
within 64 KiB in one atom of code; -r moves each to its atom's new
address, drops a coalesced-away weak copy's, and writes them atom by
atom after the data in code, the command last. ld-prime 27037 drops
them all; mold now keeps them for its final link, which applies them.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
apply_reloc_alloc read the 4-byte instruction at every relocation to
extract its register fields, so a 1- or 2-byte data relocation such as
R_SPARC_8 or R_SPARC_DISP16 in the last bytes of a section panicked.
Read the fields only for the relocations that rewrite an instruction.
The dyld shared cache builder binds each import of a cached dylib to
the dylib exporting it, once for every process; a flat lookup at run
time defeats that. ld-prime refuses a shared-region-eligible dylib that
asks for one - -flat_namespace first, then -undefined dynamic_lookup or
suppress, or any -U, whether or not a symbol is left undefined - as it
parses the options, before reading any input. mold does so now too,
with ld-prime's messages.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
-undefined dynamic_lookup (or suppress or warning) and -U leave a symbol
for dyld to find at run time by flat lookup. A -static image has no
dyld, so ld-prime reports every undefined symbol there as an error,
whatever those options say, weak references included. mold made such
a symbol an import instead, and then died looking for dyld_stub_binder
for its lazy stub, or with -fixup_chains wrote a bind nothing performs.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
__TEXT,__init_offsets holds 32-bit image offsets of the initializers
dyld runs, where __mod_init_func holds absolute pointers that each need
a rebase. ld-prime converts by default only with chained fixups for an
image dyld loads (ld64: dyldLoadsOutput() && fMakeChainedFixups); mold
also did for a -static link targeting macOS 26 or given -fixup_chains.
A kernel runs its initializers itself, and XNU's -rename_section of
__DATA,__mod_init_func then found nothing; now only -init_offsets does.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A -static image (a kernel) has no dyld, so ld-prime gives it no fixups
by default, but an explicit -fixup_chains still gets it chains for its
own loader to walk: LC_DYLD_CHAINED_FIXUPS with the rebases and weak
lookup binds a dynamic image would have, in DYLD_CHAINED_PTR_64_OFFSET
whatever the target, and no export trie. As in ld64, -fixup_chains
implies -pie unless -no_pie, and the chains replace local relocations.
A chain segment's offset now counts from __TEXT, which -image_base moves.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An exported weak definition is reached through slots dyld binds by weak
lookup, so that one copy wins across the loaded images. A -static image
has no dyld and no other image, so ld-prime binds its code straight to
the definition: a call branches to it and a GOT load becomes an address
computation, leaving no __got. mold kept the GOT slot, as can_relax_got
asked binds_at_runtime, which counts every coalescable weak definition;
it now asks binds_weak_lookup, the same thing for a dynamic image.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The relocatable tests asked the system linker to ad-hoc sign its x86_64
output because the first CI run hung executing such binaries. Later CI
runs executed every mold-linked x86_64 test binary unsigned, as
ld-prime leaves them, without a hang, so signing isn't what kept that
run alive. The tests now link with Apple's ld as a user would.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Before code generation, an executable tells libLTO to preserve only the
bitcode definitions some native object references; the rest may be
internalized and dead-stripped inside the merged module. The set of
native references was a hash set filled by one thread walking every
object's undefined symbols. It is now one flag per symbol, set from all
objects in parallel, as symbol resolution records references.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
It had merged into loader_rpath's own comment, so loader_rpath read as
the function that resolves symlinks and strips the fat-file suffix.
dyld resolves @loader_path against the directory of the real file,
symlinks resolved, which is what dir_of computes: a framework's
X.framework/X links to Versions/A/X, where its LC_RPATH entries are
written for.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld64 and ld-prime deem a dylib eligible for the dyld shared cache when
its install name is under /usr/lib, /System/Library, or their
/Library/Apple counterparts, and neither -not_for_dyld_shared_cache nor
-debug_variant is given. The same test decides whether its dependencies
must be eligible too, whether unused-dylib warnings are on by default,
and whether optimization hints are left alone. mold's own test knew
only the first two directories and ignored -debug_variant.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An LC_LINKER_OPTIMIZATION_HINT entry names an adrp sequence and its
kind; once addresses are final, ld64 rewrites a target within 1 MiB
to adr or a literal load (of the GOT slot if the load wasn't relaxed),
else folds an add into the load or store after it, and then drops the
second of two adrp of one page. mold now does all eight kinds under
ld64's conditions, reading the target back from the relocated code,
except in dylibs ld64 deems shared-cache eligible. ld-prime does none.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
clang passes its optimization level on to the linker as it was given
(-O2, -Os, -Ofast, -Og, -O4, ...; -O and -O1 become "-no_deduplicate
-O1"), and ld-prime takes -O followed by anything, bare -O included,
without consuming the next argument. mold knew only -O0 to -Oz, so
linking with clang -Ofast or -Og failed. ld-prime uses the level only to
skip function deduplication at -O0 or without any -O; mold now ignores
every level and deduplicates unless -no_deduplicate.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld64 and ld-prime merge a category into its class whenever both are
defined in the same image, and the only switch is
-no_objc_category_merging to turn that off; there is no positive
spelling, so ld-prime rejects -objc_category_merging as an unknown
option. mold accepted it, which let a command line that fails under
ld-prime link. The option now errors out the same way, and the setting
is a plain flag that defaults to on.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime makes a cstring or 4/8/16-byte literal record that a symbol
labels an atom of its own, which merges with no identical record, in
-r and final links alike; an l- or L-prefixed label doesn't count. A
-r output keeps that label with n_desc 0, even in an object without
subsections, and names only the unlabeled records LC<n> or l<nnn>;
mold merged every copy and renamed or marked the labeled ones.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Dead stripping keeps __mod_init_func and __mod_term_func entries by
their section type, and a -r output passes that on through
N_NO_DEAD_STRIP. ld-prime sets it only on the symbol naming each
entry's atom - the highest ranked at its place, an external over
locals - and leaves the other labels there at 0, with or without
subsections; everywhere else it marks every name. mold marked them all.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A section's relocation entries may come in any order, and compilers
write them by descending address. ld-prime keeps that: each atom's
relocations by descending offset, atoms in output order, and a pair -
a SUBTRACTOR and its UNSIGNED, an arm64 ADDEND and its PAGE21 or
PAGEOFF12 - together in its order. mold wrote them ascending. The
per-section loop moves out of relocatable::link into section_relocs.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime lays out __objc_methlist sorted by symbol name. That includes
the relative method list it builds when merging a category into its
class, which it names __OBJC_$_INSTANCE_METHODS_Foo(Cat). mold names
that list as an extra local symbol, but the sort key only looked at
input symbols, so the merged list got an empty name and came first. We
now take its name from the extra locals too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
When ld-prime merges a category into its class, it rewrites the
class_ro_t records (__OBJC_METACLASS_RO_$_Foo, __OBJC_CLASS_RO_$_Foo) in
place: the new record takes the atom position of the old one in
__objc_const. Only the lists it builds from scratch (merged protocol,
property and absolute method lists) go at the section's end. mold put
every rewritten record at the end, so we now put a blob that replaces
an input subsection at that subsection's position among the members.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The __LD segment carries data for the linker itself, and ld-prime reads
only __LD,__compact_unwind from it: any other __LD section is dropped
with "unknown section: __LD/<name> in <real path>", even in an archive
member the link leaves out and in -r, and a relocation to a symbol
defined there is "invalid r_symbolnum=<n>". mold dropped them silently
and reported the symbol as undefined; the warnings now come in input
order after the parallel staging.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A bundle's -bundle_loader names the executable that will load it, so
its undefined symbols may bind to that executable's exports. Under an
explicit -warn_unused_dylibs, ld-prime warns "linking with bundle
loader (<real path>) but not using any symbols from it" when nothing
does, ordered among the unused dylibs by where the command line names
it, and -t lists the loader. The loader is now an input read in that
place, not a library added after all the others.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A chained-fixups bind word indexes an import table whose entries name
a symbol in a string pool. ld-prime numbers an import as it first
meets it, walking the binds atom by atom in address order and, within
an atom, from the highest offset down (a GOT slot is an atom), and
gives each import its own string after a leading NUL, padded to 8;
mold sorted imports by symbol id and shared one string per symbol.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An ad-hoc signature is a superblob holding one code directory: 88
fixed bytes, the output's name as identifier, then one SHA-256 hash
per 4 KiB page, and an executable segment range the kernel checks for
code. ld-prime puts the hashes right after the identifier's NUL, gives
the superblob its exact length but LC_CODE_SIGNATURE that rounded up
to 8, and limits the executable segment to __TEXT,__text; mold padded
the identifier to 16, left out the 8-byte rounding, and used __TEXT.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A -static -pie image slides by LC_DYSYMTAB local relocations with a
32-bit r_address. On arm64 ld-prime counts those addresses from
-image_base plus the __PAGEZERO size and fails the link with "atom
address cannot fit in a fixup" when one overflows - so any nonzero
base under the default 4 GiB __PAGEZERO fails. mold now refuses the
same links but keeps counting from __TEXT, where the image starts. A
zero -image_base, as in ld-prime, means none.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
LC_UUID identifies a build to debuggers and crash reporters, and
-no_uuid asks for none. ld-prime then writes no LC_UUID at all, in an
executable and a dylib alike; mold wrote the command with a zeroed
UUID so that the image would still load, since dyld on macOS 26
refuses one without it ("missing LC_UUID load command"). Following
ld-prime means a -no_uuid image doesn't load, which is what the option
gives with ld-prime too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld64 filters an image's local symbols with two list files:
-non_global_symbols_strip_list drops the matching ones and
-non_global_symbols_no_strip_list keeps only the matching ones. mold
took the second as -non_global_symbols_keep_list, which ld-prime
rejects as an unknown option, and rejected ld64's own spelling, so a
command line written for ld64 failed with mold and the reverse.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Classic dyld info binds imports with a small opcode program whose
state - library ordinal, symbol, addend, address - persists between
DO_BIND opcodes. ld-prime sorts the binds by ordinal, symbol, addend
and address, sets each piece of state only on change, steps the
address by ADD_ADDR_ULEB within a segment, and packs bind-plus-step
pairs and runs of equal steps into single opcodes; mold restated
every piece for every bind in address order. The streams now match.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ARM64_RELOC_POINTER_TO_GOT with r_pcrel set makes a 4-byte field the
distance to the target's GOT slot, as a CIE's personality pointer and
an LSDA's type info use it; it has no addend, and the assembler leaves
arbitrary bytes in the field. ld-prime's -r output writes 4 there in
every section, so __eh_frame and __gcc_except_tab come out identical
to its; mold copied the assembler's bytes. On x86-64 the field's value
is an addend (4 for a field relative to its own end) and stays.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A section header's reloff and nreloc locate its relocation entries,
and ld-prime writes reloff 0 when nreloc is 0. mold already did for
the output sections it builds from input sections, but the sections
a -r link writes afresh (x86-64's __eh_frame, the ObjC and unwind
tables) took the running file offset regardless, so an x86-64 -r
output's __eh_frame pointed at the next section's relocations.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
-t traces a link's inputs, and ld-prime lists every file it loads, not
only those that contribute: all members of each archive (libclang_rt's
285 on every clang link), and every library a stub re-exports, each by
the file found for it or, for one only inlined in a stub, by its
install name. mold listed live objects and the dylibs it kept apart,
missing libSystem's 39 re-exported libraries. ld-prime's order varies
between runs; mold's follows the load order.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A dylib installed in /usr/lib or /System/Library is a candidate for
the dyld shared cache, whose builder binds every dependency inside the
cache, so it cannot load one installed elsewhere (@rpath, /usr/local,
/Library, /System/iOSSupport). ld-prime therefore rejects such a
dylib's first ineligible load command, naming LD_SHARED_CACHE_ELIGIBLE
and -not_for_dyld_shared_cache as ways out; a library that
-dead_strip_dylibs drops doesn't count.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
On arm64, ld-prime -r gives every record of the __literal4, __literal8
and __literal16 sections a private-external name l<nnn>, drawn from
the counter that names cstrings LC<n>, after merging identical
records; relocations are re-pointed at those names and the records'
own labels (lCPI0_0 and the like) vanish. On x86_64 it leaves the
records nameless, since relocations there can be section-relative.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime -r keeps every label an input object defines, including the
linker-private l-prefixed ones that alias a global or each other, and
one sitting on an empty section; at one place they are listed by
descending name. Only the arm64 assembler's ltmpN section-start labels
are dropped, and only in a subsections object where another symbol
names the same place and no relocation refers to the ltmpN.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Dead stripping roots the initializer and terminator pointer lists
(S_MOD_INIT_FUNC_POINTERS, S_MOD_TERM_FUNC_POINTERS) whatever their
attributes, and ld-prime's -r output marks their symbols
N_NO_DEAD_STRIP, as it does those of a no_dead_strip section, so a
later link that reads them per symbol keeps them. mold marked only
no_dead_strip sections' symbols.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A -r output turns a private external into a non-external symbol that
keeps N_PEXT, and ld-prime copies N_WEAK_DEF along with N_ALT_ENTRY
and N_NO_DEAD_STRIP, so the next link still sees a hidden inline
function's or an ObjC protocol record's copy as coalescable. mold kept
only the latter two.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime lists a -r output's locals object by object, in each by
section ordinal and address (a zerofill section by its ordinal, not its
address), names at one address by rank, and numbers its LC<n>/l<nnn>
atoms in that order; then the stabs, opened by an N_SO, a tentative
definition's N_GSYM first; then externals and undefineds. Its strings
follow final-link rules: externals first, stabs sharing symbols'. The
table's construction leaves relocatable::link (1197 -> 798 lines).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A debug map gives lldb and dsymutil each function's and variable's
address in the linked image. ld-prime leaves out the labels of
__gcc_except_tab (GCC_except_table<n>, an unwinder's LSDA, which
DWARF never describes) and the ivar offset variables of __objc_ivar
(_OBJC_IVAR_$_C.x), though both stay in the symbol table; mold wrote
an N_STSYM or N_GSYM for each, one per C++ function with a handler.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A final image turns a private external into a non-external symbol
that keeps N_PEXT ("was a private external"), and ld-prime keeps
N_WEAK_DEF (0x80) in its n_desc when the definition was weak: every
hidden linkonce_odr copy an inline C++ function or a Swift thunk
leaves. mold cleared n_desc, so the demoted copies read as strong
definitions to a tool that looks.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A symbol table entry names itself by an offset into the string table.
ld-prime writes " \0", then the externals' names, the locals' and the
debug notes', every entry with a copy of its own (two locals named
alike get two), except that a note naming a symbol reuses that
symbol's string, all but the first local's, which it copies again. N_SO
and N_OSO strings are never shared. mold packed deduplicated strings
by hash shard, so offsets and order followed no such rule.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An N_OSO stab names an object whose DWARF the debugger reads, and its
value is the object's modification time, which dsymutil and lldb check
the file they find against. For an archive member ld-prime takes the
time the member's ar header records; mold asked the file system for
"lib.a(m.o)" and wrote 0. ZERO_AR_DATE, set to any value, makes
ld-prime write 0 for every N_OSO, in a final image and in -r output,
for reproducible builds; mold ignored it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A debugger finds a function or variable through its stab in the unit
that holds it (N_FUN between N_BNSYM and N_ENSYM, N_STSYM for local
data), and ld-prime lists a unit's notes by address, code and data
alike, and re-sorts the notes an -r input carries through. Its N_GSYM
for global data names the symbol only, with no section or value, and
a tentative definition gets one in the first object that declares it;
mold kept nlist order, gave N_GSYM an address and skipped commons.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The local range of a Mach-O symbol table opens the table. ld-prime fills
it with the non-external symbols it keeps, the private externals it
demotes, its own names and the objc_msgSend$ stubs, by address across
all objects, then the N_AST paths and stabs, opened by an N_SO of their
own. Names at one address alias one atom, named by its best symbol (a
strong external, private external, local, weak one, each rank by
descending name) and listed after the rest. mold put stabs first, then
each object's locals in nlist order, then the private externals.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
__unwind_info is a first-level index of 4096-byte second-level pages,
each compressed (32-bit entries, page-local encodings) or regular.
ld-prime fills the pages from the first function on, the last page
partial, with page encodings in order of first use; ends each page on
an 8-byte boundary; and sizes the index for the most pages the entries
could take as regular ones (511 each) plus terminator and a spare, the
unused rows zero. mold filled pages from the end and packed the rest.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An __unwind_info entry covers the code from its start to the next
entry's, so consecutive functions with one encoding and no LSDA need
only the first one's entry, whatever alignment padding lies between
them - the padding is never executed. ld-prime merges them so; mold
merged only functions that touched, and so kept separate entries for
x86-64 functions padded to 16 bytes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An __unwind_info entry holds a function's start and encoding but no
length: the unwinder takes it for all code up to the next entry. So
ld-prime gives every atom of an instruction section an entry, encoding
0 ("no unwind info") for one the objects gave no compact unwind or
DWARF record. mold listed only functions with records, so a routine
without CFI after one with a frame was unwound with that frame's rules.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Every process maps the dyld shared cache, so a dylib bound for it -
one whose install name is in /usr/lib or /System/Library, unless
-not_for_dyld_shared_cache - should load nothing it doesn't use.
ld-prime warns "linking with (/usr/lib/libz.1.dylib) but not using any
symbols from it" for each such library there (for any output under
-warn_unused_dylibs), sparing -needed_*, -reexport_* and libSystem,
libc++ and Foundation. mold had neither the warning nor the options.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime says where an input lies by its real path, symlinks and
relative steps resolved, and names an archive member by the archive's
real path and its position among the archive's entries (the symbol
table is [1]): "/abs/libfoo.a[2](foo.o)". -why_load says "'_f' caused
load of" (or "-force_load" / "-ObjC caused load of", an archive's
forced members last first), and both it and -why_live go to stderr.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An order file only arranges atoms and an alias list only adds names,
so ld-prime treats a missing one as a lost refinement rather than a
failed link: it warns "order file 'x' could not be opened, errno=2
(No such file or directory)" - for an alias list too, in the same
words - and writes the image without it. mold stopped with a fatal
error for either.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A library or framework option that finds nothing is reported by the
name it was given, "library 'bar' not found", whatever its flavor; a
library named by path (-force_load, -weak_library, -bundle_loader) is
still a library, and a bare object path fails by errno: "file cannot
be open()ed, errno=2 (No such file or directory) path=a.o in 'a.o'". A
list file names its option, "-exported_symbols_list file 'x' could not
be opened, errno=2 (...)". mold used its own wording for each.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The export trie is a radix tree of exported names whose edges point at
child nodes by ULEB128 offset. ld-prime writes the root first, with 5
bytes reserved per child offset (the unused ones left zero), then every
other node after its subtrees, so each node's child offsets are known
and exact when it is sized. mold wrote pre-order and iterated offsets
to a fixed point, so the bytes differed and sizes often did too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Tools such as install_name_tool add or grow load commands in the space
between them and the first section. ld-prime leaves -headerpad there,
at least 32 bytes in a final image (MAXPATHLEN per dylib command under
-headerpad_max_install_names), plus what its estimate of the commands
over-counts: 28-byte dylib headers, and 16 bytes for classic dyld info,
32 for x86-64 chained fixups or -static; -r gets -headerpad, 32 by
default. mold left 0x100, and 16 bytes less when its sizing was early.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Within a segment, ld-prime gives the sections it knows fixed ranks and
keeps input order for the rest. In __TEXT, __text leads and every
other code section (S_ATTR_PURE_INSTRUCTIONS) follows it in input
order, ahead of __stubs, __objc_stubs, __init_offsets and the data
sections; in __DATA, __data keeps its input place among the unknown
sections after the Objective-C ones. mold's table, measured on ld-prime
1267, put custom code among the data and __data ahead of the rest.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A -r output's section headers set the order a later link first sees its
sections in. ld-prime puts __TEXT first and __LD last and every other
segment, __DATA_CONST included, where its first section appears; code
sections lead __TEXT in first-seen order, __text among them, with
__StaticInit after them; in __DATA the initializer lists and __data now
follow the Objective-C sections. mold used ld-prime 1267's fixed order:
__DATA_CONST before __DATA, __text first, __data among unknown sections.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An objc_msgSend$<selector> stub loads its selector and jumps through a
__got slot holding _objc_msgSend. ld-prime keeps that slot apart from
the one other code uses for _objc_msgSend - a __stubs entry's in
chained output, or a GOT load of its address - and so binds the symbol
twice, the stubs' slot first; mold shared one slot. The stubs' slot is
now tracked by index, and it sorts ahead of the other on the name tie.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
With classic dyld info a stub jumps through a lazy pointer that starts
out at its __stub_helper entry, which hands dyld_stub_binder the stub's
lazy-bind record on the first call. A stub whose symbol dyld resolves by
weak lookup is bound at load and jumps through its GOT slot instead, so
ld-prime gives it neither a lazy pointer nor a helper entry; mold sized
__la_symbol_ptr, __stub_helper and their indirect symbols for every
stub, leaving unused entries behind each weak-lookup one.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A stub, its lazy pointer and helper entry, and a GOT slot all belong to
one symbol, and dyld only cares which; ld-prime sorts them rather than
keeping the order relocations reach them. Stubs sort by name, bytewise,
across all libraries. GOT slots sort by what fills them - the image's
own addresses, the -bundle_loader executable, each library in
load-command order, weak-lookup binds, flat lookups nothing provides -
and by name within each, in classic and chained output alike.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
With lazy binding, each stub's lazy pointer first points at its entry
in __stub_helper, which pushes the stub's lazy-bind record offset and
jumps to the helper's header to enter dyld_stub_binder. On x86-64 an
entry is a 5-byte push and a 5-byte jmp; ld-prime starts every entry
on a 4-byte boundary, filling 2 zero bytes after each but the last, so
two stubs take 0x26 bytes where mold packed them into 0x24.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A chained fixup word packs a rebase's target with the distance to the
next fixup in the page; the page-start table's pointer_format says what
the target means. DYLD_CHAINED_PTR_64 holds a VM address and
DYLD_CHAINED_PTR_64_OFFSET an offset from the image's load address,
which dyld reads from macOS 12 on; ld-prime writes the latter for any
macOS 12 target and the former only when -fixup_chains forces chains
on an older one. mold always wrote VM addresses.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
-segment_order lists segment names for the layout of an image no dyld
loads. ld-prime accepts it only with -static (or -preload or firmware
platforms), once and with two names at least; it keeps __TEXT, which
holds the mach header, right after __PAGEZERO and __LINKEDIT last,
warning where the list says otherwise, and puts unlisted segments
after the listed ones, warning for each. mold let the list reorder
every segment, in any link.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A segment command carries an initial protection and a maximum the
segment may ever be raised to with mprotect. For -segprot name max
init, ld-prime keeps the first setting given for a segment, and on
arm64 makes the maximum the initial protection (rwx r gives r/r there,
rwx/r on x86-64). __LINKEDIT, which dyld reads, keeps its own with a
warning. mold took the last setting and both values on either arch.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
-image_base (alias -seg1addr) sets the address of the first segment
after __PAGEZERO. It matters for an image loaded at its link address:
a -static kernel, a non-PIE x86-64 executable, a dylib or bundle with
classic dyld info. ld-prime ignores it with a warning for a PIE
executable and a chained-fixups dylib or bundle, rounds a base up to a
page, and rejects one inside __PAGEZERO; mold started layout at the
base, then let __PAGEZERO override it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A dylib is loaded by dyld, which a -static image never meets, so
nothing it exports can be bound there. ld-prime's -l then looks for
lib<name>.a only - a directory holding only lib<name>.dylib or .tbd
makes the library not found, even under -search_dylibs_first - and a
dylib or stub named outright, by path or through -framework, is
dropped with "ignoring unexpected dylib". mold loaded it and failed.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
__DATA_CONST holds data only dyld writes (its fixups), and dyld makes
the segment read-only once they are applied (SG_READ_ONLY). A -static
image has no dyld and ld-prime gives it no such segment: __const and
the other read-only-after-fixup sections stay in __DATA. -data_const
asks for the segment anyway, and ld-prime then places it after __DATA
rather than before as in a dynamic image.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
__unwind_info is the table the userspace unwinder in libunwind looks
up, compact encodings with DWARF FDEs as the fallback. A -static image
has no such unwinder, and ld-prime gives it no __unwind_info: the
compact unwind records go, and an x86-64 image keeps every FDE of its
objects' __eh_frame, including those of functions that also had a
compact record, as a -r output does.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An arm64 Mac runs no code without a signature, so ld-prime ad-hoc
signs arm64 images by default: an LC_CODE_SIGNATURE blob of page
hashes at the end of __LINKEDIT. A -static image is no program the
running kernel loads - it is a kernel or a boot payload, signed if at
all by whoever packages it - and ld-prime signs one only when
-adhoc_codesign says so, on either architecture.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
LC_FUNCTION_STARTS and LC_DATA_IN_CODE serve debuggers and profilers of
a dynamic image, and LC_BUILD_VERSION tells dyld and the kernel which
platform an image was built for. ld-prime leaves all three out of a
-static image unless -function_starts, -data_in_code_info or
-version_load_command asks for them (the last option given wins), and
puts them where they go in any image; mold wrote all three.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A -static -pie image (a kernel) is moved by its own loader rather than
dyld, which reads no rebase info; ld-prime lists the pointers to slide
as LC_DYSYMTAB local relocations at the start of __LINKEDIT: 8-byte
non-extern UNSIGNED records naming the section each pointer points
into, addressed from the first segment (the first writable one on
x86-64), atom by atom with each atom's pointers last to first.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The rebase, bind and export streams (classic LC_DYLD_INFO or chained
fixups with LC_DYLD_EXPORTS_TRIE) are instructions to dyld, which a
-static image never meets: its pointers hold their final addresses.
ld-prime writes none of them, and LC_DYSYMTAB only under -pie, where
its local relocations let a loader slide the image; without -pie
nothing slides it and __mh_execute_header is an absolute symbol.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
LC_MAIN only gives dyld the entry point's offset; a -static image (a
kernel, a boot loader) has no dyld, so whoever loads it starts its
first thread from LC_UNIXTHREAD: a thread-state flavor and count
(ARM_THREAD_STATE64, 68 words, on arm64; x86_THREAD_STATE64, 42 words,
on x86-64) and the registers, all zero but pc or rip, which holds the
entry address. ld-prime puts it where LC_MAIN would go.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
From macOS 15 a class reference slot becomes the class's GOT entry,
and adrp+ldr loads of a class the image defines relax to adrp+add.
ld-prime relaxes a class only if each adrp of its slot is followed in
its function by one @PAGEOFF use before the next: otherwise (one adrp
feeding two ldrs, a split pair) every load of the class keeps reading
a rebased GOT slot. An adrp+add taking the slot's address keeps the
slot without stopping the loads' relaxation; a 32-bit ldr relaxes too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
With selector stubs the linker synthesizes an _objc_msgSend$<sel>
stub and a selector reference slot for each selector called. ld-prime
lays the stubs out sorted by selector, bytewise, with their slots in
the same order after the inputs' __objc_selrefs, and a stub's slot
takes over any input selector reference to its selector, which is
dropped from its input position. mold used first-reference order and
had a stub load the input's slot where one existed.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An objc_msgSend$<sel> stub loads a selector reference that the linker
synthesizes when no input has one, and that points at a selector name
in __objc_methname. ld-prime coalesces that name with an input string
of the same bytes, keeping its own, so the section holds each name
once and is placed by its surviving input strings: when a stub names
every input string, after all input-derived __TEXT. mold appended a
second copy of each name to the section's tail.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An app extension links with -application_extension, which marks it
MH_APP_EXTENSION_SAFE; a dylib built without it lacks the flag, and a
stub says so with the not_app_extension_safe flag. ld64 warned when an
extension linked such a library, but ld-prime says nothing, for a binary
or a stub, an executable, dylib or bundle, on either arch. The warning
goes, and with it the flag parsed only for it; -w's test now uses the
newer-object warning both linkers print.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A dylib may list the clients allowed to link it directly (LC_SUB_CLIENT,
a stub's allowable-clients; SwiftUICore names SwiftUI and AppKit).
ld-prime refuses any other output, matching -client_name or the output's
leaf as a prefix of a listed client, and never binds to such a library
through a re-export, so a SwiftUI program binds to SwiftUI. mold read no
stub lists, bound to SwiftUICore, and refused subframeworks that name no
clients, which ld-prime lets anyone link.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A re-exported library found neither in the search path nor at its
install path is left out of the link: its symbols are then missing
from the re-exporter's exports, and a client needing one fails as
undefined. ld-prime reports it as "ignoring missing indirect library:
library for install name '<name>' not found"; mold said "<parent>:
reexported library not found: <name>", so scripts that watch for the
message missed it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A dylib names each library it re-exports by install name. ld-prime
finds it by that name's leaf, less the extension, in the library search
path first (-L, then the SDK's /usr/lib; libB.1.dylib as libB.1.tbd or
libB.1.dylib), and at the install path only after that, or after an
@loader_path, @executable_path or @rpath name fails to resolve. The
install name the file found carries then decides whether it is public;
mold tried the install path alone and failed the link.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A dylib's private re-exports (a library outside /usr/lib and the
top-level frameworks) have their exports merged into the re-exporter's,
so their symbols bind with the re-exporter's ordinal. When the library
that defines a symbol is in the link on its own, named or auto-linked,
ld-prime binds to it instead, in either order; mold kept whichever
came first. Each dylib now remembers the private libraries it merged,
and a claim goes to the one that is linked (Swift's _Builtin_float).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The exports come from one of three sources: an export list
(-exported_symbol*), an unexport list (-unexported_symbol*), or
-no_exported_symbols. ld64 refuses any second kind, with a message
named after the option that comes second, e.g. "-unexported_symbols_list:
-exported_symbol*, -unexported_symbol* and -no_exported_symbols cannot be
used together". mold rejected only -no_exported_symbols with the lists
and applied an export list and an unexport list together.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An export list can publish a dylib's symbol from this image: ld-prime
gives the export trie a re-export entry (EXPORT_SYMBOL_FLAGS_REEXPORT
with the dylib's ordinal and no new name) and the symbol table an
N_INDR entry beside the import, as -reexported_symbols_list does. A
plain name, an initial undefine, need not be referenced and keeps its
dylib loaded under -dead_strip_dylibs; a pattern re-exports what it
matches among the symbols the link imports. mold exported nothing.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld64 starts resolution from "initial undefines": the -u names and the
names an export list gives without wildcards. Each pulls in the archive
member defining it, roots its atom under -dead_strip (a hidden one
stays, as a local), and must resolve - ld-prime reports a missing one,
"referenced from <initial-undefines>", even under -undefined
dynamic_lookup or -U, and a -r output keeps it undefined. mold matched
export lists only against symbols already there and let -u names go to
dynamic lookup.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
S_ATTR_LIVE_SUPPORT marks sections whose atoms -dead_strip keeps only
when they reference something live, and then with all they reference.
ld64 checks them once, in input order, after what the roots reach is
marked, so an atom whose only target a later live-support atom makes
live stays dead; references to dylib symbols make nothing live. mold
treated the attribute like no_dead_strip and kept every such atom along
with everything it pointed to, dead code included.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Each object records the minimum OS it was compiled for, and a linker
warns when an input needs a newer OS than the output will declare. A
-r link without -platform_version declares the first object's minimum
(the output's LC_BUILD_VERSION takes it), and ld-prime checks every
later input against that; mold skipped the check whenever no target
was given, so a 15.0 object in a 14.0 -r output went unmentioned.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
LC_BUILD_VERSION, which names a platform, minimum OS, SDK and the
tools that built the image, first shipped with macOS 10.14; older
loaders know the platform only from LC_VERSION_MIN_MACOSX {version,
sdk}. ld-prime writes that legacy command instead for an x86-64 target
below 10.14.0, in a final image and in -r output, and LC_BUILD_VERSION
for arm64 at any version. The -r target is now settled before the load
commands are laid out, since the two commands differ in size.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
LC_DYLD_CHAINED_FIXUPS replaces the classic rebase and bind opcodes
with pointers that chain through each page, and dyld reads them from
macOS 12 on. ld-prime uses them from 12 for every arm64 output and for
x86-64 dylibs and bundles, and only from 13 for an x86-64 executable;
mold held every x86-64 output back to 13, so a 12.x dylib got classic
dyld info, lazy binding stubs and __mod_init_func where ld-prime writes
chains and __init_offsets.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld64 reports library options given more than once in one warning,
"ignoring duplicate libraries: '-lbar', '-lfoo'", each repeated
spelling listed once in sorted order. The option is part of the
spelling: -lz then -weak-lz or -needed-lz repeats nothing (the second
naming changes the dylib's load command), while -weak-lz, -needed-lz,
-reexport-lz or -hidden-lz twice does. mold warned per repeat, for -l
and -weak-l alike, and not for the other forms.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
LC_LOAD_WEAK_DYLIB, LC_REEXPORT_DYLIB and -dead_strip_dylibs survival
belong to a library, and ld-prime gives it what any of its namings
asks, in any order; mold dropped a second naming of a loaded file and
marked needed only the dylibs a naming newly added, so -framework
CoreFoundation -weak_framework CoreFoundation stayed strong. A library
first loaded as a re-export takes its weakness from its first
command-line naming, not from its parent; an auto-link naming, a hint,
changes nothing.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
How a file is named decides how it loads: -force_load makes every
archive member live, the weak, reexport and needed variants of -l,
-framework and a path set the dylib's LC_LOAD_WEAK_DYLIB,
LC_REEXPORT_DYLIB and -dead_strip_dylibs survival, and -hidden-l keeps
an archive's definitions out of the export trie. Four positional bools
and a separate needed pass become one struct, as mold's ReaderContext
carries --as-needed and --whole-archive; read_input_files shrinks.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
LC_SEGMENT_SPLIT_INFO's version-2 stream tags each reference with an
adjustment kind (a 64-bit pointer, an ARM64 ADRP, an OFF12, a BR26,
...) so a cache builder can fix it up when it slides segments apart.
#34 defined the whole table in split_info.rs, eight kinds unused and
warned about; the used ones join the other format constants in
macho_consts.rs, and the grouping map gets a type name clippy wants.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An nlist's n_sect names the section a symbol belongs to, and only it
tells apart two sections at one address: a label on an empty section
starts where the next section does, and a label past a section's last
byte (an array's end, which gets no subsection of its own in an object
without MH_SUBSECTIONS_VIA_SYMBOLS) ends where the next one starts.
ld-prime keeps both in their own section; mold put the first in the
next section and left the second undefined, local or not, and in -r.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An object may name an undefined symbol that no relocation targets (a
bare .globl); ld-prime drops it without an error or import, and #34
matched that by gathering every live relocation's target into a
HashSet on each link: 3 ms became 64 ms on a 6M-relocation link. Most
links leave nothing undefined, so the relocations are now scanned only
when a symbol is, into per-symbol flags filled in parallel.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
MH_PIE tells the kernel an executable may be loaded at a random slide;
arm64 macOS refuses one without it, so ld-prime ignores -no_pie for
arm64 with "-no_pie ignored for arm64*" and keeps the flag, and warns
"-no_pie is deprecated" from the OS versions it defaults to chained
fixups for (macOS 12 on arm64, 13 on x86-64). A -static image is PIE
only with -pie. The resolution needs the target, so parse_args takes
mold's TargetTraits and settles the target from the inputs itself.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The header flags MH_BINDS_TO_WEAK and MH_WEAK_DEFINES are found by
scanning every symbol; one of the scans looks each exported name up in
every dylib's weak-export set. copy_mach_header ran those scans
serially, and it ran twice: once when the output is written and again
after the content hash is known, only to fill in LC_UUID. On a debug
link of a large Rust program, with hundreds of thousands of long
mangled names, each call took about 20 ms.
Run the scans in parallel, and after hashing write just the UUID into
the LC_UUID command that the first call wrote, leaving the rest of the
header as it is. The output is byte-identical. Linking a 157 MB
rust-analyzer debug binary on an Apple M5 Max, the uuid phase of
-print_statistics goes from 24.5 ms to 3 ms, and the first header
write shrinks the same way.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime gives each x86-64 stub its own alignment - a lazily bound one
jumping through __la_symbol_ptr byte-aligned, one jumping through a GOT
slot 2-byte aligned - and __stubs takes the largest, so a single stub
bound by weak lookup (operator new beside a lazily bound printf) makes
the section 2-byte aligned even with __stub_helper present. 5038821
byte-aligned the section as soon as one stub went through the helper.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime ignores each input section with no bytes that defines no
symbol naming an atom: it makes no output section, takes no part in
section ordering, and is dropped from -r outputs too. The arm64
assembler's ltmpN labels name atoms only in an object without
subsections, so they keep an empty section there alone. A relocation
into such a section is an error ("points to section(N) with no
content", or "invalid r_symbolnum" through an ltmpN on it). 1e65e0c
dropped only output sections whose members were all empty, counted
ltmpN everywhere, and left -r alone.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A non-extern relocation's r_symbolnum is the 1-based ordinal of the
section it points into, and ld64 reads it that way. We located the
section from the target address alone, which is ambiguous where two
sections share an address - an empty section and its successor, or
one section's end and the next one's start - and so a pointer into an
empty section resolved to the section after it. The named section now
wins when the address lies in it or one past its end.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An assembler's empty .section, or an emptied coverage section, makes
no section in ld-prime's output when no symbol is defined in it; ours
carried such sections through. A symbol keeps the section, even a
local one: the arm64 assembler starts every section with an ltmp
label, so on arm64 an empty .section stays, in ld-prime's output as
in ours, while on x86-64 it goes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime writes a dylib's LC_SUB_CLIENT and LC_SUB_FRAMEWORK commands
after its LC_RPATHs, not just after its LC_LOAD_DYLIBs. 7804751 put
them between the libraries and the rpaths, which matched only a dylib
linked without -rpath.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime sets SG_NORELOC on a segment whose sections all come from the
command line - -sectcreate contents as well as -add_empty_section
anchors - since nothing in it is relocated. 7804751 flagged only a
segment of empty -add_empty_section anchors, so a custom segment
holding an embedded plist or blob kept flags 0. A section conjured
for a boundary symbol or any input section still leaves the flag off.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime sets N_NO_DEAD_STRIP on every symbol, local or global, that a
-r output carries from an input section with S_ATTR_NO_DEAD_STRIP -
except in __objc_classrefs - so the next link keeps it even when the
output section took its attributes from another member without the
attribute. We copied the symbols' own flags only, and such a symbol was
dead-stripped by a later -dead_strip link.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A -r output is input to another link, so ld-prime (27037) copies each
output section's type and attributes verbatim from its first non-empty
input section - coalesced, no_dead_strip, live_support, no_toc and the
rest - dropping no_dead_strip only on __objc_imageinfo and
__objc_protolist. We normalized them as for a final image, so a
no_dead_strip table nothing referenced lost the attribute and a later
-dead_strip link discarded it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An import is listed only while live code or data still refers to it:
after -dead_strip, ld-prime drops the imports only stripped functions
used. A reference is a relocation from a live subsection, a stub or GOT
slot (unwind personalities, the selector stubs' _objc_msgSend and
dyld_stub_binder have one), a pointer field of a synthesized ObjC
record, a -u name, or an -alias or re-export of the import. A private
extern with no section is kept as a local too: an absolute symbol as
"(absolute) non-external (was a private external)", like the hidden
__mh_execute_header, while a hidden -alias of an import is dropped and
the import it names stays.
Measured on ld-prime: dead-stripping a function that alone called
getpid and _exit drops both imports from the table; an absolute
.private_extern symbol is listed as a local; a dylib linked with
-alias _puts _my_puts -unexported_symbol _my_puts lists neither
_my_puts in the table nor in the export trie, and _puts stays an
undefined import.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
C++'s inline thread_local is a weak TLV definition in every image that
uses it, and dyld coalesces them so all images share one variable. A
TLVP load of it must therefore read the descriptor's address from a
__got slot bound by weak lookup, as ld-prime does, not relax to the
image's own descriptor. We gave a slot only to imported thread-locals:
arm64 relaxed, splitting the variable, and x86-64 read an unallocated
slot and crashed.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld64 moves only absolute -L and -F directories under each -syslibroot,
using those that exist and falling back to the original. A relative
directory stays relative to the working directory. We rerooted both
kinds, and since the compiler driver always passes -syslibroot, -L.
searched the SDK's root and `cc main.o -L. -lfoo` failed with "library
not found"; -F. failed the same way.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Without subsections a section is one atom, and ld64 takes its
attributes from one symbol at the section's start, ignoring ltmpN: a
non-weak one if any, else the last weak one. Only that symbol can't be
coalesced; the rest are weak labels into the atom. We unweakened every
lowest-addressed weak symbol, even with nothing at the section start.
A .weak_def_can_be_hidden name becomes hidden and non-weak, except in
-r, which keeps it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Since 97f4c07 only native references pin a bitcode definition in an
executable, but two were missed. A native common (`int x;` under
-fcommon) that a bitcode definition overrides is not a separate
variable: its code addresses the winning definition's storage, so
internalizing the definition left the native side a zero-filled copy
of its own. An -alias base is referenced by the linker, not an object,
and internalizing it failed the link. ld-prime pins both.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
-exported_symbols_list and -unexported_symbols_list change a
definition's scope, not just the export trie: ld64 makes each symbol
they leave out a private extern in every output kind, -r included. nm
then shows it as "was a private external", -dead_strip can drop it from
a dylib, and a hidden weak definition is referenced directly, with no
weak-lookup bind and neither MH_WEAK_DEFINES nor MH_BINDS_TO_WEAK. We
only filtered the trie; this ports sold's handle_exported_symbols_list
and handle_unexported_symbols_list, which set the scope after symbol
resolution. A hidden __mh_execute_header stays a local in the symtab.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
mold-rust imports rayon::prelude::* at the top of each module, grouped
with the other crate imports, rather than inside every function or
block that iterates in parallel. Do the same so the two codebases read
alike. Blocks that existed only to scope the import are unwrapped; the
glob adds only rayon's parallel-iterator traits, so no call resolves
differently.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
i386 aligns 64-bit integers to 4 bytes, so Symbol and NameRecord are
smaller there than on other hosts, and the assertions that they are
exactly 48 and 16 bytes failed to compile. Assert upper bounds instead,
which still catch growth on 64-bit hosts, and check the i686 build in
CI.
std has no AtomicU64 or AtomicI64 on hosts such as 32-bit PowerPC,
32-bit MIPS and pre-v6 ARM, so mold did not compile there, whereas the
C++ version fell back to libatomic. Take those two types from
portable-atomic, which uses the std types where they exist and falls
back to locks elsewhere. Also check the 32-bit PowerPC build in CI; the
existing armv7 check did not catch this because armv7 has 64-bit
atomics.
See https://github.com/rui314/mold/discussions/1643#discussioncomment-18603572.
The .tbss layout loop gained a second end-of-list check next to the
existing one, for a case that cannot happen: .tbss is never the last
chunk. DigestMap::new was rewritten with repeat_with, which took five
lines to say what one line did. Restore both.
Follow-up to https://github.com/rui314/mold/pull/1685.
FileRecord::size was size_of::<Self>(), and record sizes were written
both ways. Use size_of everywhere. Unlike the trait method, it is a
const fn, so it also works in the compile-time assertions.
https://github.com/rui314/mold/pull/1683 unified the spelling in the
other direction.
An ignored SIGCHLD is inherited across exec, e.g. from `trap '' CHLD`
in a shell. The child that fork_child creates is then reaped
automatically, so the parent's waitpid fails with ECHILD. The parent
ignored that and read the untouched zero status as a successful exit,
so a link that failed exited with status 0. Report a failure instead.
Also read and write the completion byte through File, which removes
two raw libc calls.
Based on https://github.com/rui314/mold/pull/1682.
Both callers of from_hex already check that the byte is a hex digit,
so the new assertion was redundant. The utsname rewrite traded
MaybeUninit::assume_init for mem::zeroed without removing an unsafe
block, so restore the original code.
The fix it tests is target-independent, and only its last check was
x86-64-specific. Count the dynamic relocations against foo and bar
instead of looking for R_X86_64_64. PowerPC is skipped because GCC puts
the read-only pointers in writable sections there, so nothing is
promoted.
LLVMgold calls get_symbols with a null pointer and a zero count for an
IR file that has no symbols, e.g. one compiled from an empty source
file. slice::from_raw_parts does not accept a null pointer even for a
zero length, so the debug build aborted on its precondition check.
Use an empty slice for a zero count, in add_symbols as well.
Based on https://github.com/rui314/mold/pull/1680.
Building the thread pool fails when threads cannot be created, e.g.
under a low `ulimit -u`. That hit an expect, so mold aborted with a
panic message rather than reporting the error like other system
errors.
Taken from https://github.com/rui314/mold/pull/1678, which also
added checks for malformed inputs that are not included here.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Input files are opened in parallel, and each file is appended to the
file pool when it is opened, so the pool's order changed from run to
run. --dependency-file and --repro list files in that order. Return
the pool sorted by name instead.
The dependency-file tests expected a space after a.o, i.e. that a.o
was not the last dependency. That failed intermittently on Alpine,
where libc.so is not a linker script and no dependency reliably comes
after a.o.
report_out_of_range looked the record up again through the file's
relocation table while apply_reloc_alloc held that table mutably, which
created a shared view of memory behind a live unique reference, and it
walked the CREL iterator from the start to do so. Every caller has the
record at hand, so pass it in. The loop indices that only served the
lookup are gone with it.
Cherry-picked from https://github.com/rui314/mold/pull/1674.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
CieRecord stored a shared slice over its .eh_frame relocation records,
but reattach_fragment_relocations later rewrites those records in place
through the private input mapping, and the FDE and CIE accessors read
through the stale slice afterwards. The record now keeps only the
relocation section index and reads the current table through
ObjectFile::relocations, as decoded CREL tables already did. The raw
relocation view no longer claims a 'static lifetime.
Cherry-picked from https://github.com/rui314/mold/pull/1674.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
mold accepts a multi-letter option with a single dash, but the option
chain is first-match, and some single-letter options were tried before
long options starting with the same letter. "-entry=main" was thus read
as "-e ntry=main", "-filter libf.so" as "-f ilter" followed by an input
file, and "-fix-cortex-a53-843419" left a bogus DT_AUXILIARY entry. GNU
ld and lld read all of these as the long option.
Move each single-letter option past the last long option it shadows.
Conversely, GNU ld and lld read "-execute-only" as "-e xecute-only" and
"-omagic" as "-o magic", because those names take double dashes only.
Spell such names with double dashes in the option table, which
match_option now honors, replacing the rule that all names starting
with "o" need two dashes. Where GNU ld and lld disagree, follow GNU ld.
Thanks to @lu-zero for investigating this issue and proposing a fix in
https://github.com/rui314/mold/pull/1670.
Fixes https://github.com/rui314/mold/issues/1671
ld-prime writes the .tbd's compatibility version (compatibility-version
in a YAML stub, compatibility_versions in a JSON one: 150.0.0 for
CoreFoundation, 300.0.0 for Foundation, 0.0.0 for a stub that says 0,
1.0.0 when the stub gives none) into the client's LC_LOAD_DYLIB; we
wrote 1.0.0 for every stub. Each library of a stub takes its own
document's version, so a public library inlined in an umbrella's stub
gets its own too.
The legacy -macos_version_min flag carries no separate SDK version,
and ld-prime records the SDK as the deployment target the flag names,
in executables and -r output alike, even after a -platform_version
that gave one (clang passes both for -Wl,-macos_version_min). We now
do the same instead of keeping the earlier SDK or leaving it unset.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime puts the pointer to an imported TLV descriptor in an ordinary
__got entry - chained fixups or classic dyld info, arm64 or x86-64 -
and writes no __DATA,__thread_ptrs section. Measured on ld-prime: a
program reading a _Thread_local defined in a dylib binds
libfoo/_counter in __DATA_CONST,__got and has no __thread_ptrs, on
both architectures and with -no_fixup_chains too. The TLVP loads now
go through the GOT slot, and the __thread_ptrs chunk and its slot
index go away.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A library the link already holds as a public re-export (Foundation's
stub brings CoreFoundation) that an object's auto-link option then
names is a hint like any other auto-linked library: ld-prime lists it
only if something binds to it, and drops CoreFoundation from a Swift
program linked with -framework Foundation that never binds to it. We
kept it, since loading it for the auto-link option cleared its
implicit flag without marking it auto-linked. Naming it on the
command line still keeps it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime loads a re-exported library that lives in a public location
(/usr/lib/X, a top-level framework) as a dylib of its own: symbols
found through the re-export bind to it, and it gets a load command
after the libraries named on the command line, weak when every
reference to it is a weak import. That held for libraries in files of
their own; a library inlined as a further document of the same .tbd
was merged into the re-exporting stub instead, so _bar from
SomeFramework's inlined /usr/lib/libbar.dylib bound to SomeFramework,
where ld-prime binds it to libbar.
The stub parser now keeps each document apart: the main library with
the names it re-exports, and the inlined documents as libraries in
their own right. The re-export loader resolves a public name to its
file on disk when one exists (ld-prime takes that stub over the
inlined copy, current version and symbols alike) and to the inlined
document otherwise, loading it as an implicit dylib; a private
inlined document merges into the re-exporting stub as before.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
clang emits __gcc_except_tab right after __text, so first-seen order
put the exception tables ahead of __const and __cstring. ld-prime
places them after those sections, just before __eh_frame, whatever
the input order: its -r output of two C++ objects that throw and catch
reads __text, __cstring, __gcc_except_tab (then __eh_frame on x86-64),
on arm64 and x86-64 alike (ld 27037).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
LC_SEGMENT_64, LC_SYMTAB, LC_BUILD_VERSION, LC_DATA_IN_CODE, then the
carried LC_LINKER_OPTIONs; ld-prime writes no LC_DYSYMTAB in a -r
output. The build version comes from -platform_version, else from the
first object (ld-prime takes the first object's minos and SDK and
warns about later inputs built for a newer OS), with the linker's tool
entry as in a final image; we wrote the command version-less and
tool-less, with minos 0 without -platform_version.
LC_DATA_IN_CODE is present even with no entries. The inputs' entries
are merged at their new addresses: in an object an entry's offset is
an address, not a file offset, and ld-prime writes the address it
lands at. All measured against ld 27037 on arm64 and x86-64.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime leaves the entries of the __objc_classlist, __objc_nlclslist,
__objc_catlist and __objc_nlcatlist sections without any symbol: the
l_OBJC_LABEL_CLASS_$ and l_OBJC_LABEL_NONLAZY_CLASS_$ labels an
Objective-C object carries there vanish, and nothing takes their
place (ld 27037, arm64 and x86-64). We named each entry l<nnn>
without N_PEXT, beside the l<nnn> private externals that name the
records of __cfstring, __objc_selrefs and __objc_classrefs. Every
atom the -r writer still names is now a private external, so the
kind table only carries the record size.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An object without MH_SUBSECTIONS_VIA_SYMBOLS is made of whole-section
atoms. For such an input of code and data, ld-prime marks every
symbol no-dead-strip, drops the alt-entry marker (meaningless without
subsections), keeps the arm64 assembler's ltmp labels (they name the
atoms), and clears the flag on the output when any input lacked it
(ld 27037, arm64 and x86-64). Each object now records whether it had
the flag, for the -r writer.
The alt-entry test's input gains .subsections_via_symbols, which its
scenario (Swift metadata) has; without it the markers it checks would
now be dropped. The same source assembled without the directive is
the whole-section case.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The input CIEs and FDEs are copied through under the section's
conventional flags (0x6800000b), unnamed, with the CIE pointer,
pc_begin and LSDA fields recomputed as self-relative values; the only
relocation is a CIE's personality cell, the 4-byte pcrel GOT reference
compilers emit. ld64 classic named every CIE EH_Frame1 and every FDE
func.eh and wrote the fields as SUBTRACTOR pairs against them, which
is what we did, under no flags at all; ld-prime (ld 27037) does
neither. For the tests' inputs, the section's size, flags and
relocations now match ld-prime's on arm64 and x86-64, and so do its
bytes where __gcc_except_tab lands at the same address.
The final-image flag table in passes.rs had a -r case for __eh_frame
that the -r writer never consulted, since that section is rebuilt
from the parsed records; it goes too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime chooses the initializer layout from the fixup format that
-fixup_chains or the deployment target asks for, before -undefined
dynamic_lookup sends the fixups themselves back to classic dyld info:
an arm64 executable for macOS 12 linked with -undefined dynamic_lookup
has LC_DYLD_INFO_ONLY and __TEXT,__init_offsets. Only -no_fixup_chains
keeps __mod_init_func (unless -init_offsets asks for the offsets
outright), and an explicit -fixup_chains brings __init_offsets below
the chained-fixups deployment target too. We tied the section to the
fixup format actually used.
The deployment-target half of use_chained_fixups() becomes
chained_fixups_by_default() so that both decisions share it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime writes current and compatibility version 0.0.0 into
LC_ID_DYLIB unless -current_version or -compatibility_version says
otherwise; 1.0.0 was ours alone.
ld-prime ad-hoc signs arm64 images and leaves x86_64 ones unsigned
(Intel Macs and Rosetta run unsigned code): executables, dylibs and
bundles alike, with no codesign option from the compiler driver. So
-adhoc_codesign becomes an Option, like -fixup_chains, and the default
is decided from the target; -adhoc_codesign and -no_adhoc_codesign
still override it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
LC_SUB_CLIENT and LC_SUB_FRAMEWORK follow the LC_LOAD_DYLIB commands,
clients first. A segment holding nothing but -add_empty_section
anchors is flagged SG_NORELOC.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Sections of one rank follow the order their first input section was
seen in (object, then section ordinal): __cstring before
__gcc_except_tab when an object has them that way; a merged literal
section counts from its first input. The synthesized __common takes
its place from the first object with a common symbol. The GOT closes
__DATA_CONST after every input-derived section.
Alignment: a common symbol without one of its own is aligned to its
size rounded to a power of two (capped at the page on arm64, 16 bytes
on x86-64); __thread_data and __thread_bss share the stricter of
their alignments; a section aligned beyond the page is reduced with
ld64's warning. A final image always has a __TEXT,__text section, and
LC_FUNCTION_STARTS and LC_DYLD_EXPORTS_TRIE are present even when
empty (8-byte tables).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime lists the word the stub helper hands dyld_stub_binder as a
local symbol __dyld_private in __DATA,__data: every image it links
with classic dyld info and lazy binding has it (nm -m shows
"(__DATA,__data) non-external __dyld_private"), on arm64 and x86_64
alike. We wrote the word without a name.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A bitcode function that only other bitcode calls need not survive the
internalizer: libLTO resolves that call itself. ld-prime lets such a
function go local: linking a bitcode main that calls a bitcode
times2(), it exports _main and not _times2. So in an executable only
references from non-bitcode objects, the entry point, -u names and the
export list pin a symbol defined in bitcode.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Measured on ld-prime: relative method lists are written for every
arm64 image and, on x86-64, for dylibs and bundles but not
executables. A category merge in the absolute form writes the merged
method lists into __objc_data; the protocol and property lists stay
in __objc_const.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Three dylib-selection rules measured on ld-prime:
- -dead_strip_dylibs never drops libSystem, even from an image that
binds nothing from it (dyld needs it to run anything): an empty
main linked with -dead_strip_dylibs still loads libSystem.B.dylib.
- -needed_library and -needed_framework pin only the dylibs they name.
The public libraries a stub re-exports (CoreFoundation's libobjc)
load implicitly like any other and get a load command only when
something binds to them; -needed_framework,CoreFoundation alone
lists CoreFoundation and not libobjc.
- -all_load does not apply to clang's runtime archive
(libclang_rt.*.a, which the compiler driver adds to every link):
its members are loaded only when referenced.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The Swift compiler keeps an overlay loaded with a weak reference to
its __swift_FORCE_LOAD_$_<overlay> marker. ld-prime lists the dylib as
a dependency (weak when every reference to it is weak) but writes no
fixup for the word, which stays zero; we bound it. Measured on
ld-prime, arm64 and x86-64, chained fixups and classic dyld info
alike: a pointer to a weak-imported __swift_FORCE_LOAD_$_swiftFoo gets
no bind while a pointer to another weak import beside it binds
[weak-import], and the dylib loads weakly.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ld-prime byte-aligns x86-64 __stubs only when they go through
__stub_helper; with chained fixups, -bind_at_load or weak-lookup
stubs alone they are 2-byte aligned. The classic-vs-chained rule we
had was the common case of that.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Relative method lists (__TEXT,__objc_methlist) are an arm64
conversion only; an x86-64 image keeps the compiler's absolute lists
in __objc_const at any deployment target. x86-64 __stubs are 2-byte
aligned with chained fixups and byte-aligned with classic dyld info,
and __objc_stubs byte-aligned.
Class references coalesce whenever they are not folded into the GOT
(deployment targets before macOS 15), as ld64 keeps one reference per
class.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
-weak_framework, -weak_library and -weak-l: ld64 binds each import
from the library weak-import and marks it N_WEAK_REF in the symbol
table, whatever the references say. Measured on ld-prime, arm64 and
x86-64: a program linked with -weak_framework CoreFoundation that
calls CFStringGetLength and uses a CFSTR binds _CFStringGetLength and
___CFConstantStringClassReference [weak-import] and lists both as
weak external undefineds, although neither reference is weak. We
loaded the framework weakly but bound its symbols as ordinary imports.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Under -flat_namespace every import is a flat lookup that dyld resolves
at load time, so ld-prime does not claim MH_NOUNDEFS: a flat-namespace
executable's flags are DYLDLINK|PIE (0x00200004) only.
An exported weak definition sets MH_BINDS_TO_WEAK along with
MH_WEAK_DEFINES whether or not the image references it itself, since
another image's copy may replace it: ld-prime writes 0x00118085 for a
dylib whose only weak definition nothing calls. A strong definition
that overrides a dylib's weak export still sets MH_WEAK_DEFINES alone.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Without MH_SUBSECTIONS_VIA_SYMBOLS a section is one atom, which ld64
names after the symbol at its start - and an atom is never weak: that
symbol loses N_WEAK_DEF while later symbols in the section keep theirs
as aliases into the atom. Measured on ld-prime: a section holding only
a weak _w exports _w as a plain definition, and a weak _w followed by
a strong _pad2 makes both plain; two such weak _foo definitions in
whole-section objects are a duplicate-symbol error, so merge-scope now
builds its objects with subsections.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
sha2 0.10 uses the x86 SHA extensions by default but the ARMv8 ones
only with its asm feature, which adds a C build step, so on Apple
silicon the code signature's page hashes and the content UUID were
computed in software, at about 470 MB/s per core. sha2 0.11 detects
the ARMv8 instructions at run time without any feature flag and hashes
at about 3 GB/s.
Linking a 157 MB rust-analyzer debug binary on an Apple M5 Max, the
page_hashes phase of -print_statistics goes from 31 ms to 6 ms. The
output is unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Zip the glob NFA's three parallel arrays and fill a bracket range with
one slice fill, merge the two glob module headers left over from two C++
files, check SipHash's output width at compile time and split its input
into words with as_chunks, mark Timer must_use so a dropped one is a
compile-time warning, remove a Windows TODO from the C++ demangler that
never applied to this implementation, and fix a typo in a comment.
The merged glob header no longer lists qt_private_api_tag* as an
example of an Aho-Corasick pattern, since prefix patterns are matched
directly rather than by the Aho-Corasick matcher.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Each output section promoted the imported symbols of its read-only
absolute relocations and classified its relocations in one pass, while
other sections ran in parallel. Whether a writable section's relocation
against the same symbol became a dynamic relocation therefore depended
on which section ran first, so the output changed from run to run. In a
non-PIE that references an imported variable and function from both
.rodata and .data, 37 of 100 links in one measurement emitted two extra
R_X86_64_64 dynamic relocations.
Collect all sections' relocations, promote, and only then classify, so
that the result is the same in every run: the word is resolved at link
time whenever a copy relocation or canonical PLT entry makes its address
known.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The --undefined names were cloned only because get_symbol takes the
whole context mutably, so intern them through the symbol table instead.
The section-word reversal swaps whole words, the .tbss layout loop no
longer indexes past the last chunk, the has_lto_obj predicate replaces
an inline copy of itself, and the --exclude-libs ALL test is hoisted out
of the file loop. The dynamic symbol groups are extended in place, the
IRELATIVE count loses a temporary, and thunk addresses zip symbols with
their offsets.
In ICF, the digest no longer converts to little-endian on the way into
a hash whose key is random per run, the digest map is filled with
repeat_with, and HashMap is imported once.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
apply_reloc_nonalloc on ARM32, PPC32, m68k and SH-4 read the relocation
section of a non-allocated section as an array of ordinary records. A
CREL table of a non-allocated section is not decoded ahead of time, so
for an object with SHT_CREL relocations those bytes are compressed
records: linking a PPC32 or m68k object that Clang 19 built with -g and
-Wa,--crel panicked with a failed length assertion in
records_from_bytes. Use the relocation iterator, which decodes CREL
tables, as the other targets already do.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A relocatable link keeps every COMDAT group's members in output
sections of their own, so an object with many groups of one name, such
as the .debug_macro groups GCC emits with -g3, produces output sections
whose sort keys tie. The tie fell back to the order in which the
parallel creation phase happened to make them, which changed from run
to run: 30 runs of the link in relocatable-compressed-debug-info.sh
produced 13 different outputs. Break the tie with the first member's
file priority and section index, which are in input order.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
C++ mold adds the _savegpr0_* family after the __start_ and __stop_
symbols, and the internal object's symbol order is the order in the
output .symtab. Match it so that ppc64le outputs get the same .symtab,
and hence the same build ID, as C++ mold's.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
C++ mold defines these target-specific segments after PT_GNU_RELRO, but
the port defined them before PT_GNU_STACK. ARM and RISC-V outputs
therefore had their program headers in a different order than C++
mold's for the same input, and a different build ID. Define them where
C++ mold does.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The four places that write a target word through an IS_64 branch
(.got, .got.plt, .relr.dyn and an output section's absolute
relocations) now use the existing Word::<E>::new(value).write(slot).
The padding filler walks its buffer with chunks_exact_mut; thunk
symbols zip their two parallel vectors; relr_offsets concatenates its
shards once; the .ARM.exidx lookup uses find_map instead of position
plus a second match, and text_end borrows the section headers instead
of copying them; .gnu.hash looks each symbol's hash up once;
merged-section resolution takes the timers by destructuring instead of
cloning them; OutputSectionId::new folds its two checks into one; the
.sframe writer slices each FRE block by start and length; WORD_SIZE is
derived from the word type like IS_64; and ElfDyn derives what its
siblings derive.
The doc comment on ChunkHeader's local-symbol fields no longer starts
with a stray "For --gdb-index", and a few comment typos are fixed.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
shndx_at_in was identical to shndx_at, is_discarded_comdat re-implemented
is_discarded_comdat_sym, ObjectFile's Display duplicated display_file,
rels_mut looked its table up twice, find_null re-implemented a chunked
position search, the archive member name reader re-implemented cstr_at
and memchr, the file type probe indexed the header table it was
iterating, and the linker script reader spelled slice patterns as length
checks plus indexing.
The loop in is_dwarf32 now compares the end of the next unit header with
the section size instead of subtracting its start from the size, which
wrapped around when a unit's size field pointed past the end of a
malformed section.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The command-line and result types of the per-target link functions were
spelled out in the twenty target crates, the executable and the driver.
Name them Cmdline and LinkResult in the driver and use the names
everywhere else.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The record views in elf.rs relied on a debug assertion, and therefore on
the FileRecord contract alone in release builds, for the alignment that
their pointer casts require. An inline const assertion is evaluated per
instantiation, so a record type that violates the contract no longer
compiles.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Several comments compared the code to C++ mold's arena storage and
chunks, or named functions that do not exist in this implementation
(SharedFile<E>::mark_live_objects, Symbol<E>::get_addr and
open_file_impl); point them at the Rust functions or drop the
comparison. The ICF and thunks modules repeated their description in a
second module-level summary, and the TLS module docs had a stray title
line. drop_mappings did not say that it discards the relocation records
rewritten in place, and the madvise in write_build_id did not say why
dropping written pages of the output loses nothing. Also fix three
typos.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The page-aligned start of the interior range was turned back into a
pointer with an integer cast, which leaves it without the allocation's
provenance. Derive it from the caller's pointer with with_addr instead,
as the rest of the function already reads addresses with addr().
Co-Authored-By: Claude Opus 5.5 <[email protected]>
record_comdat_owner and set_visibility_bits hand-rolled fetch_min and
update. record_comdat_owner keeps its initial load so that a file that
does not lower the priority still leaves the cache line shared. The
packed visibility and flags bytes are no longer pub, since nothing
outside symbol.rs touches them and a direct store would corrupt the
state bits they share.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
x86-64 and i386 defined their own little-endian write helpers with the
same bodies as write_ul16, write_ul32 and write_ul64 in util::endian,
and i386 read REL addends with inline from_le_bytes calls. Call the
shared functions directly, as the other fixed-endian targets do. The
ARM64 and RISC-V instruction accessors now delegate to them, like
LoongArch's already do.
Also drop an unused parameter of relax_tlsdesc_to_ie, a redundant mask
on SPARC relocation types (the record keeps the type in a byte of its
own), two TRAP comments that described C++ target structs instead of
the instruction, and the pub on two PPC64 ELFv1 functions that only
their module uses. SH-4 reports an unknown relocation with error! like
every other target, so that all of them are listed in one run.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The key length was an UnsafeCell whose reads and write needed unsafe
blocks justified by the release/acquire pair of the key pointer. A
relaxed atomic expresses the same protocol, compiles to the same loads
and stores, and takes the probe comparisons out of unsafe code. Also
check the probe invariant at compile time and reject keys longer than
32 bits instead of truncating their lengths.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
driver is a public module, so `pub` made copy_chunks part of the
library's API although only the crate itself calls it; make it
pub(crate). from_hex now asserts its precondition in debug builds
instead of silently producing garbage for a non-hex byte.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Six flags in the ignored-for-compatibility chain were unreachable
because earlier arms already consume them: color-diagnostics,
eh-frame-hdr, fatal-warnings, enable-new-dtags, disable-new-dtags and
warn-once.
returns_etxtbsy zeroed a MaybeUninit<utsname> and then asserted it
initialized after calling uname. utsname consists of byte arrays, so a
zeroed one is already a valid value; start from that and drop the
assume_init step.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The runner starts each script in its own process group, so a script
that read the terminal would stop there until the timeout. Give it
end-of-file instead.
run already returns ExitCode, so report setup errors through it
instead of exiting the process. Also drop the second sort of the
discovered scripts (make_jobs sorts the combined list), the unused
Clone on TestJob and the intermediate vector in the PATH search, and
give the unsafe kill a SAFETY comment like the other unsafe blocks.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
R_X86_64_GOTPC32, R_386_GOTPC, R_390_GOTPC and their variants compute
GOT + A - P, the distance to the start of the GOT section rather than
to an entry in it. They were grouped with the GOT-indirect relocations,
so every link that referenced the GOT base got an entry for
_GLOBAL_OFFSET_TABLE_ that nothing ever read, plus a RELATIVE
relocation to fill it in a PIE.
R_ARM_REL32 was in the bucket of relocations that need no scanning,
so a reference to a symbol defined in a DSO was silently resolved as
if the symbol were at address zero. Route it through scan_pcrel like
PREL31 and MOVT_PREL, which gives imported data a copy relocation,
an imported function a canonical PLT entry, and a shared object an
error.
R_X86_64_GOTOFF64 and its counterparts on i386, ARM32, s390x and SH4
compute S + A - GOT, which is a link-time constant only if the symbol
is in the output file. We didn't scan them at all, so a reference to a
symbol defined in a DSO was silently resolved as if the symbol were at
address zero. Route them through scan_pcrel, which already makes the
right decision: a copy relocation for imported data, a canonical PLT
entry for an imported function, and an error in a shared object.
Fixes https://github.com/rui314/mold/issues/1668
The port kept C++ idioms in which a raw pointer or an unchecked index
stood in for a slice, guarded by an assertion. Add SyncUnsafeCell, a
slice-of-cells view for parallel passes, and use it for the file
mapping, the symbol-table allocators, the symbol editor and decoded CREL
tables. Make the fields that parallel passes write atomic, which turns
allocate_aux, the dynsym pass, the gdb_index prefix scan and the COMDAT
signature slots into safe code. Write merged sections and the gdb_index
tables through split slices, and look up files and sections with plain
bounds checks.
This removes 58 unsafe blocks and 16 assertions.
check_duplicate_symbols and check_symbol_types looked every reference
up in the symbol table, a cache miss each in a large link, before checks
on the file's own entry that skip it anyway. An undefined reference is
never a duplicate, and one without a type never mismatches.
Assisted-By: Claude <[email protected]>
R_AARCH64_TLSLE_LDST{8,16,32,64,128}_TPREL_LO12_NC were unhandled, so
objects using them failed to link with "unknown relocation". The immediate
is the local-exec TLS offset scaled by the access size.
addr_with decoded the tagged origin word into its five-variant enum up
front and matched it twice, which left the enum spilled to the stack on
every relocation. It only needs to know whether the origin is a fragment
or an input section, and each is a mask and a compare.
Assisted-By: Claude <[email protected]>
The check read every referenced symbol's ELF entry from its owning file,
a second cache miss per reference, only to get the type the symbol
already caches.
Assisted-By: Claude <[email protected]>
Sizing .rela.dyn walks every GOT symbol, a cache miss each, and the
section is sized three times on the way to the final layout. The walk
was the only serial one of that size left before the copy.
Assisted-By: Claude <[email protected]>
scan_abs_relocations already collects every R_X86_64_64 in an allocated
section, and apply_abs_rels applies it. Resolving the symbol again here
only to fall through an empty match arm is wasted work.
Assisted-By: Claude <[email protected]>
Six of the fifty relocation kinds need G, but every relocation paid for
its aux-table lookup and bounds check. Move the computation into a
closure, as the other targets already do.
Assisted-By: Claude <[email protected]>
A section's address and the GOT base do not change while its relocations
are applied, but the section offset is atomic and the context carries
interior mutability, so the compiler must reload both after every store
into the output buffer. Read them before the loop instead.
Assisted-By: Claude <[email protected]>
The tests drive Apple's toolchain (cc, nm, otool, dyld_info, lldb),
so the job runs on the hosted macOS arm64 runner, where the harness
covers x86_64 as well by running every script again under Rosetta;
Rosetta is installed first if the image lacks it. The harness writes
each test's log to a file and only names the failures on stdout, so
a failed run uploads the log and output directories as an artifact.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
The library crate now has unit tests, and cargo passes everything
after "--" to every test binary it runs, so libtest sees the shell
harness's --native and --timeout flags first and rejects them as
unrecognized options. Selecting --test integration runs the harness
alone: it is the cli crate's harness = false test target, so its own
option parser receives the arguments.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Ports mold 1967c818 and its follow-ups. The linker assumed that
arguments and file names are UTF-8, which a cross link from a Linux
host cannot guarantee. Paths are now PathBuf, library names OsString,
and load-command strings such as -install_name and -rpath byte
strings, since they are copied into the output as C strings. Only
symbol and section names must be UTF-8.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Without -lto_library the linker dlopen'ed "libLTO.dylib", which exists
only on macOS; a Linux host's LLVM ships libLTO.so. The fallback name
follows the host, so a cross link finds the library the same way.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
The code signature's page hashes and the content UUID went through
CommonCrypto's CC_SHA256, which only libSystem provides, so the
linker could not be built anywhere but macOS. A cross link from
Linux is a supported use, so the hashes come from the sha2 crate,
which uses the SHA extensions where the host CPU has them.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
mold's Rust implementation is mold now (mold af97c800 merged it and
its history), so the comments that named their model "mold-rust"
name mold. No code changes.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Ports mold 0ab442db, and gives common.inc mold's `set -o pipefail` and
`set -x` so that a failing producer fails its pipeline and a failed
test's log shows every command. pipefail is what makes `grep -q` in a
pipeline unsafe: grep exits as soon as it finds a match, and a producer
still writing dies of SIGPIPE, failing a pipeline that gates a skip or
an assertion; plain grep reads its whole input, and its output lands
in the log. Two tests wrapped the linker in a shell function and
tested the stderr captured at the call site for silence; with tracing
on, the function body's trace lands in that capture, so they build the
command in a variable instead.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Ports tests/lib.rs from mold (bb257d5a, 96ea73e7, df5c9e14, d567d10f
and the runner's earlier shape), keeping only what applies to a
single-host Mach-O linker: the architectures are the host's and, on
arm64, x86_64 under Rosetta (--all, the default, or --native), instead
of mold's cross-compiler and QEMU matrix. Each test runs in its own
process group with a 60-second timeout that kills the whole group,
writes its log and a pass/skip/fail status file under
target/<profile>/mold-test/out/test/results/<arch>/, and has its
output directory removed when it passes; failures print a FAIL line
with the log path, and the run ends with per-architecture and total
counts. Tests run from that work directory, so the harness writes
nothing into the source tree; out/ stays ignored because a script run
by hand from the source tree still writes there. Options follow
mold's: -j/--test-threads, --timeout, --list and substring patterns.
A test's process group is a background group of the terminal cargo
was started from, so the test gets /dev/null as stdin. A program that
reads the terminal, or restores its settings on exit as lldb does
even in batch mode, would otherwise be stopped by SIGTTIN or SIGTTOU
and sit there until the timeout; the debuginfo tests hung that way.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
InputSection stores its contents as a pointer and sh_size. For a
compressed section, sh_size is the Chdr's uncompressed size while the
pointer led to the compressed bytes, so reading contents() before
uncompress() made a slice far longer than its buffer. Only a debug_assert
guarded this, and a crafted SHF_COMPRESSED .eh_frame reached it through
the .eh_frame parser.
Store a null pointer for compressed and NOBITS sections instead, so that
a nonnull pointer always has sh_size bytes behind it, and pass the
compressed bytes to uncompress() from the file's section header. The gABI
forbids compressing allocated sections, so reject them with an error.
Parallel writes into an output section went through OutputBuffer, a raw
pointer whose ranges were computed by the caller and only debug-asserted.
For ARM32 BE8 byte swapping those ranges came from mapping-symbol values
in the input file, so a corrupted symbol wrote outside of its section.
Replace OutputBuffer with for_each_member, which splits the buffer at
member offsets with rayon::iter::split and split_at_mut and hands each
member its own slice. Thunks get their slices by splitting the buffer
after the member loop, and the ARM32 swap indexes mapping-symbol ranges
within the member's slice. No unsafe code remains in either file.
Ports mold a1d0c8a6 for the one diagnostic helper here: file_display
returns the mapped file's name by reference instead of cloning it for
every message.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Ports mold 9bca3eac, 0380abbb and c3cbd297's shape of archive_file.rs:
a parsed ArHeader, an atoi-like parse_decimal, and one iterator that
yields (name, body) pairs, skipping the symbol and string tables, on
which read_archive_members maps member views as they are consumed.
Besides Apple ar's BSD long names it now reads SysV long names and the
`//` string table, which GNU ar writes on macOS too.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Ports the shape of mold's mapped_file.rs (7f640167, aed83a21 and its
READ_THRESHOLD): the bytes are reached through data() and size(), a
member view is slice(name, start, size), open() and must_open() take
any path-like argument, and files of 32 KiB or less are read into
memory rather than mmap'ed. mmap(2) takes the process's address space
lock, so with tens of thousands of input files the calls serialize at
a few microseconds each no matter how many threads make them, while
read(2) takes no such lock; larger files are still mapped so that
pages the link never touches cost nothing. The by-path memoization
stays, keyed as before. Names remain Strings for now: the ld64 command
line and every path in Args are text, so the byte-preserving PathBuf
of mold 1967c818 would stop at this struct's boundary.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Word-size absolute relocations were applied after the copy pass through
a raw pointer into the output section, with only a debug_assert on the
offset. A corrupted r_offset therefore wrote past the section in release
builds instead of failing.
Apply them in the per-member copy loop instead, indexing the member's
slice so that an out-of-range r_offset panics. abs_rels is built in
member order, so each member's relocations are one run of the vector;
AbsRel now records the member index and write_to finds the run with
partition_point. This removes write_word and its SAFETY assumption that
input relocations do not overlap.
Ports mold's split_ranges (b7e53ea5, 3d06f648): copy_chunks describes
each chunk's file range as a standard Range and borrows all the
disjoint slices at once through the helper output_file.rs now shares
with mold, instead of walking the buffer with split_at_mut and gap
arithmetic of its own.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Ports mold d677902c: one macro holds the ChunkId-to-field match, and
chunk_header and chunk_header_mut expand it for a shared and a mutable
borrow, so a new chunk kind is added to the lookup in one place.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Ports mold 5a87ab3a: the parallel scan of a batch's branch relocations
accumulates each worker's claimed symbols in one vector per split and
merges them, instead of allocating a vector per input section for
flat_map to drain.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
util/mod.rs takes mold's definitions and names for the helpers the
two linkers share: align_to treats an alignment of zero as none, bits
and sign_extend carry mold's comments and sign_extend takes the width
of the signed field rather than the index of its sign bit (the one
caller, arm64's 24-bit addend field, passes 24 now), and the LEB128
writers are encode_uleb and encode_sleb, appending to `out`.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Aligns error.rs with mold's: the demangling switch sits at the top
with demangle_enabled() for Display implementations to consult, the
process-wide settings follow, and checkpoint reads the error flag
directly (has_error had no other caller). The demangler moves to
util/demangle.rs as mold's demangle_cpp, with a Mach-O display helper
beside it that strips the leading underscore Mach-O adds to every
C-level name before demangling, used by Symbol's Display and the
three diagnostics that name a symbol by string. -noinhibit-exec has
no ld64 counterpart, so its switch is the one thing not ported.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Ports mold e09ce32f and cc01683a and mold's SIGSEGV/SIGBUS handler.
The pipe to the waiting parent is an OwnedFd behind a mutex instead of
a raw descriptor in an atomic, so the write end closes exactly once.
The output path is published for the signal handler as a leaked C
string in an atomic pointer, and cleanup() unlinks it with no lock,
allocation or drop, which makes it safe to call from the handler that
now runs on a crash: the output is written in place as its ranges are
finished, so a crashing link used to leave a truncated file at the
output path. mold's disk-full diagnosis does not apply here, since
the output is assembled in anonymous memory and written with pwrite,
whose failure is an ordinary error.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Brings in mold's util/glob.rs unchanged (with its recent changes:
ebbda8f4, 6f8aa2f9, 4fa0487a, ffca665b, 1a39c931, a221b33e) and
compiles every symbol-list option into a Glob at the end of argument
parsing: -exported_symbol(s_list), -unexported_symbol(s_list),
-reexported_symbols_list, -why_live and the -non_global_symbols_*
lists. ld64's lists take `*`, `?` and `[...]` wildcards; the local
matcher knew only `*`, and the lists were consulted in two ways: by
pattern in some passes and by exact string comparison in others
(the dead-strip roots, LTO symbol preservation, weak-definition
auto-hiding and the export trie), so a wildcard entry exported a
symbol from one pass's point of view but not another's. One matcher
per list settles that. The exact names of a -reexported_symbols_list,
which must resolve, are kept beside the matcher.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Ports mold's util/perf.rs (with 7a7d03d1, 8fe5b00e, fa71e1b2 and
7d1dee4a: no clock reads or locking unless enabled, nesting inferred
from completed intervals, children printed in start order) in place
of the MOLD_TIMING environment variable, the t!/tp! macros and the
hand-rolled phase stopwatch in the driver. Every pass the old code
timed now runs under `ctx.timer("name")`, including the parallel
__LINKEDIT builders, whose timers name themselves from the shared
context and nest by interval, and -print_statistics (mold's --perf)
prints the timer tree to stderr, where ld64 reports its statistics,
followed by the input and output sizes. The Counter half of the
module is not needed: nothing here counts.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Ports mold 96e6a38d, df5a97ed, 9b051c1d and the entry-point half of
1967c818, so that the executable, the per-target crates and the
driver have mold's shape:
- driver::main takes the raw OsString arguments, expands response
files once, and hands the result to each target attempt as an
Arc<[Cow<OsStr>]> instead of re-expanding per attempt; link() takes
that slice and parse_args converts each argument to text, rejecting
a non-UTF-8 one with a message rather than mangling it.
- The executable starts with the first enabled target and switches
when link() reports another, from a TARGETS table with one entry
per target feature; -arch names are canonicalized to the targets'
own static names so a restart can carry one as &'static str. A link
without -arch deduces the target from the first Mach-O input inside
link(), where mold deduces -m, instead of in main before parsing.
- A `system-allocator` feature opts out of mimalloc, as in mold's
CLI; the library keeps its modules crate-private except driver,
macho, target and util (8a56434b), and the CLI no longer reaches
into its diagnostics.
- parse_args enables colored diagnostics when stderr is a terminal,
which mold's parser has always done and this one never set.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Nothing called these: the stdout `out` diagnostic and its macro, the
archive-member probe defined_symbol_names (resolution reads every
member's symbol table for real), two libLTO attribute constants, and
PrehashedMap, whose only user was rewritten. mold removed its own
leftovers the same way (23080837, a21598f0, 73c5095f); keeping the
library's modules crate-private, as mold does, makes the compiler
point them out.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Ports mold 011e1948. Four struct-size assertions hold only with 8-byte
pointers: InputSection stores its contents pointer as a usize, Symbol
its name pointer, and Cie and Fde hold a byte slice. They are gated on
target_pointer_width = "64"; the Reloc and UnwindRecord assertions
have no pointer-sized fields and stay unconditional.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Ports mold 54b68f6e for the two places where merging loses nothing:
the response-file tokenizer's "any other character extends the token"
arms, and the two LSDA pointer encodings of the same size. The
section-rank and DWARF form tables keep one arm per entry: the
identical ranks there are the documentation.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Ports mold fbb31440 (and the smaller lint commits around it) to a
clean `cargo clippy --workspace --all-targets`: is_multiple_of for
alignment checks, no casts to a value's own type, no needless
borrows, Default implementations delegating to the chunk types'
constructors, iterator loops where an index only selected an element,
annotated transmutes, a typed dlsym helper in place of a macro that
transmuted through inference, a type alias for the Objective-C blob
callback and the symbol gather's per-shard result, and a checked
division in the -r symbol renaming. Two doc comments had drifted: the
symbol resolution description sat on create_internal_file and one
line of it began with ">", which Markdown read as a quote; and the
dead-strip visitor threaded a rayon scope it never used.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Ports mold 2eef5735: each kind of output chunk lives in a file of its
own under src/chunks/, as the directory's README promises. got.rs held
five kinds (__stubs, __stub_helper, __la_symbol_ptr, __got,
__thread_ptrs), misc.rs five (-sectcreate, __init_offsets,
LC_FUNCTION_STARTS, LC_DATA_IN_CODE, the code signature), objc.rs
three, symtab.rs three and dyld_info.rs the four LC_DYLD_INFO
streams, each kind wrapped in an inner `pub mod` for its writer. The
wrappers are gone: a kind's writer is `chunks::<kind>::copy_buf` and
its builder `chunks::<kind>::build`, and the code signature's helpers
are `code_signature::{size, write, page_hashes, rehash_pages}`. The
segment lookup the four dyld-info builders share moves to
chunks/mod.rs next to OutputSegment. Code motion only; the two doc
comments that had drifted onto neighboring functions (the bind
builder's onto the lazy-bind builder, an orphaned paragraph onto
data_blob_pointers) are put back.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Ports mold c5f69d91: clippy's ptr_as_ptr, borrow_as_ptr and ref_as_ptr.
Raw pointers change their pointee type with `.cast()`, which cannot
silently change constness, and are taken from places with `&raw`
instead of coercing a reference.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Ports mold 73395265 and 3bf748a8: `cargo clippy --fix -- -W
clippy::use_self`, so constructors and trait implementations name
their own type as `Self` rather than repeating its name.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Ports mold ebbda8f4: clippy's collapsible_if, which on edition 2024
rewrites an `if let` wrapping another condition as one let chain.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Ports mold 61c0854d: `cargo fmt` under the 2024 style edition, which
sorts `use` items version-style and by path kind (std before crates
before crate-local) and lays out a few expressions differently.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Ports mold 1efb465b. The migration touches little: `extern "C"` blocks
are `unsafe extern`, a closure pattern that relied on match ergonomics
binding by reference spells its reference out, and the two return-
position `impl Iterator`s in the TBD parser capture only what they
use, which the 2024 capture rules give them without an annotation.
The macro fragment specifiers stay `expr`; nothing passes the new
`const {}` or `_` forms to them.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Ports mold 972347aa. The shell cases and the runner live directly under
tests/ (the runner as tests/lib.rs, named in the manifest; the root
package sets autotests = false so Cargo does not take it for an
integration test of the linker crate). Every script is executable and
the runner invokes it directly, honoring its shebang, instead of
passing it to bash. Scripts sourcing common.inc and the harness's
own unit tests follow the moved paths.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Ports mold 3d673995, which turned src/elf/{mod,consts}.rs into
src/elf.rs and src/elf_consts.rs: the format definitions are
src/macho.rs and the constants src/macho_consts.rs, re-exported from
macho as before, so no user of `crate::macho::*` changes.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Ports mold c8f06bb1 (and fa30ac90's src/chunks/README.md). The module
holding the pieces an output file is assembled from is src/chunks/,
the name mold settled on when it flattened its chunk modules; the
Mach-O chunk files were already one per kind, so only the directory
moves.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Ports mold 4afe0ffd (and fa30ac90's src/target/README.md). The trait
that describes one target is `Target`, its module is src/target/, and
the type parameter reads `E: Target` throughout, as in mold. The crate
directory is arch/ and the trait is Target, so the two names no longer
collide: "arch" is where the instantiations live, "target" is what
they instantiate. The ld64 map file's "# Arch:" line is output format
and keeps its spelling.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Ports mold 946c7114 and e2e85b3f: the per-target crates live under
arch/, each with its lib.rs at the crate root and named in the
manifest, and arch/README.md explains why the targets are separate
crates at all (Cargo compiles them in parallel; one crate would leave
a long serial monomorphization step).
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Ports mold da0de563: cli/ holds main.rs and integration.rs directly,
with their paths named in the manifest, instead of one-file src/ and
tests/ subdirectories.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Ports mold 47a35e8b. The version, edition and license live once in
[workspace.package] and every crate inherits them; libc and the
linker library itself are declared once in [workspace.dependencies].
The library crate no longer spells out its own name: Cargo derives
mold_macho from the package name, as mold derives mold.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Ports mold 722d8f02, which removed the release profile's line tables
and codegen-units override. `debug = 1` was added here for a sampling
profile (f66a44b) and is not needed for ordinary builds; a profiling
build can pass CARGO_PROFILE_RELEASE_DEBUG=1 instead.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Ports mold 6968a402. Release builds use the abort panic strategy, so a
panic terminates the process without unwinding; mold measured 12.7% to
13.6% less .text for the ELF linker with no measurable link-time
difference. The linker never catches a panic, and every fatal path
already exits without unwinding, so nothing relied on it.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
The 80k-section tests take about 2 seconds alone but 13 to 14 seconds
on every target when the suite runs 128 jobs at once, so any other load
on the machine pushed some of them past 30 seconds. The limit exists to
catch hangs, and 60 seconds still does that promptly.
Layout carried only the file format: byte order, word size and the
record forms that differ between ELF32 and ELF64. No type implemented it
without also implementing Target, so the split earned its keep only in
RISC-V and LoongArch, where the record types depend on a const generic
and could not be chosen inside one Target impl.
Those two targets now take their record types from Class<IS_64>, a
helper private to the target module that maps the const parameter to
the ELF32 or ELF64 forms. ARM32 and SH4 lose their per-byte-order impls
and read section header words through u64::from like the other 32-bit
targets.
What the trait enumerates are targets, not architectures: Ppc64V1 and
Ppc64V2 share an architecture and differ in ABI, and Arm64 and Arm64Be
differ only in byte order. The architecture-level notion already has its
own name, Family. The rest of the tree already said target, in
Arm64Target, TargetTraits, emulation_to_target and the comments, so the
trait was the one place the vocabulary broke. The workspace directory
arch/ keeps its name because target/ at the repository root is Cargo's
build directory.
common.inc sets pipefail, and grep -q exits as soon as it finds a match.
If the producer still has output to write, it dies of SIGPIPE, the
pipeline fails, and a test that gates on the match silently skips or
fails. Plain grep reads its whole input, so the pipeline's status is
grep's own.
Clippy's match_same_arms lint reports 15 pairs. Most sit in relocation
tables that follow the relocation number order, where merging would
break the order, so only the two that are not in such tables change.
Apply clippy's redundant_pub_crate suggestion. An item in a private
module is crate-visible at most, so `pub(crate)` on it says nothing
that `pub` does not.
Apply clippy's ptr_as_ptr, borrow_as_ptr, ref_as_ptr and
ptr_cast_constness suggestions. `ptr.cast()` and `ptr::from_ref()`
cannot silently change the pointee type when a declaration elsewhere
changes, and `&raw mut x` passes a pointer to C without creating an
intermediate reference.
ld64 places an atom at its input offset modulo the section alignment,
except in literal pools: Literal{4,8,16}Section gives every atom
Alignment(2/3/4) with modulus 0, whatever the section header says.
LLVM relies on that when it puts an 8-aligned 16-byte constant into
__literal16 with .p2align 3 and loads it with ldr q, whose PAGEOFF12
immediate is scaled by 16.
We kept such a literal at 8 mod 16, so the load fetched the
neighboring slot (#2). PR #3 fixed __literal16 by dropping the
modulus of any atom whose size is a multiple of its alignment, which
also moved __const and __cstring atoms ld-prime leaves in place
(iTerm2: +10KB __const, +8.7KB __cstring). Flag literal subsections
NO_MODULUS instead, for all three sizes.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01MfxCtSUkU25daAUyjeCsyF
ARM64 ldr q PAGEOFF12 is scaled by 16. A 16-byte SIMD literal that
lands at 8 mod 16 cannot be encoded; dropping the low bits loads a
neighbor. rustc libtest then parses --list as "ist".
Compilers sometimes emit __literal16 with p2align 3, and literal
merge can keep a stale input-address modulus after folding into
__TEXT,__const. Raise those atoms to 16-byte alignment, discard a
modulus when the atom size is a multiple of the alignment, and
write PAGEOFF12 by replacing the immediate instead of ORing it.
Add a regression test that places a 16-byte literal after an
8-byte predecessor. The test fails before the fix on arm64.
Validation: cargo test --offline --release --test integration --
literal16-pageoff subsection-modulus rust. All three pass on arm64.
Fixes https://github.com/rui314/mold-macho/issues/2.
Co-authored-by: Cursor <[email protected]>
rewrite_opd looks up the symbol of a function descriptor by its offset
with a binary search. Aliases share a descriptor, so the search could
land on any of them, and a section-relative reference to a static
function then went through one of its global or weak aliases: it became
preemptible and resolved to whichever definition won. Use the lower
bound instead; the stable sort keeps the local symbol first.
`dist.sh arm` failed in the networked fetch step: cargo could not read
its git database for the mimalloc dependency ("Value too large for
defined data type"). ext4 hands 64-bit directory offsets to the QEMU
process, the armhf glibc reports EOVERFLOW when they don't fit a 32-bit
off_t, and the libgit2 built into the 32-bit cargo uses that interface.
Only the git dependency is affected; everything else goes through Rust's
std, which uses the 64-bit calls.
Keep cargo's cache on a tmpfs inside the networked container and have it
`cargo vendor --locked` into target/dist-vendor-$arch together with the
printed config. The offline build reads the vendored sources through
`--config` and no longer needs a cargo home at all.
Tests: ./dist.sh x86_64
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_0141irCwtPmPMZa9s7pCN3Cz
Two struct-size assertions hold only with 8-byte pointers: SymbolAux ends
in a Vec and PendingComdatSignature starts with a fat pointer, so they
are 48 and 16 bytes on a 32-bit host and rustc rejected the crate for
armv7. Gate them on target_pointer_width = "64"; the other five size
assertions have no pointer-sized fields and pass on both widths.
CI now runs cargo check for armv7-unknown-linux-gnueabihf over the whole
workspace so this cannot regress unnoticed.
Tests: cargo check --release --target armv7-unknown-linux-gnueabihf --workspace --all-targets
Tests: cargo test --release (pass=5681 skip=654 fail=0)
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_0141irCwtPmPMZa9s7pCN3Cz
The bullseye targets (aarch64, arm, ppc64le, s390x) left apt on the live
deb.debian.org mirrors. Those mirrors dropped the architectures Debian LTS
does not support when bullseye entered LTS, so `dist.sh ppc64le` and
`dist.sh s390x` fail in `apt-get install` with "Unable to locate package
build-essential". The pinned snapshot the images ship commented out in
sources.list still has them, and using it also gives these builds the
reproducibility the header comment already claims.
Make the snapshot switch the default apt_setup; riscv64 (deb822 sources)
and loongarch64 (live sid) keep their own.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_0141irCwtPmPMZa9s7pCN3Cz
Retain the original versioned reference when following undefined symbols
in shared libraries. This prevents an unversioned archive definition from
causing unnecessary extraction while preserving symbol interposition.
Skip the retained undefined references in the secondary symbol type check
and port both upstream regression tests unchanged.
Validation: full workspace tests passed with 34 unit tests and 5,684
integration tests across 14 target configurations; 651 skipped, 0 failed.
Four additional targets lacked a compiler or QEMU. The archive regression
fails before the fix, and both new tests pass on all available targets.
Upstream-Commit: 294e22a86a
Retain the original versioned reference when following undefined
symbols in shared li6braries. This prevents an unversioned archive
definition from causing unnecessary extraction while preserving symbol
interposition.
Fixes https://github.com/rui314/mold/issues/1657
Append 70,000 empty sections to GCC-generated LTO assembly for a tiny
program. Preserve the extended section numbering checks for slim and
fat LTO objects and verify that the fat object actually invokes LTO.
Validation: this test passes on all 14 available target configurations.
The observed LoongArch run dropped from 19 seconds to 4.4 seconds; the
14-target run completed in 5.7 seconds.
Replace 1,000 grep invocations with one awk process while checking all
500 DWARF32 and 500 DWARF64 names. This avoids timeouts spent repeatedly
spawning grep during parallel test runs.
Validation: the updated test passes on all 14 available target
configurations. The check accepts a complete dump and rejects missing
names and empty input.
Remove the NOBITS debug-link placeholder and remap section indices so
symbols and section headers still refer to the correct sections. Port
the upstream regression test unchanged.
Validation: separate-debug-file tests across 14 target configurations:
29 passed, 13 skipped, 0 failed. The regression fails before the fix and
passes afterward.
Upstream-Commit: f4edcbd37e
Remove the malformed NOBITS debug link and adjust section indices so
symbols and section headers still refer to the correct sections.
Add a regression test for following the debug link and preserving
symbols in later sections.
Fixes https://github.com/rui314/mold/issues/1656
The relocatable output lays out all leader CIEs before the FDE blocks
of every file, but EhFrameRelocSection::copy_buf emitted the
relocations per file: a file's CIE relocations followed by its FDE
relocations. When a file after the first has a CIE with a relocation,
which is any object with a personality routine, that relocation was
written after the previous files' FDE relocations, so .rela.eh_frame
was not sorted by offset.
Consumers pair CIEs and FDEs with a contiguous run of relocations in a
single scan over .eh_frame, and parse_ehframe is one of them. On the
unsorted section the personality relocation was attached to an earlier
file's FDE with a negative in-record offset, so the final link wrote the
value into the preceding FDE and left the CIE's personality pointer
zero. libgcc treats an encoded zero as "no personality", so functions
under that CIE lost their cleanups and catch handlers: an exception
thrown inside their try block escaped uncaught. Found by dev-util/perf,
whose build partially links a Rust workload object after C objects.
Emit the CIE relocations of every file first and only then the FDE
relocations, which matches the layout, and add a test that links a C
object before a C++ object with `-r`, checks that the relocations are
sorted, and catches an exception through the result.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_0141irCwtPmPMZa9s7pCN3Cz
Do not treat TLS descriptors as unconditional dead-strip roots. Live
references, explicit retention and dylib exports already keep the required
descriptors and their backing storage reachable.
Add a regression test covering initialized and zero-fill TLS, per-thread
initialization and isolation, no-dead-strip symbols, -u roots and dylib
exports. The test fails before the fix on arm64 and x86-64.
Validation: cargo test --offline --release passes, including the arm64 and
x86-64 shell tests.
Related to https://github.com/rui314/mold-macho/issues/1.
This fixes unnecessary TLS retention; the reported runtime hang has not
been reproduced.
The relocatable output lays out all leader CIEs before the FDE blocks
of every file, but the relocations for them were emitted per file: a
file's CIE relocations followed by its FDE relocations. When a file
after the first has a CIE with a relocation, which is any object with
a personality routine, that relocation was written after the previous
files' FDE relocations, so .rela.eh_frame was not sorted by offset.
Consumers pair CIEs and FDEs with a contiguous run of relocations in a
single scan over .eh_frame, mold included. On the unsorted section the
personality relocation was attached to an earlier file's FDE with a
negative in-record offset: we panicked on the slice index, and C++ mold
silently wrote the value into the preceding FDE and left the personality
pointer zero, which libgcc treats as "no personality", dropping that
function's cleanups and catch handlers. Found by dev-util/perf in the
Gentoo campaign; a Rust workload object partially linked after C objects
triggered it.
Emit the CIE relocations of every file first and only then the FDE
relocations, which matches the layout. The C++ implementation received
the same change.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_0141irCwtPmPMZa9s7pCN3Cz
Move shell cases and the runner directly under tests/, and remove the
standalone symbol output chunk test files and their references.
Make shell scripts executable and invoke them directly, honoring their
shebangs and execute permissions.
Implement -sdk_imports using the version-1 JSON structure emitted by Apple ld.
Report live imports by dylib install name, including non-SDK dependencies,
with deterministic ordering and escaped JSON strings. Check output errors and
reject the unsupported relocatable combination. The test checks report
metadata and system and local-library imports on both architectures.
Wire the command-line option to the existing diagnostic policy. The test
checks successful warning-free links, failure on duplicate-library warnings,
and warning suppression with -w on both architectures.
Compare the minimum runtime version in binary dylib metadata with the output
deployment target, including private reexports. SDK versions do not trigger
warnings. A short test covers older, equal and newer runtime versions with
both load command formats on arm64 and x86-64.
Check modern and legacy platform metadata for direct binary dylibs and private
reexports. Reuse the object-file decoder so Intel legacy mobile inputs are
recognized as simulator builds. The regression test covers compatible dylibs
and both incompatible metadata encodings on arm64 and x86-64.
Retain minimum OS versions alongside input platforms and warn when a
selected object requires a newer OS than the output deployment target.
Compare minimum runtime versions, not the SDK versions used to compile
inputs, and keep unused archive members quiet.
Add a regression test covering both load command encodings, older and
equal deployment targets, newer SDKs, and direct and archived newer
objects. The test passes on arm64 and x86-64.
Mach-O objects for different Apple platforms can share the same CPU type.
Read their platforms from LC_BUILD_VERSION and legacy LC_VERSION_MIN_*
commands, distinguishing Intel simulator objects from device objects.
Validate objects after archive selection and LTO, before either final or
relocatable output, so unused archive members do not cause errors.
Add a regression test for modern and legacy platform metadata, compatible
objects, and both unused and extracted archive members. The test passes
on arm64 and x86-64.
Context already uses E through its input files and output sections, so it
does not need a PhantomData field.
Validation: workspace check across all targets.
Reserve ReaderJob for command-line inputs. Store deferred scripts and LTO
files as tuples with required mapped-file references, and remove unused
archive metadata from the command-line job. Move reader contexts into the
script queue while preserving command-line sorting.
Validation: workspace check; 28 script, archive and LTO tests passed
(two 32-bit prerequisite skips); clang, Chrome and TensorFlow debug
outputs match the baseline.
Replace the manual registration flag with std::sync::Once. Keep counter
updates atomic and skip registration when statistics are disabled.
Validation: workspace check; clang, Chrome and TensorFlow debug links
with matching counter values under --stats.
Remove PhantomData<()> from LoongArchTarget. Its const parameter needs no
marker field, and the target remains a zero-sized type.
Validation: workspace check, including both LoongArch targets.
Replace the separate shuffle mode and seed fields with ShuffleSections.
Keep the last explicit seed while parsing, so a later bare shuffle option
reuses it even after --reverse-sections. Extend the existing seed test to
cover that option sequence.
Validation: workspace check; shuffle and reverse integration tests;
the new option sequence also passed before the implementation change.
Replace SectionOrderKind and its separate fields with a SectionOrder
enum. Keep names or numbers in their variants and retain the original
token only for address diagnostics.
Validation: workspace check and existing section-order integration tests.
Allocate character tables only for bracket expressions. Literal tokens
carry their bytes, and star and question tokens carry no data. Match the
variants directly when parsing patterns and building the NFA.
Validation: workspace check; four glob unit tests; 34 glob and version
script integration tests; clang, Chrome and TensorFlow debug benchmarks.
Replace BuildIdKind and the separate value/hash_size fields with one
BuildId enum. Each parsed option sets a complete variant, and note
emission reads its payload.
Validation: workspace check; existing build-id test; 10 checks covering
absent IDs, fixed byte values and option overrides.
Remove the redundant struct and constructors. Keep the iterator opaque
and retain always-inline dispatch for ordinary and deferred CREL records.
Validation: workspace check; 15 relocation and GDB-index tests; three
large debug links with byte-for-byte identical output.
Replace Arm32InputSectionExtra with u32, using u32::MAX for an absent
.ARM.exidx section. Add InputSectionExtra::empty so ARM32 starts at the
sentinel instead of u32's default zero.
Implement InputSectionExtra directly for Box<[RelocDelta]> and use it on
RISC-V and LoongArch. Remove the wrapper that only forwarded access to
its boxed slice.
Replace the section display wrapper and both file-name display wrappers
with formatting closures. Share the file-name formatter between input
parsing and merged-section diagnostics.
Use Rayon's recursive splitter and split_at_mut to give each task a
separate slice of the preallocated edge array. Preserve edge order and
the per-vertex count checks while removing EdgeBuffer and its unsafe
Sync implementation.
Use owned vectors for parallel thunk symbol collection, removing
ThunkSymbolBins and its unsafe Sync implementation. Atomic symbol marking
still deduplicates entries, and the final sort preserves output order.
Collect files and scripts in per-worker vectors, locking only to append
completed results. Consume the bins after the scoped jobs join and restore
command line order as before.
Keep one cache per Rayon worker and lock it once per input file. Remove the
custom UnsafeCell wrapper and its unsafe Sync implementation while
preserving cache reuse across jobs.
Report a parse error instead of silently defining address zero when a
decimal --defsym value exceeds u64::MAX, as for hexadecimal values.
Test the largest valid decimal and hexadecimal values, zero, and values
beyond the limit. The new regression fails with the original Rust linker;
all seven defsym integration tests pass with this fix.
Keep generated names as bytes and replace each non-ASCII-alphanumeric byte
with an underscore, matching C++ mold for valid and invalid UTF-8 input.
Add a regression that links references to ordinary start/stop symbols.
It passes on all 14 available target configurations and with C++ mold,
and fails with the original Rust linker.
Keep linker-script assignments, --defsym definitions and targets, retained
names, and forced undefined symbols as byte vectors through lookup and GC.
Accept raw command-line symbol names and trim retain-file entries like C++ mold.
Add a regression with distinct invalid UTF-8 and replacement-character names,
raw alias names, archive extraction, and GC roots. It passes with the Rust and
C++ linkers and fails with the original Rust linker. Existing defsym and
retain-symbols tests also pass.
Store the temporary winning file priority in an actual AtomicU32 instead of
casting an immutable u32 field to an atomic reference. Keep ordinary
symbol-index access behind getters and an exclusive setter.
Add a concurrent ownership regression verified under Miri with both Stacked
Borrows and Tree Borrows.
Store files in Vec<Box<T>> and live entries as u32 pool indices. Resolve
access through the pool so indexed access and live iteration do not rely
on cached pointers across Box borrows.
Use disjoint pool slices for mutable and parallel iteration, preserving
stable file indices while letting Box and Vec manage ownership.
Use the abort panic strategy for release builds to reduce generated
code. A panic now terminates the process without unwinding the stack.
An isolated five-run comparison across three workloads reduced .text
size by 12.7% on the local x86-64 machine and 13.6% on Fedora AArch64.
The measured link-time differences were small relative to run-to-run
variation.
Validated release builds for all 20 targets and successful links for
all three workloads on both machines.
Avoid clock reads, getrusage calls and shared record locks during ordinary
links. Explicit --perf keeps the existing timing output. Cover nested timers,
background handles and repeated stops in a unit test.
Match C++ output growth by passing the old length and appended size to
fallocate. Revisiting the already-written prefix flushes dirty extents on btrfs
and adds about a second to Chromium debug links on Fedora. The GDB-index suite
passes across available targets, and the change is neutral on ext4.
Inspect ELF symbol type and definition with readelf instead of requiring nm to
label foo as text. PPC64 ELFv1 function symbols name descriptors in .opd and
correctly appear as data to nm. This lets the existing path and reproduction
tests run across the full target suite.
Initialize reachability when creating object, LTO, and shared inputs so
mandatory objects outrank unextracted archive members during initial resolution.
Process every --undefined-glob match, including multiple matches in reachable
files, so GC retains each matching definition. Port both upstream regressions
verbatim.
Upstream-Commit: af7d056853
Mark dead fragments, copy relocations, and dead input sections as cold paths,
matching the upstream branch hints while retaining ICF leader resolution and
discarded-section diagnostics.
Upstream-Commit: 5bdd5e6284
Collect strong undefined references during the existing import/export traversal
and apply binding changes together with the import flags. Remove the later full
object-symbol scan, preserving weak binding when every undefined reference is
weak.
Upstream-Commit: a4bcbb45ae
Remember up to 32 merged-section keys and IDs in each conversion worker,
avoiding the shared lock on cache hits. Copy immutable keys rather than
retaining pointers into the growable section vector, and discard the cache when
conversion finishes.
Upstream-Commit: 4a83b5d856
Record shared-library names and default-version aliases directly into worker
bins during parsing. Reserve both symbol vectors first so their recorded
slots stay stable until the existing parallel gather fills them. FileList
retains the backing files through SONAME deduplication. This removes the
temporary name arrays and the later registration pass.
Upstream-Commit: 20eb664a7e
Load flag bytes before setting bits, skipping the read-modify-write when all
requested bits are already present. Apply the shared helper to symbol and
input-section setters; keep fetch_or for operations that need the previous
value.
Upstream-Commit: c72d19dcca
Initialize reachability when creating object, LTO, and shared inputs so
mandatory objects outrank unextracted archive members during the first
symbol resolution pass.
Process all --undefined-glob matches regardless of file reachability so
--gc-sections retains every matching definition. Cover archive ordering,
directly linked objects, and multiple matches in one object.
MergedSection::get_instance took a shared lock for every mergeable
input section, though nearly all of them resolve to the same few
output sections. Each worker now remembers up to 32 sections it has
already resolved, keyed on the same name, flags, type and entry size,
and checks them before taking the lock. The cache lives only for the
conversion pass, during which those fields are immutable.
Geometric-mean link time over the 21-program suite fell 0.87% at 32
threads on a Threadripper 7980X and 3.88% at 20 threads on an M1
Ultra, where create_merged_sections on clang-release went from 24 ms
to 10 ms.
Shared libraries inserted their symbol names into the symbol map one
at a time on the parsing thread, leaving a serial tail behind large
DSOs. Their unversioned names now go through the same per-thread bins
as object-file symbols, so gather_symbols creates the canonical
Symbols in parallel across hash shards. The symbol vector is reserved
up front, so the recorded slots stay valid until the gather fills them.
Geometric-mean link time over the 21-program suite fell 1.41% at 32
threads on a Threadripper 7980X, almost all of it from blender-release
(0.177 s to 0.142 s), which links against many shared libraries.
Relocation scanning requests GOT, PLT and TLS entries for the same
symbols repeatedly, and each request took exclusive ownership of the
symbol's cache line even when the bits were already set. Atomic's
operator|= now loads first and skips the read-modify-write when no
bit would change. fetch_or stays unconditional for callers that need
it.
Measured before the check moved from a symbol-specific helper into
the wrapper: geometric-mean link time over the 21-program suite fell
1.95% at 32 threads on a Threadripper 7980X and 2.71% on an M1 Ultra.
Use OsString for arguments and PathBuf for filesystem names so valid Unix paths
survive parsing and reach the filesystem unchanged. Preserve their bytes in
loader metadata, LTO callbacks, reproduction archives and dependency rules as
well.
Add a regression using compiler-generated objects with non-UTF-8 bytes and
spaces in their names. Cover direct arguments, nested response files, library
search, linker scripts, thin archives and reproduction replay.
Store compact chunk IDs in symbol origins instead of pointers with erased
lifetimes and target types. Resolve each ID through an append-only context
registry, preserving the 48-byte symbol layout and removing the raw pointer
dereference.
Add runtime tests for context moves, chunk reordering and 32-bit layouts, plus
compile-fail tests for dangling and incorrectly typed references. The unit
tests, API tests, doctests and 15 related native linker tests pass.
Keep SONAME bytes intact during library deduplication, dependency checks and
dynamic string and version-table output. Distinct non-UTF-8 names must retain
distinct library identities.
Add two versioned libraries whose names previously collapsed under lossy UTF-8
decoding. Verify both names in .dynstr and run the linked program. All 43 native
SONAME, dependency and version-script tests pass.
Make archive member paths absolute without resolving symlinks, matching the
names retained in response.txt. Reopen each input to copy its original contents
as before.
Add a regression that extracts and replays archives for a relative symlink and
an absolute path through a symlinked directory. Both native reproduction tests
pass.
Use the reader context to determine the target of a nested script before
accepting it. This lets an incompatible script beside its parent be skipped in
favor of a compatible script in a later library directory.
Add an x86-64 regression with competing i386 and x86-64 nested scripts. It fails
before the fix and passes in C++ mold; all 15 native linker-script tests pass.
Stop decompression when the requested output buffer is full, allowing the
twelve-byte DWARF classification probe to read a prefix of a larger frame.
Preserve the premature-end diagnostic for short output.
Add a test with verified Zstd-compressed DWARF32 and DWARF64 objects and check
their output order. The new test fails before the fix and passes in C++ mold.
All seven native Zstd and debug-sorting tests pass.
Recognize global:foo and local:* in the shared version-command parser so VERSION
blocks behave like standalone version scripts. Keep colons intact in input
filenames.
Add a regression covering local and global visibility and colon-containing input
names. Discover the new test/ directory alongside the existing shell suite. All
32 native version-script tests pass.
Record how all 27 upstream commits are represented in Rust, including
existing equivalents and C++-only changes. Describe the Rust-specific
optimizations and the completed build, integration, unit-test and static
checks.
Document the 21-workload release benchmark, build settings and noise
rejection criteria. Include raw samples supporting the measured 6.1%
improvement over the original Rust build and the remaining 2.9%
geometric-mean gap to C++.
Inline addr_with and the small auxiliary-index accessors used during
relocation and symbol-table writes. Move rare discarded-section and
.eh_frame diagnostics into a cold, non-inlined helper while retaining
ICF leader handling in the common address path.
Read the auxiliary record once when checking for PLT entries. These
changes reduce call and lookup overhead without changing computed
addresses or diagnostic behavior.
Force AhoCorasick::walk into its caller because it runs once per input
byte while matching symbol names. Keeping the state index and current
priority in the caller's registers removes per-byte call overhead. The
transition logic and highest-priority match rules stay unchanged.
Use mark_reachable for shared-library edges so repeatedly visiting an
already reachable DSO does not keep writing its reachability flag. This
avoids unnecessary cache-line traffic during parallel traversal.
Allocate child-work buffers only when a batch discovers new sections,
and transfer full buffers with mem::take. Exported symbols can already
make most sections roots, leaving many batches with no children; those
batches no longer need a heap allocation.
Feed ICF digest words directly to SipHash when its byte buffer is empty,
avoiding temporary byte arrays and buffering work during propagation.
Keep the byte-update fallback for partial buffers, preserve the native
digest byte order and wrapping length counter, and inline the hot update
and finalization paths.
Compare word updates with byte updates across partial prefixes, scalar
tails and length wraparound to verify that the digest is unchanged.
Replace the eight Option<u32> fields in SymbolAux with four-byte indices
using u32::MAX for absence. This removes separate discriminants and
reduces each auxiliary record from 96 to 64 bytes. Use u32::MAX - 1 for
the provisional dynamic-symbol index so it remains distinct from the
missing-entry sentinel.
Update table insertion and lookup helpers, assert the record size, and
test absent indices, index zero, the provisional value and reuse.
Upstream changes temporary-output-name construction to avoid a GCC
-Wrestrict warning from C++ string concatenation. Rust already builds
these names through formatting and has no matching expression to fix.
This empty commit records that no Rust change is needed.
Upstream-Commit: dad2695f3e
Allocate decoded_crel for all section indices on the first CREL section.
This avoids repeatedly growing and copying the table during section
parsing. Files without CREL allocate no table.
Upstream-Commit: ff0cb501b0
Parsing already omits losing COMDAT members and marks retained sections
alive, so skip redundant state updates for newly parsed files. Keep the
member walk when reusing parsed files, when LTO can change ownership, or
with --gdb-index.
Upstream-Commit: 91252d8aa5
Upstream increases per-thread arena blocks from 64 KiB to 1 MiB to
reduce global reservations, retaining the local-allocation cutoff at one
quarter of a block. Rust uses vectors and mimalloc rather than that
arena, so there is no corresponding block-size setting to change. Record
the C++ change with an empty commit.
Upstream-Commit: 4a11b7849d
Populate name-length caches during symbol registration. Scan global
names up to '@' or NUL, then scan the version suffix only when present,
retaining the raw length before version normalization or --wrap. This
removes a separate cache-population pass for both regular and LTO
objects.
Upstream-Commit: 53715c07ee
Mark the .dynsym, .plt and .plt.got add_symbol functions inline so the
compiler can expand their calls in the serial table-allocation loop.
This exposes the small insertion helpers to optimization while
preserving table insertion order and index assignment.
Upstream-Commit: 41d2f376c3
Reuse the version-suffix flag set during global-symbol registration
instead of scanning COMDAT signature names for '@' again. Handle a bare
trailing '@' separately because it does not set the flag.
Upstream-Commit: 3a58944ff9
Check dynamic-symbol version conflicts in parallel, using existing
symbol-map locking and synchronized diagnostics. Keep the error
checkpoint after all checks finish.
Upstream-Commit: ec7c803207
Create fragment records as section-symbol relocations are reattached,
collecting only the records each file needs in relocation order. Reserve
exactly their final slots in the central symbol vector, then construct
symbols and append their IDs to each file in parallel. This removes the
preliminary relocation-counting pass and unused dummy symbols. Retain
the cheap CREL-header capacity bound to avoid moving existing symbols.
Upstream-Commit: f9bdf4b4de
Rust already runs the synthesized-symbol, object-file and shared-file
symbol-table writers concurrently. Their symbol, string and
extended-section-index ranges are disjoint, so independent writers need
no shared append point. This empty commit records that the upstream
parallelization is already present.
Upstream-Commit: e9de4def18
Keep each dynamic symbol's name and string offset after sizing .dynstr,
reusing cached GNU hash sort keys when available. Both output writers
can then use independent parallel loops without repeating prefix scans
or name lookups. The cache costs 24 bytes per dynamic symbol.
Upstream-Commit: 18d173697c
Rust already caches exact u32 symbol name lengths while keeping Symbol
at 48 bytes. Its name accessors use that cache without rescanning long
names, so there is no corresponding layout or lookup change to apply.
This empty commit records the upstream equivalence.
Upstream-Commit: fa1e8edc39
Rust already uses flat hashbrown tables for symbol-map shards. Compare
cached hashes before strings and fold higher hash bits into bucket
placement so fixed shard-selection bits do not concentrate keys in the
same buckets. Preserve entropy in the high-bit lookup tags and keep
symbol storage stable while reducing lookup overhead.
Upstream-Commit: 841880212f
Resolve non-allocated merged sections and assign their offsets in a
background Rayon task while symbol processing, GC and relocation
scanning continue. Give the task owned metadata and immutable input
bytes, create .comment before starting it, and publish the result before
section sorting and sizing. Keep allocated-fragment layout after GC, and
reassign offsets when debug sections are reordered.
Upstream-Commit: f585fee6ca
Fill .gnu.version slots in parallel after allocating the array. Each
dynamic symbol owns a distinct slot, so these writes are independent.
Upstream-Commit: 95137b235e
Rust already collects newly marked GC roots in local Rayon worker
vectors and combines them before graph traversal. This avoids contention
on a shared concurrent vector, matching the upstream change. No source
change is needed; this empty commit records that equivalence.
Upstream-Commit: 95dbca6708
Give consecutive version-script patterns with the same version index one
priority. Stop matching when no higher-priority result is possible, and
check simple substring patterns before glob patterns.
Upstream-Commit: f8cb3daf00
Compute fragment hashes and cardinality estimates during the string or
record scan, avoiding a second traversal of the input contents and
fragment offsets.
Upstream-Commit: 0f582b0a23
Debug relocations often visit consecutive merged strings. Cache the last
symbol and mergeable-section metadata in each relocation stream, so
input sections do not share mutable lookup state. Try the next fragment
before binary search and prefetch upcoming entries.
Upstream-Commit: 2c402f707f
Process four unsigned bytes per iteration and factor the DJB recurrence
into pairs to shorten its dependency chain. Hash the remaining bytes
with the scalar loop.
Upstream-Commit: 046a644906
Add a stable partition helper that evaluates predicates and scatters
elements into disjoint output ranges in parallel. Use it for the
local/global and import/export partitions in sort_dynsyms, preserving
the relative order within both groups. Evaluate predicates sequentially
for small ranges.
Upstream-Commit: 8566358143
Prefetch symbol records ahead of their auxiliary records to hide the two
dependent pointer loads in the serial table-allocation loop.
Upstream-Commit: 9887339f72
Keep dynamic symbols grouped by their owning input file so auxiliary
records can be initialized and indexed in parallel without globally
sorting symbol IDs. File ownership makes writes independent. Preserve
first-use order within each file, suppress repeated IDs, and retain
existing auxiliary data before the serial table-allocation loop.
Upstream-Commit: 0706198355
Cache GNU hash bucket indices and name views before sorting dynamic
symbols. This avoids repeated pointer chasing, name-length lookups and
bucket calculations in the comparison function.
Upstream-Commit: cbcf79c0ef
Compute string offsets with parallel scans and write dynamic symbols and
strings in parallel. This removes serial work on large dynamic symbol
tables while preserving output order.
Upstream-Commit: 6060657622
Allocate decoded_crel for all section indices on the first CREL section.
This avoids repeatedly growing and copying the table during section
parsing. Files without CREL allocate no table.
Parsing already omits losing COMDAT members and marks retained sections
alive, so skip redundant state updates for newly parsed files. Keep the
member walk when reusing parsed files, when LTO can change ownership, or
with --gdb-index.
Populate name-length caches during symbol registration. Scan global
names up to '@' or NUL, then scan the version suffix only when present,
retaining the raw length before version normalization or --wrap. This
removes a separate cache-population pass for both regular and LTO
objects.
Reuse the version-suffix flag set during global-symbol registration
instead of scanning COMDAT signature names for '@' again. Handle a bare
trailing '@' separately because it does not set the flag.
Check dynamic-symbol version conflicts in parallel, using existing
symbol-map locking and synchronized diagnostics. Keep the error
checkpoint after all checks finish.
Create fragment symbols in the arena as section-symbol relocations are
reattached. Collect pointers locally and reserve the file's symbol
vector once before appending them in relocation order, eliminating the
preliminary counting pass.
Run synthesized-symbol, object-file and shared-file symbol-table writers
concurrently. They write disjoint symbol, string and
extended-section-index ranges; initialize section symbols before
starting these writers.
Keep each dynamic symbol's name and string offset after sizing .dynstr,
reusing cached GNU hash sort keys when available. Both output writers
can then use independent parallel loops without repeating prefix scans
or name lookups. The cache costs 24 bytes per dynamic symbol.
Store a signed 32-bit name length in Symbol's existing padding to avoid
rescanning long names. Saturate at INT32_MAX and scan the suffix only
for saturated lengths.
Replace per-key unordered_map nodes with linear-probing slots containing
cached hashes and pointers to arena entries. This reduces lookup
overhead while keeping symbol addresses stable.
Resolve non-allocated merged sections and assign their offsets in a
low-priority TBB task while symbol processing, GC and relocation
scanning continue. Create .comment before starting the task and join
before section sorting and sizing consume the results. Keep
allocated-fragment layout after GC, and reassign offsets if debug
sections are reordered.
Collect newly marked root sections in per-thread vectors, then flatten
them before graph traversal. This avoids contention on the shared
concurrent vector.
Give consecutive version-script patterns with the same version index one
priority. Stop matching when no higher-priority result is possible, and
check simple substring patterns before glob patterns.
Compute fragment hashes and cardinality estimates during the string or
record scan, avoiding a second traversal of the input contents and
fragment offsets.
Debug relocations often visit consecutive merged strings. Cache the last
symbol and mergeable section per thread, keyed by input section and
symbol index. Try the next fragment before binary search and prefetch
upcoming entries.
Process four unsigned bytes per iteration and factor the DJB recurrence
into pairs to shorten its dependency chain. Hash the remaining bytes
with the scalar loop.
Add a stable partition helper that evaluates predicates and scatters
elements in parallel. Use it for the local/global and import/export
partitions in sort_dynsyms, keeping the standard algorithm for small
ranges.
Initialize SymbolAux in the per-file collection pass, where symbol
ownership makes the writes independent. This reduces work in the serial
allocation loop.
Cache GNU hash bucket indices and name views before sorting dynamic
symbols. This avoids repeated pointer chasing, name-length lookups and
bucket calculations in the comparison function.
Compute string offsets with parallel scans and write dynamic symbols and
strings in parallel. This removes serial work on large dynamic symbol
tables while preserving output order.
Deferred LTO jobs already own archive names, but claiming each member
allocated a second copy. Move the name into the object and use the object's
archive name when setting as_needed.
Validation: all 27 native LTO tests pass. Workspace Clippy passes with
warnings as errors. All 545 upstream test files match C++ mold exactly.
Remove five Rust-only shell tests so the test cases and common support file
match the C++ suite exactly. The Cargo runner still provides test discovery and
execution.
Validation: compare every tracked test file and Git file mode against C++ mold
at 92eb17a07c60a88b3a1a8c9cef014ac84eaf0cf9; no missing, extra or modified test
cases.
Port C++ mold 92eb17a07c. Record IR files from
direct inputs, regular and thin archives, and linker scripts, then claim them
serially after sorting by their command line positions.
Remove the redundant claim mutex and once-only initialization guard. Require
mutable context access when claiming an IR file, and retain the simple
loaded-state check used by the serial plugin lifecycle.
Copy the upstream regression test unchanged. It reproduces an undefined
__cxx_global_var_init before the fix. All 27 native LTO tests pass afterward.
Port C++ mold 450832db6b. Apply the
internal-object binding rule after handling input-local symbols and relocatable
output, preserving global binding for exported synthetic symbols.
Copy the upstream symtab-binding test unchanged. It fails before the fix and
passes afterward. Native symbol-table, synthesized-symbol, start/stop, export
and relocatable-output checks: 24 passed, 1 skipped.
Port the test from C++ mold d3cf4a23e0 unchanged.
The Rust symbol and section arenas were already replaced with vectors, so there
is no large writable arena reservation to change.
Validation: the native rlimit-data test passes with the current allocator.
Port C++ mold 32626eba5d. Ignore empty SFrame
input before reading its header, as required for startup objects assembled by
GNU assembler 2.45.
Copy the upstream regression test unchanged. It failed with a corrupted-section
diagnostic before the fix and passes afterward. Native SFrame tests: 1 passed, 2
skipped because the assembler lacks SFrame v3 support.
The parallel file reader handed IR files to the LTO plugin in
whatever order it found them. LLVM's LTO assumes that the module
holding the prevailing copy of a COMDAT group arrives before the
modules holding the other copies, as it does with a serial linker;
otherwise the losing copies' internal symbols, such as
__cxx_global_var_init.N, become undefined references in the LTO
result. The reader now only records IR files and claims them after
all input files have been found, sorted by command line position.
Claims were already serialized, so no parallelism is lost, and the
serializing mutex and once_flag in lto-unix.cc are no longer needed.
Fixes https://github.com/rui314/mold/issues/1649
Co-Authored-By: Claude Fable 5.1 <[email protected]>
35f4a334 changed is_local() to derive a symbol's binding in .symtab from
its visibility. Linker-synthesized symbols never have their visibility
merged from the internal object's ELF symbols, so they went from
STB_LOCAL to STB_GLOBAL by accident. That adds _DYNAMIC, __bss_start,
_end and the like to the set of global symbols, which confuses ABI
checkers that read .symtab.
Restore the old rule for the internal object's symbols: they are local
unless we export them. The exported ones, such as __global_pointer$ on
RISC-V or __start_*/__stop_* under -z start-stop-visibility=protected,
stay global as before.
Reported by Yao Zi in https://github.com/rui314/mold/pull/1650
Fixes https://github.com/rui314/mold/issues/1651
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Keep Endian, byte order markers, and integer wrappers together in
util::endian. Add fixed-endian read/write helpers and use them from
Endian and architecture code.
Use one macro to provide new, get, and set consistently for all five
standard integer wrappers.
Make create_phdr's define and append helpers accept &ElfShdr directly.
Reuse section headers at their call sites and use short osec bindings
for optional sections.
Validation:
- cargo build --locked --workspace
- cargo test --locked --workspace: 5,487 integration tests passed,
637 skipped, no failures; all 14 library tests passed. Four cross
targets lacked a compiler or QEMU.
Rename the module directory and update its declaration, imports, and
call sites to use crate::chunks.
Validation:
- cargo check --locked --workspace
- cargo test --locked --workspace: 5,487 integration tests passed,
637 skipped, no failures; all library tests passed. Four cross targets
were unavailable because their compiler or QEMU was missing.
Give each section a sibling module containing its type and functions,
and update callers to use the direct paths. Remove the misc and version
grouping modules and redundant nested wrappers.
Validation:
- cargo check --locked --workspace
- cargo test --locked --workspace: 5,488 integration tests passed,
636 skipped, no failures; all library tests passed. Four cross targets
were unavailable because their compiler or QEMU was missing.
ConcurrentMap exposed lookup and traversal while insertion was still
possible. Move those entry points to FrozenMap and keep the construction
API limited to allocation, insertion, prefetch, and freezing.
Freeze the GDB name map before collecting sorted entry references.
Extend the existing map test to verify lookup by the original entry IDs
after freezing, including the associated keys and values.
ConcurrentMap::prefetch only issued a cache hint on x86-64. Move the hint
into a shared utility and use stable inline assembly for x86, ARM32/64,
RISC-V32/64, PowerPC32/64, s390x, and LoongArch64.
Assume SSE on both x86 variants. Encode RISC-V PREFETCH.R as its ORI hint
so it remains a no-op without Zicbop. Keep unsupported architectures as
no-ops and preserve the map's empty-table guard.
fold_objc_classrefs collected each object's class-reference slots in
a HashMap keyed by the slot subsection, then walked the map to add a
GOT entry for every class that keeps a slot or is imported. hashbrown
seeds its hasher afresh in every process, so the walk visited the
classes in a different order each run: the GOT entries landed in
different slots, every adrp/ldr (arm64) or RIP-relative load (x86-64)
that encodes a slot address changed, and __got itself changed. Two
links of the same inputs produced different bytes.
The slots are now sorted by their subsection index - the object's
class-reference order - before the GOT entries are added, so a class
gets the same slot every run. This was a latent bug, not something
the chunk/symbol refactor introduced; it surfaced when an A/B run of
the refactor over the app corpus found twelve NetNewsWire, CotEditor,
Ghostty and iTerm2 test bundles whose __text and __got differed run
to run under both the old and new linker. With the sort, all twelve
link byte-identically across runs.
tests/cases/objc-classrefs-deterministic.sh links an object with
eleven Foundation class references to one path twice and compares;
before the fix the two outputs differed on essentially every run.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Move the Linux and Android MADV_HUGEPAGE calls into shared utilities,
with no-op implementations on other targets. Reuse them for output
mappings, concurrent-map storage, and symbol allocations, keeping the
symbol advice limited to complete pages within the allocation.
Extract platform-specific allocation and deallocation helpers for the
concurrent map, and guard page-alignment rounding against overflow.
A symbol's origin() used to be an enum of its own - Undef, Obj(index),
Dylib(index) - stored as a file index plus a kind byte. mold-rust
calls the same thing the symbol's file: Symbol::file() returns an
Option<FileId>, FileId being Obj or Dso (Dylib here), set_file and
clear_file change it, and the storage is SymbolFile, one u32 with the
top bit telling a shared library's index from an object's and
u32::MAX for none. Same here now; the kind byte is gone. The one
Mach-O wrinkle is the import resolved by dynamic lookup, which the
code models as Dylib(u32::MAX), a dylib index naming no dylib: that
value packs as the single u32 the NONE sentinel and the dylib bit
leave free, so the passes' `d != u32::MAX` tests read as before.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Subsections the linker synthesizes (the __common zero-fill of a
tentative definition, a GOT slot that absorbed a __objc_classrefs
entry, a rewritten method list, a merged Objective-C record, the
__dyld_private word) used to carry file u32::MAX, with their section
headers in a side table on Context, and the symbols the linker
defines (__mh_execute_header, ___dso_handle, aliases, the
_objc_msgSend$sel stubs, converted commons) had an origin of their
own, Synthetic. Every pass that walks subsections or resolves a
header, a relocation list or an owner then special-cased the
fileless ones: seventeen `file == u32::MAX` checks, a synthetic
branch in hdr_of and isec_relocs, and a Synthetic arm wherever an
origin is matched.
mold-rust gives those things a file: an internal ObjectFile, created
right after the inputs are read, that owns the synthesized symbols
(and here the synthesized sections too - Mach-O subsections are the
unit everything else is built on, so synthesized data is a
subsection like any other). The internal object takes part in every
pass as an input object with no symbol table: resolution, liveness
marking, duplicate checks and the -r writer walk its empty nlist
table and find nothing, and it stays alive from creation, so the
sections it owns never look like a dead file's. Origin::Synthetic is
gone, replaced by Obj(internal), and the one place that still cares
(a synthesized symbol with no section is __mh_execute_header, whose
nlist names section 1) asks ctx.is_internal, mold-rust's test.
ObjectFile.sect_hdrs is now a Cow: borrowed from the mapped file for
a real object, owned and growing for the internal one. hdr_of no
longer hands out a 'static reference, so create_output_sections
copies the header it consults before placing the subsection.
The map file, -print_statistics, -t and -dependency_info skip the
internal object when listing inputs, as before when it did not
exist.
One output difference, and a fix: a private-extern tentative
definition (int x __attribute__((visibility("hidden"))) under
-fcommon: N_UNDF | N_EXT | N_PEXT with the size in n_value) is
allocated in __common and, being a private external, belongs among
the output's local symbols. The symtab's private-external classifier
required an object owner, which a converted common no longer had, so
the symbol was silently dropped from the table; ld-prime lists it
(LuaSkin's ____asan_globals_registered and _VPMergeHook, which this
change puts back). tests/cases/common-private-extern.sh covers it.
Every section of the A/B links is otherwise byte-identical.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Pure code motion: build_rebase_info, build_bind_info,
build_lazy_bind_info and build_weak_bind_info, with the
segment_and_offset and data_blob_pointers helpers they share with
the chained-fixups builder, join the four LC_DYLD_INFO section
structs, the way mold-rust's dynamic.rs keeps its relocation table
builders next to their structs.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Pure code motion: collect_fixups, build_chained_fixups and
write_fixup_chains join ChainedFixupsSection, the way mold-rust keeps
each chunk's builder next to its struct. data_blob_pointers stays
with the dyld info builders, which share it.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Pure code motion: build_function_starts and build_data_in_code join
FunctionStartsSection and DataInCodeSection in output_chunks/misc.rs,
the way mold-rust keeps each chunk's builder next to its struct.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Pure code motion: copy_symtab, which writes the symbol and string
tables, joins SymtabSection and StrtabSection, the way mold-rust
keeps each chunk's writer next to its struct.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Pure code motion: the Export terminal, the trie builder and
encode_export_trie join ExportTrieSection, the way mold-rust keeps
each chunk's writer next to its struct.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Pure code motion: code_signature_size, the page hashing and
write_code_signature join CodeSignatureSection, the way mold-rust
keeps each chunk's writer next to its struct.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
The output used to be a Vec<Chunk>, each Chunk a header plus a
ChunkKind enum, with the data each kind is written from scattered
over Context (stub_syms, got_syms, rebase_data, unwind_info_data,
...), chunks located by a linear find_chunk scan or one of eight
cached indices (stubs_chunk, got_chunk, ...), and every kind's bytes
produced by one 230-line match in copy_chunk. mold-rust's model
replaces all of that: each kind is a struct holding its ChunkHeader
and its data (GotSection { hdr, got_syms }, SymtabSection { hdr,
entries, ... }), living in output_chunks/{got,objc,unwind_info,
eh_frame,dyld_info,chained_fixups,export_trie,symtab,misc,
output_section}.rs; Context has a typed field per kind (ctx.got,
ctx.symtab, ctx.output_sections); a ChunkId names a chunk, with
Output(OutputSectionId) and SectCreate(index) for the two kinds that
repeat; ctx.chunks lists the ChunkIds of the output in file order;
ctx.chunk_header(id) reaches any header and output_chunks::copy_buf
dispatches to the kind's own copy_buf. Passes read and fill the
typed fields directly, so the cached indices and the scans are gone.
Where Mach-O differs from mold-rust's ELF shape:
- Every chunk struct is always present, and a chunk is emitted when
its id is in ctx.chunks. mold-rust wraps its conditional chunks in
Option, but most Mach-O synthetic sections accumulate their data
(GOT slots, stubs, thread pointers, init offsets) long before
create_output_sections decides the chunk list, so the struct has to
exist from the start anyway.
- InputSection.output_section holds a packed ChunkId (top two bits:
output section, -sectcreate section, or one of the unique kinds),
read through output_section(). mold-rust stores an
Option<OutputSectionId>, a word thanks to the id's niche, since an
ELF subsection only ever lands in an output section; a Mach-O
subsection may also stand for a GOT slot (a folded __objc_classrefs
entry) or a rewritten method list in __objc_methlist, and a
general Option<ChunkId> would grow the 56-byte struct by a word on
every one of the millions of subsections of a debug link.
- ChunkHeader.n_sect is mold-rust's shndx: the 1-based section
ordinal an nlist's n_sect holds, assigned once the segments are
formed (the -r writer numbers its own section order over the same
field) in place of the section_ordinals vector every symtab
builder used to compute and index by chunk position.
- The linker-synthesized tails of __objc_methname, __objc_selrefs
and the merged Objective-C data sections are fields of
OutputSection, and the objc stubs section records which output
sections carry its two tails, replacing the two cached indices
layout used to search for by tail kind.
- The __LINKEDIT tables set their header size the moment their
contents are built (the symbol table, string table, dyld info
streams, chained fixups, export trie, function starts and
data-in-code all build together when layout reaches __LINKEDIT),
so the layout loop no longer needs a per-kind size match; only the
mach header (load-command size), __unwind_info (encoded at its
turn, since it embeds __TEXT offsets) and the code signature
(sized from the file offset it lands at) are special.
ctx.chunks is now sorted into file order after the segments are
formed, as mold-rust's is, rather than kept in creation order next to
the per-segment lists.
The encoders and stream builders (export trie, unwind info, eh_frame
copy, the dyld info and chained-fixup builders, the symtab copy, the
code signature) still live in passes.rs and output_chunks/mod.rs;
they move to their kinds' files in follow-up commits.
Verified by re-running 14 recorded app links (executables, dylibs,
bundles and -r prelinks from the corpus) with the previous build and
this one: every output is byte-identical.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Duplicate strong definitions used to be collected inside the symbol
resolution race: whenever an object's rank lost the compare-and-swap
for a name, the loser was pushed into a mutex-guarded list if both
ranks were strong and the resolution round was the final one, and
the list was reported once the race settled. mold-rust reports
duplicates from a separate check_duplicate_symbols pass that runs
right after unreachable files are removed: every live object walks
its global symbols and complains when a strong definition of its own
resolved to a different object. The result is the same set of
messages, but the check no longer depends on the shape of the race
(the rank encoding, the only_alive round flag) and reads as one
plain rule.
The Mach-O version of the rule: a global nlist that is not a stab,
is external, has N_SECT or N_ABS type and lacks N_WEAK_DEF is a
strong definition. Tentative definitions (N_UNDF with a size in
n_value) are not, so they yield to any real definition, and weak
definitions coalesce silently: both as ld64 does. Messages are
sorted by symbol name and then by the losing object's index so the
output is deterministic under parallel collection.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
ctx.symtab was the SymbolTable and ctx.symtab_data the output symbol
table, which is exactly inverted from mold-rust, where ctx.symbols is
the SymbolTable and ctx.symtab the output section. The same name meant
opposite things in the two linkers; now it means the same.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
The 8 GiB arena was mapped read-write, so on hosts with overcommit disabled
(vm.overcommit_memory=2) Linux ignored MAP_NORESERVE and charged the whole
reservation against the commit limit, and fork() charged it again for the
child. Even a hello-world link then failed with "fork: Cannot allocate
memory". Reserve the range with PROT_NONE instead and make it writable in
64 MiB chunks as the bump pointer advances, as the Windows code already does
with MEM_COMMIT. Only the chunks in use are charged, by mmap and by fork()
alike, and transparent huge pages still back them.
Under RLIMIT_DATA, which counts writable private mappings the same way, a
trivial link now needs 0.5 GiB instead of more than 8 GiB.
Fixes https://github.com/rui314/mold/issues/1644
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Port of the net of mold-rust e70f3ca and 4a0485c. A symbol caches its
name as a pointer and a u32 length; the length was cast from usize
without a check. mold-rust narrowed it to u16, found that ClickHouse's
C++ template instantiations produce names longer than 65535 bytes,
and restored u32 with a checked conversion and a regression test. The
conversion here is now checked the same way, and the test links a
65536-byte name through the symbol table, the string table and the
export trie whole, on both architectures.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Port of mold-rust 94e2104. gather_thunk_addresses collected every
(symbol, address) pair into a vector and then walked it, only
because writing a symbol's aux entry needs the context mutably while
the chunks are being read. The two borrows are now split - the
chunks read-only, the symbol table and aux table mutably - through a
variant of sym_aux_mut that takes the two tables it touches, so the
addresses are recorded as the thunks are walked, as C++ mold does.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Port of mold-rust a37ae7e. Opening an input went through a
"does the path exist" check and then reported any failure as "no
such file" or through errno read after the fact, so a permission
problem or an unmappable file was misreported. The shared open path
now returns the io::Error itself: an optional lookup maps NotFound to
None (a path that is not a regular file, such as a framework
directory, still reads as not found), and every other failure - and
every failure of a file that must exist - is reported with the
operating system's message ("Permission denied (os error 13)").
The map-file, dependency-info and output-file writers report their
create and chmod errors the same way, and errno_string is gone.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Port of mold-rust 2e73100. The linker links once per process, so the
diagnostic settings (color, -fatal_warnings, -w), the "an error was
reported" state and the mutex that keeps worker threads' messages
from interleaving are process-wide statics in error.rs, and the
fatal!/error!/warn!/out! macros take just the message. That removes
the Diagnostics handle from the Context, from every parser and
writer that carried a `diag: &Diagnostics` parameter (twenty-six of
them), from the two Arc clones made to hand it to worker threads,
and from the target crates' link() entry points; the archive reader
and the fat-slice lookup drop the Context they only used to report.
Behavior is unchanged.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
InputSectionId stored an already packed integer while FragmentRef kept
its component indices. Store a private ObjId and zero-based u32 index in
InputSectionId, with direct field access in file() and index(). Confine
the packed representation to raw() and from_raw().
Keep the eight-byte size and the existing Origin bit layout. Use the
reserved u32::MAX index for NONE and apply the index-plus-one encoding
at the packing boundary so its payload remains zero. Extend the existing
round-trip test to cover zero indices, maximum supported indices, and
the placeholder value.
Origin was assembling and splitting FragmentRef's two indices itself,
while InputSectionId already supplied raw and from_raw methods. Add the
same encoding interface to FragmentRef and use it from Origin::new and
Origin::get.
Keep FragmentRef's typed fields and preserve the existing packed layout.
Origin still checks the 30-bit merged-section limit before shifting the
payload to make room for its tag.
ObjectFile section access and output-section construction already know
the owning file ID, but building a section ID reloads it from each input
section. Construct IDs from the known owner and the decoded dense index,
and pass the owner once into regular_ids_mut.
Move paired ID/reference lookup to ObjectFile so it resolves the ELF
section index once. Reuse the validated dense-index accessors for shared
and mutable section access, avoiding redundant owner loads and checks.
InputSectionIds are assigned when a section is inserted into a file's
dense vector, and their file indices refer to the stable file pool.
Resolving these IDs currently repeats bounds checks for both indices on
hot section-access paths.
Add internal unchecked accessors for the pool and dense section vector,
and use them in Context::input_section. Document their insertion-based
preconditions and retain debug assertions on both bounds. Keep the
unsafe operations within these accessors and the ID resolution method.
The early fragment estimate scans ordinary relocation tables before
archive extraction, including members that may never be selected. That
adds work to large links such as ClickHouse just to predict capacity.
Restrict the early estimate to cheap CREL header counts from direct
inputs and already reachable archive members. Leave ordinary relocation
counting to the existing preparation of selected files after COMDAT
selection. This retains the early reservation needed by CREL-heavy links
while avoiding an extra scan of ordinary relocation records.
Rewriting mergeable-section relocations can append millions of dummy
symbols after global symbols have been constructed. Reserving only local
symbols allows this later append to grow the vector and copy the already
populated table, which is particularly costly for CREL-heavy links.
Estimate fragment demand before gathering globals, using relocation
counts from CREL headers and inspecting ordinary relocations that refer
to mergeable section symbols. Include that estimate in the initial
reservation and compute it across files in parallel. Reapply huge-page
advice only when vector capacity actually changes.
The ClickHouse benchmark contains C++ symbol names longer than the u16
limit, causing valid input to panic in Symbol::new. Restore u32 storage
for the cached name length and retain the checked conversion into that
representation.
Change the boundary test to construct a 65536-byte name successfully and
verify that Symbol::name returns all of its bytes. This removes the
restriction introduced by narrowing the length field.
Replace obsolete arena terminology with the actual storage and lifetime
rules: symbols and section members live in vectors, input files belong
to a stable pool, and parallel construction reserves disjoint ranges
before exposing writable slots.
Remove stale ArenaPtr distance restrictions and describe background
debug-info work in terms of tasks and concurrency. These are comment
updates; allocation, ownership, and linker behavior are unchanged.
Reserve an upper bound for local symbols when gathering globals so that
appending locals does not immediately move a populated symbol vector.
Request transparent huge pages for whole pages inside that allocation,
leaving partial boundary pages that may contain allocator metadata
alone.
Let per-file section vectors grow with the inputs actually inserted;
reserving for every ELF header wastes space on relocation and metadata
headers that never become InputSections. Validate the packed file-index
limit when inserting files into the pool, and use debug assertions for
the redundant checks when encoding and decoding validated section IDs.
Section lookups were constructing an InputSectionId and decoding it
again even when the caller only needed a reference. Factor the ELF
section table decoding into input_index and use the resulting dense
index directly for shared, mutable, and paired ID/reference access.
Keep regular entries and entries with merge metadata on the same lookup
path. Extend the Origin round-trip test to cover the full supported
32-bit section-index range.
Have InputSectionExtra expose r_deltas_mut, returning a mutable
reference to the owned delta array. InputSection::set_r_deltas can then
perform the assignment itself while retaining its nonempty-array
assertion.
RISC-V and LoongArch return their embedded field. Targets without
relaxation metadata retain the unreachable guard for attempts to mutate
it, keeping the target-specific implementation limited to field access.
Rename MergeableSection to MergeInfo and update the section-table flags
and accessors to describe its role as metadata for an ordinary input
section. Update merging, symbol attachment, and debug-info callers to
use the new names.
Preserve the existing ownership model: eligible SHF_MERGE inputs remain
in their dense slots and are marked dead while their contents are
represented by fragments. Store merge metadata only for those inputs,
without adding a merge-info field to every InputSection.
Logical section IDs and directly owned target state allow input sections
to move when their storage grows. Store InputSection values in each
SectionList's dense vector and resolve IDs by indexing that vector. Keep
merged inputs in their slots so existing references remain valid.
Remove SectionArena, its thread-local allocation blocks, and the manual
section destruction code. Vector ownership now supplies allocation and
destruction, and the shared virtual-memory reservation module can be
deleted because neither symbols nor input sections use it anymore.
The self-relative pointer to InputSectionExtras prevents input sections
from moving and requires a separate arena allocation for target state.
Make InputSection generic over Arch and embed an associated
InputSectionExtra value directly in each section.
ARM32 stores its exception-index section link, and RISC-V and LoongArch
own their relaxation-delta arrays. Other targets use a zero-sized extra
type. Propagate the target type through section owners and callers, and
remove the self-relative pointer encoding and extras allocator.
Arena-relative section IDs tie callers to the location of an allocation.
Represent InputSectionId as an object-file ID and a dense index within
that file, packed into 64 bits. Reserve two bits for Origin's tag and
keep zero available as the placeholder value.
Add a dense per-file slot table that still resolves to the existing
arena during this transition. Update symbol origins, output-section
members, relaxation, and layout lookups to resolve the logical ID
through its owner. This allows section storage to change independently
of the IDs used throughout the linker.
Symbols are already referenced through SymbolId, so their addresses only
need to remain stable while a construction or mutation phase borrows
them. Replace the 8 GiB SymbolArena reservation with Vec<Symbol> and let
the vector own and release the initialized symbols.
Keep the existing parallel construction scheme: reserve capacity before
publishing writable slots, assign disjoint ranges to workers, and set
the initialized length after they finish. Remove the custom virtual
memory allocation, slice access, and destruction code for symbols.
libc++ exports operator new and delete as weak definitions so a
program may override them. ld64 resolves a reference to a dylib's
weak export the way it resolves this image's own coalescable weak
definitions - by weak lookup, so dyld picks one copy across all
loaded images - rather than binding it to that one dylib: with
chained fixups the import's library ordinal is -3
(BIND_SPECIAL_DYLIB_WEAK_LOOKUP), with classic dyld info the slot is
bound to the dylib and also listed in the weak_bind stream, and the
call never binds lazily. An image whose own strong definition
overrides such an export is marked WEAK_DEFINES and lists the symbol,
flagged NON_WEAK_DEFINITION, first in the weak_bind stream, so dyld
lets this image's copy win. We bound these like any import, so the
program's operator new never won coalescing, and NetNewsWire's debug
dylib lacked the stubs and GOT slots ld-prime gives PLCrashReporter's
weak template methods.
The classic path also gained the machinery a weak-lookup stub needs:
its GOT slot is rebased, not lazily bound, and it takes no lazy
pointer or stub-helper entry.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Our compressed second-level pages listed every encoding page-locally
and held at most 200 records, so NetNewsWire's debug dylib needed 33
pages where ld-prime's has 8. ld64 puts the encodings an image uses
more than once - most frequent first, up to 127 - in the section's
common table (a compressed entry's 8-bit index names a common
encoding below that count and a page-local one above it; the one-off
DWARF-mode encodings, each carrying its FDE offset, stay page-local)
and fills each 4096-byte page from the end of the record list, using
the compressed format when it holds more records than a regular page
would. The personality pointers, patched in once the GOT has its
address, now follow the common table rather than sitting at a fixed
offset.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
A _objc_msgSend$<selector> stub loads the selector from a reference
slot; we synthesized one slot per stub in the __objc_selrefs tail
even when an input already had a reference to the same selector, and
the relative-method-list converter synthesized another for a
selector no input referred to even when a stub's slot existed. ld64
keeps one slot per selector. NetNewsWire's debug dylib had 155 slots
more than ld-prime's (0x39e8 vs 0x3510 bytes); it now matches.
A stub now loads an input's slot when one names its selector, only
the rest get tail slots, and a method list's synthesized reference
reuses a stub's slot before making its own.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
The indirect symbol table names the symbol behind each stub and GOT
slot. ld64 writes INDIRECT_SYMBOL_LOCAL for a GOT slot that holds one
of the image's own definitions dyld never rebinds - an exported one
included (otool -I shows 79 such slots in ld-prime's NetNewsWire
dylib) - and names only imports and weak-coalesced symbols. We named
every slot whose symbol was in the symbol table.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
ld64 gives every _objc_msgSend$<selector> stub a non-external symbol
with N_PEXT set (nm: "was a private external"), so a backtrace or a
disassembly names the stub. We emitted none; NetNewsWire's debug
dylib had 851 symbols fewer than ld-prime's for that reason alone.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Compilers mark a weak definition whose address is never observed
with .weak_def_can_be_hidden: no one can tell which image's copy they
use, so ld64 demotes such a symbol to non-external in every kind of
output and references it directly. We only did so in executables,
on the theory that an executable is first in load order anyway; in
a dylib the symbols stayed exported and coalescable, so NetNewsWire's
debug dylib exported and stubbed PLCrashReporter's template
constructors that ld-prime hides (12 stubs, 96 GOT slots, and 99 vs
111 weak externals). Auto-hiding now applies to every final link.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
A weak definition that stays exported - neither private nor
auto-hidden - is subject to coalescing: dyld keeps one copy across
all loaded images, whichever loaded first, and every image must then
use it (C++'s one-definition rule: an inline function's static local
is one variable, not one per dylib; a template's static member too).
ld64 routes every reference to such a symbol through a slot dyld
binds by weak lookup - a GOT entry for a GOT load, a stub with a GOT
slot for a call, the pointer itself for a data reference - and marks
the image BINDS_TO_WEAK. We referenced our own copy directly, so two
dylibs sharing an inline function each counted on their own static,
and NetNewsWire's debug dylib lacked the 64 stubs and 47 GOT slots
ld-prime gives PLCrashReporter's weak template methods.
With chained fixups the slot is a bind whose import names the symbol
with library ordinal -3 (BIND_SPECIAL_DYLIB_WEAK_LOOKUP); with classic
dyld info it is rebased to this image's copy and listed in a new
weak_bind stream (sorted by name, then address) that dyld applies
only if another image's copy won. A call to such a function never
binds lazily. The relaxations - a GOT load to a direct address, a
call to the definition - are skipped for these symbols; nothing
changes in a -r output, where references stay relocations.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Swift keeps a class's ro data (__DATA_<class>) in __objc_data, clang
puts __OBJC_CLASS_RO_$_ in __objc_const; ld64's merged record stays
where the original was, so NetNewsWire's debug dylib had 0x378 bytes
less __objc_data and 0x410 bytes more __objc_const than ld-prime's.
The rewritten record now goes to the original's section.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
ld64 keeps one __objc_selrefs entry per selector and one __cfstring
constant per distinct string in a final image as well: NetNewsWire's
debug dylib had 592 selector references more than ld-prime's
(0x4790 vs 0x3510 bytes) and two extra CFStrings. The pass that
folds them in -r output now runs in final links after literal
merging, for those two sections; class references are left to
fold_objc_classrefs, which turns them into GOT slots. Relative method
lists and selector stubs resolve a folded slot through its
replacement like any merged subsection.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Category merging rewrites the class_ro_t of a class that absorbed
categories as a new 72-byte record. A Swift class whose metadata
needs singleton initialization (stored properties of Foundation's
resilient types, say) carries RO_HAS_SWIFT_INITIALIZER (flag 1 << 6)
and an eighth pointer field at offset 72, the initializer the runtime
calls while realizing the class. The rewrite dropped it, and the
runtime read whatever followed the record - a raw, unfixed chained
pointer word - and called it: NetNewsWire's and iTerm2's test hosts
crashed in objc_copyClassList and OpenEmu's launch in
NSClassFromString, all in realizeClassMaybeSwiftMaybeRelock. The
earlier retargeting of the class's data pointer had just made such
records eligible for merging.
The rewrite now emits the eighth field when the flags say so, and a
record of that length is still replaced in place. The regression
test needs swiftc; the previous linker crashes on it.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
BIND_SPECIAL_DYLIB_SELF is 0 and BIND_SPECIAL_DYLIB_MAIN_EXECUTABLE
is -1 (mach-o/loader.h); we had them the other way round. Chained
fixups were unaffected (their import table maps the named constant to
0xff), and so were the nlist n_desc ordinals, so app-hosted XCTest
bundles worked while every reference to the host bound through a
chain. Xcode links those bundles with -undefined dynamic_lookup,
which selects classic dyld info, and once classic outputs gained lazy
binding their lazy binds named "this image" (SPECIAL_IMM 0) instead
of the main executable: dyld's lazy binder could not find the symbol
and the first call through the stub jumped to address zero -
Hammerspoon's test bundle crashed in every test's setUp calling the
host's MJLuaRunString.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Among weak definitions ld64 keeps the copy with the greatest
alignment, whichever object came first (at equal alignment the first
wins, whatever the sizes). Swift metadata records come 8-aligned from
one object and 16-aligned from another, and ld-prime's layout follows
the 16-aligned copy - one of the few remaining differences in the
__DATA and __TEXT,__const layout of NetNewsWire's RSCore prelink. We
always kept the first copy. A live weak definition's resolution rank
now carries its subsection's alignment, inverted, between the class
and the object priority.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
An object without .subsections_via_symbols has one subsection per
section. When such a section began with a weak definition that
another object also defined, coalescing folded the whole subsection
into the other copy - and every other symbol in it lost its bytes:
a global following the weak string in __TEXT,__const came out
aliased to the winner's neighbor, reading the wrong value. ld64
splits sections at symbols regardless of the flag and drops only the
weak atom. A losing subsection that holds a symbol at any other
offset is now kept.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Swift emits its __swift5_typeref mangled-name strings as weak private
externals, and the same string comes with or without a trailing pad
byte from one object to the next. Our coalescing folded only copies
of exactly the same size, so the odd-sized copies survived as
anonymous dead weight: NetNewsWire's RSCore prelink had 200 bytes of
them, and its debug dylib 0x3e0. ld64 discards a losing weak
definition regardless of its size. A loser now also folds when the
longer copy's tail beyond the shorter one is all zero, which is the
only mismatch that can be padding; RSCore's __swift5_typeref matches
ld-prime's.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Clang splits a function's rarely-run part into foo.cold.1 and marks
both parts N_COLD_FUNC. ld64 lays those atoms out after every other
atom of their section - in final images and -r outputs alike - so hot
code stays dense; ld-prime's images and prelinks end __text with the
cold code. We kept them in input order.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
ld64 -r keeps the whole of each input's __eh_frame - the FDEs of
compactly-encoded functions included, which a final link never needs
- laid out per object in input order, each CIE named EH_Frame1 and
each FDE func.eh, with an FDE's self-relative fields written as
SUBTRACTOR pairs against those symbols: the CIE pointer is
func.eh + 4 - EH_Frame1, pc_begin the function's symbol - 8 - func.eh,
and the LSDA pointer its symbol - offset - func.eh. A CIE's
personality cell keeps the object's pcrel GOT reference and addend
(4 on x86-64, where the field is relative to its own end). We
re-synthesized only the FDEs of functions with DWARF-only unwind
info, with self-relative values and no symbols, so a prelink's
__eh_frame was a fraction of ld-prime's (NetNewsWire's RSCore.o:
119 atoms missing, 0x10bc bytes on arm64) and its relocations took a
different shape.
The loader keeps covered functions' FDEs only for a -r link; the
writer lays the records out with the section, names them, and
re-targets the fields. A function or LSDA without a symbol keeps a
self-relative value. The x86-64 probe's __eh_frame and symbol table
now match ld-prime's byte for byte.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
An object's __compact_unwind may hold records whose encoding is
"DWARF mode": the unwinder must find the function's FDE instead.
Compilers emit them for frames the compact encoding cannot describe
(Swift closures gave NetNewsWire's RSCore prelink 98 of them), and
ld64 -r copies them as they came; the final link then regenerates
the encoding from the FDE. We dropped them at load and synthesized a
record from the FDE, which the -r writer then skipped (the next link
would synthesize it again), so the prelink's __compact_unwind was 98
records short of ld-prime's.
The loader now keeps DWARF-mode records and parse_eh_frame attaches
their FDE (a DWARF-mode record whose FDE never turns up is dropped);
a record without any is synthesized as before, and the -r writer
copies the input ones. The writer also names the function and the
LSDA by symbol - an extern relocation with a zero field - when they
have one, as ld64 does for every record; a nameless one stays
section-relative.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
ld64 gives a -r output's sections the same flags as a final image's:
a coalesced input section becomes regular, and no_dead_strip,
live_support, strip_static_syms and no_toc are dropped except on the
ObjC list sections the runtime scans. We kept the union of every
input's attributes, so NetNewsWire's RSCore prelink showed
__objc_protolist, __objc_protorefs, __swift_as_entry and
__swift_as_ret as coalesced no-dead-strip where ld-prime's are plain
regular. The synthesized __eh_frame is bare (flags 0) in a -r output,
where a final image gives it the compiler's coalesced/no-toc/strip/
live-support flags.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
The compiler emits, per object, a __objc_selrefs entry for each
selector it uses, a __objc_classrefs entry for each class, and a
__cfstring constant for each @"literal". ld64 treats each as an atom
coalesced by content - the selector's name, the class symbol, the
CFString's bytes and targets - and keeps one copy in a -r output as
in a final link. We carried every object's copy: NetNewsWire's RSCore
prelink had 56 class references and 17 selector references where
ld-prime's has 30 and 11.
A new pass folds the duplicates after literal merging (the first copy
wins, the rest redirect to it, and their symbols follow, as merged
literals do). For that the loader now splits __objc_selrefs (a
literal-pointer section) into one subsection per pointer and
__DATA,__cfstring into one per 32-byte record, as ld64's atoms are:
we had kept a literal-pointer section whole, and x86-64 compilers
emit CFString constants without labels to split on. A record's key
zeroes its relocated fields (x86-64 embeds the target's address) and
names the targets instead.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
ld64 drops __LLVM sections (bitcode, Swift's __swift_modhash) from
every output; we only did so in final links, and Xcode's -r
prelinks of Swift packages carried a __LLVM,__swift_modhash that
ld-prime's do not.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
ld64 -r writes local symbols in atom (address) order and names some
atoms itself with one shared counter: every cstring literal becomes
LC<n> with N_PEXT set (nm -m: "was a private external"), and the
anonymous records of __cfstring, __objc_selrefs and __objc_classrefs
(N_PEXT) and of the __objc_classlist/nlclslist/catlist/nlcatlist
sections (no N_PEXT) become l<nnn>, one per record. Their original
labels (l_.str, l_OBJC_METH_VAR_NAME_, l__unnamed_cfstring_,
_OBJC_SELECTOR_REFERENCES_, l_OBJC_LABEL_CLASS_$ ...) vanish, and the
relocations that named them - or that were section-relative, as
x86-64 objects refer to literals - now name the new symbols, with
the field holding the addend relative to the atom. Other assembler
labels (ltmpN, l_...) survive only when they name an atom of their
own: one no real symbol names at that address, or that a relocation
refers to; a label on an empty section is dropped. Private externals
are demoted as before, N_PEXT kept.
We emitted locals per object in input order and kept the compilers'
label names, so a prelink's symbol table differed from ld-prime's in
nearly every local. NetNewsWire's RSCore.o is now within 33 symbols
of ld-prime's 6540 (numbering, one empty-section label, and the
__eh_frame atoms ld64 keeps).
The section-relative pcrel fields the -r writer patches now leave
out x86-64's SIGNED_1/2/4 bias, as the objects' own encoding does.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
We wrote a -r output's sections in first-seen order across segments,
so __TEXT and __DATA sections interleaved and the synthesized
__objc_imageinfo, __compact_unwind and __eh_frame trailed everything.
ld64 (measured with ld-prime) groups by segment - __TEXT, __DATA_CONST,
__DATA, other segments as first seen, __LD last - and within __TEXT
puts __text first, other code sections (__StaticInit) next, the rest
in first-seen order and __eh_frame at the end. __DATA has a fixed
rank table: __got, __mod_init_func, __mod_term_func, __const,
__cfstring, __objc_classlist, __objc_nlclslist, __objc_catlist,
__objc_nlcatlist, __objc_protolist, __objc_imageinfo, __objc_const,
__objc_selrefs, __objc_protorefs, __objc_classrefs, __objc_superrefs,
__objc_ivar, __objc_data - the order a final link gives __DATA_CONST
and then __DATA - then unknown sections in first-seen order (__data
and __thread_vars among them), then __thread_data, __thread_bss and
__bss. Zero-fill sections stay in place: __bss precedes
__LD,__compact_unwind, and the sections after it have file offsets
that skip its address span.
The writer now lays out merged and synthesized sections from one
ranked list, sizing the synthesized ones up front (their contents
are built once addresses are known), and the file layout skips
zero-fill spans instead of mirroring addresses exactly.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
A CFI record whose length field runs past the end of the section
made the parser index out of bounds. ld64 reports "malformed
__eh_frame section: CFI length too long"; we now fail the same way,
and also on a section too short to hold a length field.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
ld64 -r turns a private external into a non-external symbol but
leaves N_PEXT set (n_type 0x1e, which nm -m reports as "non-external
(was a private external)"). We cleared it too (0x0e). The next link
does not care, but the symbol tables of Xcode's -r prelinks differed
from ld-prime's in every hidden Swift helper: NetNewsWire's RSCore.o
has 1624 such symbols, plus the cstring and anonymous-atom labels
ld64 also marks that way.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Clang flags the cold part it splits off a function (foo.cold.1, and
the function it came from) with N_COLD_FUNC (0x0400) in n_desc. ld64
-r keeps the flag on the merged object's symbols so the final link
can still lay cold code out apart; the final link itself drops it
with the other layout hints. Our -r writer masked it out, so the
symbol tables of Xcode's -r prelinks differed from ld-prime's in the
"[cold func]" column of nm -m.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
ld64 gives a merged method or protocol list a local symbol named
after the class and the categories it absorbed:
__OBJC_$_INSTANCE_METHODS_Foo(A|B), __OBJC_$_CLASS_METHODS_Foo(A|B),
__OBJC_CLASS_PROTOCOLS_$_Foo(A|B) (NetNewsWire's debug dylib has 886
of them, e.g. ..._AppDelegate(NetNewsWire|NetNewsWire1|NetNewsWire2|
NetNewsWire3)). We left the class's original list symbol pointing at
the merged list and dropped the categories' with the categories, so
the names differed and the symbol counts did not match.
The merger now records a name for each list it synthesizes; the
symbol table emits those as non-external N_SECT entries after the
objects' locals, with the list's final address. The class's own
lists are dropped outright rather than aliased to the merged one.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
ld64 -r keeps one copy of each weak definition (C++ inline functions
and template instances, Swift's per-object conformance and metadata
records), the .weak_def_can_be_hidden marker still on it for the
final link to auto-hide. We carried every copy into the merged
object: Xcode's -r prelink of NetNewsWire's RSCore package had a
__DATA,__const of 0x5298 bytes where ld-prime's has 0x2b90, and a
__text 27KB larger. The final link coalesced them anyway, so nothing
misbehaved, but the prelinks were larger than ld-prime's and their
symbol tables listed the duplicates.
The -r path now runs the same coalescing pass as a final link (but
not the auto-hiding, which is the final link's decision); the
re-synthesized compact-unwind records skip the dropped copies. The
RSCore prelink's __DATA,__const and __text now match ld-prime's sizes
exactly.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Swift emits a class's ro record in a subsection larger than the
72-byte record (NetNewsWire's DetailWebViewController: 80 bytes), so
requiring the record to be a subsection of its own left three of the
app's categories unmerged where ld-prime merges them. The class's
data field is now retargeted to the new record (keeping the Swift
flag bits in the pointer's low bits) instead of the record's
subsection being replaced, which still happens when the record is a
subsection of its own so that its symbol names the new record.
NetNewsWire's __objc_catlist is now the two entries ld-prime leaves
(categories on NSOpenPanel and NSApplication). MOLD_OBJC_DEBUG=1
reports the classes the pass skips and why.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Below the chained-fixups deployment targets (macOS 12 on arm64, 13
on x86-64, and under -undefined dynamic_lookup), ld-prime calls
imported functions through lazy pointers bound on first use: __stubs
jumps through __DATA,__la_symbol_ptr, whose slots start out pointing
at entries in __TEXT,__stub_helper that push the slot's lazy-bind
record offset and branch to the helper's header, which pushes
__dyld_private (an 8-byte word in __DATA,__data) and jumps to
dyld_stub_binder through its GOT slot; the lazy-bind opcode stream
(LC_DYLD_INFO's lazy_bind_off) holds one self-contained record per
stub; each lazy pointer is rebased; the indirect symbol table lists
the stubs, the GOT's entries, then the stubs again for the lazy
pointers. We bound every stub eagerly through the GOT (which
-bind_at_load asks for, and which ld-prime does then; it does not set
MH_BINDATLOAD, so neither do we now). The helper's code and the lazy
stream now match ld-prime's byte for byte on both architectures.
dyld_stub_binder is resolved from whichever loaded dylib exports it
when the first stub appears.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
The records category merging synthesizes (ro data, protocol and
property lists, rebuilt category lists) are ordinary data with
rebased pointer fields, and lazy binding is about to synthesize
__dyld_private the same way; the types and the tail carrying them
lose their Objective-C names.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Two bugs in category merging, found by the app corpus (NetNewsWire
and IINA aborted in the runtime's method-list scan):
- A Swift class's class_t.data pointer carries flag bits (bit 1,
FAST_IS_SWIFT_STABLE), so the ro record lives at the pointer with
the low two bits cleared. The walks took the relocation's addend
literally, looked for ro fields two bytes off, found nothing, and
so neither rewrote Swift classes' method lists in relative form
nor merged their categories.
- Worse, the merge pass retired the categories' method lists before
it checked the ro records' shape; a Swift class failed that check
(the offset-2 ro), its categories stayed, and their instanceMethods
now pointed at dead subsections - written as zero and rebased to
the image base, where the runtime read the Mach-O header as a
method list ("entsize_list_tt overflow ... with entsize 64204").
Every check now precedes every change.
NetNewsWire's AppDelegate (a Swift class with four @objc extensions)
now merges to an 80-entry relative list, and its debug dylib's
__objc_methlist is within 16 bytes of ld-prime's.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Three symbol-table differences from ld-prime's NetNewsWire, fixed:
- A -r output dropped .weak_def_can_be_hidden (N_WEAK_DEF together
with N_WEAK_REF on a definition), so the final link could no longer
auto-hide the definition: PLCrashReporter's template instantiations,
prelinked by Xcode, came out exported (T) where ld-prime hides them
(t). The marker is now among the n_desc bits a -r output keeps.
- A linker-private label (l...) that arrived as a private external
(older clang's l_OBJC_LABEL_PROTOCOL_$_X) was emitted as a demoted
local; labels are never emitted, whatever their scope.
- A non-external local in one of the Objective-C list sections
(Swift's _objc_classes_* entries of __objc_classlist, 127 of them)
is not emitted; ld64 never names those entries. A demoted private
external there stays, as clang's __OBJC_LABEL_PROTOCOL_$_X does in
ld-prime's output.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Assembler-local labels (L...) and linker-private ones (l...) do not
appear in an output image, and ld64 leaves them out of a -r output
too, except that a relocation needs its target symbol: a label
something refers to stays (ld64 renames those to lNNN; the arm64
assembler makes every section-relative target an ltmpN). Ours kept
every label, and Xcode's -r prelink of NetNewsWire's RSCore package
had 7739 symbols where ld-prime's had 6540; dropping the unreferenced
ones brings it to 6747. The remainder is ld64's renaming of referenced
anonymous atoms (LCn for literals, lNNN for the ObjC lists, EH_Frame1
and func.eh in __eh_frame), which is cosmetic.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
ld64 merges a category into its class when the class is defined in
the same image (-objc_category_merging, on by default, at every
deployment target): the runtime then has no category to attach at
load. Measured on ld-prime's output: the merged method list holds the
categories' methods, last category first, then the class's own (a
category's method precedes the class's, as after attachment); the
protocol list likewise (P3, P2, P1 for categories A and B on a class
adopting P1); the property lists take the categories in order, then
the class's. The class_ro_t and metaclass ro records are rewritten to
point at the merged lists, the merged category leaves __objc_catlist
(a category on a class from another image stays), and a class that
absorbed a +load category joins __objc_nlclslist so its +load still
runs. NetNewsWire's __objc_catlist went from 42 entries in our output
to 2 in ld-prime's; a small program with two categories now links to
the same __objc_const and __objc_methlist sizes as ld-prime's.
The pass runs after the method lists are rewritten in relative form
and merges those; with classic lists it writes classic merged lists.
Merged data - ro records, protocol and property lists, rebuilt
category lists, the non-lazy class entry - is synthesized as records
in the tail of the section of that name, each pointer registered as
a rebase. The class's original lists resolve to the merged ones, so
their symbols name them (ld64 names its __OBJC_$_INSTANCE_METHODS_
Foo(A|B)); the categories' lists and records are dropped. Relative
method lists are now laid out as ld64 does, sorted by symbol name and
8-byte aligned. -no_objc_category_merging keeps the categories.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
From a deployment target of macOS 11, ld64 rewrites every method
list in the relative form (-objc_relative_method_lists, on by
default): three 32-bit self-relative offsets per entry - to a
selector reference slot, the type string and the implementation -
instead of three pointers. The lists then need no fixups (dyld does
not touch them and the runtime finds the selector already uniqued
through the reference slot), and they move out of __objc_const into
the read-only __TEXT,__objc_methlist. ld-prime's output for a small
Objective-C program had every class, metaclass, category and protocol
list (optional ones included) there; ours kept the 24-byte form, so
each image carried three fixups per method and a larger __objc_const
(NetNewsWire: 0x535c bytes of __objc_methlist in ld-prime's output).
The lists are found the way the runtime finds them: __objc_classlist
and __objc_nlclslist to each class's and metaclass's ro data,
__objc_catlist and __objc_nlcatlist to the categories' two lists,
__objc_protolist to a protocol's four, and Swift's __objc_clsrolist
to the ro data of generic classes. A selector with no reference in
any input gets one synthesized in the __objc_selrefs tail next to the
objc stubs' (and registered as a rebase like them). Each rewritten
list becomes a synthetic subsection in the __objc_methlist chunk,
replacing the original so class data and the list's own symbol
(__OBJC_$_INSTANCE_METHODS_Foo, kept by ld64) point at it. A list that
is not the whole of its subsection or not in the classic 24-byte form
is left as it is. -no_objc_relative_method_lists keeps the classic
form; -objc_category_merging / -no_objc_category_merging are accepted
for the merging that follows.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Three things ld-prime does with weak references that we did not:
- A dylib every reference to which is a weak import gets
LC_LOAD_WEAK_DYLIB. The Swift overlays (libswiftIOKit, libswiftXPC,
...) are reached only through the compiler's weak
__swift_FORCE_LOAD_$_ references, so ld-prime lists all of them
weak and libswiftCore strong; we listed every library strong.
- A symbol referenced weakly by one object and strongly by another is
strong: ld64's default -weak_reference_mismatches treatment is
non-weak, so the bind carries no weak-import flag and the dylib
loads strongly. We flagged the symbol weak if any reference was.
- MH_BINDS_TO_WEAK is set when the image binds to a symbol a dylib
defines weakly (dyld must then consider weak coalescing when it
binds); ld-prime sets it on an executable calling a dylib's
__attribute__((weak)) function, and on NetNewsWire's debug dylib.
Dylibs now record their weak exports (a .tbd's weak symbols, a
binary's N_WEAK_DEF externals and WEAK_DEFINITION trie entries).
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
ld64 binds a symbol found through a dylib's re-exports to the dylib
that defines it, provided that dylib lives in a public location - a
top-level /System/Library/Frameworks framework or /usr/lib/lib*.dylib
(not /usr/lib/system/, not a private framework, not a sub-framework)
- and gives it a load command of its own, after the libraries named
on the command line, if anything binds to it. A private re-exported
library's symbols bind to the dylib that re-exports it. Linking only
-framework AppKit, ld-prime binds NSHomeDirectory to Foundation and
CFRelease to CoreFoundation (AppKit re-exports Foundation, which
re-exports CoreFoundation and libobjc), lists both after AppKit and
libSystem, and binds NSAttachmentAttributeName - defined in the
private UIFoundation, also re-exported by AppKit - to AppKit itself.
-no_implicit_dylibs turns this off.
We merged every re-exported library's exports into the re-exporting
dylib's, so everything bound to AppKit (or Foundation) and the image
listed only the libraries named on the command line.
Public re-exported libraries now load as implicit dylibs (recursively,
a weak parent's children weak), stripped when unused like auto-linked
ones. Ordinals, and so the load commands, follow ld64's order: the
libraries named on the command line or by auto-link options in naming
sequence - a library named after a re-export reached it keeps its
command-line position - then the implicit ones by install name.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
ld64 treats an object's LC_LINKER_OPTION entries as a set: it
processes them sorted, so its load commands list the auto-linked
libraries alphabetically ("-framework AppKit" ... "-framework WebKit",
"-lswiftCore", "-lswiftCoreFoundation" ...), which also fixes their
ordinals, after the libraries named on the command line in their
command-line order. We processed them in the order the objects
mentioned them.
An auto-linked library is a hint: ld64 gives it a load command only
if something binds to it. NetNewsWire's objects auto-link 43
frameworks and Swift overlays (Accessibility, CoreData, Metal, the
libswift* overlays ...) that nothing in the image binds to, and
ld-prime lists none of them; we listed all 91 libraries where it
lists 48. Dylibs loaded through auto-link options are now marked and
stripped when unused, the way -mark_dead_strippable_dylib ones
already were, without needing -dead_strip_dylibs.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
ld64 writes the load commands in a fixed order: the segments; a
dylib's LC_ID_DYLIB (with its umbrella and client commands); the dyld
tables (chained fixups and export trie, or classic dyld info); the
symbol tables; an executable's LC_LOAD_DYLINKER; LC_UUID,
LC_BUILD_VERSION, LC_SOURCE_VERSION; an executable's LC_MAIN; the
libraries in ordinal order; the run paths; LC_FUNCTION_STARTS and
LC_DATA_IN_CODE; and the code signature last. We had the code tables
before the libraries and the identity, dynamic linker and entry point
after them.
An image with nothing to fix up still gets a chained-fixups payload
(a header and a starts table with no pages, 48 bytes) rather than
falling back to classic dyld info: dyld reads the format from the
load command, and ld-prime writes the same 48 bytes for a dylib with
no fixups.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
From a deployment target of macOS 15, ld-prime emits no
__objc_classrefs section: a class reference is an 8-byte slot holding
a class's address, fixed up by dyld, which is exactly a GOT entry for
the class symbol. The code that loads a class from its slot (adrp/ldr
on arm64, a RIP-relative mov on x86-64) becomes a GOT load of the
class symbol: an imported class is read from its GOT entry, shared by
every reference in the image, and a class defined in the image relaxes
to computing its address directly (adrp/add, lea) and needs no entry
at all - ld-prime's output for a two-object test has GOT binds for
NSMutableArray and nothing for the local class. NetNewsWire's 581
class references became 168 GOT entries in ld-prime's output, and the
slots' local symbols (_OBJC_CLASSLIST_REFERENCES_$_n) are gone from
its symbol table. The runtime only ever read the section to remap
references to swapped classes, which macOS 15's dyld handles through
the GOT. Below macOS 15 the section stays (verified at 14.0).
The fold retargets the loads as GOT-load relocations of the class
symbol (a new Arch::got_load_form hook names the type), so the
existing GOT-load handling does the rest. A reference that cannot be
a GOT load (a slot whose address is taken, or a pointer to it) keeps
the slot: the slot subsection is replaced by a synthetic one standing
for the class's GOT entry, placed once the __got chunk exists.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
ld-prime picks chained fixups by default from macOS 12 on arm64 and
from macOS 13 on x86_64 (we required 13 on both, so a deployment
target of 12.x got classic dyld info from us and chained fixups from
ld-prime: DuckDuckGo's package frameworks at 12.3, and with them
__mod_init_func instead of __init_offsets). It also drops back to
classic dyld info under -undefined dynamic_lookup or suppress -
though not under -undefined warning - unless -fixup_chains is given
explicitly; Hammerspoon links with dynamic_lookup at 13.0 and came
out classic from ld-prime, chained from us.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Measured over ld-prime's output for the 15-app corpus (343 images
both linkers produced) and confirmed with small probes:
- Data that needs no writes after dyld's fixups moves from __DATA to
__DATA_CONST: __const, __cfstring, __got, __mod_init_func,
__mod_term_func, the ObjC class/category/protocol lists (and their
non-lazy variants), __objc_imageinfo, and clang's constant ObjC
literal objects (__objc_arraydata/arrayobj/dictobj/intobj/...).
Protocol and superclass references (__objc_protorefs,
__objc_superrefs) join them only from macOS 15 on; before that the
runtime may still write them, so they stay in __DATA with the flags
they came with (ld-prime keeps 0x1000000b on __objc_protorefs).
-no_data_const keeps everything in __DATA.
- __TEXT,__StaticInit joins __text; the fixed-size literal pools
(__literal4/8/16, already merged per element) join __TEXT,__const,
so a final image has no __literalN sections; the __LLVM segment
(bitcode, __swift_modhash, __cmdline) is never copied out; and
__DATA,__objc_clsrolist - a compiler-to-linker list of the
class_ro_t records of generic Swift classes that ld-prime consumes
(it asserts in ObjCClassROListAtom on a malformed one) - is dropped.
- Output flags: the section type is kept except that coalesced becomes
regular; instruction attributes are kept (pure implies some); every
other input attribute (no_dead_strip, live_support,
strip_static_syms, no_toc) is dropped, since they direct the linker,
not dyld; the ObjC lists dyld scans (classlist, catlist, nlclslist,
nlcatlist, selrefs, classrefs) are marked no-dead-strip; __eh_frame
gets the conventional 0x6800000b.
- Order within a segment: __text, __stubs, __stub_helper,
__objc_stubs, __init_offsets, __objc_methlist lead __TEXT and
__unwind_info, __eh_frame end it; __got, __mod_init_func,
__mod_term_func, __const, __cfstring, then the ObjC lists in a fixed
order lead __DATA_CONST; __la_symbol_ptr, __objc_const,
__objc_selrefs, __objc_classrefs, __objc_superrefs, __objc_ivar,
__objc_data, __data lead __DATA. Everything else keeps input order,
which is what ld-prime does too (the relative order of, say,
__cstring and __const varies across its outputs).
A -r output keeps every input section, name and flags, as it came.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
ld64 places an atom at an output offset congruent, modulo its input
section's alignment, to the offset the atom had in that section: an
8-byte atom at offset 8 of a 16-byte-aligned section lands at 8 mod
16. When the atom's predecessor is still there, that costs nothing;
when it is gone (dead-stripped, folded, or reordered) ld64 pads to
restore the modulus rather than rounding the atom up to the section
alignment. ld-prime does the same: after dead-stripping the 8-byte
atom ahead of _b, it pads 8 bytes past a 16-byte _a so _b stays at
8 mod 16.
We rounded every subsection up to its section's full alignment, which
pads half the alignment per atom on average and is why our output
sections ran larger than ld-prime's across the app corpus:
NetNewsWire's __TEXT,__const was 0xfff4 vs 0xd2c8 bytes with identical
atoms, __text 0x2e60d8 vs 0x2e4a98, __data 0x808a vs 0x74aa.
Subsections now carry the ld64 rule (InputSection::align_offset, via
util::align_to_mod) in both the plain and the thunked-section layout.
The sold-derived subsections-via-symbols test expected two 4-byte
functions in a 16-aligned section to be 16 bytes apart; ld-prime
keeps them 4 apart, and the test now says so.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
An LC_LINKER_OPTION is a hint from the compiler, and ld64 says
nothing when the library or framework it names cannot be found:
`ld -framework NoSuchFramework` is an error, but the same request
arriving through an object's auto-link options is dropped without a
diagnostic. Header-only SDK frameworks make that the normal case:
CoreAudioTypes.framework holds headers and a module map but no
binary or TBD, and every Swift object that imports it carries
`-framework CoreAudioTypes`, so CotEditor's build printed our
"auto-linked framework not found" warning 317 times where ld-prime
printed nothing.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
A dylib's exports live in its export trie (LC_DYLD_EXPORTS_TRIE, or
the export section of LC_DYLD_INFO), which is what dyld binds
against. The symbol table is optional: `strip` on a dylib leaves it
with no defined externals at all (nextdefsym 0), and binary
frameworks ship that way - Lottie.xcframework's macOS slice keeps 16
of its 1846 exports in the symbol table. We built the export set
from the symbol table's extdef range only, so every symbol from such
a dylib was undefined (DuckDuckGo's NetworkProtectionUITests link,
thirty undefined Lottie symbols) and a `strip`ped mold-built dylib
was unusable as a link input.
The dylib reader (and the re-export chain walker) now merges the
trie's entries with the symbol table's. A thread-local export is
recognized by the trie entry's kind bits (EXPORT_SYMBOL_FLAGS_KIND
_THREAD_LOCAL), since without a symbol table there is no section to
look at; our own trie writer did not set those bits, so it now marks
TLV descriptor exports as ld64 does (dyld_info shows [per-thread]).
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
A -r output re-emits the input symbols, and the next link reads their
n_desc flags the same way this one did. We wrote a defined global's
n_desc as just N_WEAK_DEF, a demoted private extern's as 0, and an
undefined's as 0, losing N_ALT_ENTRY, N_NO_DEAD_STRIP, N_WEAK_REF,
N_SYMBOL_RESOLVER and REFERENCED_DYNAMICALLY.
N_ALT_ENTRY is the one that breaks programs. It marks a symbol that
does not begin a new subsection: Swift's class metadata symbol
$s..CN is an alternate entry 0x18 bytes into the full-metadata
object $s..CMf, and the metadata accessor reaches the class as
CMf + 0x18 (a PAGE21/PAGEOFF12 pair against CMf plus an add). Once
the flag was gone, the final link split a new subsection at CN and
aligned it to the section's 16 bytes, so CN moved to CMf + 0x20 and
every CMf-relative reference pointed 8 bytes short of the class.
NetNewsWire's RSCore and Account frameworks, built from Xcode's
-r prelinked Swift packages, crashed in objc_opt_self and
swift_getSingletonMetadata as soon as their classes were touched.
Now a defined global carries the flags its definition had in its
object (ld64 shows [alt entry] / [no dead strip] on its -r output
too), a demoted private extern keeps N_ALT_ENTRY and N_NO_DEAD_STRIP,
and an undefined keeps N_WEAK_REF.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
-ObjC makes the linker load every archive member that defines an
Objective-C class or category, whether or not anything references it
by symbol. ld64's test for "has Objective-C" also fires on Swift
metadata: any __TEXT section whose name starts with __swift
(__swift5_types, __swift5_proto, __swift_modhash, __swiftast, ...)
qualifies, while an __objc_imageinfo, __objc_selrefs or
__objc_classrefs section alone does not. A Swift type registers with
the runtime through its type descriptors, so a member with Swift
metadata is as much "Objective-C" as one with a class list.
We only looked at __objc_classlist / __objc_catlist (and their
non-lazy variants), so an unreferenced Swift member stayed out.
iTerm2's ModernTests bundle failed to link under mold: it expects the
app's debug dylib, linked with -ObjC against libiTerm2SharedARC.a, to
export every Swift symbol in that archive (CompanionTouchMapper,
CompanionHEVCSampleBuilder ...), which nothing in the app references.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
The Objective-C runtime only looks at an image that has an
__objc_imageinfo section: dyld's ObjC notifier skips images without
one, so no class or category defined in such an image is ever
registered. A class referenced from another image (via its
OBJC_CLASS_$_ symbol) then aborts at first use with "Attempt to use
unknown class", and categories on framework classes silently never
attach ("unrecognized selector").
Our final link merges the inputs' records into one synthesized
section, and to do that it discards the input __objc_imageinfo
sections up front. The -r writer only emits merged input sections, so
a relocatable output lost the record entirely, and any image later
linked from that object had no ObjC metadata as far as the runtime
was concerned. Xcode prelinks every Swift package's ObjC target with
`ld -r`, which is how NetNewsWire's RSDatabase (FMDB) and RSCore
frameworks ended up without the record: SyncDatabaseTests crashed in
the FMDatabase type metadata accessor, and the app raised
+[NSMenuItem rs_disableIcons] unrecognized selector.
Like ld64, -r now writes the merged record as an 8-byte
__DATA,__objc_imageinfo (version 0, flags merged the same way as for
a final link: shared Swift version, newest language version, the
category-class-properties bit only if every ObjC input has it).
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
In a -r link ld64 turns private external symbols (visibility hidden)
into plain non-external ones unless -keep_private_externs. Ours kept
them as private externals and rejected the option. Apple's strip
depends on it: `strip -S` on an archive runs the toolchain's
`ld -r -keep_private_externs -S` on each member, so with the
toolchain's ld replaced, Sparkle's build failed in its strip step
with "internal link edit command failed".
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
A -r link concatenated the inputs' __DWARF sections. That cannot be
right on Mach-O: the section-relative offsets inside DWARF (each
unit's abbreviation offset, its line table, ranges, string offsets)
carry no relocations, so every unit but the first pointed into the
wrong place in the merged sections; lldb reported "invalid
abbreviation code" for every prelinked package object in Sequel Ace
and dwarfdump looped on them. ld64 does not merge DWARF at all: its
-r output has no __DWARF and instead carries debug-note stabs, per
input object with DWARF, that name the object (N_OSO, with the file's
mtime) and where its functions landed, and the final link copies
those notes through so a debugger's debug map still points at the
original objects.
The -r writer now does the same, through one per-object planner
shared with the final link. Two details of ld64's shape mattered: the
run opens with two N_SO entries, the compilation directory (with a
trailing slash) and the source file name, both read from the DWARF
compile unit (a small DWARF 2-5 reader; ld64's own stab parser takes
an N_SO with an empty name as the closing one and crashed on a -r
output without them), and each function's N_FUN pair is bracketed by
N_BNSYM and N_ENSYM. The final link propagates the notes of an input
that already carries them, rebasing the address-bearing entries to
their subsections' output addresses and dropping those of dead
subsections; an empty name must be string-table offset 1, since 0 is
the leading space and lldb then never sees the unit's end. The
N_OSO mtime is stamped as ld64 does, so dsymutil and lldb can detect
an object rebuilt after the link.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
The entry symbol is interned and marked used before resolution so a
dylib export can claim it. A -r link's output type is still the
executable default, so its output acquired an undefined _main, and a
dylib built from Xcode's prelinked package object (NetNewsWire's
RSParser.framework, DuckDuckGo's IssueReporting) failed with
"undefined symbol: _main".
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
The install name a dylib gets when -install_name is absent; the
compiler driver passes it when invoked with several -arch, and
Transmission's CMake-driven links pass it for their bundles.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
XCTest.framework re-exports XCTestCore, a universal binary; the
re-export loader handled thin dylibs and stubs only, so the chain
stopped with "unsupported reexported library" and every XCTest class
was undefined in test bundles. The file found for a re-export is now
sliced to the target architecture like a top-level input.
libXCTestSwiftSupport.dylib re-exports @rpath/XCTest.framework/...
with rpaths that cannot reach it; ld64 matches such a name against
the libraries already in the link (-framework XCTest loaded it). A
re-export whose install name matches a loaded dylib now takes that
dylib's exports without a file search.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
A bundle's remaining undefined symbols may resolve to the executable
that will load it. Xcode links every app-hosted XCTest bundle with
`-bundle_loader <app>` against the app linked with -export_dynamic
(Amethyst's and Maccy's test bundles), so no hosted test ran.
The executable is read like a dylib, minus the install name: its
exports come from the symbol table's defined externals and from the
export trie (an executable may be stripped). It takes no
LC_LOAD_DYLIB, so it is left out of the load-command ordinal
numbering, which now counts only real dylibs (dead_strip_dylibs'
renumbering included). Its symbols bind to the main executable, and
the three places that encode a library ordinal spell that
differently: the bind opcodes use BIND_SPECIAL_DYLIB_MAIN_EXECUTABLE
(0) in SET_DYLIB_SPECIAL_IMM; the chained-fixups import formats use
-1 in the field's two's complement (0 means the image itself there,
and the 64-bit format's field is 16 bits wide, which the flat-lookup
ordinal was also getting wrong); the symbol table's n_desc uses
EXECUTABLE_ORDINAL (0xff). dyld_info shows the binds as
<main-executable>, as for ld64's output.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Xcode's "eager linking" lets a target link against a framework built
in the same workspace before the framework is built, through a stub
that tapi writes in TBD version 5: JSON, unlike the YAML of versions
1-4 (Stats' Kit.framework, Sparkle, Hammerspoon's LuaSkin). The
version key may be the last thing in the file, so a .tbd starting
with '{' is taken as JSON.
The format: a "main_library" object with install_names,
current_versions, target_info, flags (attributes such as
not_app_extension_safe), reexported_libraries, and exported_symbols
grouped per target set and split into "data" and "text", each listing
global, weak, thread_local, objc_class, objc_eh_type and objc_ivar
names (the Objective-C kinds expand to their _OBJC_*_$_ symbols); a
"libraries" array holds inlined re-exported libraries of the same
shape. As with the YAML formats, target groups are merged. A small
JSON reader suffices; no dependency is added.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Two failures from Xcode's -r prelinks of package targets, which keep
the DWARF sections a final link discards before reading relocations:
A section-relative relocation may address one past a section's end.
The assembler's DWARF for a -g assembly file sets DW_AT_high_pc to
the end of __text (BoringSSL's hand-written routines in DuckDuckGo's
JWT package), and a compiler's __debug_ranges ends there too
(Transmission's libutp). With an alignment gap after __text that
address is inside no section and the reader refused it as "bad
relocation"; it now falls back to the section that ends there, and
the subsection lookup maps it to one past the last subsection.
An empty output section (every subsection of a coverage section
dead, say) shares its file offset with its neighbor, and the copy
phase's sort by offset could place it after a non-empty section
starting at the same offset, making the gap arithmetic wrap
(Ghostty's debug dylib: an overflow in debug builds, a slice panic in
release builds). Empty sections have nothing to copy and are skipped.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
A framework binary is named through the X.framework/X symlink, but
dyld resolves its @loader_path from the real file in Versions/A,
and the framework's LC_RPATH entries are written for that location.
Xcode's XCTest.framework re-exports XCTestCore through
@rpath/XCTestCore.framework/... with the rpath
@loader_path/../../../../PrivateFrameworks/; taken from the
symlink's directory that reached the wrong place, the re-export was
not followed, and every XCTest class was undefined when linking a
test bundle. The loader directory is now that of the canonical path
(minus the "(for architecture ...)" suffix a fat file's name
carries).
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Xcode links every app extension's debug dylib with
`-alias _NSExtensionMain ___debug_main_executable_dylib_entry_point`,
and _NSExtensionMain is imported from Foundation. ld64 handles an
alias of a dylib symbol by making the new name an indirect symbol:
an N_INDR entry whose n_value is the string-table offset of the
original name, and an export trie terminal with
EXPORT_SYMBOL_FLAGS_REEXPORT carrying the dylib's ordinal and the
name the symbol has there, so dyld resolves clients of the alias
through the dylib. (An alias of a defined symbol was already a
second definition at the same address.)
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
App extensions are linked with `-e _NSExtensionMain`, a function in
Foundation that nothing in the extension references. Two things
were missing: the symbol has to exist and count as used before
resolution so that a dylib export can claim it (it is now interned
with the -u names, and -alias bases likewise), and LC_MAIN, whose
entryoff must fall inside __TEXT, has to name a stub for it. ld64
gives the entry symbol a stub; so do we, allocated after
scan_relocations with the stubs of the branch targets.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
ld64 takes the platform by name or by its PLATFORM_* number, and
Xcode passes the number for some steps (`-platform_version 1 11.0`
on a CMake-driven prelink in Transmission).
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Two bugs from the app corpus:
The pointer-fixup collectors skipped the UNSIGNED half of a
SUBTRACTOR pair (is_subtracted) but not the SUBTRACTOR relocation
itself, which classifies as a plain 8-byte data relocation. For the
4-byte pairs Swift emits the size filter hid this; code-coverage
instrumentation (Xcode test builds) emits 8-byte pairs in
__llvm_prf_data, so every such slot, holding a difference rather than
an address, was also recorded as a rebase: a negative difference was
refused as "rebase target unencodable" (Maccy, Sequel Ace's QueryKit,
Hammerspoon's hs, Ghostty's dock tile plugin), and a positive one
would have been slid by dyld. The fatal message now names the section
and value.
Laying out __eh_frame dropped the FDEs of folded subsections without
remapping the unwind records' FDE indices, so a record could point
one past the shortened table and the __unwind_info encoder panicked
(Sparkle's generate_appcast, CotEditor, DuckDuckGo). The records are
remapped as the dead-strip pass already does, and a folded copy's
own record is dropped since its leader carries one.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
The _objc_msgSend$<selector> stubs need a selector reference slot and
a selector name string each. Those were emitted as sections of their
own, named __objc_selrefs and __objc_methname, next to the sections
of the same names built from the inputs, so an image with both stubs
and compiler-emitted selector references had two __DATA,__objc_selrefs
sections.
The Objective-C runtime reads one __objc_selrefs section per image
(getsectiondata returns the first) and uniques the selectors in it at
load time. Every @selector() in the second section stayed a plain
string pointer: Sequel Ace's debug dylib, linked with the stubs,
died in Firebase's +load because
[FIRInstallations respondsToSelector:@selector(componentsToRegister)]
compared an unregistered selector against the method list. ld64
appends its synthesized selector references to the section the
inputs provide.
The synthesized strings and slots are now a "tail" of the output
section of that name, created empty when no input provides one, laid
out after the input subsections and copied after them; the stubs,
rebase and chained-fixup records address the slots through the tail.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Relocations are sorted by offset when an object is staged so that
each subsection's run is contiguous. The sort was unstable, and a
SUBTRACTOR relocation shares its offset with the UNSIGNED it pairs
with: their order in the object is the pairing. Swift emits every
relative pointer in its metadata sections as such a 4-byte pair, and
a release build of a Swift app (Stats' SMC helper, -Os) had enough
of them in __swift5_fieldmd for the sort to swap some. The lone
4-byte UNSIGNED that resulted was written as 8 bytes, which
corrupted the next entry or overran the subsection and panicked
("range end index 8 out of range for slice of length 4").
The sort is now stable. A 4-byte UNSIGNED relocation on its own is
also legitimate (`.long sym`, a 32-bit absolute address); it is now
written as 32 bits, with ld64's "32-bit absolute address out of
range" error when the value does not fit, instead of being asserted
away in debug builds and mis-sized in release builds.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Xcode prelinks every Swift package target with `ld -r`. Such a link
auto-linked libc++ through the objects' LC_LINKER_OPTION commands,
the dylib then claimed operator new, and the relocatable writer
found no symbol-table entry for the relocation against it ("-r:
cannot re-emit relocation against __Znwm").
ld64 does not act on auto-link requests in a -r link: each distinct
LC_LINKER_OPTION is copied into the output object, in first-seen
order, for the final link to resolve (Xcode's ArgumentParser.o
carries 29 of them), and a dylib or text stub named on the -r
command line is ignored with the warning "ignoring unexpected dylib
text stub file". The relocatable path now does both, and its output
for that link matches ld-prime's: 29 linker options, 748 undefined
symbols.
The -r path also returned before writing -dependency_info; Xcode
asks its prelinks for the file and fails the build when it is
missing.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Building real apps with xcodebuild (the ~/mold-test corpus) showed
that nothing links until the linker answers Xcode's probes and
accepts a few options ld64 takes as no-ops:
- `ld -version_details`: swift-build (Xcode 26's build system) runs
it before the first link and fails the whole build if the output is
not JSON. It decodes "version", an ld64 version number compared
against thresholds to choose flags (-sdk_imports needs 1164), and
"architectures". Apple's ld also reports LTO and TAPI versions,
which are ignored. We report the ld64 version whose command line we
implement (1267, Xcode 26.6) so Xcode drives us as it drives
ld-prime, and the two architectures we support. Xcode also tries
`ld --version`, which Apple's ld rejects too; that one may fail.
- -Os: Xcode passes -O0 for debug and -Os for release links; ld64
treats -O<n> as an optimization-level hint. -Oz for completeness.
- -debug_variant: silences ld64's warnings that only matter for
binaries shipped to customers; there are none here.
- -dylib_compatibility_version / -dylib_current_version: the older
spellings, still passed by Xcode for dynamic libraries.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Ports mold-rust's install-mold.sh: the release binary is installed as
$PREFIX/bin/mold with an ld64.mold symlink next to it, the name to
give clang's --ld-path or swiftc's -use-ld, as lld is reached through
ld64.lld. No wrapper library or man page exists here yet, so only the
binary and the license are installed.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
The executable was ld64-mold. The intent is to invoke it as
ld64.mold, mirroring ld64.lld, but Cargo derives the crate name from
the binary target and rejects a dot ("you might have meant to use
--crate-name=ld64_mold"), so the target is now plain `mold`, as in
mold-rust, and ld64.mold is the name to give a symlink at install
time, the way mold-rust installs ld.mold.
`ld -v` with nothing else on the command line is how build systems
and configure scripts probe the linker: Apple's ld prints its banner
(version, supported archs, LTO and TAPI versions) and exits 0. Ours
printed the banner and then failed with "undefined symbol for entry
point: _main" because it went on to link nothing. Now, like mold with
-v/--version, it exits 0 after the banner when there are no inputs.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Use u16 for Symbol::name_len, reducing the width of the cached length
field while keeping the existing thin pointer to the name bytes. This
representation imposes a maximum symbol name length of 65535 bytes.
Check the conversion in Symbol::new so that longer names produce an
explicit diagnostic. Add a regression test for a 65536-byte name to
exercise that boundary.
Let the compiler decide whether to inline private and generic helpers that do
not correspond to explicitly inline C++ code. Keep forced inlining on the hot
ELF and relocation paths identified by the C++ implementation and perf
profiles.
Firefox-debug performance remained neutral in both single-threaded and
32-thread runs. The full test suite and release clippy checks pass.
The output was assembled in full and then written with parallel
pwrites, ~70ms of a debug clang link's 773 that nothing overlapped.
mold for ELF has no such phase: its output is an mmap of the file, so
the copy is the write and the kernel flushes after exit. That is not
available here - macOS kills an ad-hoc-signed binary whose vnode ever
had a writable mapping. A second look this time found that
clonefile() of a mapped-written temp file does give a fresh vnode that
executes, but every way of making the mapped pages visible to it
(clonefile, msync, munmap) is a synchronous per-page push at 2.5-4.5
GB/s, while pwrite of the same 195MB takes 33ms and a following fsync
2ms - the cluster write has reached the drive before pwrite returns.
So mapped output would be about twice as slow, and the writer module
doc records both experiments.
What can be had is overlap. The write is bound by the kernel's page
cache and drive path, not by our CPU work, so the file is created up
front and byte ranges are queued to writer threads the moment nothing
will modify them again: everything between the header and the symbol
table once the copy, the fixup chains and the optimization hints are
applied; the symbol and string tables after copy_symtab; the header
after the UUID; the signature last. The writers read the buffer
through a bare pointer while the linking thread keeps its &mut for the
ranges still being produced (a queued range is never written again).
Four writers: the exposed tail after the last queue on debug clang is
18ms with one writer and 5ms with four, and four measured best overall
even though they take cores from the symbol table and page hashing
they overlap (copy_symtab 19 -> 30ms while sharing the machine).
Interleaved A/B against the previous commit, best 6 of 12 (ms):
clang 769.6 -> 729.0 (-5.3%), nu 329.4 -> 311.9 (-5.3%), mold
135.2 -> 131.2 (-3.0%); the copy+write lap on clang 151 -> ~100ms. The
outputs are byte-identical to the previous commit's; 322 integration
tests pass; the debug clang compiles and runs a program, nu and mold
run. The -r path still writes its complete buffer through the same
writer.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
The UUID that identifies a build was a content hash computed in its
own pass over the output - 4MiB blocks digested on all cores, the
digests digested once more - and the ad-hoc code signature then hashed
every 4KiB page of the same bytes again. Both passes stream the whole
403MB of a debug clang link. The code directory's page-hash array is
already a complete digest of the contents, so the UUID now comes from
it: SHA256 of the array, stamped as a version-4 UUID.
The circularity - the signature covers the header, and the header
holds the UUID - is broken the way ld64 breaks it: the pages are hashed
while the LC_UUID field is still zero, the header is rewritten with
the UUID, and only the pages the header spans are hashed again for the
signature (page_hashes / rehash_pages / write_code_signature split
the old function). As with ld64, the UUID depends on the contents
before the signature and not on the signature blob, whose identifier
is the output's basename. Signed and unsigned output still get
different UUIDs, because their headers differ (the LC_CODE_SIGNATURE
command and the __LINKEDIT size), which is also true of ld64. Unsigned
output hashes its pages the same way for its UUID; -no_uuid skips the
derivation and the signature uses the hashes directly.
The UUID pass is gone from the tail of the link: on debug clang the
uuid + codesign steps went from 18 + 19 ms to page hashes 25, uuid 1,
signature 0.5. Interleaved A/B, best 4 of 8 (ms): clang 754.7 -> 730.9
(-3.2%), nu 342.8 -> 335.4 (-2.2%), mold 155.1 -> 155.3 (+0.1%). Output
is still reproducible for the same -o path, codesign -vv accepts the
signature, and 322 integration tests pass; the debug clang compiles
and runs a program, nu and mold run.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
mold-rust follows its pessimistic first thunk pass with
remove_redundant_thunks once addresses are final, then places the
section again. Measured here, interleaved on an idle machine, the
thunk port with that pass costs +5.4% on the debug clang link and
+4.8% on nushell (790.9 vs 750.1 ms, 362.2 vs 345.4 ms); without it the
port is neutral (+0.1%, +0.2%). The pass rescans every branch of every
executable section and forces a second __TEXT placement, which
re-encodes __unwind_info - while it recovered 119KB of a 160MB __text,
because our first pass already skips the targets the section's size
bound proves reachable (mold-rust's first pass is more pessimistic, so
its trim pays off there).
So the symbol-owned model stays - atomic-mark dedup in the parallel
scan, entries released by the reach cursor, per-symbol thunk_addrs at
apply time, no per-relocation field - and the trim pass and the
re-placement hook go. The unused entries are dead code in the thunk
islands. 322 integration tests pass; the debug clang compiles and runs
a program, nu and mold run.
In the per-commit series (interleaved A/B against the parent, debug
links, best 4 of 8 runs, ms): clang 820.2 -> 776.7 (-5.3%), nu
370.1 -> 360.6 (-2.6%), mold 149.4 -> 148.5 (-0.6%). The seven commits
from the Cie/Fde compaction together: clang 785.2 -> 771.4 (-1.8%), nu
360.8 -> 358.9 (-0.5%), mold 150.5 -> 147.2 (-2.2%).
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
merge_literals folds identical literal elements by giving each losing
subsection a `replacement` link to the survivor, and every address
lookup followed that chain. mold-rust instead makes the merged
fragment the symbol's origin, so a symbol's address is a direct read.
Do the same: after the folds, a parallel pass over the symbol table
repoints every symbol defined in a merged-away copy at the survivor
(the copies are identical, so its offset is unchanged). sym_addr no
longer meets a redirect; section-relative relocations still resolve
through the chain in isec_addr.
Output is unchanged: the debug clang, nu and mold links are
byte-identical to before, and 322 integration tests pass.
Interleaved A/B against the parent commit, debug links, best 4 of 8
runs (ms): clang 815.8 -> 824.9 (+1.1%), nu 372.1 -> 376.3 (+1.1%),
mold 148.3 -> 150.2 (+1.3%); an earlier run of the same pair read
-1.0%, -2.5%, +0.3% - within noise either way.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
The dead-strip walk kept a live: Vec<bool> for the roots and the
serial -why_live walk, copied it into a visited: Vec<AtomicBool> for
the work-stealing parallel walk, copied that back, and only then wrote
each section's is_alive; -why_live's predecessor array was allocated
whether or not it would be printed. mold-rust marks the section itself:
its InputSection flags are an atomic byte with IS_ALIVE and IS_VISITED
bits.
Do the same. InputSection's is_alive bool becomes an atomic flag byte
(same size; the struct stays 56 bytes): is_alive() is a plain load, the
&mut setters write through get_mut without an atomic operation, and
mark_visited() claims the visited bit with fetch_or - the parallel walk
uses it directly, so the two side arrays and both copies are gone, and
the predecessor array exists only under -why_live. Liveness is then
is_alive &= visited, consuming the bit.
The reachable set is unchanged. A link without thunks is byte-identical
to before; 322 integration tests (which cover -dead_strip and
-why_live) pass, and the debug clang, nu and mold links run.
Interleaved A/B against the parent commit, debug links, best 4 of 8
runs (ms): clang 799.3 -> 806.2 (+0.9%), nu 365.0 -> 366.1 (+0.3%),
mold 146.6 -> 142.7 (-2.7%) - within run-to-run noise.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Range-extension thunk entries were recorded per relocation: the layout
scan wrote a thunk offset into every branch Reloc that might not reach
(4 bytes on ~12M records), deduplicated per batch in a serial pass
after the parallel scan, and a forward-reachability heuristic stood in
for knowing which entries were really needed. mold-rust's model, which
this ports:
- An entry belongs to a symbol. The first pass is pessimistic (a
cross-section, still-unplaced or out-of-range target gets an
entry) and deduplicates inside the parallel scan with the symbol's
atomic mark; a symbol stays marked - so gets no second entry - for
as long as its entry remains within reach of the batches that
follow, and is released by a cursor over the thunks that have
fallen out of reach (mold-rust's cursor A). Entries are sorted by
symbol id for a deterministic layout.
- Once __TEXT is placed and addresses are final,
remove_redundant_thunks re-marks only the symbols some branch truly
cannot reach, drops every other entry, re-lays each thunked section
out by a merge walk over its subsections and thunks, and the
segment is placed again (the __unwind_info encoding, which depends
on function addresses, is redone). Trimming only brings targets
closer, so the first pass's decisions stay valid.
- gather_thunk_addresses records each symbol's entry addresses in
its sparse aux entry, and applying an out-of-range branch picks the
entry within reach - no per-relocation field; thunk_off is gone
from Reloc.
On the debug clang link __text shrinks by 119KB (nushell: 8KB) from
entries the heuristic used to keep; links without thunks are
byte-identical to before. 322 integration tests pass; the linked clang
compiles and runs a program, nu evaluates an expression.
Interleaved A/B against the parent commit, debug links, best 4 of 8
runs (ms): clang 827.1 -> 848.1 (+2.5%), nu 354.2 -> 367.6 (+3.8%),
mold 145.4 -> 146.2 (+0.5%). That is a regression; the next commit
isolates it to the trim pass and its second __TEXT placement (the
model itself is neutral) and drops the pass.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Two pieces of mold-rust's Symbol/SymbolAux shape that the thunk port
and the remaining raw-pointer parallel passes need:
- The packed boolean attributes become an AtomicU16. The eight is_*
bits are still read with a plain load and written through &mut
via get_mut, so there is no atomic read-modify-write on the hot
resolution paths; the new F_MARK bit is set with fetch_or by
mark(), which reports whether the caller won it, and cleared by
unmark() - mold-rust's IS_MARKED, used to deduplicate thunk entries
inside a parallel scan instead of in a serial pass afterwards.
Symbol keeps its 40 bytes (35 of fields) and gains a manual Clone.
- SymAux gains thunk_addrs: Vec<u64>, the sorted addresses of a
symbol's range-extension thunk entries (mold-rust's
SymbolAux::thunk_addrs), so applying an out-of-range branch can
find the entry within reach without a per-relocation record. The
table entry is no longer Copy; sym_aux() returns a reference, with
a shared static "no slots" entry for symbols without one.
No behaviour change yet. 322 integration tests pass.
Interleaved A/B against the parent commit, debug links, best 4 of 8
runs (ms): clang 821.1 -> 823.2 (+0.2%), nu 381.6 -> 379.1 (-0.7%),
mold 157.1 -> 157.7 (+0.4%) - neutral, as the plain loads predict.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
The subsection held an 8-byte pointer to its parent section's header.
mold-rust's InputSection holds a u32 shndx and resolves the header
through its file; do the same. Context::hdr_of(&isec) looks the header
up in the owning object's section list, or in a small synthetic-header
table for the sections the linker makes itself (the __common section
for tentative definitions). The subsection's size becomes a u32 as
well - one subsection is far under 4 GiB - which with the 4 bytes from
the header index crosses the alignment boundary: 62 -> 54 bytes of
fields, 64 -> 56 after alignment, asserted at compile time. That is 8
more bytes off the ~4.4M-element arena of a debug clang link, and
InputSection is now smaller than mold-rust's (it has no section-name
or flags word; we keep the compact-unwind range instead).
The output-section assignment loop binds the header once per
subsection before it takes the chunk mutably (the same pointer cache as
before). 322 integration tests pass; the debug clang, nu and mold links
run.
Interleaved A/B against the parent commit, debug links, best 4 of 8
runs (ms): clang 778.1 -> 785.1 (+0.9%), nu 370.0 -> 371.6 (+0.4%),
mold 148.6 -> 153.5 (+3.3%); an earlier run of the same pair read
-0.7%, +0.9%, +1.1% - within noise either way. The gain is the 8 bytes
per subsection, not time.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
An object's LC_DYSYMTAB names its local, external-defined and undefined
symbol runs, and ld64 and clang always lay the nlist array out in that
order. The passes that only want externals - the intern batch,
resolution, the used/weak-ref marking, commons, undefined-symbol
checks, weak-def coalescing and auto-hiding, the referencer map - and
the ones that only want locals - the anonymous symbol slots and their
initialization, the local symbol table - walked every nlist and tested
each with is_stab()/is_extern(). mold-rust keeps first_global for
exactly this and slices elf_syms[first_global..].
Parse LC_DYSYMTAB at staging and, when its three runs tile the table,
record first_global; otherwise scan once and use the split only if the
table really is partitioned. Each such loop now walks obj.local_range()
or obj.global_range(); the per-entry filters stay, so an unpartitioned
table (both ranges are the whole table) is still handled exactly as
before, and the local count in integrate is a field read. The
split-point, stab-planning and ICF loops need both halves and are
unchanged.
322 integration tests pass; the debug clang, nu and mold links run.
Interleaved A/B against the parent commit, debug links, best 4 of 8
runs (ms): clang 754.9 -> 753.1 (-0.2%), nu 345.6 -> 341.4 (-1.2%),
mold 144.0 -> 145.6 (+1.1%) - within run-to-run noise.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Both DWARF unwind records copied their bytes into an owned Vec<u8> (24
bytes of header plus the copy) and indexed their object, CIE and
subsection with usize; the FDE's LSDA was an Option<(usize, u32)>.
Borrow the record bytes from the object's __eh_frame instead - the
parser's processed copy of the section (subtraction pairs pre-applied)
is leaked once per object, like its section headers, and the records
slice it; mold-rust's CieRecord borrows its contents
the same way - and store every index as u32 (the LSDA as an
Option<(u32, u32)>). Cie is 48 bytes, Fde 56, asserted at compile
time; mold-rust's FdeRecord is 16 because it derives nearly everything
from the CIE and the section, which is a larger change than the
records' current readers warrant.
A debug clang link on arm64 has no __eh_frame at all (compact unwind
covers every function), so this is for x86-64 and DWARF-unwind-heavy
inputs. 322 integration tests pass; the debug clang, nu and mold links
run.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
SymbolId and InputSectionId were usize. Every vector of them - each
object's nlist-to-symbol map (4.6M entries on a debug clang link), the
output sections' member lists and each object's subsection list (4.4M
each), the stub/GOT/thread-pointer slot lists, the Option<SymbolId> in
every output symbol table entry - carried 8 bytes per id. mold-rust's
ObjId, SymbolId and InputSectionId are u32 newtypes; make ours u32 too.
To keep the change to the storage width rather than a rewrite of every
arena access, ctx.isecs becomes a thin InputSections wrapper indexable by u32
or usize (through Deref to the Vec), SymbolTable indexes by either, and
the context helpers (resolve_isec, isec_addr, isec_relocs) keep taking
usize for local arithmetic. Only the places that push into or read out
of an id vector, or index another table by an id, cast - the same split
mold-rust has between its u32 ids and .index().
On the debug clang link this halves ~140MB of id vectors, and an output
symbol table entry drops from 32 to 24 bytes. 322 integration tests
pass; the debug clang, nu and mold links run.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Symbol carried its name as a 16-byte &str, its origin as an 8-byte
enum and its defining subsection as an 8-byte Option<u32>; and every
symbol, slot or not, had a 16-byte SymAux entry in a table sized to the
whole symbol count. mold-rust's Symbol holds more (a version index, a
symbol index) in 48 bytes by storing the name as ptr + u32 len, the
definition in a compact origin word, and a u32 aux_idx into a sparse
SymbolAux table. Mimic all three:
- name_ptr/name_len (12 bytes) with a name() accessor.
- file (u32) + isec (u32) + a kind byte replace Origin + Option<u32>
(16 -> 9 bytes); origin()/isec() reconstruct the same enum and
Option the readers matched on, set_origin()/set_isec() write them.
- aux_idx (u32): ctx.sym_aux holds an entry only for the few symbols
that take a __stubs/__got/__thread_ptrs/__objc_stubs slot,
allocated on first use - previously 16 bytes per symbol, and a
full-table fill the first time any slot was assigned.
Symbol is 40 bytes (from 48), asserted at compile time; on a debug
clang link that is ~2M symbols. The field-to-method change at the read
sites is mechanical. 322 integration tests pass; the debug clang, nu
and mold links run.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Every subsection cached its final output address in an 8-byte field,
stamped by a parallel pass when its output section was placed and
patched again at __LINKEDIT time so literal-merge losers held their
survivor's address. mold-rust keeps no such field: isec.addr(ctx) is
the output section's address plus the subsection's offset. Do the
same. isec_addr() now computes it, following a literal-merge redirect
only when one exists (one branch in the common case) and reporting 0
for an unplaced subsection, which is what the old default read. The
stamping pass and the loser patch pass are gone with the field.
The output section table is a few dozen entries and stays hot, so the
extra lookup is cheap; the 8 bytes came off ~4.4M subsections on a
debug clang link. InputSection is now 64 bytes, matching mold-rust's,
asserted at compile time. 322 integration tests pass; the debug clang,
nu and mold links run.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
RelocTarget::{Sym, Section}(u32) is a two-word enum, and as a field it
padded Reloc from 24 to 32 bytes - on a debug clang link that is ~12M
records, 95MB of extra traffic through every relocation pass. Store the
index in the low 31 bits of a u32 with a tag bit for a subsection, and
keep the enum as the API: target() unpacks it, set_target() and
RelocTarget::pack() build it, so the match sites read as before.
Reloc is now exactly the size of an ELF RELA entry. mold-rust never
materializes those at all - it reads them from the mapping - but a
Mach-O record has to be processed (ADDEND fusion, SUBTRACTOR pairing,
subsection rebasing), and this is the form that processing yields.
Asserted at compile time; 322 integration tests pass, the debug clang
and nu links run.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Every object's symbol table was copied into a Vec<NList> at staging -
16 bytes per nlist, 74MB and a memcpy-shaped pass on a debug clang link
with 4.6M nlists - although it is never written afterwards. NList is
#[repr(C)] nlist_64 (plain integers, valid for any bytes), so when the
array is 8-aligned in the mapping (ld64 always aligns it) it is used
in place as a &'static [NList]; an unaligned or truncated table falls
back to the copy. The field is a Cow<'static, [NList]>: Borrowed for
mapped objects, Owned for the synthetic symbol list an LTO module
gets - the same shape as mold-rust's Cow<[ElfSym]>.
322 integration tests pass; the debug clang and nu links run.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
The output string table is built in 64 shards: names are binned by a
key, each shard deduplicates its bin by pointer identity and lays its
unique strings out as one blob, and the blobs are placed by a prefix
sum over the shards. A name's n_strx is therefore its shard's base plus
its rank within the shard, so the bin key must be deterministic - if one
name lands in a different shard from one run to the next, every later
shard's base moves and most of the symbol table's n_strx values change.
The key was a hash of the name's address. That is stable for names
that point into the mmap'd inputs (ASLR slides are page-aligned, so
ptr * FIB mod 64 is slide-invariant) but not for heap-allocated names:
the N_OSO stab paths are leaked Strings created in a parallel
per-object pass, and their addresses follow thread scheduling. On the
mold-rust debug link two runs differed in ~7.7M bytes (11% of the file)
for this reason; nushell happened to be stable. mold guarantees
reproducible output, so this is a bug.
Key by the entry's symbol id instead. It is assigned deterministically,
equal interned names share one id (so the in-shard pointer-dedup sees
the same copies as before and the string table is unchanged in
content), and reading it touches no name bytes - a content hash of the
name's tail was tried first and cost 1.2% on clang-dbg purely in cache
misses. Only the id-less stab entries (N_SO, N_OSO: one or two per
object) fall back to hashing their name.
Two runs with the same output path are now byte-identical on the
mold-rust, nushell and clang debug links. Interleaved A/B against the
pointer key: clang-dbg -0.5%, nushell/mold-rust unchanged. 322
integration tests pass.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
The trie's pre-order flattening (which also stamps each node's index)
was a serial recursive walk - ~8ms on a 373k-node trie. Replace it with
two parallel passes in mold's prefix-sum shape: count every subtree's
node total, then let each subtree write its pre-order run into its own
disjoint slot range of one preallocated array, so nothing is appended
or copied and the pre-order index is just the slot number. Fan-out
happens at nodes with eight or more children: every Mach-O symbol
starts with '_', so the root has a single child and the real branching
is one level down.
A/B on an idle machine (interleaved): mold-rust-dbg -1.5ms (-0.9%),
nushell-dbg -5.3ms (-1.4%), clang-dbg unchanged. The trie bytes are
identical: nu and clang link byte-for-byte the same as before (the
mold-rust link has a pre-existing run-to-run variance in its string
table, unrelated - it varies the same amount before and after).
322 integration tests pass.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
A compact-unwind record exists for every function, and the records are
walked by __unwind_info encoding, dead-stripping and ICF. The struct
carried three Option-typed fields - personality: Option<SymbolId> (16
bytes), lsda: Option<(usize, u32)> (24) and fde: Option<usize> (16) -
plus a usize section index, padding it to 80 bytes.
Store every index as a u32 with UNWIND_NONE (u32::MAX) for "absent",
the way mold-rust's Fde/Cie hold u32 indices: isec, personality_sym,
lsda_isec + lsda_off, fde_idx. That is eight u32s, 32 bytes, asserted
at compile time. personality()/lsda()/fde() accessors return the same
Options the readers used before, so the encoding, dead-strip and ICF
sites change only from field to method; the two integrate rebases, the
dead-strip FDE remap and the two parsers write the raw fields (a
dropped FDE's usize::MAX remap narrows to UNWIND_NONE, preserving the
old "absent" meaning).
322 integration tests pass; the rebuilt debug mold, nu and ffmpeg link
and run.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Continue narrowing the highest-count struct toward mold-rust's 64-byte
InputSection:
- input_addr u64 -> u32. It is a subsection's address within its
object's address space; object files stay far under 4 GiB. The
find_subsec search and the staging arithmetic cast it up to u64
where they mix it with 64-bit addresses.
- p2align u32 -> u8. An alignment is a small power of two; a byte is
ample. Output-section alignment (ChunkHeader.p2align, still u32)
takes the max with a widening cast.
Another 8 bytes off the ~4.4M-element arena on a debug link. size stays
u64 and addr stays u64 (an arm64 image's addresses sit above the 4 GiB
__PAGEZERO). size asserted at 72. 322 integration tests pass.
Note: not re-benchmarked - the scratchpad debug-link inputs were
cleared - but this is the same type-narrowing as the 112->96->88->80
steps, each of which measured a few ms on clang-dbg.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
The subsection contents were a &'static [u8] - 16 bytes of pointer and
length - but the length is always `size` for a non-empty section (they
are built with size == contents.len()). Keep just the pointer, as an
integer (0 for a zero-fill or empty section), and reconstruct the slice
through a data() accessor. mold-rust stores its `contents` as a bare
address for the same reason, and an integer keeps InputSection
trivially Send/Sync.
That is the last 8 bytes: InputSection is now 80, down from 112 at the
start of this compaction (32 bytes, ~29%, off the ~4.4M-element arena
on a debug link). 322 integration tests pass and the linked clang, nu
and ffmpeg run.
output_offset is a subsection's offset within its output section. An
output section stays far below 4 GiB (a Mach-O image's addressable span
is bounded well under that), so a u32 holds it, and the "not yet
placed" marker used during thunk layout moves from u64::MAX to
u32::MAX. Absolute addresses (addr, chunk addr/fileoff) stay u64; the
arithmetic that combines them with output_offset casts it up at the use
site.
With is_alive already leaving padding in the struct, dropping this one
field crosses an alignment boundary: InputSection goes 96 -> 88 bytes,
another ~9% off the ~4.4M-element arena on a debug link. 322
integration tests pass and the linked clang and nu run.
InputSection is the highest-count struct in a link - a debug clang link
has ~4.4M of them - and it is walked in dead-strip, ICF, relocation
scanning, output-section assignment, thunk layout and the copy phase,
so its width drives the cache traffic of all of them. mold-rust keeps
its InputSection at 64 bytes for exactly this reason; ours had grown to
112 with three oversized fields:
- obj and osec were usize (8 bytes each). They index the object and
chunk arenas, neither of which approaches 2^32, so they become u32
(their usize::MAX sentinels become u32::MAX). mold-rust indexes
with u32 too.
- replacement was Option<usize> (16 bytes) for a literal-merge
redirect. A u32 subsection index with a NO_REPLACEMENT (u32::MAX)
sentinel is 4 bytes and needs no niche.
That is 20 bytes off the fields, 16 off the struct after alignment
(112 -> 96), asserted at compile time. Call sites cast at the arena
boundary (obj/osec as usize) and test the sentinel instead of Option.
322 integration tests pass and the linked binaries run.
Symbol carried eight separate bool fields plus common_p2align - nine
bytes of attributes that padded the struct to 56. mold-rust keeps its
Symbol flags in a packed byte precisely because Symbol is touched in
every resolution and layout scan, so its width, not its logic,
dominates the cache traffic of those passes.
Replace the eight bools with a single flags byte and generate is_*/
set_* accessors (a small macro), leaving common_p2align as the only
other byte. The struct is now 48 bytes, matching mold-rust, asserted at
compile time. Every read site becomes a method call and every write a
setter; the packed byte is the only change in behaviour.
Helps every build that is symbol-heavy - a Rust debug link pushes
nushell-dbg past 25% faster than ld-prime. 322 integration tests pass
and the linked binaries run.
Range-extension thunk layout is the serial spine of a large arm64 link:
clang-dbg's __text is ~161MB (ICF is off for debug objects), so it
needs thunks, and one giant output section means no cross-section
parallelism. Within a batch, though, deciding which branches are out of
reach is a pure read of addresses layout has already assigned - and
mold-rust scans each batch's members with par_iter for exactly this
reason (create_range_extension_thunks).
Port that: each subsection in the batch scans its branch relocations in
parallel and emits, in relocation order, the (object, arena index,
target) triples of the out-of-reach branches. The serial pass then
assigns thunk entries in the same batch-then-reloc order the old scan
used, so the dedup order - and thus the thunk's contents and every
branch's thunk_off - is byte-identical. Only the reachability scan,
which iterates millions of branches, moves off the serial path.
sections (create_output_sections, which owns thunk layout) ~72ms ->
~60ms on clang-dbg. 322 integration tests pass and the linked clang
runs.
gather resolves a whole link's extern names against the sharded symbol
table. The shard resolution was already parallel, but three O(batch)
steps around it were serial or wasteful - on clang-dbg the batch is
3.8M names:
- New symbols were pushed into the table one at a time. Prefix-sum
the per-shard counts (already done for id assignment) and construct
the new symbols into their disjoint ranges in parallel - the same
contract integrate uses for local symbols.
- The insert pass recomputed each new name's xxh3 hash. Carry the
hash staging already computed in the per-shard news list and reuse
it.
- The final id scatter wrote ids[i] serially. Each batch index is
produced by exactly one shard, so the writes are disjoint - scatter
in parallel.
gather ~100ms -> ~78ms on clang-dbg; ids and symbol ordering are
unchanged (shard order still drives id assignment). 322 integration
tests pass.
Two serial scans over every nlist of every object remained on the
load path, each O(total symbols) - millions on a debug link:
- load_pending built the intern batch by walking each staged
object's symbols serially to collect extern (name, hash) pairs.
Filter each object's list in parallel, then concatenate in object
order; the batch and its per-object counts are unchanged.
- integrate_objects counted each object's local symbols inside the
serial prefix-sum loop. Precompute the counts in parallel and let
the prefix sum consume them, so the serial part is just
arithmetic.
Both keep object order, so symbol ids stay deterministic. 322
integration tests pass.
create_output_sections maps every live subsection to an output chunk
by hashing its 32-byte (segname, sectname) key. On a debug link that
is millions of hashes, yet every subsection of one input section
shares the same leaked header pointer and the subsections of a section
are contiguous in the arena. Cache the last header pointer and reuse
its chunk index while it holds, so only the first subsection of each
section pays the hash. Behaviour is unchanged - a header uniquely
names one (object, section), so the cached run always shares the key.
322 integration tests pass.
On a Rust debug link (nushell) the export trie holds ~264k symbols and
373k nodes encoding to ~13.5MB, and trie_encode sits on the layout
critical path (globals_sort -> symtab/trie). Two serial stretches
dominated it:
- Sizing built a HashMap<*mut TrieNode, u32> to map each child
pointer to its pre-order index, then hashed a pointer per edge.
flatten now stamps each node's index into the node itself, so the
sizing pass reads child.index directly - no map, and the whole
per-node size computation runs in parallel. ~20ms -> ~11ms.
- Emission appended every node's bytes to one Vec serially. The
fixpoint already assigns each node a final offset, so node i owns
the disjoint range [offs[i], offs[i+1]); pre-size the buffer and
write every node into its slot in parallel. ~13.5ms -> ~3ms.
trie_encode ~63ms -> ~46ms, and the layout phase ~131ms -> ~107ms on
nushell-dbg. The trie bytes are unchanged: 322 integration tests
(which byte-check the trie) pass, and the exported-symbol set is
identical.
The defined-global name sort (feeding the symbol table and the export
trie) and the undefined-symbol sort used par_sort_by_key, a stable
sort. Symbol names are unique, so a tie never occurs and stability is
irrelevant to the result; an unstable sort does the same work with
less memory traffic and no merge buffer.
On a Rust debug link (nushell) the global sort is the front of the
layout critical path - hundreds of thousands of long mangled names
that share leading bytes, so the 8-byte-prefix key falls through to
full string comparisons constantly. Switching both sorts to
par_sort_unstable_by_key cuts globals_sort ~67ms -> ~43ms. 322
integration tests pass.
On a C++ debug link these two passes together cost ~215 ms - the
largest untimed slice of the link - because a debug build carries
millions of weak-def nlists (every inline function and template
instance) and both passes scanned them in one serial loop over every
alive object's symbols. clang-dbg is the worst case; ld64 pays the
same C++ tax.
auto_hide_weak_defs built a HashMap<SymbolId,bool> serially. Replace
it with a dense AtomicU8 array keyed by the symbol id - symbol ids are
a contiguous range, so no hashing and no allocation per entry - and
reduce into it in parallel across objects (both bits are monotonic, so
racing relaxed fetch_or is safe), then apply in a parallel pass over
the symbol table. 69 ms -> 10 ms.
coalesce_weak_defs did a find_subsec binary search per weak def inside
a serial double loop. Split it: the scan (filtering plus find_subsec,
all pure reads of state resolution already fixed) runs in parallel per
object and emits each object's candidate (loser, winner) subsection
pairs unresolved; the replacement writes stay a single serial walk in
object order, with the same resolve_isec and size checks as before, so
the folding is bit-for-bit identical and deterministic. 135 ms ->
57 ms.
Also add per-pass MOLD_TIMING timers around the symbol passes, the
same gated-eprintln pattern the other phases already use; the two weak
passes were previously invisible in the phase profile. 322 integration
tests pass and the linked clang runs.
Reloc is the highest-count struct in a link - a debug clang link has
~5.9M of them - so its width dominates the cache traffic of every
relocation pass (scan_relocations, thunk layout, apply_relocations).
Shrink two fields that never need 64 bits:
- RelocTarget::Sym/Section payloads usize -> u32. A target is an
index into an object's symbol list or the global subsection arena;
neither approaches 2^32. mold-rust likewise indexes with u32.
- thunk_off u64 -> u32. It is an offset of a range-extension thunk
within one output section. Mach-O caps a single section's span far
below 4 GiB (arm64 __TEXT sits above a 4 GiB __PAGEZERO, and
ADRP/branch reach is bounded), so u32 is always sufficient; the
no-thunk sentinel moves from u64::MAX to u32::MAX.
Absolute virtual addresses stay u64: an arm64 executable's addresses
start at 0x1_0000_0000 because of the 4 GiB __PAGEZERO, so they never
fit in 32 bits. 322 integration tests pass.
Origin::Obj/Dylib carried usize payloads (16-byte enum). Object and
dylib counts fit in u32, so they become u32 (8-byte enum), and the
dynamic-lookup sentinel changes from usize::MAX to u32::MAX
(bind_ordinal now takes u32). Match sites cast the payload back to
usize where they index ctx.objs/ctx.dylibs. Symbol drops from 64 to
56 bytes. Output unchanged; 322 tests pass, large programs link/run.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Symbol.isec was Option<InputSectionId> (Option<usize>, 16 bytes).
Subsection indices fit comfortably in u32, so it becomes Option<u32>
(8 bytes); call sites map it back to usize where they index ctx.isecs.
Symbol drops from 72 to 64 bytes. Output unchanged; 322 tests pass.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
The __stubs/__got/__thread_ptrs/__objc_stubs slot indices were four
u32 fields on Symbol, but only the few symbols that take a slot ever
use them, and Symbol is loaded in every symbol scan (resolution,
classification, dead-strip, ICF, relocation scan). They move to a
SymbolId-indexed side table grown lazily on first assignment -
mold-rust's SymbolAux - so Symbol drops from 88 to 72 bytes. The ~13
slot-index sites go through ctx.sym_aux()/sym_aux_mut(). Output
unchanged; 322 tests pass, large programs link and run.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Symbol carried four Option<u32> slot indices (stub/got/tlv/objc_stub),
8 bytes each because u32 has no niche - 32 bytes of a struct that is
loaded in every symbol scan. They become plain u32 with a NO_IDX
(u32::MAX) sentinel, matching mold-rust's compact per-symbol data.
Symbol drops from 104 to 88 bytes; only 13 call sites touch these
fields. Output unchanged; 322 tests pass.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Every subsection embedded a full 88-byte MachSection header, even
though subsections of one section share it and the object already
keeps the headers in sect_hdrs. mold-rust keeps only a reference to
the header on its InputSection, not a copy. InputSection.hdr is now a
&'static reference into the object's leaked header slice, with the one
field the linker raises per subsection - p2align - held inline. The
struct drops from 200 to ~124 bytes, so every pass that scans
subsections (resolve, classify, dead-strip, thunks, section grouping)
walks a third less memory. Output unchanged; all 322 tests pass and
the large programs still link and run.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Each subsection owned a Vec<Reloc>, so a debug link built and then
to_vec-copied millions of small heap allocations - ~5.9M relocations
on the debug clang link, re-copied out of the per-section buffer they
were just decoded into. sold instead keeps rel_offset/nrels on each
subsection into one per-section relocation array; mold-rust goes
further and reads ELF relocations straight from the mmap without
materializing them at all (Mach-O can't - ARM64_RELOC_ADDEND fusion,
SUBTRACTOR pairing and subsection rebasing force a decoded form).
InputSection now carries rel_offset/nrels into the owning object's
relocs arena (built once during staging, no per-subsection to_vec),
and ctx.isec_relocs()/isec_relocs_of() slice it. Section relocation
targets rebase once over the object arena at integration; the thunk
scan writes thunk_off back through the arena. InputSection shrinks and
staging drops millions of allocations. Output is unchanged; all 322
tests pass and every large program still links and runs.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Merge duplicated C++ and Rust comment blocks into single explanations that
describe the Rust implementation while retaining the original rationale.
Remove stale C++ type references and restore the missing rustdoc summary for
sorted concurrent-map entries.
Remove helpers, accessors, and constants that are unreachable from the mold
command and its tests. This includes wrapper methods whose only callers were
other dead wrappers, unused ELF integer operations, and unused concurrent-map
APIs.
Keep the remaining internal len methods crate-visible so Clippy does not treat
them as incomplete public collection APIs.
ld-prime folds identical functions by default - about 23k of them on
a release clang link - but disables its deduplication pass entirely
for objects built with debug info, and that heuristic is most of why
it links debug builds faster than a linker that always folds. The
reason is correctness as much as speed: folding two identical
functions to one address leaves the DWARF that describes the
folded-away copy pointing at code that is gone, so source-level
debugging of it breaks.
ICF now skips any function whose object carries debug info (mold's
own ICF is opt-in and off by default, so this stays consistent with
mold-rust while matching ld64's on-by-default-except-debug
behavior). A release link still folds everything; a debug link folds
nothing and skips the pass's cost. On the 343MB debug clang link
that is about 190ms of wall time, and the output is the same one
ld-prime produces. Release links are unaffected.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Keep Origin eight bytes and use its low two bits to distinguish input
sections, output chunks, merged-section fragments, and symbols. Remove the
separate origin-kind bits from Symbol’s visibility byte.
This follows C++ mold’s tagged-pointer representation without the auxiliary
tables and per-variant codecs required by the discarded 32-bit experiment.
The debug stab entries were planned per object in parallel and then
appended to the symbol table object by object - a serial walk over
~3M entries on a debug link (33ms). They now write directly into
prefix-summed ranges in parallel, the same populate_symtab shape
mold-rust uses for its symbol table, each object owning a disjoint
range after whatever entries (AST paths) precede the stabs. Stab
generation on a 343MB debug clang link: 54ms to 23ms.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
The output symbol string table was built into a temporary Vec during
layout - allocated, zero-filled, and populated - and then copied into
the output buffer during the copy phase. On a debug link that is a
150MB buffer allocated, cleared, written, and re-copied: the string
data was handled three times. Neither mold-rust nor mold C++ does
this; they size a chunk during layout and write its bytes once,
straight into the output.
Layout now computes only the string table's size and the
deduplicated (offset, string) list; copy_symtab writes the strings
directly into the output file on all cores (the NUL terminators and
padding come free from the zero-initialized output buffer). The
symbol table build on a 343MB debug clang link drops from ~186ms to
~126ms, and the copy phase no longer re-copies 150MB. Output is
byte-identical (same 154257016-byte string table).
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
The experiment reduced Symbol from 48 to 40 bytes, but it required a
manual codec for every ChunkId variant, a separate namespace for allocated
merged sections, and context-dependent origin decoding. It added 434 net
lines.
It also made the previous Rust linker about 2% slower on Firefox debug and
10% slower on TensorFlow debug, even though it remained faster than C++ mold.
Prefer the simpler 64-bit origin until the memory saving justifies those
costs.
Use unary low-bit tags so fragment origins have a 31-bit payload. Pack
allocated merged-section IDs and fragment bucket IDs into that payload,
and use compact encodings for input sections, chunks, and symbols.
Also compact the remaining symbol metadata so that Symbol shrinks from 48
to 40 bytes.
Split the output-section and symbol-table borrows so
gather_thunk_addresses can record addresses while traversing thunks. This
matches the C++ loop and avoids a temporary allocation for each section.
Replace the callback-based internal parser with an Iterator built using
std::iter::from_fn. Keep the eager Vec results and member creation order
unchanged.
Return the original io::Error from the shared file-opening path instead of
discarding it and reading errno later. Keep optional file lookup semantics by
mapping NotFound to None, and remove errno_string.
Rewrite comments copied verbatim from C++ so they describe the Rust
implementation and terminology. Remove notes that have no Rust counterpart,
and update source-file references to their Rust names.
Keep C++ comparisons when they document an intentional implementation
difference or a remaining performance gap.
Each refinement round counted its distinct digests by cloning all
840k of them, parallel-sorting, and deduping - and the grouping after
the loop sorted them once more. mold-rust never sorts: it inserts
every digest into a lock-free open-addressing map that counts the
distinct digests and, as a side effect, records the lowest-index
candidate of each digest as its class leader, in one pass. The table
is stamped with the round number and reused without clearing, so a
slot written in an earlier round reads as vacant. Ported as-is
(candidate index in place of mold's SectionRef); the final grouping
is then just a parallel find() per candidate, no sort. ICF on a 343MB
debug clang link: 267ms to 187ms, output unchanged.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Building the output string table hashed every symbol name in full to
deduplicate it - on a debug link that is 1.1M long mangled names and
was the most expensive single step of the whole link (78ms). Every
name is an interned symbol name or a name pointing into an object's
mapped string table, so a name that repeats has the same pointer;
hashing and comparing the 8-byte pointer instead of the string folds
the same names at a fraction of the cost. Interned names - the
globals, undefineds and private externals, where the weak-template
repetition across thousands of objects lives - dedup completely.
Local and stab names point into their own object's string table, so
they keep one copy per defining object; that adds about 1MB to a
154MB string table, which is still smaller than ld64-prime's for the
same link, and mold-rust deduplicates none of these strings at all.
Symtab build on a 343MB debug link: 180ms to 119ms.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
The identical-code-folding hash had drifted from mold-rust in three
ways, which together left ICF both slower and slightly divergent - on
a debug clang link (840k candidates, unoptimized template
instantiations) it ran over a second.
- The digest is mold-rust's SipHash13-128, not xxh3. The propagation
rounds hash fixed 16-byte digests, which SipHash's update_digest
takes on an aligned fast path; both halves widen the digest to 128
bits so the converged classes fold on digest equality with no byte
re-verification (kept as a debug assertion). Grouping is one
parallel sort of (digest, id) pairs and folding a single pass over
the runs, replacing a serial byte-verifying fixpoint (307ms of the
old runtime).
- Edges are a flat CSR - one values array indexed by a prefix-summed
per-candidate offset - exactly mold-rust's Edges, instead of a
Vec<Vec<u32>> that heap-allocated once per candidate and scattered
the hot propagation loop across 840k allocations.
- Propagation hashes the vertex's own current digest H(cur[i],
cur[neighbors]), as mold-rust does, not a fixed base digest; the
two converge to the same partition but only the former is the
canonical depth-n tree unfolding.
The leader of each folded class now also takes the strongest
alignment among its members (mold's update_alignment), which the
port had dropped: a member aligned more strictly than its leader
would otherwise leave references to it, redirected to the leader,
under-aligned. ICF on that link: 1.07s to 210ms; every folded
output still verified (clang compiles, nushell runs).
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Thunk creation triggered per output section, on that section's own
size alone. Branches are not confined to their section: __text,
__StaticInit, the stubs and the rest of __TEXT's executable content
share one address space, so a 400KB __StaticInit placed after a
127MB __text held backward branches spanning well past the arm64
reach with no thunks anywhere - the debug clang link (342MB, found
by the debug-build benchmarks) failed with "branch target out of
range" while every section individually passed the old gate. The
gate now sums every executable output section and, within a 64MiB
margin of the branch reach, routes them all through thunked layout;
a branch whose target lives in a different output section reserves
its thunk entry unconditionally, since offsets in another section's
space say nothing about reachability.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Dead-stripping rebuilt a HashMap from function subsection to its
compact-unwind record indices on every run - 17ms of serial inserts
on a debug link. sold instead keeps unwind_offset/nunwind on each
subsection: records arrive grouped by function, so integration hands
every subsection its contiguous range in the records arena and the
liveness walk just slices it. The two places that compact the
records vector (weak-coalesce loser discard, dead-strip's own sweep)
refresh the ranges with one walk over the runs; forgetting the first
of those was caught by the rust tests, whose weak losers shifted the
surviving records. Dead-strip on that link: 65ms to 38ms.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Every object with debug info contributes a run of stab entries (N_SO,
N_OSO, N_FUN pairs per function) to the symbol table, and the runs
were planned in one serial walk over all objects - 35ms on a debug
link where every object carries debug info. Each run only reads its
own object, so they now plan in parallel and append in object order,
the same shape as the per-object locals planning. 35ms to 13ms.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
The output symbol table and the export trie each collected and
name-sorted the same set - every defined non-private external - and
on a debug link that set is hundreds of thousands of long mangled
Rust names whose sort comparisons walk deep shared prefixes, so the
two sorts cost about 150ms each. The __LINKEDIT group now sorts the
list once up front and hands the slice to both consumers: the symtab
emits it as its globals range and the trie filters the explicit
export/unexport lists order-preservingly and builds directly,
sorting nothing. Layout on a 193MB debug link: 507ms to 162ms.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Dead-stripping marked the graph in synchronous frontier rounds: every
round dispatched the frontier to the pool and collected the next one.
Unoptimized debug code has deep call chains - nothing is inlined - so
the rounds multiply while each carries little work, and marking a
193MB debug link took 1.1 seconds. mold's gc-sections instead runs a
work-stealing traversal: each visit follows edges up to three levels
inline and banks further finds in batches of 16 that spawn as pool
tasks (rayon tasks are heavier than TBB feeder items, so the batch
amortizes them), keeping every core fed regardless of graph depth.
Ported as-is, with the unwind-record edges walked the same way; the
redirect map now builds in parallel and the symbol-level root scan
runs on all cores too. Marking that link: 1.1s to 77ms. The serial
spanning-tree walk stays for -why_live, which needs stable chains.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
GNU assembler 2.45 emits a zero-size .sframe section for an input file
that needs no unwind info, such as glibc's Scrt1.o on Ubuntu 25.10.
parse_sframe() read a header past the end of such a section and reported
it as corrupted.
Fixes https://github.com/rui314/mold/issues/1646
Co-Authored-By: Claude Fable 5 <[email protected]>
Integration rebased every staged object's indices in parallel but
then appended the results - hundreds of megabytes of subsections,
unwind records and frame-description entries on a large link - into
the context arenas one object at a time. The staged vectors now move
into the exclusive ranges the existing prefix sums already assigned,
under the same all-slots-written contract as the parallel local-
symbol initialization. Integration on a 110MB C++ link: 31ms to 16ms.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Every resolution round walked all objects' nlists serially, binary-
searching the subsection list once per local N_SECT symbol, and reset
every symbol slot serially before each round. Local symbols get fresh
slots, never interned ones, so one object's claims touch no other
object's symbols and the per-object walks run in parallel; the
clear-claims reset is a plain parallel sweep. Resolution on a 110MB
C++ link: 32ms to 17ms.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Copying the symbol table walked its millions of entries serially,
filling each n_value with a sym_addr lookup - about 25ms of the copy
phase on a 110MB link, dwarfing the parallel section copies. The
entries now emit in 4096-entry blocks in parallel, the same shape as
the per-subsection parallel copy (mold's copy_buf runs its chunks in
parallel too). Copy phase on that link: 47ms to 26ms.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Staging looked up the owning subsection of every relocation with a
binary search over the subsection list. Sorted by offset, the
relocations of one subsection are contiguous, so sorting once and
walking the subsections with a cursor hands each its run in linear
time - the same walk sold uses to assign each subsection its
rel_offset/nrels range into the section's relocation vector (the
range-into-shared-storage half stays unported: ADDEND/SUBTRACTOR
pairs are already fused into single relocations by read_relocs, and
per-subsection vectors keep icf/dead-strip/apply untouched).
Section-relative targets still rebase individually; sold's
find_subsection does too. Staging a 110MB C++ link: 70ms to 22ms.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Layout assigns addresses front to back, so when the scan reaches
__LINKEDIT every address any of its tables reads is final, and the
tables are independent of one another. They now build as one task
group - the symbol table (which needs no addresses at all and
previously ran serially before layout), data-in-code, the fixup
streams (chained, or rebase plus bind), function starts and the
export trie - instead of back to back; the chunk loop then just
consumes the cached bytes. sold sizes its __LINKEDIT members with
the same parallel_for_each, and its DataInCodeSection::compute_size
builds the entry list exactly once the same way: ours previously
walked every object's data-in-code ranges once for the size and
again at copy time. create_output_symtab and build_chained_fixups
become pure functions returning their tables so the group can run
them off a shared borrow.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
The sharded string-table dedup already computed every unique name's
final offset via prefix-summed shard bases, but then concatenated
per-shard blobs serially and stamped every entry's n_strx in one
serial walk over all shards' resolutions. Both now happen in the
shards' parallel pass, writing straight into the final buffer - the
populate-at-precomputed-offsets shape of mold's populate_symtab. A
name is binned to exactly one shard and an entry resolved in exactly
one bin, so the write sets are disjoint.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
mold's refinement loop propagates three times between class counts,
because counting the equivalence classes costs about as much as a
propagation round; ours counted after every round and allocated a
fresh digest vector each time. The loop now ping-pongs between two
buffers (collect_into_vec reuses the scratch) and counts every third
round, running until the count stops growing - classes only ever
split, so the count is monotone and the loop terminates without the
old log2(n) cap. ICF on a 110MB C++ link: 45ms to 26ms, the rounds
themselves 32ms to 11ms.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Splitting the sorted exports by leading byte and building a subtrie
per group parallelized nothing in practice: every Mach-O symbol
starts with '_', so all names landed in one group and the whole trie
was built serially by repeated insertion. The builder now recurses
over the sorted slice directly - split the run by the byte where the
names diverge, take each group's remaining common prefix as the edge
label (for a sorted group, the common prefix of its first and last
name), and build sibling subtries in parallel. Path compression is
canonical, so this constructs bit-for-bit the same radix trie as
sorted insertion (node counts match), in linear total work, with
parallelism at every branching level instead of only the useless
first one. The trie's input sort also adopts the eight-byte prefix
key. Serial build time on a 74MB link: 6.4ms to 1.6ms.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
mold keeps each symbol's output-symtab position as direct per-file
data (output_sym_indices plus local/global bases), not in a map; the
indirect-symbol-table writer here instead looked up every stub and
GOT symbol in a HashMap<SymbolId, u32>, and building that map cost
about 5ms on a 74MB link. One flat Vec<u32> indexed by SymbolId
(u32::MAX for symbols absent from the table) is the minimal
adaptation: Mach-O sorts its defined globals by name across all
files, so per-file bases cannot express a global's position and one
image-wide array takes their place.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Mach-O name-sorts tables that ELF never sorts: the defined-global and
undefined ranges of the output symbol table (dysymtab consumers
binary-search them) and the export-trie input. Mangled names share
long prefixes - every symbol starts with '_', C++ names with '_ZN' -
so most of the sort's time went into memcmp calls that walk those
prefixes. Sorting by (first eight bytes as a big-endian u64, name)
settles most comparisons on one integer compare and falls back to
the string only on a prefix tie; NUL cannot appear in a symbol name,
so zero-padding keeps the order identical to plain name order.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Mach-O layout has a natural single-pass order that ELF lacks: visit
one output section at a time, front to back, and give its contents
their global addresses on the spot. Everything whose content depends
on code or data addresses - the export trie, the LINKEDIT streams -
sits later in the file than what it describes, so by the time the
scan reaches it every address it needs is already final and nothing
is ever computed twice. sold is laid out the same way.
Each InputSection now carries its final output address, filled in the
moment its output section is placed (in parallel across the members;
literal-merge losers borrow the survivor's address when the scan
reaches __LINKEDIT, so lookups never chase redirects). isec_addr and
sym_addr become plain field reads, and the whole-symtab sym_vas
snapshot - a repeat-compute stage bolted on earlier - is deleted.
The synthetic slot sections (__stubs, __got, __thread_ptrs,
__objc_stubs) are resolved to chunk indices once at the start of the
scan, as mold keeps direct chunk references on its Context, so
per-slot address lookups stop searching the chunk list.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
The local-symbol section of the output symtab is now planned per
object in parallel - mold's plan_symtab shape - and appended in
object order, replacing the serial walk over every object's nlists.
Dead stripping's root scan (a filter over every subsection's type
and attributes) runs on all cores too, with the trivial marking
serial. The export trie's offset fixpoint now iterates over flat
per-node fixed sizes and child-index arrays instead of re-walking
the recursive structure and re-deriving invariant ULEB widths every
pass.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
A Rust executable exports tens of thousands of long mangled names,
and the trie construction was the worst way to receive them: a
String allocation per edge, a linear scan of siblings per level, and
edge splits reallocating as insertion order dictated. Construction
now collects and sorts the exports in parallel, builds a sub-trie
per leading byte on all cores, and strings them under the root in
sorted order. Sorted insertion means a new name can share a prefix
only with the most recent child, so each level compares one edge and
the build is linear in total name length; labels borrow from the
symbol names, so nothing allocates but the nodes. Local symbols got
the same treatment at the other end of the pipeline: integration now
initializes them in parallel into pre-reserved disjoint ranges -
mold's ParallelSymbolAllocator contract, prefix sums handing each
object an exclusive slice of the arena.
nushell: trie 33ms to 19ms, integration 31ms to 20ms.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Two round-trims, both mold's own behaviors. Auto-linking usually
adds only dylib stubs (-lz, frameworks), and a dylib added after
resolution carries a later priority than every file already ranked -
it can steal nothing, only claim still-unresolved names. The driver
now runs the full re-resolution loop only when auto-linking pulled
in object files; a dylibs-only round gets a parallel claim pass over
the unresolved symbols, which reaches exactly the state the full
race would.
ICF's digest refinement ran its log2(n) worst-case rounds
unconditionally; like mold, it now counts equivalence classes per
round and stops when the count stabilizes - most graphs settle in a
handful of rounds. Fold results verified byte-identical on the
clang link.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
mold-rust plans each file's symtab contribution in parallel
(plan_symtab per object) and then assembles serially; the Mach-O
symtab needs its globals and undefineds name-sorted rather than
file-grouped, so the equivalent here is a single parallel
classification over the symbol table - private-external, global,
undefined, or absent - replacing the three separate full scans the
emit loops used to make over millions of slots. Emission then walks
the classification. Same plan-parallel/assemble-serial shape, adapted
to the format's ordering rules.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Each second-level page's compressed entries, encoding table and LSDA
rows depend only on that page's records, so the pages now encode on
all cores and a serial walk over the finished blobs lays down the
first-level index - the same split-compute/serial-assemble shape as
the rest of the LINKEDIT builders. Relocation-scan collection also
stopped carrying plain references (the overwhelming majority, which
need no slot of any kind), shrinking the collected list tenfold.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Both the C++ tree (by default) and mold-rust (as the Rust global
allocator) run on mimalloc - one of the paper's system-level
choices, since a parallel linker allocates and frees from every
thread at once and the system allocator's cross-thread costs show up
directly in profiles; ours put macOS's xzone malloc among the top
entries. The executable now sets mimalloc as the global allocator,
matching mold-rust's cli crate.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Ported from mold-rust's subprocess.rs, one of mold's signature
tricks: exiting a process that mapped gigabytes of input takes on
the order of a hundred milliseconds - all of it after the output is
already complete on disk. The linker now forks at startup; the
child does the whole link and pokes a pipe the moment the output is
written, at which point the parent exits and the build system moves
on while the child unmaps in the background. A child that dies
before notifying has its exit status (or fatal signal) relayed by
the parent. MOLD_NO_FORK=1 keeps a single process for debuggers and
profilers. The fork sits after argument parsing and the wrong-arch
retry check, before any threads or large mappings exist.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
create_output_symtab's last serial cost was the string table:
every entry's name went through a lookup-or-append, one at a time,
because stab names must deduplicate against the very global names
emitted later. The table now assembles in one parallel pass with
the sharded shape used everywhere else: entry loops record names
alongside entries, names bin by xxh3, each shard deduplicates its
bin in first-appearance order and lays its unique names out as one
blob, prefix sums place the blobs, and every entry's n_strx follows
from its shard base plus local offset. Placeholder entries (the ""
and "-" cells at offsets 1 and 2) keep their fixed values via an
empty-name sentinel. Literal merging got the same sharded
treatment: element hashes in parallel, shards deduplicate
independently, first occurrence still wins.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
The stub parser rescanned its input once per key - split into
documents, then a find() sweep over each document for every list
kind, nine passes over what can be tens of megabytes of SDK stub
text. It is now a single cursor: memchr hops line to line, a line
whose head matches a key has its flow list consumed in place
(across lines when the list wraps), and nothing is read twice.
Same accepted dialect, same results; the two framework stubs that
cost 24ms and 15ms on the nushell link now parse in under a
millisecond each.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Stub parsing was the last serial chokepoint on framework-heavy
links: two umbrella .tbds cost 45ms of the nushell link, parsed one
at a time in the input loop, allocating a String per exported
symbol. Three changes, all in mold's direction:
Exports now borrow straight from the mapped file - TbdFile and the
dylib export sets hold &'static str (hashbrown sets), and only the
few Objective-C names needing a mangling prefix are materialized.
Parses are memoized by the file's data address, and read_input_files
warms that cache before its serial loop: every input that resolves
to a stub parses on all cores, then the stubs those reexport (two
waves cover an SDK's umbrella trees). For the memo to hit,
MappedFile::open itself is now memoized by path, so a file named
twice gets one mapping - which also deduplicates repeated command
line libraries for free. Hot query maps elsewhere (dead-strip's
unwind index, ICF's groups, section grouping) move from SipHash std
maps to hashbrown, as mold-rust uses throughout.
nushell: 370ms to 317ms.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Mold performs one link per process, so keep diagnostic settings, error state,
and output serialization in a process-wide singleton. This removes
diagnostic plumbing from contexts and helper APIs while preserving the mutex
that keeps messages from worker threads from interleaving.
sample(1) put Context::sym_addr at the top of the remaining
profile: relocation application asks for a target address per
relocation, and each call walked the replacement chain and chunk
table. Addresses stop changing the moment layout reaches
__LINKEDIT, so they are snapshotted there - computed for every
symbol in parallel - and sym_addr becomes one indexed load for the
LINKEDIT builders and the whole copy phase. Symbols whose
subsections never joined an output section (dead or folded code
nothing asks about) snapshot as zero; the eager pass must skip what
on-demand lookups never touched.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
A time profile (sample) showed a quarter of staging spent in
per-name from_utf8 and byte-wise NUL scans: 2.8 million short
strings validated one at a time on a clang-sized link. Each object's
string table is now checked as UTF-8 once - SIMD-friendly over
megabytes instead of call overhead over dozens of bytes - and names
slice out of it unchecked, with the NUL found by libc's vectorized
memchr. A non-UTF-8 table (never produced by real tools) degrades
to the names in its valid prefix rather than erroring. Release
builds also carry line tables now (debug = 1, as mold-rust), which
is what made the profile readable in the first place.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Two hot-loop cleanups. Assigning subsections to output sections
allocated two Strings and linearly scanned the chunk list per
subsection; the map is now keyed by the raw 16-byte name pairs from
the section headers - no allocation, no scans, chunks still created
in first-encounter order.
The final write() was a single-threaded copy of the whole output
into the page cache; disjoint 8MiB pwrite()s now do that copy on
all cores. A shared mapping would beat both, but macOS kills
ad-hoc-signed binaries written through one, so parallel pwrite is
the ceiling here.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Integration - rebasing every staged object's local indices into the
global arenas - ran one object at a time. mold's front end does the
positioning arithmetic up front: prefix sums over the batch give
each object its subsection, CIE, FDE and local-symbol bases, the
rebasing (the real work: every relocation's section target, every
unwind record's links) runs on all cores, and the serial remainder
is Vec moves and local-Symbol pushes. The resulting arenas are
byte-identical to the one-at-a-time layout.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
__unwind_info was encoded twice - once for its size during __TEXT
layout, once for its bytes at copy - because the encoding embeds
personality pointers as image-relative GOT addresses, and the GOT
(in __DATA_CONST) has no address yet when __TEXT is sized. But those
are the only late values in the whole section, and they live at
fixed offsets 28, 32, ... in the header; the encoder now writes
zeros there and returns the personality symbols as a patch list the
copy phase fills in, so the expensive part - filtering and sorting
140K records, building the compressed pages - runs once. The record
filter and sort also went parallel, as did the identical-code-
folding candidate scans (the weak-only AND accumulates in a three-
state atomic), the relocation-scan classification (collection is
read-only; the apply loop stays serial so GOT slot order is stable),
and linker-optimization-hint application (each object rewrites only
its own instructions).
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
The remaining serial walks over every symbol of every object become
parallel, following mold's designs for each:
Symbol resolution is mold's rank race. Ranks ((class << 32) |
priority, lower wins) race into an atomic array with fetch_min -
order-free, because the winner is the same under any interleaving -
and since each object's priority is unique, exactly one object owns
each symbol afterwards, so the claim phase writes symbol fields in
parallel with no locks: every slot has a single writer. Common-
symbol attribute merging (max size, strictest alignment) and
duplicate-definition reporting collect during the race and settle
serially, duplicates sorted by name so errors stay deterministic.
Dylib claiming parallelizes per symbol the same way. 50ms to 28ms
on clang's 2800 objects.
Chained-fixup collection and LC_FUNCTION_STARTS gathered their
entries serially from every subsection and symbol; both now collect
with parallel iterators and sort with parallel sorts (38ms to 5ms
and 26ms to 4ms on clang). The output symbol table's global and
undefined filters walk millions of slots and sort by name; both
parallelized.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Each ICF refinement round rehashed every candidate's bytes,
relocations and unwind records - the whole identity - log2(n) times.
mold hashes content exactly once into a base digest and lets the
rounds mix only fixed-size values: a candidate's next hash is
xxh3(base digest, candidate-edge targets' previous digests), with
the edge list precomputed. Rounds drop from 61ms to 9ms on clang's
58K candidates (87ms to 42ms for ICF overall), and the fold results
are identical - the fixpoint over digests distinguishes exactly what
the fixpoint over full rehashes did.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
-print_statistics answers "which phase"; finding this batch of
optimizations needed "which pass inside the phase", so the timers
used for that stay in, behind the MOLD_TIMING environment variable:
staging, the symbol gather, integration, .tbd parses, literal
merging, dead stripping, ICF, relocation scanning and the three
layout steps each report to stderr. Costs nothing when the variable
is unset.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
The copy phase parallelized across output sections, which for a real
program means one thread hauls the entire __text - tens of megabytes
of memcpy and relocation arithmetic - while nine idle. mold
parallelizes within a section: every subsection owns a disjoint
[output_offset, +size) slice of the output, and a relocation only
ever writes inside its own subsection (the GOT-load relaxation's
opcode byte included), so subsections are free to copy and fix up
concurrently. Rayon can't express the many-disjoint-slices split
directly, so a Sync pointer wrapper hands each iteration its own
raw slice.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
The content-hash UUID ran one CC_SHA256 over the whole pre-signature
file - 35ms of one core's time on a 70MB output, serialized between
the parallel copy and the parallel signature. mold hashes big inputs
as a hash of hashes, and the UUID now does the same: 4MiB blocks
digested on all cores, then one digest of the digests. The value
changes (it is a different, equally deterministic function of the
content), which only reproducibility across linker versions could
notice - and that was never promised.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Sizing __LINKEDIT chunks called encode_export_trie for its length
and copying called it again for its bytes - the radix construction
and ULEB fixpoint both ran twice per link. __LINKEDIT is laid out
after every address is final, so the first encoding is already the
real one; it is now kept on the context and copied out verbatim.
__unwind_info deliberately does not get the same cache: it is sized
while __TEXT is laid out, and its content embeds personality GOT
addresses the data segments haven't fixed at that point - caching it
shipped stale addresses and broke exception tests until the copy
went back to re-encoding.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
mold's gc-sections propagates liveness in parallel rounds: the
frontier's out-edges are computed on every core and an atomic
visited bit decides which targets join the next frontier. The live
set is a fixpoint independent of visit order, so the result stays
deterministic; only the spanning tree of who-marked-whom is not,
which is exactly the part -why_live reports - so a link that asks
for chains keeps the serial walk, and every other -dead_strip link
takes the parallel one. 43ms to 30ms on the 70MB Rust link (the
graph is wide and shallow, so rounds are few but sync-heavy).
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
The output string table's dedup map allocated a boxed copy of every
unique name and SipHashed each probe - 300K allocations and a second
full hashing pass over tens of megabytes of mangled names. It now
uses the symbol table's key discipline (PrehashedMap: borrowed
'static names, xxh3 computed once, pass-through hashing), which
allocates nothing; the few owned strings involved - N_OSO paths and
-add_ast_path entries, one or two per object at most - are leaked to
'static, the same lifetime discipline input names already have.
create_output_symtab's share of layout drops from 82ms to 31ms on
the 70MB Rust link.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
The sharded symbol table becomes mold-rust's, exactly: keys are
(name, xxh3-of-name) pairs, the maps are hashbrown keyed by a
pass-through hasher over the precomputed hash, equality compares the
bytes only on collisions, and the bulk gather - mold's name for it -
never hashes at all, because staging computes every extern name's
hash in parallel while the files are read. Binning by shard becomes
a modulo on a stored value, and the previous serial FNV pass over
50MB of mangled names disappears: the batch intern drops from 92ms
to under 10ms on mold-rust's own link.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
mold's input model: every file is memory-mapped, the kernel pages
bytes in on first touch, and pages the link never reads - the debug
sections of dead archive members, most of an rlib that only
contributes two symbols - never cost anything. This port had been
fs::read()ing every input into the heap, which for a Rust link
means copying gigabytes of rlibs up front: the input-processing loop
alone took 139ms on mold-rust's own 70MB link. Mapping brings that
to 12ms and the whole link from 584ms toward ld-prime's
neighborhood. Mappings are leaked like the buffers were, so nothing
downstream changes.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
The two linkers are meant to read as one program with two backends,
so passes that correspond one-to-one now carry mold-rust's names:
sweep_dead_files is remove_unreachable_files, scan_relocs is
scan_relocations, assign_offsets is set_osec_offsets, compute_symtab
is create_output_symtab, create_output_chunks is
create_output_sections, create_synthetic_symbols is
add_synthetic_symbols, resolve_boundary_symbols is
fix_synthetic_symbols, and ICF's entry point is icf_sections.
Passes with no ELF counterpart (dead_strip's Mach-O naming follows
the C++ tree's dead-strip.cc, resolve_entry serves LC_MAIN) keep
their names. Pure rename, no behavior change.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Symbol resolution picks one definition per weak symbol, but the
losing objects' subsections still held the duplicate bodies, and
nothing downstream removed them: the map attributed them to no
symbol, ICF never saw them as candidates (their defining symbols
point at the winners), and dead stripping only runs on request. On a
C++-heavy link the cost is enormous - clang's 67MB of live __text
carried 12MB of anonymous duplicate template instantiations, one
copy per object that happened to lose the toss.
Losing subsections now redirect to the winners through the same
replacement mechanism literal merging and ICF use, so section-target
relocations into a loser (vtable references, local jumps) land in
the winning copy at the same offset. Only exact-shape losers fold:
the defining symbol must sit at the same offset in both subsections
and the sizes must match - identical weak instantiations are what
C++ guarantees, and anything else is odd enough to keep.
With the thunk fix alongside: clang-by-us 123MB -> 111.4MB
(ld-prime: 109.9MB), __text 79.7MB -> 68.5MB (ld-prime: 67.0MB);
the sold C++ tree drops from 32.8MB to 26.6MB.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
The thunk layout reserved an entry for every branch to a
still-unplaced target, on the grounds that its final address was
unknown. But the section's total size bounds where any forward
target can land: if the whole remainder fits within forward branch
reach of a batch (with margin for alignment and the thunks
themselves), no entry is needed. Without the bound, a section just
past the creation trigger but far under the branch range - clang's
80MB __text against arm64's 128MB reach - drowned in
reserved-but-never-used entries: 11.5MB of them.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
ld64 warns when one library is named more than once on the command
line, and CMake-generated links routinely silence it with
-no_warn_duplicate_libraries (LLVM's do; linking clang hit the
unknown option). Both spellings are accepted, the warning is on by
default like ld64's, and it fires once per repeated -l name.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Rebase locations cluster - pointer arrays, vtables, __const tables -
and the classic dyld-info opcodes have run-length forms for exactly
that shape: DO_REBASE_IMM_TIMES/ULEB_TIMES rebase N consecutive
slots, the state machine's address advances past every slot it
rebases, and a gap within a segment costs only ADD_ADDR_ULEB (0x30 -
not to be confused with 0x40, ADD_ADDR_IMM_SCALED, whose immediate
is a scaled nibble; using the wrong one makes dyld read a ULEB byte
as the next opcode and the binary dies on launch). Emitting one
SET_SEGMENT_AND_OFFSET per pointer, as this linker did, produced a
155KB stream on a 70MB Rust binary; runs plus deltas bring it to
87KB. ld64 gets further still with the skipping variants, but the
remaining delta is noise against the binary.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Identical names now share one string-table entry, as they do in
ld64's output. The overlap is systematic, not incidental: every
debug stab's N_FUN/N_GSYM entry repeats the very name the regular
symbol table already carries, so an unduplicated string table stores
nearly everything twice. With Rust's mangled names averaging well
over 100 bytes, linking mold-rust's 70MB binary produced a 55.8MB
string table where ld64 makes 27.4MB; deduplication brings ours to
26.2MB and the whole binary from 101MB to 71.6MB - slightly smaller
than ld-prime's own output.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
The last unwind gap in relocatable mode: a function whose unwind
info exists only as a DWARF FDE - hand-written assembly with CFI the
compact format can't express (.cfi_escape defeats it), or compiler
output that fell back to DWARF - lost it through -r, since only
__LD,__compact_unwind was re-synthesized.
-r now also re-synthesizes __TEXT,__eh_frame from the parsed CIE and
FDE records, in the exact shape compilers emit: implicit
self-relative addressing throughout - pc_begin and the LSDA pointer
are distances valid in the merged object's own address space and
need no relocations - except the CIE's personality cell, which names
an external symbol and so carries the section's one conventional
relocation, a 4-byte pc-relative GOT reference (X86_64_RELOC_GOT
pcrel, exactly what clang produces). The next linker, ours or
Apple's, parses the section as it would any object's and regenerates
DWARF-mode unwind entries; compact-unwind records that point at FDEs
stay out of the merged __compact_unwind for the same reason. The
section gets S_COALESCED and the no-TOC/strip/live-support
attributes, matching compiler output.
Self-relative cells can only be written once the section's own
address is known, so the synthetic-section mechanism (shared with
__compact_unwind, now generalized) gained patch lists resolved at
layout time.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Symbol interning was the last serial stretch of input processing:
objects stage in parallel, but each staged file's global names then
went one by one through a single hash map. mold's symbol table
shards by a hash of the name - one-off lookups route to a single
shard, and the bulk load becomes embarrassingly parallel - and this
ports that design.
intern_batch takes every staged object's global names in input
order, bins them by shard, and lets the shards resolve their bins in
parallel: each name either hits the shard's map or joins the shard's
list of new symbols. New symbols then take contiguous id ranges laid
out by a prefix sum over the shards' new counts, the shard maps
absorb them in a second parallel pass, and one serial scatter hands
the ids back in batch order. Ids depend only on input order and the
sharding hash, so links stay bit-reproducible (verified on the 34MB
C++ link). The serial integration loop keeps only the per-nlist
bookkeeping and local-symbol slots, neither of which hashes
anything.
On the 34MB C++ link the parse phase drops from ~87ms to ~38ms and
total link time from ~195ms to ~147ms.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
mold for ELF maps the output file and lets the parallel copy write
straight into the mapping, saving a full memcpy pass. Porting that
design to Mach-O fails in a way worth recording: on macOS, a vnode
that has ever had a writable shared mapping is killed by the kernel's
ad-hoc code-signature validation at exec time - SIGKILL, even though
codesign(1) declares the very same bytes valid on disk. msync before
unmapping does not help, and neither does writing a temp file via
mmap and rename()ing it over the destination, since the taint follows
the vnode; only content that arrived via write() (or a fresh copy of
the file) will execute. LLVM's FileOutputBuffer has the same
discovery encoded as a Darwin-specific fallback to in-memory buffers
for executables. So the buffered-write design stays, now with the
reason written down.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
ld64's -print_statistics reports where the link's time went to
stderr; mold carries the same facility as --perf, and porting it
gives this linker a first-party answer to "what's slow". The driver
laps a timer at the phase boundaries it already has - parse,
resolve (including LTO), the transformation passes, layout, and
copy+write - and prints the table with a summary of the input and
output volumes that drive them (under mold's eager model the object
count reads "N alive of M", M counting every archive member parsed).
Diagnostics stay on stderr so a build system capturing stdout (for
-t or -why_load, say) is unaffected.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
An arm64 compiler can't know how close a symbol will land, so it
emits the conservative two-instruction materializations - adrp+add
for an address, adrp+ldr for a load - and records
LC_LINKER_OPTIMIZATION_HINT entries naming the instructions, leaving
the linker to shorten them once addresses are final. The payload is
ULEB128 records: kind, argument count, then instruction addresses in
the object's address space, which map to output locations through
the subsection table (hints on dead-stripped or folded code vanish
with it).
Four kinds are rewritten, matching what ld64 and lld do:
AdrpAdd (7) becomes nop+adr when the target is within +-1 MiB;
AdrpAddLdr (3) folds all the way to a single pc-relative literal ldr
when possible, else shortens the address computation; AdrpLdr (2)
becomes nop + literal ldr; AdrpAdrp (1) drops a second adrp of the
same page into the same register; and AdrpLdrGot (8) handles both an
intact GOT load and one the GOT relaxation already turned into an
add. Every rewrite is guarded by instruction-shape checks - hints
are advisory, and one that no longer matches (because another
rewrite got there first) is skipped, which is exactly how ld64
treats them. The pass runs after relocation and before the UUID
hash, so the identity still covers the final bytes;
-ignore_optimization_hints turns it off, as in ld64.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
An order file line's full grammar is [arch:][object-file:]symbol: a
fat build shares one order file across architectures by prefixing
lines with the arch they apply to, and an object-file qualifier pins
a line to one file's symbol - the disambiguator for static functions
with the same name in different objects. Lines for another
architecture now drop out entirely, and a file qualifier restricts
the match to symbols whose object has that leaf name, instead of the
previous approximation that took everything after the last colon as
the symbol.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
The mold paper's layout phase computes each output section's offsets
independently - a per-section prefix sum over member alignments and
sizes - so the sections parallelize trivially. This port had been
walking them serially; now the plain sections' offset vectors are
computed on all cores with rayon and written back serially (the
write-back is what keeps determinism obvious). Sections that need
range-extension thunks are the exception: thunk creation interleaves
layout with a relocation scan that annotates each branch with its
thunk entry, so those - at most one big __TEXT per link in practice -
keep the serial path.
Byte-identical output verified against the serial version on the
34MB C++ link.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
mold-rust parses static archives in archive_file.rs; the same split
here moves the ar-format walker - 60-byte headers, BSD "#1/<len>"
long names, the skipped __.SYMDEF index member - out of
input_files.rs verbatim, leaving that module to Mach-O objects and
dylibs. No behavior change.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
mold keeps garbage collection in its own module (gc_sections.rs in
mold-rust; the C++ tree and sold call the Mach-O version
dead-strip.cc), and this port mirrors mold-rust's layout, so the
-dead_strip mark/sweep and its -why_live chain printer move out of
passes.rs verbatim, under the feature's Mach-O name. No behavior
change.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
mold keeps thunk creation in a module of its own (thunks.rs in
mold-rust, thunks.cc in the C++ tree and in sold), and this port's
organization is meant to mirror mold-rust's, so the batch-layout
algorithm and its relocation scan move out of passes.rs verbatim.
No behavior change.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
ld64's -map has a third section when -dead_strip ran: after the live
symbols, "# Dead Stripped Symbols:" lists every atom the strip
removed, "<<dead>>" standing in for the address it never got, with
the size it would have occupied and its owning file number. Build
engineers read it to see what dead stripping actually bought and to
confirm something they expected gone really went. The report walks
the same symbol records as the live section but keeps the ones whose
subsection lost the liveness walk, so the two sections partition the
inputs' symbols.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
The output symbol table's local entries only exist for debugging and
crash symbolication, so ld64 lets a build trim them by name:
-non_global_symbols_strip_list removes matching locals and
-non_global_symbols_keep_list keeps only matching ones, each reading
a file of names with * wildcards and #-comments - the same list
dialect as -exported_symbols_list. Shipping binaries use this to
shed internal helper names without going fully -x. Global symbols
and debug stabs are untouched; the filter applies exactly where
local nlists are emitted.
The wildcard matcher moved to util as glob_match, shared with
-why_live.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
ld64's -t traces the inputs of a link, one path per line, archive
members as "archive.a(member.o)" - the quick way to see what a
complicated link line actually consumed. ld64 logs files as it opens
them; in mold's model every input, archive members included, is
parsed eagerly and a liveness walk then decides what joins the link,
so logging opens would list members the link immediately discards.
The trace therefore reports the converged state: live objects and
the dylib stubs resolved, which for archives matches ld64's output
(only loaded members appear).
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Thread-local access parallels GOT access: code loads the address of a
__thread_vars descriptor through a pointer slot (__thread_ptrs) that
dyld fills, because the descriptor might live in another image. When
the thread-local is defined in the image being linked, the slot is
pure indirection, and ld64 rewrites the load into computing the
descriptor's address directly - the same transformations as the GOT
relaxation, keyed by the TLV relocation family's fixed instruction
shapes: movq->leaq on x86-64, and the adrp retarget + ldr->add pair
rewrite on arm64.
With the relaxation, __thread_ptrs slots are allocated only for
thread-locals imported from dylibs; an image whose TLS is all its
own carries no __thread_ptrs section at all, matching ld64's output.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
The GOT_LOAD relocation family exists precisely so linkers can do
this: a compiler that needs a symbol's address but cannot know
whether it is local emits a load through the GOT and marks it
GOT_LOAD (rather than plain GOT), promising a fixed instruction
shape. When the target turns out to be defined in the image being
linked, the indirection is pure cost, and ld64 and lld both rewrite
the instruction to compute the address directly:
- x86-64: "movq sym@GOTPCREL(%rip), %reg" (opcode 0x8b) becomes
"leaq sym(%rip), %reg" (0x8d) - the same one-byte rewrite ELF
linkers do for R_X86_64_REX_GOTPCRELX.
- arm64: the adrp of the pair retargets from the slot's page to the
symbol's, and the "ldr Xn, [Xm, #off]" under GOT_LOAD_PAGEOFF12
becomes "add Xn, Xm, #pageoff". Both halves must agree, so the
pair relaxes as a unit.
Relaxed references need no GOT slot, so the scan allocates one only
for imported targets (or the odd unrelaxable instruction shape,
which conservatively keeps the GOT). Fewer slots also means fewer
rebases and less __DATA_CONST.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
The other half of umbrella frameworks. A subframework built with
-umbrella <Name> is supposed to be reachable only through its
umbrella; ld64 enforces that at link time, refusing any client that
is not the umbrella itself, a sibling subframework of the same
umbrella, or explicitly admitted. -allowable_client <name> grants
that admission, recorded as LC_SUB_CLIENT load commands (the same
single-string layout as LC_SUB_FRAMEWORK).
On the consuming side, LC_SUB_FRAMEWORK and LC_SUB_CLIENT are now
read from input dylibs, and the current link's identity is checked
against them: the identity is -client_name when given, otherwise the
output's leaf name with any "lib" prefix and extension shed (ld64's
derivation) - so an app named Big or a link declaring -umbrella Big
passes, as does anything the subframework lists, and everyone else
gets "cannot link directly with" the library.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
-sectalign <seg> <sect> <hex> raises an output section's alignment -
the classic use is page-aligning an embedded blob so it can be mapped
or measured on its own. Mach-O section headers store alignment as a
power-of-two exponent, so the argument must be a power of two (ld64
rejects anything else), and the override can only raise the value:
member subsections were placed assuming their own alignments, which a
larger section alignment preserves but a smaller one would break. The
override lands after subsections are grouped into output sections and
before layout, so both the section's address and the following
sections' move accordingly.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
-alias <existing> <new> publishes a definition under a second name:
the new symbol shares the original's subsection and offset, so both
names resolve to one address, both appear in the symbol table, and
both export. Apple leans on this for compatibility names - libSystem
publishes dozens of $VARIANT aliases this way - and -alias_list reads
the same pairs from a file, one "existing new" pair per line with
#-comments, for when there are many.
Aliases are created after symbol resolution settles (the base must be
defined somewhere, or the link fails with ld64's undefined-base
error) and before undefined-reference checking, so code may reference
a name that exists only as an alias.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
The companion question to -why_load: with -dead_strip on, why did
this symbol survive? ld64's -why_live <pattern> (with * wildcards,
repeatable) prints the chain of references that kept each matching
symbol alive, one "symbol from file" line per hop, indented as the
chain walks away from the symbol toward a dead-strip root - the
entry point, an exported symbol, an initializer, or a
no-dead-strip section.
The mark phase of dead stripping already visits exactly these edges,
so the implementation records the walk's spanning tree: whichever
live subsection first marks another becomes its predecessor, and the
chain is that tree read backwards from the queried symbol. Hops are
labeled by a symbol defined at the subsection (preferring an
external one at offset zero) or the section name when nothing is.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
"Why is this archive member in my binary?" is the most common
question a linker gets asked. ld64's -why_load answers it: one line
per loaded member, "_symbol forced load of archive.a(member.o)",
naming the undefined symbol that pulled the member in.
In mold's resolution model there is no load-time event to hook - all
members are parsed up front and a liveness walk decides which join
the link - so the reason is recorded where liveness is decided:
whichever undefined reference (or entry-point/-u root) first marks a
member alive during the walk becomes its recorded cause, refreshed
each resolution round so the report reflects the final converged
state. Members alive for reasons other than a symbol (-all_load,
-force_load) report the option instead.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Mach-O debugging leaves DWARF in object files, so when -r merges
objects, the merged object must keep their __DWARF sections or the
debug info's only home disappears: the final link's N_OSO stab names
the merged object, and that is where lldb and dsymutil will go
looking. Debug sections are normally dropped at parse time - a final
image never contains them - so relocatable mode now keeps them,
carries them through layout and relocation regeneration like any
section, and preserves their S_ATTR_DEBUG marking so the next linker
recognizes them.
Two latent layout bugs surfaced. Section file offsets were aligned
independently of addresses, so a segment's filesize could creep past
its vmsize - which dyld tooling rejects; an object's file image now
mirrors its address space (fileoff = segment offset + address),
which is also how compilers lay out MH_OBJECTs. That in turn
requires zero-fill sections to come last, since they occupy
addresses but no file bytes.
With this, lldb sets source breakpoints and reads variables in code
that passed through -r, and Apple's linker accepts the merged
object.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
An exception thrown inside a static initializer unwinds through
dyld's initializer-running frames and, uncaught, must reach
std::terminate with the exception object intact - the what() string
in libc++abi's abort message proves the throw, the RTTI and the
personality routine all survived the trip through __init_offsets.
The only adaptation from sold's copy: newer libc++abi says
"terminating due to uncaught exception" where older releases said
"terminating with", so the pattern accepts both.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Apple moves symbols between system libraries across OS releases, but
old binaries keep binding them where they used to live. The
compatibility trick lives in the stub libraries: exported names
beginning with $ld$ aren't symbols at all, they are directives ld64
interprets per deployment target. $ld$add$os<ver>$<sym> exports
<sym> only when the target equals <ver> exactly, $ld$hide$os<ver>$
<sym> hides one, $ld$install_name$os<ver>$<name> substitutes the
dylib's recorded install name - so a client targeting the old OS
writes the old library's name into its LC_LOAD_DYLIB - and
$ld$previous$<name>$<compat>$<platform>$<lo>$<hi>$<sym>$ is the
modern range form: apply <name> when the platform matches and
lo <= minos < hi. The per-symbol variant of $ld$previous never
worked in ld64 (as sold discovered) and is ignored.
The directives are interpreted wherever a .tbd is parsed, including
stubs reached through reexport chains, and the affected names never
enter the export sets.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Apple's newer linker can narrate its symbol resolution:
--print-dependencies writes one line per resolved undefined symbol,
"referencing-file<TAB>providing-file<TAB>u<TAB>symbol", objects by
path (archives as archive(member)) and dylibs by install name. It
answers build-graph questions - why an archive member was pulled in,
which dylib satisfies a call - with a grep instead of a -map
spelunking session. Emitted right after resolution settles, so the
report reflects exactly the definitions the link used.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Mach-O debugging keeps DWARF in object files; the executable carries
only stabs naming them (N_OSO). LTO breaks that arrangement: the
machine-code object exists only inside the linker, produced by
libLTO moments before layout, so its debug info would evaporate with
the process. -object_path_lto <path> writes that transient object to
disk - Xcode points it into the dSYM staging area so dsymutil can
read the DWARF later, the same way it reads ordinary objects.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
App extensions run under a restricted sandbox, and a library must
declare itself safe for that environment: building it with
-application_extension sets MH_APP_EXTENSION_SAFE in its header, and
SDK .tbd stubs express the opposite with a not_app_extension_safe
flag (absence of the flag means safe). When a client links with
-application_extension against a library lacking the declaration,
ld64 warns - "linking against a dylib which is not safe for use in
application extensions" - rather than failing, since the library may
in fact use no forbidden API; -w suppresses it like any warning.
The check sits at dylib registration, so it covers libraries pulled
in indirectly as well as those named on the command line.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
A dylib's install name may not be a real path: @loader_path/ stands
for the directory of whatever image loads the dylib, @rpath/ defers
to the loading image's runpath list (its LC_RPATH commands, each of
which may itself start with @loader_path/), and @executable_path/ is
the running process's executable. dyld resolves these at load time;
a linker meets them earlier, when following LC_REEXPORT_DYLIB chains
to learn what a dependency ultimately exports.
The link-time interpretation mirrors dyld with the referencing dylib
in the loader role: each queued reexport carries its referrer's
directory and parsed LC_RPATH list, @loader_path expands against
that directory, @rpath tries each of those runpaths, and
@executable_path resolves against the output executable being linked
(or the -executable_path override, which exists for linking plugins
against an app's frameworks). Reexports discovered inside a
dependency then use that dependency's own directory and runpaths,
not the original dylib's - exactly how dyld walks the chain.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
ld64's answer to the ELF __start_SECTION/__stop_SECTION convention:
an undefined reference to section$start$__SEG$__sect or
section$end$__SEG$__sect resolves to the section's final start or
one-past-end address, and segment$start$__SEG / segment$end$__SEG do
the same for a whole segment. Code uses them to iterate registration
records that other objects scatter into a named section; referencing
the boundaries of a section nothing emits wills an empty section into
existence, so the loop simply sees zero elements.
The names are claimed during symbol resolution (they'd otherwise be
undefined-symbol errors) but their values exist only after layout, so
they resolve in a small pass after address assignment - the one
symbol kind whose value depends on the finished layout rather than
contributing to it.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Profile-guided binary layout, ld64 style: -order_file names symbols
one per line (with #-comments, and optional arch: and object-file:
qualifiers narrowing a match), and the atoms defining those symbols
are placed at the front of their output sections in exactly the
file's order, the rest following in input order. Apple tooling
generates these files from startup traces so the code touched during
launch shares as few pages as possible.
Ordering happens between grouping subsections into output sections
and laying them out: symbols are ranked by their line number, each
subsection inherits its best-ranked symbol, and a stable sort by rank
reorders each section - unlisted subsections all rank equal-last, so
stability preserves their input order, which is also what makes the
pass a no-op without the option. Range-extension thunk placement runs
after the sort and so respects the new order.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Two related pieces of how a main executable treats its globals.
Compilers mark a weak definition whose address is never observed with
.weak_def_can_be_hidden - the nlist carries N_WEAK_DEF and N_WEAK_REF
together - which licenses the linker to drop its visibility. dyld's
runtime weak coalescing considers only images that export a weak
symbol, and the main executable is always first in load order, so
exporting these buys nothing and costs binding time. ld64 therefore
auto-hides them in executables: a C++ inline function or template
instantiation is demoted to a non-exported symbol - absent from the
export trie and the external symbol table. The scopes of coalesced
copies merge: one plain .weak_definition among the definitions pins
the symbol exported (sold's merge-scope test), and so does an
-exported_symbols_list naming it. The MH_WEAK_DEFINES header flag
counts only exported weak symbols, so it stays clear for auto-hidden
ones.
LTO makes visibility observable for regular globals too: libLTO
internalizes anything not on the must-preserve list, then discards
what's unreferenced. A dylib must preserve every bitcode-defined
external (each is an export), but an executable only needs symbols
non-LTO code references, the entry point, -u names and explicit
exports. -export_dynamic - which Xcode passes for executables that
expect dlsym() or plugins to find their globals - restores the
preserve-everything behavior.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Chained fixups exist to make __DATA_CONST pages clean: dyld resolves
the chain in place and the kernel can share the result. Classic
__mod_init_func would defeat that - every initializer is an absolute
pointer needing a rebase - so ld64 silently upgrades initializers to
S_INIT_FUNC_OFFSETS (32-bit image-relative offsets in
__TEXT,__init_offsets, nothing to fix up) whenever it emits
LC_DYLD_CHAINED_FIXUPS, with or without an explicit -init_offsets.
We now do the same, which also means a default link targeting
macOS 13+ gets offset-based initializers, like ld64's.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
A chained fixup encodes the distance to the next fixup in its page as
a count of 4-byte strides, so a pointer at an address that is not a
multiple of four cannot join a chain at all - typically the result of
a packed struct or a stray .byte before a .quad in assembly. The
error now points at the input, "file(__DATA,__data): unaligned base
relocation", the way ld64 words it, instead of a late generic failure
at chain-writing time; it fires while collecting fixups, where the
input section is still in hand. Classic rebase opcodes address bytes,
so the same input still links under -no_fixup_chains - the test
checks both.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
A data pointer initialized with "&arr + constant" imports a symbol
plus an addend, and each dynamic-binding format carries it
differently. Classic dyld info threads it through the bind state
machine with BIND_OPCODE_SET_ADDEND_SLEB. Chained fixups hold small
addends (up to 255) inline in the 64-bit bind word; anything larger
moves the import table to DYLD_CHAINED_IMPORT_ADDEND entries, and an
addend past 32 bits to DYLD_CHAINED_IMPORT_ADDEND64 - the two tests
push through 2^12 and 2^42 byte offsets to exercise both, in both
fixup formats.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Covers behavior implemented by the literal-merging pass: S_8BYTE_
LITERALS sections split into 8-byte elements and identical elements
coalesce, so two objects each embedding 3.1415926535 yield a single
8-byte __literal8 in the output. The test pins -arch x86_64: arm64
clang never emits floating-point literal pools, it builds doubles
inline with mov/movk, so the section exists only in x86-64 objects.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
LC_BUILD_VERSION carries, after the platform/minos/sdk triple, an
array of build_tool_version entries ({u32 tool, u32 version}) naming
the tools that produced the image; Apple's ld records TOOL_LD (3)
with its own version, which is how one can later tell what linked a
given binary. This linker stamps tool number 54321 - the id sold
chose for itself, outside Apple's 1..3 range - keeping the "which
linker made this" check from sold's test suite working, and making
our output distinguishable from ld64's in the wild.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Compilers mark ranges inside __text that hold data rather than
instructions - jump tables (.data_region jt8/16/32), inline
constants - as data-in-code entries: {u32 offset, u16 length, u16
kind} records referenced by an LC_DATA_IN_CODE load command. In a
relocatable object the offsets are addresses in the object's own
zero-based section layout; in a linked image they are file offsets
from the Mach-O header. Disassemblers use them to stop decoding
bytes as instructions, and codesign's page hashes treat them as
opaque data.
The linker merges its inputs' entries: each is mapped through its
object's subsections (so dead-stripped code sheds its entries),
rebased to the output file offset, and the table sorted by offset.
ld64 always emits the load command, entries or none - its absence
marks a pre-10.7 linker - and -no_data_in_code_info suppresses it
while -data_in_code_info restores the default.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Tools parse -map output, so the details matter: ld64 numbers file
[ 0] as "linker synthesized" (the Mach-O header symbol, stubs and
other atoms with no source object), real objects from [ 1] in link
order, and gives every symbol line a size column - the atom's extent,
i.e. the span from the symbol to the next symbol in its subsection or
the subsection's end. Columns are tab-separated. Compiler temporary
labels (l*/L*) are not atoms and are omitted; __mh_execute_header is
reported at the image base with size zero, attributed to file 0.
sold's map test expected space-separated columns, which real ld64
does not produce; the ported test asserts the tab-separated ld64
layout instead.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
"undefined symbol: _foo" sends the user grepping their build; ld64's
messages carry the provenance and ours now do too. An undefined
symbol is reported as "undefined symbol: <file>: <name>" where <file>
is an object that references it - recovered on demand by scanning
live objects' undefined nlists, since the symbol table intentionally
does not track referencers. A duplicate definition is reported as
"duplicate symbol: <newer file>: <older file>: <name>", the two
liveness-ranked strong definitions that collided. Archive members
display as "archive.a(member.o)". The formats follow ld64 closely
enough that sold's stricter test versions pass unchanged.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
A Mach-O thread-local is not addressed directly: the symbol names a
24-byte descriptor in __thread_vars (thunk, key, offset), and code
reaches the variable by calling through the descriptor with TLV
relocations (X86_64_RELOC_TLV, ARM64_RELOC_TLVP_LOAD_PAGE*). The
reference kind must therefore agree with the symbol's nature. If a
TLV load resolves to ordinary data, the running code would interpret
the variable's own bytes as a descriptor and call through garbage;
if an ordinary load resolves to a thread-local, it reads the
descriptor as if it were the data. Both arise from declaring an
extern variable _Thread_local in one file but not the other, and ld64
diagnoses them at link time; we adopt its wording.
Whether a symbol is thread-local is visible in every definition
source: an object symbol sits in an S_THREAD_LOCAL_VARIABLES section;
a dylib export's nlist points into one (found by mapping n_sect
ordinals over the dylib's section headers); and .tbd stubs list TLVs
under a separate thread-local-symbols key - which this change also
starts parsing, so thread-locals exported by SDK stub libraries
resolve at all.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
The __PAGEZERO segment is meaningful only in a main executable: it
occupies the image's own zero-based address range so NULL and small
integers can never be mapped. A dylib or bundle has no fixed load
address - dyld slides it wherever fits - so a __PAGEZERO there
protects nothing, and ld64 rejects -pagezero_size outside the
MH_EXECUTE link with the exact message this change adopts. Silently
ignoring the option (what we did before) hides real build-system
mistakes, like passing an executable's LDFLAGS to a dylib target.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
A dylib can opt into being dropped by its clients: building it with
-mark_dead_strippable_dylib sets MH_DEAD_STRIPPABLE_DYLIB (0x400000)
in its Mach-O header, and any later link that binds no symbol from
the dylib omits its LC_LOAD_DYLIB - as if -dead_strip_dylibs applied
to that one library. Apple uses this for libraries that exist only to
satisfy historical link lines. The client side reuses the
dead-strip-dylibs pass, which now runs whenever any loaded dylib
carries the flag and strips just the strippable (or, with
-dead_strip_dylibs, all) unused entries; -needed_library still pins a
dylib regardless.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Mach-O debugging leaves DWARF in the object files and records only
stabs in the executable; each linked object appears as an N_OSO stab
whose string is the object's absolute path (plus mtime), which is how
lldb and dsymutil find the debug info later. Absolute paths poison
build reproducibility and break when a build tree moves, so
-oso_prefix <path> strips that leading prefix from every N_OSO,
leaving relative paths the debugger resolves against its working
directory or a source map. A bare "." stands for the current working
directory with a trailing slash, matching ld64.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
-add_empty_section <seg> <sect> puts a zero-length section into the
output. That sounds useless, but a Mach-O section is also a namespace
anchor: its load-command entry gives tools and the section$start /
section$end synthetic symbols something to resolve against, and
runtime code can find it with getsectiondata() to test for a marker a
build step may or may not add. Implemented as a -sectcreate with no
bytes; segments and layout treat a zero-size section like any other,
it just occupies no file or memory space.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Umbrella frameworks bundle several subframeworks behind one public
framework (Cocoa is the classic example: it reexports AppKit,
Foundation and CoreData). A subframework built with -umbrella <Name>
carries an LC_SUB_FRAMEWORK load command naming its umbrella, and
ld64 then refuses to link anything against the subframework directly
unless the client is the umbrella itself or is named by
-allowable_client - pushing users to link the umbrella. The load
command is the single-string layout shared with LC_RPATH: the
cmd/cmdsize header plus an inline NUL-terminated name.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Xcode passes -reproducible to ask ld64 for bit-identical rebuilds
(ld64 otherwise embeds nondeterminism, historically even random
UUIDs). This linker is deterministic by construction - the UUID is a
content hash and layout depends only on inputs - so the option is
accepted as a no-op, the same way it behaves in lld.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Before LC_BUILD_VERSION existed (macOS 10.14 SDK), the deployment
target was stated with per-platform version-min load commands
(LC_VERSION_MIN_MACOSX and friends), driven by ld64 options of the
same shape; clang still passes -macosx_version_min (or the newer
-macos_version_min spelling) instead of -platform_version when
targeting old systems. The option pins the platform to macOS and sets
minos; there is no SDK version argument, so that field stays zero,
which otool renders as "sdk n/a" - the same thing ld64 records. We
always emit the target as LC_BUILD_VERSION, which is valid for any
deployment target on current tools.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
ld64 has two library search policies. The modern default,
-search_paths_first, tries each -L directory for lib<name>.tbd, then
.dylib, then .a before moving to the next directory - so an archive
early on the path shadows a dylib later on it. -search_dylibs_first
restores the historical behavior (and the one other Unix linkers
never had): scan the entire path for a dynamic library first, and
only fall back to archives if no dylib exists anywhere. The option
matters to projects that ship both static and dynamic builds of a
library into different directories and want the dynamic one picked
regardless of -L order.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
sold accumulated a test suite of ld64 behaviors over years of
development; these fifty scripts exercise features this linker already
implements - archive semantics (undef, weak-def-archive, force-load,
lib1), dylib details (install names, reexport chains, umbrella .tbd
stubs, dead-strippable dylibs, two-level lookup through libSystem's
reexports), weak definitions and bindings across objects, archives and
dylibs, universal (fat) inputs, -S/-x symbol stripping, search-path
order, subsections-via-symbols splitting, private-extern merge scopes
and platform versioning. Only the driver-side plumbing differs from
sold's copies: the scripts source our common.inc and address the
linker as $mold.
Not ported: sold's eh-frame.sh never invokes the linker under test
(its final link omits --ld-path), and uuid2.sh asserts two identical
links get distinct UUIDs - sold generated random UUIDs, but ld64
derives the UUID from a content hash, and this linker follows ld64
(our reproducibility test asserts exactly the opposite).
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
A merged object must let the *final* linker build __unwind_info, so -r
re-synthesizes __LD,__compact_unwind exactly the way compilers emit
it: one 32-byte {function, length, encoding, personality, LSDA} entry
per record, with the pointer fields set by UNSIGNED relocations - the
function and LSDA as section-relative relocations whose embedded
values are merged-space addresses, the personality as an external
relocation against the merged symbol table. The section keeps its
S_ATTR_DEBUG marking, so like any compiler-emitted compact unwind it
never reaches a final image as bytes; C++ exceptions now survive an
-r round trip through either this linker or Apple's. Records
synthesized from DWARF FDEs are still dropped, since representing
them would mean carrying __eh_frame as well.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
-r produces a relocatable object instead of a final image: the merged
result of its inputs, still linkable by any linker (the test
round-trips our merged object through Apple's ld and through
mold-macho itself). What changes versus a final link, structurally:
- MH_OBJECT with a single *nameless* segment whose sections carry the
original (segment, section) names; everything is laid out from
address zero and mapped rwx by whoever links it next. The segment's
file span covers section contents only - relocations, symbols and
strings live past its end, addressed by the section headers' reloff
and the symtab command.
- Relocations are regenerated, not applied. Section bytes are copied
raw, so fields that embed their addend (all of x86-64, arm64's
UNSIGNED) keep it; arm64 instruction-patching relocations get their
ARM64_RELOC_ADDEND prefix record re-emitted. External relocations
are renumbered against the merged symbol table (extern relocations
may reference local symbols, so the index map covers them too);
non-external ones are renumbered to merged section ordinals with
their embedded target addresses rewritten into the merged address
space.
- Symbol resolution runs normally across the inputs - that is the
point of merging - but nothing binds to dylibs and undefined
symbols are expected: they stay undefined nlists. Common symbols
stay tentative (size in n_value, alignment in n_desc), private
externals keep N_PEXT, and the output is marked
MH_SUBSECTIONS_VIA_SYMBOLS so the next linker can split it again.
Not carried over yet: DWARF debug sections and unwind info; a final
link of -r output currently loses exception tables from the merged
part.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Milestone note recorded here: ripgrep builds with this linker and
passes its entire test suite (323 tests) with every binary linked by
mold-macho, alongside the earlier validations (self-hosting, the C++
mold tree at -O3, Swift/Objective-C/C++/Rust hello-to-nontrivial
programs).
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
-flat_namespace reverts to the pre-two-level binding model: imports
carry the BIND_SPECIAL_DYLIB_FLAT_LOOKUP ordinal instead of naming a
providing dylib, dyld searches all loaded images by name at bind time,
and the mach header drops MH_TWOLEVEL. Nothing modern should use it -
name collisions across images become silent hijacks - but old build
systems and interposition tricks still pass it, and dyld still honors
it.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
-Z removes the standard search directories (/usr/lib and the framework
directories, under each syslibroot) from library resolution, leaving
only explicit -L and -F paths - for hermetic builds that must not pick
up SDK libraries by accident.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
The counterweight to -dead_strip_dylibs: a library marked "needed"
keeps its LC_LOAD_DYLIB even when no symbol binds to it. That matters
for libraries linked purely for side effects - Objective-C categories,
static initializers, interposers - which look unused to the linker but
change program behavior by being loaded.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
-hidden-l<name> links an archive like -l<name> but demotes every
external symbol its members define to a private external: visible to
this link for resolution, but neither exported nor an external symbol
in the output. It is the link-time equivalent of rebuilding the static
library with hidden visibility - used to embed a library into a dylib
without leaking its API.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
-U <symbol> is the targeted version of -undefined dynamic_lookup: this
one symbol may stay unresolved and becomes a flat-namespace import,
while everything else still errors. -undefined warning (and suppress)
downgrade the diagnostic for all unresolved symbols while treating
them the same way.
Worth remembering about running such binaries: with the modern eager
binding schemes (non-lazy GOT, chained fixups) every import is
resolved at load time, so an image linked with -U aborts at launch
unless *something* - the host process of a plugin, an inserted
library - actually provides the symbol. The old lazy-binding era let
such programs limp along until the first call; that grace period is
gone.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Xcode passes -dependency_info on every link and feeds the resulting
file to its incremental build system, so it can know which inputs the
link actually consumed (archive members included) and when a relink is
needed. The format is a flat sequence of opcode-prefixed
NUL-terminated strings: 0x00 linker version, 0x10 consumed input,
0x11 path probed but not found, 0x40 output file.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
A thread-local variable exported by a dylib works with no extra
machinery on the provider side: its symbol already points at the TLV
descriptor in the dylib's __thread_vars, and the export trie serves it
like any data symbol. On the consumer side the __thread_ptrs slot
(what TLVP relocations load through) simply becomes a bind record
instead of a rebased local pointer: dyld writes the foreign
descriptor's address at load time, and from there the usual
thunk-calling access sequence works unchanged across images.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Global initializers have two on-disk encodings. The classic
__DATA,__mod_init_func (type S_MOD_INIT_FUNC_POINTERS) holds absolute
function pointers, each of which costs a rebase fixup in a PIE image.
-init_offsets converts them to __TEXT,__init_offsets (type
S_INIT_FUNC_OFFSETS): 32-bit offsets from the image base, which dyld
runs identically but never needs to slide - half the size, zero
fixups, and the data moves into the read-only segment. dyld
distinguishes purely by section *type*, so the conversion is: gather
each __mod_init_func pointer's relocation target in order, drop the
input sections, emit one offset per initializer, and (since the source
sections are gone) root the initializer functions for dead-stripping
explicitly. ld64 flips this on by default alongside chained fixups.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
-reexport_library / -reexport-l / -sub_library mark a dependent dylib
as re-exported: the load command becomes LC_REEXPORT_DYLIB, and the
MH_NO_REEXPORTED_DYLIBS optimization flag is withheld, telling dyld
that resolving a symbol against this image must search that dependency
too. This is how umbrella libraries are built - libSystem is nothing
but ~40 re-exported /usr/lib/system/*.dylib pieces behind one install
name, and clients never know.
The linker consuming such a dylib must mirror dyld's behavior at
*link* time: a client of the umbrella resolves the dependency's
symbols through it, so reading a dylib binary follows its
LC_REEXPORT_DYLIB chain (and a .tbd's reexported-libraries), merging
every reachable export into the umbrella's set. Install names in
those commands resolve against each syslibroot first and then
verbatim, since a freshly built dylib's absolute install name lives
outside any SDK.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
-no_uuid skips the content hash but keeps LC_UUID with sixteen zero
bytes: modern dyld hard-refuses to load an executable whose load
commands lack LC_UUID ("missing LC_UUID load command"), so the old
behavior of omitting the command entirely is no longer an option -
only the value can be suppressed.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
ld64 deduplicates identical functions by default and clang passes
-no_deduplicate to turn that off at -O0; this implements the
equivalent, modeled on mold's ICF pass.
Two subsections fold when their bytes, relocation records and unwind
behavior all match - and when folding cannot be observed. C++ function
address identity makes ICF subtle in general, but weak definitions are
exactly the case the language already permits to coalesce (inline
functions and template instantiations get merged across translation
units by symbol resolution anyway), so folding is restricted to atoms
whose named symbols are all weak definitions. Compiler-generated 'l'
temporary labels don't count: they name locations for the assembler,
not addresses a program can take.
The comparison follows mold's hash-refinement scheme: an initial hash
covers each candidate's literal content (bytes, section flags,
relocation shapes and addends, its compact-unwind encodings and LSDA
edges); a logarithmic number of parallel rounds then rehashes every
candidate with the previous-round hashes of the candidates its
relocations reference, so equal hashes come to mean "the whole
reachable subgraphs are equal" - mutually recursive functions
included, with no synchronization needed. Equal-hash groups are
verified structurally and fold onto one leader through the same
redirect mechanism literal merging uses; references, exported
addresses and debug stabs all follow automatically, while the folded
copies' unwind records and FDEs are dropped to avoid duplicate
entries.
Folding the C++ mold binary this way shaves 7.7% off its size
(37.6 MB to 34.7 MB), template instantiations being what they are.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
The second half of mold's front-end design: with resolution already
decoupled, parsing becomes a pure function per file and runs on the
rayon pool. Each object is staged in isolation - subsection splitting,
relocation reading and redistribution, compact-unwind and __eh_frame
parsing - with every cross-reference expressed as an index local to
the file: relocation targets and unwind/FDE links index the staged
subsection list, personalities index the staged symbol list, FDEs
index the staged CIE list. A cheap serial integration step then
appends each staged file to the global arenas in input order, rebasing
the local indices by the arenas' base offsets (pure arithmetic) and
interning symbol names.
Input-order priorities are assigned at collection time, before the
parallel stage, so resolution semantics are exactly those of a serial
left-to-right read regardless of staging order. Dylib stubs stay on
the collection thread (they are order-sensitive and cheap), as does
libLTO module registration.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
This replaces on-demand archive member loading with the architecture
described in the mold paper (arXiv:2608.23228): parsing is decoupled
from resolution, every input - including every archive member - is
parsed up front, and resolution is a pure function over the parsed
symbol tables. The structure this leaves behind:
- Parsing only interns names and records per-file nlists; it makes no
resolution decisions at all. Archive members are parsed like any
object but marked not-live.
- Resolution ranks competing claims per symbol: strong definition in a
live file > weak definition in a live file > definition in a lazy
archive member or a dylib export > common (tentative) definition,
with ties broken by input order. Commons additionally merge: largest
size and strictest alignment win regardless of order. A dylib listed
before an archive therefore beats the archive's member, and vice
versa - matching ld64's positional behavior without processing
archives positionally.
- A liveness walk then follows undefined references of live files to
their owning archive members, marking them live transitively (with
the entry point and -u symbols as extra roots); a second resolution
round restricted to live files settles final owners. This makes the
linker order-insensitive for archives: mutually recursive archives
need no -l repetition and no grouping.
Eager parsing has a subtle discipline: nothing a dead member says may
have side effects. Its undefined references must not count as symbol
uses (or they would demand definitions nothing live needs - the
compiler-rt archive alone references symbols its own dead members
define), its sections must not serve as dead-strip roots even when
flagged no-dead-strip, its __objc_imageinfo must not join the merge,
its auto-link requests must not load libraries, and its unwind records
and FDEs must be dropped with the record-to-FDE links remapped.
Non-external symbols never compete and are claimed directly by their
own object; they still need address assignments because relocations
reference local symbols by symbol index too.
LTO placeholders retire cleanly under this model: after codegen their
symbol tables are emptied and the compiled object joins a fresh
resolution round.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
An arm64 b/bl instruction has a 26-bit word displacement: +-128 MiB.
Once an output section's code exceeds that, some calls cannot encode
their target and must hop through a linker-synthesized "thunk" - an
adrp/add/br sequence with full addressing range - placed close enough
to the caller.
The layout algorithm follows mold's design (described in the mold
paper, arXiv:2608.23228): code is placed in batches, each batch
followed by a thunk holding one entry per branch target that might be
out of reach from that batch (forward references, stubs and other
sections pessimistically included; already-placed targets skipped when
still within reach). The crucial invariant is that the layout cursor
never runs more than one branch-reach (minus margin) ahead of the
batch being scanned, so a thunk is always reachable from every branch
it serves and placing one can never invalidate an earlier decision. A
single subsection too large for any batch gets its thunk placed in
front of it instead. At relocation-application time a branch uses its
direct target when in range and its recorded thunk entry otherwise,
so unused pessimistic entries cost only their 12 bytes.
x86-64 needs none of this: its call/jmp displacement is 32 bits.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
-add_ast_path records a path as an N_AST stab at the front of the
symbol table. Swift debugging depends on it: swiftc serializes each
module's typed AST into a .swiftmodule, and lldb finds those files
through the N_AST entries the way it finds Clang DWARF through N_OSO.
Without them, Swift expressions in the debugger lose type information.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
-bind_at_load sets MH_BINDATLOAD, asking dyld to resolve every binding
when the image loads instead of lazily. With the modern non-lazy GOT
and chained fixups everything is load-time bound anyway, so today the
flag is mostly a marker, but tools still expect it to round-trip.
-application_extension marks a dylib MH_APP_EXTENSION_SAFE, declaring
it uses only API available to app extensions (which run in a
restricted sandbox); ld64 warns when an extension-safe image links a
non-safe dylib.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
-dead_strip_dylibs removes LC_LOAD_DYLIB commands for libraries no
symbol actually binds to. By default ld64 keeps every library named on
the command line even when unused - loading a library can matter for
its side effects (initializers, Objective-C category registration) -
so dropping them is opt-in. Since bind records and undefined-symbol
n_desc bytes name dylibs by their 1-based load-command ordinal,
removing one renumbers all that follow, and every recorded ordinal
must be remapped.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
The escape hatch from two-level namespace strictness: with
-undefined dynamic_lookup, symbols still unresolved at the end of the
link become imports bound with the special ordinal
BIND_SPECIAL_DYLIB_FLAT_LOOKUP (-2) instead of errors. dyld then
searches every loaded image for the name at bind time, the old
flat-namespace behavior, one symbol at a time. This is how plugin
ecosystems link bundles against symbols only their host executable
provides (Python C extensions are the canonical example: they
reference CPython API symbols without linking against any Python
library).
The special ordinal shows up in three encodings: the high byte of an
undefined nlist's n_desc (as 0xFE), the classic bind stream's
SET_DYLIB_SPECIAL_IMM opcode (whose 4-bit immediate is the
sign-extended ordinal), and the chained-fixups import table's
lib_ordinal field.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
A batch of smaller ld64 options, each with a test, plus test ports
from the C++ mold's Mach-O suite (comdat, common alignment, weak
definitions, TLS statics, reproducibility, header padding):
- Numeric ld64 option arguments (-pagezero_size, -stack_size,
-headerpad) are hexadecimal whether or not they carry a 0x prefix.
-stack_size lands in LC_MAIN's stacksize field; -pagezero_size
resizes __PAGEZERO and thereby moves the image base - though on
arm64 the kernel insists on the full 4 GiB pagezero, so shrinking it
is an Intel-only affair.
- -sectcreate seg sect file injects a file's bytes as a synthesized
section, a common trick for embedding data.
- -x drops local symbols from the output symbol table; -w suppresses
warnings; -help prints usage.
- @file response files are expanded by the linker itself, but only
when the named file exists: arguments like
"-install_name @rpath/libfoo.dylib" also begin with '@' and must
pass through untouched.
The reproducibility test enforced a subtle invariant: two links of the
same inputs are byte-identical *only* with the same output path,
because the ad-hoc code signature embeds the output's basename as its
identifier - a fact worth knowing when comparing linker outputs.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Three options every CMake-generated link line carries:
-search_paths_first is the modern ld64 *default*: each -L directory is
tried for both a dylib and an archive before moving to the next
directory (the ancient behavior searched all directories for dylibs
first, then again for archives). Our search already works this way, so
the flag is accepted as a no-op.
-headerpad_max_install_names grows the padding after the load commands
to MAXPATHLEN so that install_name_tool can later rewrite install
names and rpaths in place without relinking - the reason the mach
header area is padded at all.
-O<n> is a linker optimization-level hint (branch island placement and
the like in ld64), forwarded by some build systems; accepted and
ignored.
With these, a full CMake+Ninja build of a large C++17 project (the old
C++ mold/sold tree with its vendored tbb, zstd, zlib and xxhash,
hundreds of translation units at -O3) configures, builds and links
into a working, correctly signed 37 MB executable using this linker.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
With -flto, "object files" are LLVM bitcode, and link-time
optimization is literally the linker's job: clang hands over the path
to Apple's libLTO.dylib as -lto_library, and the linker drives it
through the C API in llvm-c/lto.h (loaded with dlopen, so the linker
itself has no LLVM dependency).
The flow has three phases. First, each bitcode input becomes a
*placeholder* object: lto_module_create exposes the module's symbol
list (name plus attribute bits encoding definition kind -
regular/tentative/weak/undefined - and scope - internal/hidden/
default), and the placeholder claims defined symbols and records
undefined references so that normal resolution, archive member
selection (bitcode members in .a files included) and auto-linking all
work unchanged. Second, once every input is known, a codegen instance
compiles all modules into one Mach-O object; symbols the rest of the
link can observe - externals defined by bitcode, and the entry point -
are named with lto_codegen_add_must_preserve_symbol so the optimizer
cannot internalize or delete them. Third, the placeholders' symbol
claims are released and the compiled object joins the link as a
regular input; resolution then runs again, since freshly generated
code may pull in archive members (compiler-rt helpers, for example)
that nothing needed before.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Object files can carry LC_LINKER_OPTION load commands: a count
followed by NUL-terminated strings such as "-lswiftCore" or the pair
"-framework"/"Foundation". They are instructions to the *linker* to
add those libraries as if they had been on the command line. clang
emits them for #pragma comment(lib) and module autolinking; Swift
depends on the mechanism entirely - a swiftc link line names no Swift
runtime libraries at all, and every compiled Swift object instead
asks for libswiftCore and friends by auto-link options.
Processing is a fixed point interleaved with archive resolution: an
auto-linked archive can contribute members whose own LC_LINKER_OPTION
commands name further libraries. Each file is loaded once even when
named both explicitly and by auto-link, and dylibs already deduplicate
by install name on top of that.
With this, Swift programs link and run: their heavy use of chained
fixups targets, metadata sections, weak definitions and unwind info
all rides on the machinery already in place.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
LC_DYLD_CHAINED_FIXUPS is the modern replacement for the classic
rebase/bind opcode streams, default for deployment targets of macOS 13
and later (use -no_fixup_chains to opt out). The design moves the
fixup list out of __LINKEDIT and into the data itself:
The __LINKEDIT payload holds only a header, a per-segment page table,
an import table and a string pool. For each page of each segment it
records the offset of the *first* fixup in that page (or 0xFFFF for
none). Every fixup location is a 64-bit word in the mapped data whose
top bit says bind or rebase, and whose "next" field gives the
4-byte-stride distance to the following fixup in the same page - the
fixups form intrusive linked chains that dyld walks page by page. The
point is efficiency under page-in linking: the kernel can apply a
single page's fixups when the page faults in, with no opcode
interpretation and no need to touch other pages.
A rebase word encodes the target as a 36-bit offset plus the pointer's
top byte (kept for tagged/authenticated pointer schemes); that is why
targets beyond 64 GiB or with middle bits set can't use chained fixups
and need the classic format. A bind word holds a 24-bit ordinal into
the import table and an 8-bit inline addend; larger addends move the
addend into the import table itself, growing its entry format
(DYLD_CHAINED_IMPORT -> _ADDEND -> _ADDEND64). Import entries carry
the library ordinal, a weak-import bit and a name-pool offset; names
are deduplicated for symbols appearing with several addends.
With chained fixups the export trie moves to its own
LC_DYLD_EXPORTS_TRIE command and LC_DYLD_INFO disappears. The linker
writes the chains as a serial pass after the parallel copy: rebase
words re-encode the absolute address the copy phase already wrote,
bind words are synthesized from the import list, and both must happen
before the UUID hash and code signature, which cover the final bytes.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
The first step of making this linker mold-fast: the copy phase carves
the output buffer into disjoint per-chunk slices and writes them from
a rayon thread pool - every chunk (input-section copying plus
relocation, stubs, GOT, unwind info, __eh_frame, the dyld-info
streams) writes only within its own byte range, so no synchronization
is needed. The mach header, the symbol table (which also fills the
string table's range), the UUID and the code signature still run
serially, in that order: each covers or depends on bytes the previous
steps produced.
Code-signature page hashes are computed in parallel too; on large
outputs SHA-256 over every 4 KiB page is the single most expensive
part of the link, and each page's hash is independent.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
-map writes a report of the final layout in ld64's format: the object
files with their bracket indices, every emitted section's address and
size, and each defined symbol's address attributed back to the object
that supplied it. Build systems and size-tracking tools parse this
file, and it is the quickest way for a human to see where a byte in
the output came from.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Resolving archive members was quadratic: every round re-parsed every
member's symbol table and scanned it against the undefined set. Build
the name-to-member index once up front (first definer in command line
order wins, matching the previous semantics) and iterate the cheap
lookup to a fixed point instead.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
How debug info works on Apple platforms - very differently from ELF:
The linker never links DWARF. Objects keep their __DWARF sections
(marked S_ATTR_DEBUG, never copied to the output), and the executable
instead carries "stab" entries in its symbol table - nlist records
whose N_STAB bits are set - forming a per-object debug map:
N_SO ("-") start of an object's stabs; debuggers ignore
the source name here, so a placeholder works
N_OSO (path, mtime) absolute path to the .o file, or
"archive.a(member.o)"; n_desc carries the
object's mtime for staleness checks
N_FUN pairs a function's name and output address, then a
nameless entry holding its size
N_GSYM/N_STSYM a global/static variable's name and address
N_SO ("") end marker
lldb (and dsymutil, which produces .dSYM bundles the same way) reads
this map, opens the referenced object files, and translates their
DWARF - which still describes object-file addresses - to output
addresses using the FUN/GSYM entries as the address map. That is why
debugging a freshly linked binary needs the .o files to still exist,
and why the linker itself stays fast: no DWARF parsing or rewriting at
all, just one symtab entry per function.
Stabs sit at the front of the symbol table's local block, and -S
suppresses them. With this, source-level breakpoints, argument
inspection and line display work in lldb against our output.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
When -arch is absent, the driver reads the cputype field of the first
Mach-O input's header to pick the target, the same
speculate-and-restart scheme mold uses: the driver guesses a target,
and a mismatch discovered during parsing restarts the link with the
right instantiation.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
-current_version/-compatibility_version stamp LC_ID_DYLIB. dyld checks
a client's recorded compatibility version against the loaded dylib's
at run time, which is the whole versioning scheme of Mach-O dylibs:
"current" is advisory, "compatibility" is enforced.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
-exported_symbols_list/-exported_symbol switch the export set from
"every external defined symbol" to an explicit allowlist;
-unexported_symbols_list subtracts from whichever set is active. The
list file format is one symbol per line with '#' comments. These
control only the export trie - the symbols still appear in the
symtab, and real ld64 additionally treats allowlisted-out symbols
like private externs.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
-u <symbol> plants an undefined reference before any input is read.
Its purpose is archive control: an archive member is only linked when
it satisfies an undefined symbol, so -u forces specific members in
(historically how crt bits and pieces were pulled from libc).
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
__objc_imageinfo is required, not optional: the Objective-C runtime
treats an image as containing Objective-C *only if* this 8-byte
{version, flags} record exists - drop it and classes in
__objc_classlist silently go unregistered, crashing later with
"attempt to use unknown class". Each object contributes one record and
the linker must merge them into exactly one: the Swift version fields
must agree, the Swift language version takes the max, and the
"category class properties" bit survives only if every Objective-C
object set it (a category compiled by an old compiler could otherwise
clobber class properties at runtime).
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Symbol visibility on Mach-O: a "private external" symbol (N_PEXT with
N_EXT, from __attribute__((visibility("hidden"))) or -fvisibility=
hidden) participates in symbol resolution across objects exactly like
an external, but the linker then demotes it: it is excluded from the
export trie, excluded from dead-strip roots when building a dylib, and
lands in the *local* partition of the output symbol table with N_PEXT
still set and N_EXT cleared. Practically all C++ code depends on this,
since -fvisibility-inlines-hidden and hidden default visibility are
everywhere.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
-filelist reads input paths, one per line, from a file (with an
optional ",dir" prefix argument). Xcode drives ld64 almost exclusively
this way to sidestep command-line length limits.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Weak *dylibs* are the third meaning of "weak" (after weak defs and
weak imports): -weak_framework/-weak_library/-weak-l load the library
normally at link time, but record it with LC_LOAD_WEAK_DYLIB so dyld
tolerates the entire library being absent at run time; every symbol
bound to it then behaves like a weak import.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
LC_UUID is filled with a hash of the linker output rather than zeros:
every content change gets a distinct UUID, and rebuilding identical
inputs reproduces the same one (ld64 does the same by default;
debuggers and crash symbolication match binaries to dSYMs by UUID).
The hash is taken with the UUID field zeroed and the header is then
rewritten; the ad-hoc code signature is computed after that, since it
must cover the final bytes of every page it hashes.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Literal sections are the second kind of atom granularity in Mach-O.
Section types S_CSTRING_LITERALS and S_{4,8,16}BYTE_LITERALS declare
that a section is an array of elements - NUL-terminated strings or
fixed-size constants - with no interior structure the linker must
preserve. The linker splits them per element and merges elements with
identical bytes across all inputs, so equal string literals in
different translation units collapse into one copy with one address
(the C standard doesn't guarantee that "x" == "x" across TUs, but ld64
provides it, and code in the wild relies on it). Merged elements keep
the strictest alignment of any contributor. References into the middle
of an element (e.g. "str"+5 folded into a relocation addend) still
work because the reference resolves to the containing element plus an
offset, and the surviving copy has identical bytes. Combined with dead
stripping, unreferenced literals vanish - a mark reaching a merged
loser is forwarded to its surviving copy.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
LC_FUNCTION_STARTS points at a __LINKEDIT blob listing every function's
address as ULEB128 deltas, starting from the image base and ending with
a zero terminator. Nothing at runtime needs it; debuggers, samplers and
crash reporters use it to attribute PC values to functions even in
stripped binaries, which is why ld64 always emits it. With subsections,
"function" simply means "symbol defined in __TEXT,__text".
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Weak symbols come in two independent flavors that share a word:
a weak *definition* (N_WEAK_DEF, C++ inline/template instantiations)
may be coalesced with other images' definitions at load time - the
output advertises them with MH_WEAK_DEFINES and the export-trie
WEAK_DEFINITION flag; a weak *import* (N_WEAK_REF on an undefined,
from __attribute__((weak_import))) tolerates the symbol missing at
load time - the bind opcode carries BIND_SYMBOL_FLAGS_WEAK_IMPORT and
the undefined symtab entry keeps N_WEAK_REF, so dyld binds NULL
instead of aborting when the dylib lacks the symbol.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Archive semantics have three modifiers, all needed by real builds:
-all_load links every member of every archive; -force_load does the
same for one named archive; -ObjC links any member containing
Objective-C class or category metadata (__objc_classlist, __objc_catlist
and friends). -ObjC exists because Objective-C classes register
themselves with the runtime by mere presence - nothing references a
class that is only ever named in strings or messaged dynamically, so
the normal undefined-symbol test would never load its member.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
How x86-64 Mach-O differs from arm64, mostly in relocations:
Where arm64 needs ARM64_RELOC_ADDEND prefix records (its relocations
patch instruction immediates that can't hold a full addend), x86-64
embeds every addend directly in the relocated field - an i32 or i64
read back from the instruction stream. Pcrel fields are displacements
from the *end* of the 4-byte field, hence the ubiquitous "- 4" when
resolving and the "+ 4" when converting a pcrel field back to a target
address. The SIGNED_1/2/4 variants describe a displacement whose
instruction continues for K more bytes after the field (e.g. an
immediate operand follows), so the linker biases the addend by K while
locating the target and removes it again when writing.
The stub and GOT machinery maps almost one-to-one: a stub is a single
6-byte "jmp *slot(%rip)"; GOT_LOAD/GOT relocations become 32-bit
pcrel displacements to the GOT slot (the compiler's movq
foo@GOTPCREL(%rip) form); TLV resolves to the __thread_ptrs slot the
same way (mov _x@TLVP(%rip), %rdi; call *(%rdi)). Compact unwind's
"use DWARF" mode is a different constant (0x04000000 vs arm64's
0x03000000). Pages are 4 KiB rather than 16 KiB, which changes segment
alignment but not code-signature hashing (always 4 KiB). A code
signature is optional for x86-64 - the kernel only mandates it on
arm64 - but emitting one everywhere is harmless and simpler.
The target lives in its own crate under targets/, mirroring mold's
one-crate-per-target build layout, and the test runner now runs every
test for both architectures, running x86-64 binaries under Rosetta.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
MH_BUNDLE is the loadable-plugin flavor of Mach-O: like a dylib it has
no entry point and no pagezero, but it also has no LC_ID_DYLIB - a
bundle is only ever loaded explicitly by path via dlopen, never linked
against, so it has no identity to record. dlsym on it resolves through
the same export trie as everything else.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
LC_RPATH commands record run-path search entries; when a dependent
dylib's install name starts with @rpath/, dyld substitutes each LC_RPATH
entry (themselves subject to @loader_path/@executable_path expansion)
until the library is found. The install name baked into the *client* is
whatever LC_ID_DYLIB said at link time, which is why a dylib intended
for @rpath loading must be built with -install_name @rpath/libfoo.dylib.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
With atoms in place, -dead_strip is a straightforward mark & sweep:
- Roots: the entry point; every exported symbol when building a dylib;
symbols marked N_NO_DEAD_STRIP or REFERENCED_DYNAMICALLY; sections
whose type dyld or the runtime processes wholesale (initializer
pointers S_MOD_INIT_FUNC_POINTERS/S_INIT_FUNC_OFFSETS, TLV
descriptors) or flagged S_ATTR_NO_DEAD_STRIP (which clang sets on
the Objective-C metadata sections) or S_ATTR_LIVE_SUPPORT.
- Edges: relocations, and the unwind graph - a live function keeps its
LSDA (the __gcc_except_tab atom), its personality function, and for
DWARF records the FDE's LSDA and its CIE's personality. Unwind
records and FDEs for dead functions are dropped before the
__unwind_info and __eh_frame sections are built.
A pleasant side effect: duplicate weak definitions (C++ inline
functions instantiated in several objects) now vanish - the losing
copies are unreferenced atoms, since every reference resolves to the
winning symbol.
rustc always passes -dead_strip, and it is why: a Rust hello-world
program shrinks from 9.6 MB to 0.5 MB, because rlibs are linked as
whole archive members whose contents are mostly unreferenced.
Milestone note: with this, the linker self-hosts. mold-macho built
with ld64-mold as its linker produces a working, correctly signed
linker binary that passes the whole test suite - covering archives
(rlibs), dead stripping, TLS, unwind info and dyld fixups in one real
workload.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
The Mach-O model of linker granularity:
ELF compilers emit one section per function to enable --gc-sections;
Mach-O compilers instead emit a handful of big sections and set
MH_SUBSECTIONS_VIA_SYMBOLS in the object's header, promising that the
linker may cut every section at each symbol and treat the pieces
("subsections") as independent atoms. The promise implies that no code
references a point in another subsection without a relocation, so
atoms can be reordered, over-aligned or dropped freely; only
intra-subsection layout is fixed. Consequences:
- Symbols marked N_ALT_ENTRY are secondary entry points into the
preceding atom and must not start a new subsection.
- Literal sections (S_CSTRING_LITERALS, S_{4,8,16}BYTE_LITERALS,
S_LITERAL_POINTERS) are element-oriented, not symbol-oriented: they
are meant to be split per element and deduplicated; symbols don't
delimit them, so they stay whole until literal merging exists.
- Because non-extern relocations and symbol values address the
object's flat address space, everything that resolved "the section
containing address X" now resolves "the subsection containing X" -
relocation targets, symbol definitions, compact-unwind function and
LSDA references, and FDE targets alike. Relocation records also get
distributed to the subsection containing their r_address, with
offsets rebased.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Rust programs (and complex C++ functions) exposed the part of macOS
unwinding that compact unwind cannot express. What I learned:
__eh_frame in an object cannot be copied through like other sections.
Its records contain absolute-pointer fields set by SUBTRACTOR/UNSIGNED
relocation pairs and a personality reference; treating those UNSIGNED
relocations like data-pointer relocations produces rebase records
inside __TEXT, which dyld then tries to write into a read-only segment
and crashes at load ("rebase is in non-writable segment"). The linker
must instead parse and re-synthesize the section:
- SUBTRACTOR/UNSIGNED pairs are pre-applied at parse time (adding
target2 - target1 of the raw input-address symbol values to the
field), turning every pointer field into a self-relative value that
survives concatenation and needs no dynamic fixup.
- The section splits into CIEs (record ID 0) and FDEs (ID = distance
back to their CIE). A CIE's augmentation string declares the
encodings; Apple's toolchain uses pcrel|indirect|sdata4 (0x9b) for
personality - "indirect" meaning the 4-byte cell holds a pcrel
offset to a *pointer to* the personality, i.e. its GOT slot, which
the linker rewrites and which gives dyld a bindable location without
touching __TEXT.
- An FDE for a function that already has a compact unwind record is
dropped: compact unwind wins, and most C/C++ objects carry both, so
typical binaries end up with no __eh_frame at all. Each *surviving*
FDE gets a synthesized compact-unwind entry whose encoding is
UNWIND_MODE_DWARF | (FDE's offset within __eh_frame) in the low 24
bits - that is how libunwind gets from __unwind_info to the DWARF
record. This forces __eh_frame's internal layout to be fixed before
__unwind_info is encoded (both are position-independent, so neither
depends on final addresses for its *layout*).
- When writing, the linker re-targets each FDE's CIE-pointer, its
self-relative pc_begin, and its LSDA pointer at their output
locations; CIE bytes are copied verbatim except the personality
cell.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Several distinct libraries (-lc, -lm) are stubs whose install name is
the same /usr/lib/libSystem.B.dylib, and dyld refuses an image listing
one install name in two load commands ("duplicate linked dylib"), so
dylibs must be deduplicated by install name with their export sets
merged. rustc's default link line (-lSystem -lc -lm) hits this
immediately.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Objective-C mostly needs no linker smarts: classes, categories,
selector tables and @"strings" are ordinary __DATA/__TEXT sections
whose UNSIGNED relocations become rebases (local) and binds
(imported, e.g. a superclass _OBJC_CLASS_$_NSObject or the constant
string class ___CFConstantStringClassReference).
The exception is message sends. Since Xcode 14, clang emits calls to
undefined symbols named _objc_msgSend$<selector> and expects the
*linker* to synthesize them. Each synthesized stub entry (32 bytes on
arm64) loads x1 (the selector argument) from a slot in
__DATA,__objc_selrefs and tail-calls _objc_msgSend through the GOT;
the selref slot points to the selector's NUL-terminated name in
__TEXT,__objc_methname (type S_CSTRING_LITERALS) and needs a rebase.
The Objective-C runtime uniquifies selectors by processing every
S_LITERAL_POINTERS __objc_selrefs section at load time, which is why
pointing at our own copy of the string is sound even when the same
selector appears in other images.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Umbrella frameworks resolve symbols transitively: Foundation.tbd
itself exports only Foundation's own symbols and lists
`reexported-libraries` - CoreFoundation (a framework path with
/Versions/A/) and /usr/lib/libobjc.A.dylib - in *separate* .tbd files,
unlike libSystem which inlines its reexports as extra YAML documents.
NSObject actually lives in libobjc, and CFString machinery in
CoreFoundation. Under the two-level namespace an import bound to the
umbrella's ordinal is found by dyld in its reexported dependencies, so
the linker attributes a reexported library's exports to the umbrella
and records only the umbrella in LC_LOAD_DYLIB. Reexport install
names map to files by appending or substituting ".tbd"
(libobjc.A.dylib -> libobjc.A.tbd; .../Versions/A/CoreFoundation ->
.../Versions/A/CoreFoundation.tbd), followed recursively since
reexports nest.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
-framework Foo searches <dir>/Foo.framework/Foo(.tbd) through -F paths
and then <syslibroot>/{System,}/Library/Frameworks. A framework's .tbd
works exactly like a library's; only the lookup differs.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
What changes when the output is a dylib (ld64 -dylib, clang -shared):
A dylib has no __PAGEZERO - it is loaded at an arbitrary address and
its own addresses start at 0, so the mach header sits at vmaddr 0 and
everything is expressed relative to it. There is no LC_MAIN and no
LC_LOAD_DYLINKER; instead LC_ID_DYLIB records the install name (the
path clients will embed; -install_name sets it, defaulting to the
output path) plus current/compatibility versions. The file type is
MH_DYLIB and MH_PIE is not set (it only means anything for
executables). The ad-hoc code signature drops CS_EXECSEG_MAIN_BINARY.
When *linking against* a dylib binary (rather than a .tbd stub), the
authoritative export list is its export trie, but the
defined-external range of its symbol table (dysymtab's
iextdefsym/nextdefsym partition) matches it for ordinary dylibs and
is simpler to read; the install name and versions come from its
LC_ID_DYLIB and are copied into the client's LC_LOAD_DYLIB.
Also defines ___dso_handle, a linker-synthesized, never-exported
symbol resolving to the image's mach header; C++ ABI code passes it to
__cxa_atexit so destructors are grouped per image.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
dyld resolves imports against an image's *export trie* (LC_DYLD_INFO
export_off, or LC_DYLD_EXPORTS_TRIE with chained fixups), not its
symbol table. The trie is a radix tree over symbol names: each node
has an optional ULEB-encoded terminal payload (flags such as
WEAK_DEFINITION, then the image-relative address) and edges labeled
with NUL-terminated string fragments, each pointing to a child node by
ULEB offset within the trie. Because offsets are variable-length and
nodes contain offsets, sizing is iterated to a fixed point.
Executables get a trie too: ld64 exports their defined externals by
default, and dlsym on the main image resolves through it.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
How TLS works in Mach-O: a thread-local variable's symbol (e.g. _x) does
not point at its storage. It points at a 24-byte "TLV descriptor" in
__DATA,__thread_vars: { void *(*thunk)(desc), long key, long offset }.
Code accesses a thread-local by materializing the descriptor's address,
loading the thunk from it and calling it with the descriptor as the
argument; the thunk returns the address of the variable in the current
thread. dyld binds every descriptor's thunk field to __tlv_bootstrap
(from libSystem/libdyld), which on first use allocates the thread's copy
of the TLS image and replaces itself with the fast path.
The initial values of thread-locals live in __thread_data (initialized)
and __thread_bss (zero-fill), collectively the "TLS initialization
image", which dyld copies for each thread. Consequences for the linker:
- The two sections must be contiguous in memory, since a descriptor's
`offset` field is relative to the start of the image (the address of
the first thread-local section). So __thread_data must be the last
file-backed section in __DATA and __thread_bss the first zero-fill
one.
- An UNSIGNED relocation whose target is in __thread_data/__thread_bss
(the descriptor's offset field is set by one, pointing at the
"_x$tlv$init" local symbol that marks the variable's initial value)
must be resolved to `target - tls_begin`, an image-relative offset,
not an address, and therefore must NOT get a rebase record.
- The TLVP_LOAD_PAGE21/PAGEOFF12 relocations do not resolve to the
descriptor itself: the instruction sequence is
`adrp x0, _x@TLVPPAGE; ldr x0, [x0, _x@TLVPPAGEOFF]; ldr x8, [x0];
blr x8` - a LOAD through a pointer slot. The linker synthesizes that
slot in a __DATA,__thread_ptrs section (type
S_THREAD_LOCAL_VARIABLE_POINTERS), one pointer per thread-local
symbol, holding the descriptor's address, with a rebase record since
it is an absolute pointer. Getting this wrong (resolving TLVP relocs
directly to the descriptor) makes the loaded "thunk" be instruction
bytes and crashes at a garbage PC.
- The mach header needs the MH_HAS_TLV_DESCRIPTORS flag, or dyld will
not process descriptors at all. dyld finds TLS metadata purely by
section *type* (S_THREAD_LOCAL_*), not by section name.
Also of note from this change: a section's absolute pointers need
rebase records because the executable is PIE; anything dyld must slide
or bind is expressed in the LC_DYLD_INFO opcode streams, keyed by
(segment index, offset within segment) in load-command order.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
How stack unwinding metadata works on macOS, and what the linker must
synthesize:
Apple platforms do not primarily use DWARF CFI at runtime. The
compiler describes each function's unwinding in one 32-byte
__LD,__compact_unwind entry: {function, code length, 32-bit encoding,
personality, LSDA}, where function/personality/LSDA are set by
UNSIGNED relocations. The *linker* is expected to consume these
(they are marked S_ATTR_DEBUG and never copied through) and emit a
compressed __TEXT,__unwind_info section; libunwind locates unwind data
exclusively through that section, so without it every C++ throw calls
std::terminate even though the code is otherwise correct.
The __unwind_info format: a header pointing at (a) a common-encodings
array, (b) a personalities array, (c) a first-level page table. Each
first-level entry is {first function offset, offset of a second-level
page, offset into a global LSDA table}; a terminator entry with
page_offset 0 bounds the last page, and a page's LSDA slice is
[its lsda_offset, next entry's lsda_offset). Second-level pages are
"compressed": 32-bit entries with the function's offset from the
page's first function in the low 24 bits and an index into a per-page
encoding table in the high 8 bits - hence a page can cover at most
2^24 bytes of code and 255 distinct encodings (we cap records per
page instead). All addresses in the section are image-relative
32-bit offsets.
Personalities: at most three per image, because the encoding word
reserves only 2 bits (UNWIND_PERSONALITY_MASK) for the 1-based index.
Each personality array entry is the image-relative address of the
personality function's *GOT slot*, not the function - the personality
(___gxx_personality_v0, from libc++abi via libc++'s inlined reexport
docs) is usually imported, so the reference must be bindable.
Adjacent records with identical encoding/personality and no LSDA are
merged (functions without unwind state share entries); records whose
encoding mode is "use DWARF" are dropped (we would synthesize our own
from __eh_frame; only `ld -r` output has them).
A subtlety worth recording: the section's *size* must be known while
laying out __TEXT (the section lives there, at the end), but its
contents reference __got addresses that are only final after
__DATA_CONST is laid out. Since the size depends only on record
count, page structure and LSDA count - all fixed once function
addresses are known - the encoder runs twice: once for size during
layout, once for bytes when writing.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Notes on static archives in the Apple toolchain:
Archives use the BSD ar variant: after the "!<arch>\n" magic, each
member has a 60-byte header; a name of "#1/<len>" means the real name
is the first <len> bytes of the member body (NUL-padded) and the
header's size field *includes* it. Members are 2-byte aligned. The
ranlib index appears as fake members named "__.SYMDEF",
"__.SYMDEF SORTED" or "__.SYMDEF_64" and is skipped (the linker can
index members itself). A universal build wraps one whole archive per
architecture in a fat container, so fat-slice extraction must happen
before archive parsing.
Member semantics: an archive member is linked in only if it defines a
symbol that is undefined at resolution time, iterated to a fixed point
because a loaded member can itself pull in others. This is mold's
archive model (order-independent, as if all members were lazy objects)
rather than ld64's strictly positional one. A member is *not* loaded
just to satisfy a tentative (common) definition.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Common symbols (C tentative definitions like `int x;` at file scope)
are still a thing on macOS: clang emits them as N_UNDF|N_EXT nlists
whose n_value is the *size* and whose n_desc bits 8-11 carry the
log2 alignment. Resolution rules: any real definition beats a
tentative one; among tentative definitions the largest size and
strictest alignment win; survivors are materialized by the linker as
zero-fill space in __DATA,__common.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
ASLR slides the whole image, so every absolute address the linker
writes into the file (a pointer in __data initialized to a local
symbol's address, a GOT slot holding a local symbol) must be declared
to dyld as a *rebase*: LC_DYLD_INFO's rebase_off points at an opcode
stream (SET_TYPE_POINTER, then per location
SET_SEGMENT_AND_OFFSET_ULEB + DO_REBASE) telling dyld which words to
add the slide to. A data word initialized with an *imported* symbol's
address is instead a bind record, same as a GOT slot, and the linker
leaves the word zero. A SUBTRACTOR/UNSIGNED relocation pair produces
a difference of two addresses - position-independent by construction -
so it gets neither.
Zero-fill sections (S_ZEROFILL) occupy address space at the *end* of
their segment with no file bytes (file offset 0 in the section
header), so a segment's vmsize can exceed its filesize; the linker
must sort them after all file-backed sections in the segment.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
How dynamic linking against dylibs works on macOS, as implemented in
this change:
macOS uses a two-level namespace: an undefined symbol in the output
records *which* dylib provides it, as a 1-based library ordinal (the
order of LC_LOAD_DYLIB commands). In the symbol table the ordinal
lives in the high byte of an undefined nlist's n_desc, and the symbol
appears only in the undef partition - a symbol resolved from a dylib
is "defined" for resolution purposes but must never be emitted as an
extdef.
The SDK's libSystem.tbd shows how TAPI stubs compose: one .tbd file
holds many YAML documents - the first is the library itself, the rest
are the libraries it reexports (libSystem reexports ~40
/usr/lib/system/*.dylib libraries; that is where _printf actually
lives, in libsystem_c). Since reexported symbols resolve through the
umbrella library, a linker can merge every document's exports and
attribute them all to the top-level install name. Exported Objective-C
classes are listed by class name and must be re-mangled to
_OBJC_CLASS_$_ / _OBJC_METACLASS_$_ symbol pairs.
A call to an imported function compiles to a plain BRANCH26; the
linker routes it through a synthesized stub. Modern ld64 (and this
linker) uses the non-lazy scheme: every imported-function stub is
three instructions - adrp/ldr/br through a __got slot - and dyld binds
all GOT slots at load time. (The older lazy scheme with
__stub_helper, __la_symbol_ptr and dyld_stub_binder is gone; chained
fixups replace even this scheme in newer ld64, but classic
LC_DYLD_INFO opcodes are still fully supported by dyld.)
Binding is expressed as an opcode stream (LC_DYLD_INFO bind_off):
SET_DYLIB_ORDINAL, SET_SYMBOL (the name inline, NUL-terminated),
SET_TYPE_POINTER, SET_SEGMENT_AND_OFFSET_ULEB - the location as a
load-command-order segment index plus a ULEB offset into that segment
- then DO_BIND. dyld interprets this little program to patch the GOT.
The __stubs and __got sections carry section *types* (S_SYMBOL_STUBS
with reserved2 = stub size, S_NON_LAZY_SYMBOL_POINTERS) and reserved1
indexes into the indirect symbol table in __LINKEDIT, which maps each
stub/pointer slot back to a symbol-table index. dyld doesn't need the
indirect table with dyld-info binding; tools (otool, strip) do.
__DATA_CONST, where __got lives, gets segment flag SG_READ_ONLY: dyld
maps it writable, applies binds and rebases, then makes it read-only.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Findings about the Mach-O format and what a minimal macOS executable
needs, learned while writing this first step:
Object files (MH_OBJECT) have a single nameless LC_SEGMENT_64 holding
all sections; the (segname, sectname) pair on each section decides
where it goes in the output. Section addresses in an object are offsets
in one flat address space covering the whole file; symbols' n_value and
non-extern relocation targets are addresses in that space, so "find the
section containing address X" is a core primitive. Relocation records
live per-section and have no addend field: UNSIGNED relocations keep
the addend in the relocated bytes, and other arm64 relocations get one
from a preceding ARM64_RELOC_ADDEND record whose 24-bit symbolnum field
is the (sign-extended) addend. r_extern selects whether r_symbolnum is
a symbol-table index or a 1-based section ordinal.
Sections with S_ATTR_DEBUG (including __LD,__compact_unwind, consumed
separately) never become part of the image. clang marks every object
MH_SUBSECTIONS_VIA_SYMBOLS: sections are meant to be split at symbol
boundaries for dead-stripping, which a linker may ignore for
correctness.
A minimal arm64 macOS executable needs, in order: LC_SEGMENT_64 for
__PAGEZERO (4 GiB of unmapped space catching NULL derefs; its vmsize
is why the image base is 0x100000000), __TEXT (which contains the mach
header itself plus load commands at file offset 0 - exec'd segments map
the file directly, so each segment's file offset and size are page
multiples; arm64 pages are 16 KiB), and __LINKEDIT (all metadata
tables; must be last in the file). Then LC_DYLD_INFO_ONLY (empty
tables are fine), LC_SYMTAB/LC_DYSYMTAB (nlist_64 entries grouped
local/extdef/undef, extdef and undef each sorted by name),
LC_LOAD_DYLINKER (/usr/lib/dyld), LC_UUID, LC_BUILD_VERSION (platform
1 = macOS, minos/sdk as xxxx.yy.zz packed into 32 bits), LC_MAIN
(entryoff is a file offset into __TEXT, not an address; dyld's libdyld
calls main and exits with its return value, which is why even a
do-nothing executable must link against libSystem), LC_LOAD_DYLIB for
libSystem, and LC_CODE_SIGNATURE.
SDKs ship no dylib binaries: -lSystem resolves to a .tbd "text-based
stub" YAML file declaring the install name and exported symbols; the
executable records only the install name (/usr/lib/libSystem.B.dylib),
and dyld finds the real library in the shared cache. clang also always
appends libclang_rt.osx.a, a *fat archive*: a big-endian fat header
wrapping one archive per architecture, so fat handling is needed on
day one even for `int main(){}`.
The code signature is the surprising part. On arm64 the kernel refuses
to exec any binary without one, so the linker must append an "ad-hoc"
signature: a SuperBlob containing a CodeDirectory (all fields
big-endian, unlike everything else in Mach-O) with flags
CS_ADHOC|CS_LINKER_SIGNED, a SHA-256 hash per 4 KiB page of the file
up to the signature itself, the code limit equal to the signature's
file offset, and the executable segment marked CS_EXECSEG_MAIN_BINARY.
No key or identity is involved - it is pure integrity data, but
mandatory. The signature must be the last bytes of the file. The
kernel caches signature validity per vnode, so a linker must never
overwrite an executable in place; unlink and create fresh.
Tests are shell scripts driving the real toolchain via
`cc --ld-path=<linker>`, mirroring mold's test setup; a Rust harness
under tests/ discovers and runs them for `cargo test`.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
Port mold dist.sh to build Rust release binaries in old distro
containers. Download a pinned official Rust toolchain as gzip, fetch
Cargo.lock dependencies before disabling the network, and normalize
package timestamps.
Allow install-mold.sh to consume an external Cargo target directory.
path_clean("../../a/b") returned "a/b": the ParentDir branch popped a
trailing ".." from the output instead of appending another one, so any
path with two or more leading ".." components collapsed into a wrong
path. C++ mold uses std::filesystem::path::lexically_normal, which
keeps leading ".." components intact.
Library search paths are cleaned with this function, so mold failed
with "library not found" whenever a Makefile referenced an in-tree
library as -L../../<dir>, which is a very common pattern. Found by the
Gentoo all-package campaign within its first hour: sys-fs/lvm2,
sys-fs/xfsprogs, sci-astronomy/montage, dev-libs/OpenNI2,
dev-erlang/fast_yaml, and app-backup/spideroak-bin all failed with
mold-rust and built with mold-c++.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_0118ERbwNepcG1Z9gh5cm6Ut
Benchmark record, 2026-09-01 (unrelated to this change; this tree is
what was measured, built as 6becb4da before this message was amended):
C++ reference: mold at e322a3cc, i.e. the translation base fbaaa75f
plus 15 fixes that do not touch hot paths.
Protocol: ~/bench/tab4-bench.sh as-is — run 1 warms the page cache,
median of runs 2-6 reported; all runs of one linker complete before
the other linker starts; sync + dirty-writeback wait between runs;
--no-fork; default thread count (min(ncores, 32) = 32 on this
128-core machine); frequency boost off, performance governor,
THP madvise, idle machine.
Workload C++ (s) Rust (s) Rust vs C++
blender-debug 0.94 0.82 -12.8%
blender-release 0.20 0.17 -15.0%
chrome-debug-arm64 1.88 1.97 +4.8%
chrome-debug-x86_64 1.75 1.80 +2.9%
chrome-release-arm64 0.77 0.79 +2.6%
chrome-release-x86_64 0.66 0.69 +4.5%
clang-debug 1.49 1.28 -14.1%
clang-release 0.11 0.10 -9.1%
clickhouse-debug 1.14 1.09 -4.4%
clickhouse-release 0.43 0.43 +0.0%
firefox-debug-arm64 1.07 0.98 -8.4%
firefox-debug 0.87 0.87 +0.0%
firefox-release 0.21 0.20 -4.8%
godot-debug 0.51 0.47 -7.8%
godot-release 0.08 0.08 +0.0%
libmergedlo.so-debug 0.46 0.47 +2.2%
libmergedlo.so-release 0.20 0.18 -10.0%
pytorch-debug 0.93 0.84 -9.7%
pytorch-release 0.15 0.14 -6.7%
tensorflow-debug 3.16 2.89 -8.5%
tensorflow-release 0.69 0.63 -8.7%
Geomean -5.1% (Rust faster); 13 faster, 3 even, 5 slower. No workload
regressed against the import-time table in the initial commit; the
open item remains chrome's +2-5%.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_0118ERbwNepcG1Z9gh5cm6Ut
Apply relocation deltas, adjust symbols and recompute executable output
section layouts in parallel, matching the three file and chunk parallel
loops in C++ mold.
Process object files in parallel as C++ mold does. Use the existing
disjoint-range output-buffer abstraction so each worker can reverse its
input sections in place.
Process object files in parallel as C++ mold does. Serialize symbol access
with the existing byte-sized symbol lock and rewrite each file's relocation
records in place.
Use stock zlib's low-level API to reproduce the C++ Z_BLOCK, deflatePending,
optional deflatePrime and Z_SYNC_FLUSH sequence. This avoids the old
libbacktrace boundary bug and preserves C++ mold's compressed bytes.
Install the Unix handlers with SA_SIGINFO and track the mapped output range
as it is created, extended or moved. Report a possible full disk only when
SIGBUS or SIGSEGV identifies an address in that range.
Port the C++ Counter facility and collect the same parser, section, symbol,
ICF, relocation, COMDAT, thunk and merged-section statistics. Keep counter
updates dormant unless --stats is requested.
get_api_version handed the plugin the buffer of a stack-local
std::string, which dies when the function returns. Make it static.
Co-Authored-By: Claude Fable 5 <[email protected]>
The thin archive reader skipped a __.SYMDEF symbol table without
advancing the read position, so it kept parsing the same member header
forever. Skip past the member contents like the other symbol table
branch does.
Co-Authored-By: Claude Fable 5 <[email protected]>
R_RISCV_TLSDESC, R_RISCV_GOT32_PCREL, R_SPARC_SIZE32 and R_SPARC_JMP_IREL
were printed as "unknown (n)" in diagnostics.
Co-Authored-By: Claude Fable 5 <[email protected]>
parse_defsym_value returned a default-constructed variant on a bad hex
string such as 0xz, which holds a null Symbol pointer. The null pointer
was then dereferenced when marking GC roots, crashing the linker instead
of printing a diagnostic.
Co-Authored-By: Claude Fable 5 <[email protected]>
reattach_section_pieces rewrote every symbol whose local definition is in
a mergeable section, including global symbols that lost resolution to a
definition in another file. The loser then redirected the symbol to its
own section piece. Since the pass runs on all files in parallel, it was
also a data race on the shared Symbol.
Co-Authored-By: Claude Fable 5 <[email protected]>
R_390_12, R_390_PC12DBL, R_390_PLT12DBL, R_390_GOT12 and R_390_GOTPLT12
OR'd their 12-bit value into the instruction through a little-endian
16-bit accessor. s390x is big-endian, so the value landed in the wrong
bytes, corrupting both the displacement and the base register field.
Co-Authored-By: Claude Fable 5 <[email protected]>
create_phdrs walks a filtered, address-sorted copy of ctx.chunks, but the
loop that merges adjacent note sections into one PT_NOTE examined
ctx.chunks with an index into that copy. The two vectors differ e.g. when
the ELF and program headers are not memory-mapped, in which case every
note section got its own PT_NOTE segment.
Co-Authored-By: Claude Fable 5 <[email protected]>
When a merged section is laid out in two regions, 32-bit-addressable
fragments followed by the rest, the loop that rebases per-shard offsets to
section-global ones started at shard 1. That is fine for the 32-bit region,
whose shard 0 needs no adjustment, but it left shard 0 of the 64-bit region
unadjusted, so its fragments overlapped the beginning of the section. Start
at shard 0; the 32-bit fragments of that shard just get zero added.
Co-Authored-By: Claude Fable 5 <[email protected]>
The alloc relocation path incremented the local pointer loc instead of the
byte it points to, so the relocated byte was left untouched. The result was
wrong whenever an 8-bit label difference spanned code whose size changed
during relaxation. The non-alloc path already did this correctly.
Co-Authored-By: Claude Fable 5 <[email protected]>
The non-alloc relocation paths of i386, ARM64, s390x, SPARC64 and PPC64V1
passed the relocation value and the relocation index to check_range in the
wrong order. The intended range check was thus never performed, and if the
relocation index happened to fall outside the value range, the error path
indexed the relocation table with the relocation value, reading out of
bounds.
Co-Authored-By: Claude Fable 5 <[email protected]>
Use the same KERN_PROC_PATHNAME sysctl as C++ mold when locating the
current executable on FreeBSD, where /proc may not be mounted. Run one
shell test at a time under sanitizers so large cases do not overlap,
while leaving two CPUs available to the linker under TSan.
Use Cargo per-target features in native and platform jobs so they do not
rebuild all 20 linker instantiations. Tests that require an i386 linker
already skip when that target is disabled. Keep test-all-targets using
defaults for complete QEMU coverage.
If an object file has SHN_LORESERVE (0xff00) or more sections, e_shnum
is zero and the actual number of sections is stored in the first section
header's sh_size field. is_gcc_lto_obj() used the raw e_shnum, so it
scanned no sections and misclassified such LTO objects as regular
object files.
Fixes https://github.com/rui314/mold/issues/1642
Co-Authored-By: Claude Fable 5 <[email protected]>
Give ThreadSanitizer extra time for the many-output-sections test.
Disable incremental artifacts and debug info in the disk-constrained FreeBSD VM.
Also suppress the non-x86 unused prefetch parameter warning.
Add the four platform jobs from C++ mold and make the Rust implementation
compile on each native platform.
Use the corresponding Windows virtual-memory, output-file, subprocess, LTO
and process-timer facilities. Keep the Unix paths for macOS and FreeBSD while
applying platform-specific system calls only where C++ mold does.
Tested with cargo build --release, cargo test and cargo clippy --release
--all-targets. The MinGW executable also links against dlfcn-win32.
The all-target CI job exhausted its runner disk because it retained the
output of every completed shell test. Remove each work directory after a
passing or skipped test while preserving logs and all failed-test files for
diagnosis.
The multi-architecture build generated incremental and debug artifacts on
an ephemeral runner, leaving too little room for the test outputs and
causing the one-hour timeout. Disable both for this job.
In Ubuntu 24.04 Podman, the clean build used 693 MB and the completed test
tree used 7.4 GB. All targets passed with pass=6782 skip=895 fail=0.
ARM BE8 instructions are little-endian, but data embedded in PLT and
thunk entries uses target byte order. Match C++ mold's ul32 instruction
templates and U32<E> literal stores.
CI-equivalent Ubuntu 24.04 Podman:
all targets: pass=6782 skip=895 fail=0
armeb: pass=368 skip=53 fail=0
All workspace crates are built before testing, but the target crates and CLI
binary contain no unit tests. Select the library and integration test targets
so Cargo does not exhaust the runner disk linking empty harnesses.
musl uses an unsigned type for statfs.f_type while libc exposes
TMPFS_MAGIC as a signed value. Convert the constant to the field's type so
the comparison builds with both musl and glibc.
Match C++ mold by attempting fallocate when fstatfs fails.
The job-level environment did not populate the container configuration, so
the Rust toolchain action still lost the system directories when it extended
GITHUB_PATH. Set PATH through container.env instead.
The Tumbleweed image does not declare PATH in its container
configuration. GitHub's implicit default disappears when rust-toolchain
adds Cargo's bin directory through GITHUB_PATH. Define the normal system
path explicitly for the distro matrix so Bash remains available.
Run install-test-deps.sh with /bin/sh so minimal container images can
invoke it. Expand the Debian cross-toolchain package list without Bash
brace expansion, install Bash on Tumbleweed, and remove the Alpine
bootstrap step from CI.
The Alpine base image does not include Bash, so install it before
running install-test-deps.sh. Keep the dependency script Bash-based so it
can use brace expansion.
Run the complete multi-architecture suite and native tests across the
same Linux distribution matrix as C++ mold. Install the extra toolchains
used by the upstream tests and fail CI if any expected target is
unavailable.
Build and test ASan and TSan configurations with nightly Rust. Use the
system allocator for sanitizer builds and instrument the two C adapters as
well. Limit TSan to two CPUs to avoid false reports from Rayon while still
exercising parallel execution.
Make install-test-deps.sh portable across the distribution containers.
Tested with cargo test: pass=5360 skip=636 fail=0. ASan passed 477
native tests with 9 skips, and TSan passed 454 native tests with 32 skips.
Match C++ mold's relaxation behavior by updating input relocation records
at the point where instructions are rewritten. Store decoded CREL records
in typed mutable side tables so emitted relocation sections observe the
same updates, and remove the relocation type reconstruction hooks.
Use I386, X86_64, M68k, and Sparc64 as representative layouts when
classifying GCC LTO objects, matching C++ mold. Use real target layouts for
the compile-time record assertions and remove Elf32Le, Elf64Le, Elf32Be, and
Elf64Be.
Also remove the obsolete Record trait. Direct file records use
FileRecord, and Record had no implementations or callers.
Checked with cargo check --workspace. The test suite was not rerun as
requested.
Rename ProgramHeaderRecord and CompressionHeaderRecord to the shorter
PhdrRecord and ChdrRecord names. Expand the four plain layout definitions
directly and remove the plain_layout macro.
Infer the ELF class from the layout word size and whether relocations
carry addends from RelRecord. This removes the duplicated IS_64 and IS_RELA
declarations from every target.
Tests: cargo test (pass=5360 skip=636 fail=0)
Lint: cargo clippy --release --all-targets
Keep input section headers and symbols in their endian-aware on-file
representation instead of decoding them into target-independent mirrors.
Carry that representation through output chunks and expose fields through
record traits, so reads and writes remain target-endian.
Remove SymbolEntry, SectionHeader, SymTable, ShdrTable, and RecordLayout
now that no converted record mirrors remain. Move the SFrame constants to
elf/consts.rs.
On the 21-workload, 32-thread benchmark, the geometric-mean time changed
by -1.1% versus 801fc. Excluding tensorflow-debug's unusually large
improvement, it was effectively flat at -0.1%.
Tests: cargo test (pass=5360 skip=636 fail=0)
Lint: cargo clippy --release --all-targets
Represent uniform ELF records with public target-endian fields and select
class-dependent symbol, program-header, and compression-header layouts
through Layout. Convert differing layouts as complete records instead of
adding field-by-field forwarding accessors.
Remove the raw st_info API and the old offset-based record readers.
Tests: cargo test
Tests: cargo clippy --release --all-targets
Define byte-backed integer and relocation types that mirror the C++ ElfRel
specializations, including the SPARC64 type-data field.
Select each relocation record through Layout and view input and output
tables as ordinary typed slices. This removes the host-native conversion
wrappers and the synthetic r_info representation.
Preserve target byte order independently of the host.
Tests: cargo test
Tests: cargo clippy --release --all-targets
Define relocation constants and diagnostic-name functions for each
architecture from a single macro invocation. Keep each macro input to the
relocation name and value, and use stringify! to produce the diagnostic
spelling.
Remove the separate relnames module so relocation names and values can no
longer drift apart.
Validation passes with cargo build --release and cargo clippy --release
--all-targets. Cargo test reports 5360 passed, 636 skipped, and no failures.
Replace the callback-only relocation traversal API with streaming Rust
iterators. Make CrelReader and RelocationIter implement Iterator while
preserving deferred CREL decoding without allocation.
Use ordinary for loops in the architecture backends and gdb-index reader.
Keep a specialized fold implementation so Iterator::for_each can select the
ordinary or CREL representation once.
The full test suite passes with 5360 cases passed, 636 skipped, and no
failures. Clippy is clean.
Five-run firefox-debug medians improved from 0.798 s to 0.765 s at 32
threads and from 11.135 s to 11.002 s with one thread. The C++ reference
at fbaaa75f measured 0.792 s and 11.999 s, respectively.
Configure Rayon's process-global pool after target detection and process
forking. Register the main thread as worker zero so --threads=N limits the
linker to N active threads.
Run the linker directly instead of wrapping its entire body in
ThreadPool::install. This follows mold's command-oriented execution model
and its process-wide TBB parallelism limit.
Rename Rust modules to match their C++ source counterparts where
practical. Use archive_file, cmdline, error, output_chunks,
shrink_sections, and perf while keeping mapped_file, output_file, lto,
and subprocess unchanged.
Remove filename-only comments added during the port. They were not copied
documentation and became redundant once the filenames were aligned.
Update all module paths and target entry points for the renamed modules.
Carry over mold's distribution-specific dependency installer as
install-test-deps.sh. Drop CMake packages that the Rust build no longer uses.
Keep test setup out of the user build instructions. Explain the stable Rust
and native C compiler requirements directly in README.
Copy the current README, manual, logos, and license from mold. Adapt the
build and installation instructions for the Rust implementation.
Add install-mold.sh to install the binary, preload library, manual, license,
and linker symlinks under a configurable PREFIX with DESTDIR support.
Move the two remaining C source files from csrc/ to c/ and update the
build script and LTO documentation to use the new paths. The files are
otherwise byte-identical.
Tests: cargo build --release
Tests: cargo test (pass=5360 skip=636 fail=0)
Tests: cargo clippy --release --all-targets
Tests: cargo fmt --all -- --check
Replace the local allocator wrapper and vendored mimalloc sources with the
rui314/mimalloc_rust repository pinned at:
cc58b72775b8b5bc89ee37752af61fcb4f7c2909
The dependency contains mimalloc 3.5.0 and its overlapping upstream files
are byte-identical to the deleted copy. Update the workspace, allocator
import, lockfile, and README.
Tests: cargo build --release
Tests: cargo test (pass=5360 skip=636 fail=0)
Tests: cargo clippy --release --all-targets
Tests: cargo fmt --all -- --check
Register a custom integration-test target for mold-cli and move the Rust
runner into the mold-tests workspace crate. Keep all 537 shell-test files
byte-for-byte unchanged under tests/cases, run every available target and
QEMU job by default, and stage Cargo-built test artifacts in the active
profile directory.
Remove run-tests.sh and make every workspace crate a default member so
plain cargo test is the complete test entry point.
Use explicit wrapping arithmetic for unsigned C++ operations exposed by
the debug-profile test suite.
Tested with:
cargo test
cargo build --release
cargo clippy --release --all-targets
cargo fmt --all -- --check
Full shell-suite result: 5,996 scheduled, pass=5360, skip=636, fail=0.
Treat every C++ implementation comment at mold commit
fbaaa75f9f as a first-class porting
asset. Restore all 2,179 non-namespace comment occurrences at their
Rust counterparts, including file-level explanations, ABI notes,
examples, TODOs, original typos, inline instruction annotations, and
repeated comments. Add provenance where one Rust implementation
consolidates multiple C++ implementations. Closing namespace labels
are the sole comments without a Rust construct.
Verify that all 537 upstream test files remain byte-for-byte identical
to the pinned C++ tree.
The audit also exposed a few implementation-fidelity gaps: reopen
input mappings before writing a repro archive, reset discarded-COMDAT
symbol indices after owner selection, mirror C++ GOT
dynamic-relocation accounting branches, and reserve version-section
output storage as C++ does. These are faithful translations, not new
algorithms.
Validated with cargo build --release, ./run-tests.sh --all (pass=5360
skip=636 fail=0), cargo clippy --release --all-targets, cargo fmt
--check, and git diff --check.
Mirror C++ ConcurrentMap sorted-entry handling: reserve each shard vector, use unstable sorting, retain direct stable entry references, and test occupancy through the atomic key pointer alone.
Store direct stable NameEntry references in indexed-name records so the table writer does not translate a bucket ID for every public-name occurrence.
Reference C++ mold: fbaaa75f9f
Validation:
- cargo build --release
- ./run-tests.sh --all: pass=5360 skip=636 fail=0
- cargo clippy --release --all-targets
32-thread benchmark, median of five, with sync before and sync plus sleep 1 after every linker invocation:
- chrome-debug-arm64: C++ 1.736 s, Rust 1.834 s (+5.65%)
- chrome-debug-arm64 --no-gdb-index: C++ 1.576 s, Rust 1.592 s (+1.02%)
- chrome-release-arm64: C++ 0.715 s, Rust 0.738 s (+3.22%)
The remaining Chrome debug difference is isolated to TBB low-priority arena behavior; Rayon has no corresponding task-priority feature.
mold demangles with the platform's __cxa_demangle, and FreeBSD's libcxxrt
spells `operator delete[]` as `operator delete []`, so the quoted name in
the test never matched there. Test literal matching of brackets with
`S::operator[](int)` instead, which every demangler spells the same way.
Co-Authored-By: Claude Fable 5 <[email protected]>
So far -z rewrite-endbr was implemented only for x86-64, where it
rewrites unnecessary ENDBR64 landing pads (Intel CET) with NOPs for
functions whose addresses are not taken. arm64 has an equivalent
control-flow-integrity feature, BTI, whose landing pad is `bti c`.
This commit generalizes rewrite_endbr into a template shared by both
targets: the landing pad and NOP byte patterns are selected per-arch
(ENDBR64/NOP on x86-64, `bti c`/`nop` on arm64). The whole-program
analysis that preserves landing pads for address-taken functions is
unchanged.
Unlike x86-64, arm64 needs range extension thunks, and a thunk reaches
its target with an indirect branch (`br x16`). A function that is only
called directly can therefore still be entered indirectly once its call
is routed through a thunk, so we must keep its landing pad. We preserve
the landing pad of every thunked symbol; the thunk symbol set has
already been narrowed to the ones that actually need a thunk by
remove_redundant_thunks().
Note that on arm64 with recent glibc, `main`'s landing pad is
legitimately removed: the CRT passes a local __wrap_main stub (whose
landing pad is kept) to __libc_start_main and reaches main through a
direct branch, which does not require a landing pad.
Also note that code built with -mbranch-protection=standard (BTI +
pointer authentication) starts functions with `paciasp`, which doubles
as a landing pad, so there is no separate `bti c` to rewrite and this
option has no effect on such code.
This was locally tested on an Apple M2 machine running Asahi Linux.
Fixes#1552.
The relocation has a 16-bit displacement from the TOC pointer, but the
ELFv2 backend applied it without a range check and silently truncated
displacements to .toc entries beyond 32 KiB, producing a load from the
wrong address. The ELFv1 backend already checked it; do the same here.
Co-Authored-By: Claude Fable 5 <[email protected]>
FDEs refer to their functions with 32-bit PC-relative relocations, and
.eh_frame_hdr stores 32-bit offsets, but mold applied them without any
range check. A function more than 2 GiB away from .eh_frame, which
happens with a huge text section or with -Ttext or --section-start
placing code far from the default layout, silently got a wrapped-around
FDE that the unwinder cannot use, while GNU ld and lld reject such
links. Those relocations are now range-checked like all others, and so
are the .eh_frame_hdr entries.
The aarch64 long thunk test placed code with unwind tables 4 GiB away
from .eh_frame and relied on the bogus FDEs going unnoticed. It now
compiles the far functions without unwind tables and keeps the far
section fixed while swapping the functions between the sections, so
calls in both directions are still covered.
Co-Authored-By: Claude Fable 5 <[email protected]>
GNU ld takes a quoted name in a version script or a dynamic list as a
literal string, while an unquoted one is a glob pattern. mold dropped
the quotes and treated everything as a pattern, so `"operator
delete[](void*)"` in an `extern "C++"` block never matched anything
because `[]` parsed as an empty character class. Glob metacharacters in
a quoted name are now escaped before the name is handed to the matcher.
Co-Authored-By: Claude Fable 5 <[email protected]>
The tokenizer keeps colons within a token for the C++ scope operator,
so `local:*;` lexed as a single token that the parser then took for a
global symbol pattern named `local:*`, and nothing was localized. The
label is now split off such a token.
Co-Authored-By: Claude Fable 5 <[email protected]>
mold took the instruction set of a branch target from the LSB of the
symbol value alone, but only STT_FUNC symbols encode the instruction
set that way. A Thumb BL to a plain label in hand-written assembly,
which is NOTYPE and has an even address, was rewritten to BLX and
crashed with SIGILL, and a Thumb B.W to it went through a mode-switching
thunk. Like GNU ld, we now switch the instruction set only for function
symbols and for PLT entries, which are always ARM code.
Co-Authored-By: Claude Fable 5 <[email protected]>
With -Os, GCC calls linker-synthesized routines not only to save and
restore general-purpose registers but also floating-point and vector
registers. mold only provided the former, so such links failed with
undefined symbol errors. The new routines use the same layout and
encodings as GNU ld; the section that holds them is renamed to
.save_restore_regs accordingly.
Co-Authored-By: Claude Fable 5 <[email protected]>
With -r, common symbols are not converted to .bss, but to_output_esym()
had no case for them and wrote them out as absolute symbols whose value
was their alignment, so linking the -r output again resolved them to a
bogus fixed address. They are now emitted as SHN_COMMON symbols with the
alignment in st_value, as in the input files.
Co-Authored-By: Claude Fable 5 <[email protected]>
The addend of R_RISCV_ALIGN is the number of NOP bytes the assembler
emitted, which is the requested alignment minus the minimum instruction
size, so the alignment is the smallest power of two greater than the
addend rather than the addend rounded up to a power of two. `.balign 4`
in RVC code (addend 2) and `.balign 8` in non-RVC code (addend 4) were
therefore treated as 2- and 4-byte alignments; in a static link that
left glibc's __sigsetjmp at a 2 mod 4 address.
Co-Authored-By: Claude Fable 5 <[email protected]>
An unresolved weak function resolves to address 0. In a huge executable,
a call site can be more than 2 GiB away from it, so a call relocation
such as R_X86_64_PLT32 cannot encode the displacement and mold reported
an out-of-range error. Such a call is never executed because the program
has to null-check the function first, so we now skip the range check for
call relocations against such symbols and write the truncated value.
This covers x86-64, RISC-V, s390x, SPARC64 and LoongArch; ARM already
rewrites such calls to NOPs, and PowerPC calls go through thunks. Data
references keep their range checks because their results feed the null
check.
This fixes linking Godot's sanitizer build, whose .text ends 70 MB past
the 2 GiB mark.
Fixes https://github.com/rui314/mold/issues/1640
Co-Authored-By: Claude Fable 5 <[email protected]>
Without .sdata, we defined RISC-V's __global_pointer$ as absolute
address 0. Since crt1.o initializes gp with a PC-relative
`lla gp, __global_pointer$`, such an executable could not be linked
more than 2 GiB away from address 0, for example with -Ttext-segment.
We now define the symbol at the beginning of the image, like GNU ld
which defines it relative to .data or .bss in that case. The exact
value doesn't matter because we don't relax gp-relative accesses.
Co-Authored-By: Claude Fable 5 <[email protected]>
reattach_section_pieces() traversed every relocation of every alloc
section once just to count the references to mergeable sections, so
that it could allocate their symbols at once, and then traversed them
again to create the symbols. The count needs only ELF data, so it is
now taken while the sections are parsed, where a CREL relocation table
has just been decoded and is still in cache.
Co-Authored-By: Claude Fable 5 <[email protected]>
The tokenizer tested each byte against six whitespace characters and
three quoting characters in turn. A table makes the scan a single
load and compare per byte, and halves the time to tokenize Chromium's
1.9 MiB response file.
Co-Authored-By: Claude Fable 5 <[email protected]>
The loop looked up every undefined symbol in the --wrap set even when
no --wrap option was given, hashing 5.1 million long C++ names on a
Chromium link, and fetched the thread's ShardedMap bin through TBB's
thread-local lookup once per symbol rather than once per file.
Co-Authored-By: Claude Fable 5 <[email protected]>
create_output_sections() copied each output section's members from its
per-file vectors into the final array on one thread and then freed the
vectors one by one, which took about 10 ms on a Chromium link. Copy
and free per file in parallel instead, computing the alignment in the
same pass.
Co-Authored-By: Claude Fable 5 <[email protected]>
The pass took each symbol's lock once per undefined reference, 5.1
million times on a Chromium link, although nearly every reference is
to a defined symbol, which the pass leaves alone. Popular symbols are
referenced from thousands of files, so their locks bounced between
cores. Find the references to symbols without a definition first,
which needs no lock because the pass does not change defined symbols,
and take the locks only for those.
Chromium release on a Threadripper 7980X at 32 threads: the pass goes
from 21 ms to 2 ms. Output files are bit-identical.
Co-Authored-By: Claude Fable 5 <[email protected]>
When a symbol with hidden visibility resolved to a DSO, symbol
resolution started over from scratch for all symbols with that DSO
definition excluded, and Chromium has three such symbols. Resolution is
independent per symbol, so only those symbols are resolved again, and
if one of them moves to an archive member that is not reachable yet,
liveness is propagated from that member alone. The final resolution
pass after section parsing recomputes everything else as before.
Chromium release on a Threadripper 7980X at 32 threads: resolve_symbols
goes from 143 ms to 115 ms. Output files are bit-identical.
Co-Authored-By: Claude Fable 5 <[email protected]>
Sorting the 44,683 files of a Chromium link into command line order
took 7 ms on one thread at the end of the input reading phase.
Co-Authored-By: Claude Fable 5 <[email protected]>
scan_abs_relocations() gathered each member's absolute relocations into
a vector of its own and then flattened the vectors on one thread. For
.data.rel.ro of a Chromium link that was 147,000 vectors holding 1.1
million entries, and the flattening and freeing took about 10 ms of
serial time. Count the entries per member first and write them to
their final positions in parallel instead.
Co-Authored-By: Claude Fable 5 <[email protected]>
The job that read a thin archive opened all of its members before
feeding them to the reader loop, so a large archive's members were
opened one after another on one thread. Chromium's libblink_core.a
has 2,424. The archive job now only lists the member paths, and each
member's job opens its file, in parallel with the others.
Co-Authored-By: Claude Fable 5 <[email protected]>
Each MergeableSection added itself to its MergedSection's member list
under a mutex from the parallel conversion loop. An output has only a
few merged sections, so on a Chromium link, 32 threads took the same
mutex 150,000 times and spent most of the pass in futex calls. Collect
the members from the files after the loop instead.
Chromium release on a Threadripper 7980X at 32 threads:
create_merged_sections goes from 55 ms to 34 ms and its system time
from 0.74 s to 0.07 s. Output files are bit-identical.
Co-Authored-By: Claude Fable 5 <[email protected]>
mmap(2) takes the process's address space lock, so with tens of
thousands of input files, the calls serialize at a few microseconds
each no matter how many threads make them. Linking Chromium maps 44,683
files, and that serialization alone bounds the input reading phase at
about 220 ms, more than a third of the link. Files up to 32 KiB, which
are 64% of Chromium's inputs by count but 16% by size, are now read
into malloc'ed buffers instead; read(2) takes no such lock, and larger
files are still mmap'ed so that their pages are shared with the page
cache rather than copied.
Chromium release on a Threadripper 7980X at 32 threads: the input
reading phase goes from 228 ms to 108 ms and the whole link from 0.69 s
to 0.56 s, with peak RSS unchanged. Output files are bit-identical.
Co-Authored-By: Claude Fable 5 <[email protected]>
The tokenizer pushed every byte through an ostringstream and copied
each token twice. Tokens without quotes or backslashes, which is nearly
all of them, are now substrings of the mapped file, and whitespace is
tested inline instead of through isspace().
Expanding Chromium's 1.9 MiB response file of 19,000 arguments took
33 ms of single-threaded startup on a Threadripper 7980X; it now takes
5 ms. Output files are bit-identical.
Co-Authored-By: Claude Fable 5 <[email protected]>
scan_abs_relocations() classified an output section's absolute
relocations and copy_buf() applied them in serial loops, although
write_dynrels() already processes them in parallel shards. A PIE
Chromium has 1.1 million of them in .data.rel.ro alone, so each loop
ran for about 30 ms on one thread while the others waited.
Chromium release on a Threadripper 7980X at 32 threads: scan_relocations
goes from 84 ms to 50 ms, copy from 89 ms to 75 ms, and the whole link
from 0.90 s to 0.86 s. Output files are bit-identical.
Co-Authored-By: Claude Fable 5 <[email protected]>
Most relocations refer to a section that another thread has already
visited, yet visit() always did an atomic fetch_or, so popular sections
bounced between cores on every reference. Check the bit with a plain
load first, as Atomic::test_and_set() does; it is never cleared during
marking, so a stale positive answer is still correct.
Chromium release on a Threadripper 7980X at 32 threads: the mark phase
goes from 65 ms to 25 ms, the whole link from 0.96 s to 0.92 s. Output
files are bit-identical.
Co-Authored-By: Claude Fable 5 <[email protected]>
GDB 10 aborts on GCC's DWARF 5 type units even when the executable
contains no .gdb_index section, which broke CI on Debian 11. Probe GDB
with an index-less executable first and skip the test if it cannot
print a type from it.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01CKdZWtcaghcU7m5zoZtMfN
Emit version 9 indexes when type units are present so GDB 12 falls
back instead of interpreting DWARF 5 type-unit offsets as
.debug_types. Keep version 7 for indexes without type units.
Allow the regression test to run with older readelf versions and skip
it under TSAN, where relocatable links hit known oneTBB races.
With -gdwarf-5 -fdebug-types-section, GCC moves each type into its own
type unit and emits it as a one-section COMDAT .debug_info contribution,
so an object file contains several .debug_info sections. We assumed one
.debug_info section per file containing nothing but compilation units:
type units were missing from the index, and a pubnames offset could be
interpreted relative to the wrong contribution.
Read every live .debug_info contribution instead, classifying units by
their header's unit_type. Type units are collected into the .gdb_index
type-unit table along with their signatures, and each pubnames set is
matched to its contribution through the relocation on the set header's
debug_info_offset field. COMDAT group selection has already discarded
duplicate type units by the time we read them, so exactly one copy of
each type unit is indexed.
Fixes https://github.com/rui314/mold/issues/1634
Co-Authored-By: Claude Fable 5 <[email protected]>
If an IR file claims a COMDAT group, the LTO result provides the group's
contents. However, post-LTO COMDAT group selection started from a clean
slate, so an archive member extracted only after LTO could win a claimed
group, resurrecting a copy that collides with the LTO result.
Remember the claim on the signature symbol, and let only LTO-generated
files win such a group after LTO. LLVM keeps claimed groups in its
output; GCC emits their contents without a group, so no file wins.
Fixes https://github.com/rui314/mold/issues/1637
Co-Authored-By: Claude Fable 5 <[email protected]>
shrink_section() fills r_deltas once, and the later passes only read it,
so it does not need to be a growable vector afterwards. Build the deltas
in a local vector and copy them into the arena. This shrinks InputSection
by eight bytes on RISC-V and LoongArch and makes the class trivially
destructible on every target, so InputSection objects can live in the
arena without an owner that runs their destructors.
Co-Authored-By: Claude Fable 5 <[email protected]>
Several InputSection booleans were stored separately even though one
atomic byte is sufficient. Pack liveness, GC visitation, address
significance, decompression, and ICF-removal state into flags, and add
relaxed fetch_or/fetch_and operations for updating them.
Also remove fde_end by marking the final FDE in each section range in
existing FdeRecord padding. This reduces InputSection<X86_64> from 56 to
48 bytes without increasing FdeRecord or Symbol.
The output offset, dense ICF index, and arena-relative ICF leader are
needed at disjoint points in the link. Keeping separate members wastes
space and requires a separate eligibility flag.
Store them in a union, using icf_idx == -1 to represent ineligibility.
Process the gathered ICF sections directly and retain icf_leader only for
eliminated sections after ICF, allowing leaders to restore their offsets.
Counting distinct ICF classes costs about as much as a digest
propagation round, but it was done after every round. Since refinement
only splits classes, convergence does not need to be checked that often.
Run three propagation rounds between class counts and move digest-buffer
swapping into propagate(). This reduces counting overhead while preserving
the final partition.
When an ODR violation leaves a relocation referring to a discarded COMDAT
section, ObjectFile::scan_relocations() reports an error, but
scan_relocations() continues with the absolute-relocation pass. That pass
assumes symbols are resolved and may dereference a null Symbol::file,
causing mold to crash instead of exiting with the diagnostic.
Add a checkpoint between the two relocation passes so mold exits before
inconsistent symbol state reaches the absolute-relocation scan.
Fixes https://github.com/rui314/mold/issues/1635
create_comdat_group_sections assumed that a relocation section in a
comdat group is always SHT_RELA or SHT_REL. If an input file uses the
CREL format, the relocation sections are SHT_CREL instead, so we took
the branch for ordinary member sections and dereferenced a null
pointer, as mold does not create an InputSection for a relocation
section. mold -r had been crashing on such files since CREL input
support was added in mold 2.38.0.
Recognize SHT_CREL the same way as SHT_RELA/SHT_REL so that the
member's relocation section is propagated to the output group.
Co-Authored-By: Claude Fable 5 <[email protected]>
Commit 35f4a334 changed Symbol::is_local() to check visibility and
version-script localization, but get_output_sym_idx() also relies on it
to decide whether a symbol's slot offset is relative to the local or the
global partition of .symtab. An input-local symbol has default
visibility and no version, so is_local() returned false for it, and a
relocation emitted by --emit-relocs against a named local symbol
referred to an arbitrary global symbol.
This went unnoticed on most targets because GAS usually redirects
relocations against local symbols to section symbols, which take a
different code path. On LoongArch and RISC-V, the assembler keeps them
as-is because linker relaxation may change section-relative offsets, so
relocations against symbols like .LC0 were visibly wrong.
Fixes https://github.com/rui314/mold/issues/1631
Compilers may emit a meaningless FDE covering a zero-length address
range, e.g. for a function fragment that hot/cold splitting has
entirely optimized away. Such an FDE begins at the same address as the
FDE of the next real function, giving the binary search table in
.eh_frame_hdr duplicate keys. If a search lands on the empty FDE
instead of the real one, the unwinder fails to find an existing
exception handler and aborts the process.
We now write a tombstone value to .eh_frame_hdr for such an FDE. The
tombstone compares greater than any real address, so a binary search
never returns it. The FDE itself is harmless because an empty address
range doesn't match any program counter.
AArch64 range extension thunks are small and unlikely to benefit
measurably from aligning every entry to a 16-byte boundary. Padding
every entry with brk instructions wastes meaningful space when an
output contains many thunks.
Keep each thunk group 8-byte aligned, but pack its entries tightly so
the ARM64 short and long forms occupy 12 and 24 bytes instead of 16
and 32 bytes.
A weak TLS variable is usually guarded by a separate weak non-TLS
symbol indicating whether the TLS definition is available. A typical
use looks like this:
extern char foo_used __attribute__((weak));
extern __thread int foo __attribute__((weak));
if (&foo_used)
foo = 1;
If foo remains undefined, its access is unreachable and there is no
meaningful value to apply to its TLS relocations. Leave those
relocations unapplied.
This prevents AArch64 from rejecting an otherwise unreachable TLS
access when an R_AARCH64_TLSLE_ADD_TPREL_HI12 relocation is out of range.
https://github.com/rui314/mold/issues/1626
SH4 raises a slot-illegal exception when a PC-relative load is placed
in a branch delay slot. Our PLT entries did exactly that:
jmp @r0
mov.l 2f, r1
QEMU now checks for such illegal delay-slot loads as of
https://github.com/qemu/qemu/commit/b754cb2dcde26a7bc8a9d17bb6900a0ac0dd38e2,
matching real hardware and the SH-4 ISA. Older QEMU versions accepted
this sequence because they did not enforce the restriction, so upgrading
QEMU caused dynamically linked mold output to trap when entering a PLT
slot.
Move the relocation-index load before the jump and put a nop in the
delay slot. Grow each PLT entry from 16 to 20 bytes to make room while
keeping the two literal values 4-byte aligned.
`exit` in an awk script that follows readelf in a pipeline closes the
pipe as soon as the first match is found. If readelf has not finished
writing its output by then, it is killed by SIGPIPE, which pipefail
turns into a test failure. The race is timing-dependent and was
observed roughly once per thousand runs on a loaded machine.
Co-Authored-By: Claude Fable 5 <[email protected]>
This rewrites lib/gentoo-test.sh to make a full-distro run practical
and its results easier to trust:
- Build each package with mold first, and run the GNU ld control build
only when the mold build fails. Results are classified into success/,
failure/ (builds with GNU ld but not with mold, i.e. a mold bug) and
broken/ (doesn't build with either linker, so not our fault).
- Run test suites only for the package under test by scoping
FEATURES=test through package.env. Applying it globally drags in the
test dependencies of the entire dependency graph and makes a large
part of the tree unresolvable.
- Let emerge apply the configuration changes it would otherwise just
suggest (--autounmask-continue). An ephemeral container has no
hand-maintained package.use, so without this, thousands of packages
are unbuildable out of the box.
- Bring the container image to a consistent ~amd64 state with a world
update at image build time. A stable stage3 base with testing
packages on top otherwise accumulates unsolvable dependency
conflicts, such as perl disagreeing with its installed virtuals.
- Share work between containers through host bind mounts: source
tarballs are downloaded once into a shared DISTDIR, and binary
package pools let each dependency be built once instead of once per
container that needs it. The mold and GNU ld runs use separate pools
so that a control build can never install mold-linked binaries.
- Serialize the container image build with flock. Parallel invocations
used to race to build the same image, one per job slot.
- Work around a deadlock in portage's binary package extraction with
lib/gentoo-gpkg-workaround.py: tar_stream_reader.close() waits for
the external decompressor to exit without draining its stdout, which
hangs when the undrained residue exceeds the pipe buffer.
- Refuse to run if dist/mold doesn't embed a git commit hash, since
test results are keyed by it.
Co-Authored-By: Claude Fable 5 <[email protected]>
MergeableSection::resolve_contents inserts every section fragment into
the parent's ConcurrentMap. The table is typically much larger than
the cache, so each insertion stalls on a cache miss for its first
probe. Since split_contents has already computed all fragment hashes,
prefetch the bucket eight insertions ahead.
On firefox-debug at 32 threads, this cuts the CPU time of
compute_section_sizes, which performs the insertions, by about 15%
(2.7 to 2.3 CPU-seconds) and the pass's wall time from 0.11 s to
0.10 s; the end-to-end change is within noise. All 5809 tests pass.
Co-Authored-By: Claude Fable 5 <[email protected]>
get_sorted_entries splits the bucket array into NUM_SHARDS pieces, and
MergedSection::compute_section_size assigns fragment offsets with one
task per shard, so the shard count caps that pass's parallelism at 16
threads even when more are available. Raise it to 64.
Probing is confined to a shard, and a probe that visits MAX_RETRY
occupied slots aborts, so MIN_NBUCKETS grows with the shard count to
keep at least MAX_RETRY buckets per shard in the smallest maps.
Since fragment offsets are assigned shard by shard, the layout of
merged sections changes slightly (inter-shard alignment padding).
Outputs remain bit-reproducible for a given linker version, which is
the guarantee mold makes.
On firefox-debug at 32 threads, compute_section_sizes goes from
0.13 s to 0.11 s; the end-to-end change is within noise. All 5809
tests pass.
Co-Authored-By: Claude Fable 5 <[email protected]>
RelDynSection::update_shdr runs several times per link because the
size of .relr.dyn feeds back into section layout: set_osec_offsets
calls it until the size converges, and compute_section_headers and
the final pre-copy refresh add more calls. Each call walked all
chunks serially and ended with a serial sort of every RELR offset.
On a Firefox debug link (~360k GOT slots, ~1M relative relocations),
this accounted for most of a ~0.2 s single-threaded stretch between
layout and output copy that is plainly visible in a CPU utilization
trace.
Three changes:
- GotSection::get_num_dynrels no longer materializes the full GOT
entry array just to count relocations. Computing each entry's
value involves a symbol address lookup, which dominated the cost.
The counting logic mirrors get_got_entries' relocation choices.
This is most of the win: the GOT was the longest pole on every
call even with the collection parallelized.
- Collect per-chunk dynamic relocation counts and RELR offsets in
parallel; chunks are independent of one another.
- Process chunks in address order when concatenating their RELR
offsets. Each chunk emits its offsets in ascending order and
chunks occupy disjoint address ranges, so the concatenation is
sorted by construction and the global sort is gone.
The output is byte-identical on firefox-debug against the unpatched
linker at the same commit. All 5809 tests pass. An interleaved A/B
on firefox-debug shows the 32-thread link going from 1.18 s to
about 1.05 s (medians of five).
Co-Authored-By: Claude Fable 5 <[email protected]>
RELR needs only the addresses to which relative relocations apply. Even after
the parent commit, mold creates complete REL/RELA records, partitions them in
RelDynSection, and then discards every field except r_offset.
Have each output chunk return a compact vector of its RELR offsets. Aggregate
and sort those vectors in RelDynSection, then encode them directly. Each chunk
also records how many of its dynamic relocations moved to RELR, so it can write
the remaining records straight to its final output slice.
Android+RELR keeps a temporary array for the remaining records because the
size of their APS2 encoding affects layout.
Seven interleaved 32-thread runs with --no-fork gave these median wall times:
Parent This commit
Firefox release 0.472518 s 0.396796 s (-16.03%)
Chromium debug 2.081396 s 2.044275 s ( -1.78%)
Compared with 64b92151, the accumulated improvements are 17.79% for Firefox
release and 1.68% for Chromium debug.
An earlier seven-run A/B test found no consistent cost from returning each
vector instead of appending through an output parameter:
Firefox release: append 0.396 s, return 0.390 s
Chromium debug: append 2.005 s, return 2.028 s
The differences are less than 2% and point in opposite directions.
All 471 native x86-64 tests pass, with nine expected skips. A release build for
all supported targets also passes.
RelDynSection currently asks each producer for a vector and appends it to a
second vector. OutputSection also builds one vector per shard and flattens
them. Large links therefore allocate, copy, and destroy relocation records
multiple times.
Have each producer report its exact relocation count on Chunk. RelDynSection
allocates one destination array and gives each producer a pointer to its exact
slice. OutputSection records per-shard prefix counts so shards can fill
disjoint slices in parallel.
Recompute counts on every layout pass because synthetic GOT relocation counts
can change before layout converges.
Seven interleaved 32-thread runs against 64b92151, with --no-fork, gave these
median wall times:
Firefox release: 0.482679 s -> 0.472518 s (-2.11%)
Chromium debug: 2.079169 s -> 2.081396 s (+0.11%)
All 471 native x86-64 tests pass, with nine expected skips. Release builds for
x86-64 and PPC64V1 also pass.
After fix_synthetic_symbols, we re-ran set_osec_offsets
unconditionally. That pass is needed only under
--pack-dyn-relocs=android, whose packed relocation format encodes
addends in variable-length SLEB128, so fixing synthetic-symbol
values can change the encoded size of .rela.dyn. No other mode
emits anything whose size depends on addends; in particular,
RELR's size depends only on relocation locations, which are fixed
by this point. Make the pass conditional on the Android mode.
On the Firefox release benchmark with 32 threads and --no-fork,
this shortens the link from 0.44 s to 0.41 s. Outputs are
byte-for-byte identical before and after for both a RELR workload
(Firefox) and a RELA workload (Chromium release).
Co-Authored-By: Claude Fable 5 <[email protected]>
The scalar glob matcher is faster for ordinary version scripts, but its
work grows linearly with the number of patterns. Compile sets of 64 or more
complex patterns into one bit-parallel NFA. Each state occupies one bit, so
a machine word advances 64 states at once. Keep the existing scalar and
Aho-Corasick paths unchanged for smaller and simpler pattern sets.
On 200,000 synthetic 60-byte symbol names, the standalone matcher changes
as follows:
128 patterns: 1.00 s -> 0.197 s (5.1x faster)
256 patterns: 1.98 s -> 0.380 s (5.2x faster)
A normal TensorFlow debug link remains on the scalar path. A sanity check
shows apply_version_script changing from 0.177 s to 0.175 s.
The general glob matcher represents all patterns as one NFA and updates
several bit vectors for every byte of every symbol. Its cost therefore
depends on the total pattern length even when a version script contains only
a few complex patterns.
Compile each complex pattern into literal, star, question, and bracket
tokens. Match without recursion, remember the most recent star, and use
string_view::find for literal runs following a star. This replaces MultiGlob
and removes the Bitvector class.
Also store non-root Aho-Corasick edges as sibling lists. Most trie nodes have
one child, so this reduces a node from 1,040 to 24 bytes on a 64-bit host. A
dense root table keeps the common transition fast. Construct failure links
breadth-first, as required by the algorithm.
With symbol-table traversal held constant, the median of five 32-thread
TensorFlow debug links changes as follows:
apply_version_script wall time: 0.503 s -> 0.179 s (-64%)
apply_version_script CPU time: 14.137 s -> 5.467 s (-61%)
total wall time: 3.626 s -> 3.285 s (-9%)
Version script wildcard matching currently visits each object file and scans
its global symbol slots. A symbol is processed only when that file provided
the prevailing definition, so many entries are skipped. More importantly, an
entire file is one task, which leaves workers idle when large object files
reach the end of the pass.
Iterate over the symbol table instead. Its 64 shards visit every symbol once
and distribute the work more evenly. Rename ShardedMap::for_each to
parallel_for_each to make the scheduling behavior explicit.
With the same glob matcher and 32 threads, the median of five TensorFlow
debug links changes as follows:
apply_version_script wall time: 0.699 s -> 0.503 s (-28%)
apply_version_script CPU time: 13.456 s -> 14.137 s (+5%)
total wall time: 3.800 s -> 3.626 s (-5%)
A defined dynamic symbol at VER_NDX_GLOBAL with the VERSYM_HIDDEN bit set
is a legacy compat alias. GNU as emits one for `.symver foo, foo@VER`
without the `remove` modifier, so that binaries linked before the symbol
was versioned keep resolving. It must not satisfy a new unversioned
reference to `foo`; that has to bind to the default version `foo@@VER`.
SharedFile::parse cleared VERSYM_HIDDEN before classifying the symbol, so
such an alias took the "unversioned symbol" path and claimed the plain
name. With both it and the `foo@@VER` definition claiming that name,
resolve_symbols keeps whichever comes first in .dynsym, which is the
alias. No verneed entry is then emitted, and glibc refuses to hand out a
non-base version for an unversioned lookup, so the alias wins at runtime
too and the caller silently gets the old implementation.
lksctp-tools' libsctp.so.1 exports sctp_connectx this way. Linking with
mold selects the pre-SCTP_SOCKOPT_CONNECTX3 ABI, which reports success
but never fills in the caller's association ID.
Skip such symbols while parsing. They cannot be referred to by any name,
as there is no version string to form `foo@VER` from. ld.bfd reports an
undefined reference when one is the only definition, which is what
skipping produces.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Each provisional thunk owns independent symbol and offset vectors. Filter
unneeded entries and recompute their offsets concurrently instead of
shrinking the thunks one at a time.
On the Chromium Arm64 debug link, seven interleaved A/B runs reduce the
remove_redundant_thunks median from 0.080 to 0.073 seconds and median
wall time from 1.924 to 1.897 seconds. The output is byte-identical.
Symbol order does not affect thunk placement, so defer deterministic sorting
until all thunks for an output section have been constructed. Sort independent
thunk vectors concurrently instead of sorting each one in the serial batch
loop.
On the Chromium Arm64 debug link, nine interleaved A/B runs reduce median wall
time from 1.98 to 1.97 seconds. The compute_section_sizes median falls from
0.116 to 0.095 seconds. The output is byte-identical.
Initialize input-section offsets in parallel. Section offsets and end offsets
are monotonic, so use lower_bound to find A and C instead of walking over
millions of sections a second and third time.
On the Chromium Arm64 debug link, five interleaved A/B runs reduce median wall
time from 2.01 to 1.95 seconds. create_range_extension_thunks falls from about
0.17 to 0.11 seconds. The output is byte-identical.
Collect newly discovered thunk targets in per-worker vectors and concatenate
them after each batch. The existing atomic flag still removes duplicates, and
the final sort preserves deterministic output without serializing every vector
insertion through one mutex.
On the Chromium Arm64 debug link, five interleaved A/B runs reduce median wall
time from 2.19 to 2.03 seconds. The compute_section_sizes phase falls from
0.341 to 0.181 seconds, and its system CPU time falls from 5.146 to 0.620
seconds. The output is byte-identical.
Allocate MergedSection objects in the linker arena and store each fragment's
parent section as a 32-bit arena index. This shrinks SectionFragment from 24
to 16 bytes and its ConcurrentMap bucket from 64 to 32 bytes.
Chromium's x86-64 debug link contains about 2.17 million merge-map buckets.
Across five interleaved 32-thread runs, median wall time changes from 1.91 to
1.90 seconds and median peak RSS falls from 24,870,408 to 24,808,228 KiB, a
reduction of 62,180 KiB.
Build the symbol table and constant pool in a low-priority task after
debug-info offsets are fixed. The final output path then reads relocated
address ranges and copies the prepared tables.
The stages have different dependencies and useful parallelism. Reading
input name data needs only input sections and can use all available
workers. Building the table needs final .debug_info offsets but stops
scaling near 12 workers, so run it in a separate bounded arena. Reuse one
task group for both stages.
Keep the standard serial open-addressed hash construction. This preserves
deterministic output without the prototype atomic claim table and repeated
parallel probe rounds.
Against an unrestricted table arena on the same tree, the bounded arena
reduced the 10-run median from 1.935 s to 1.905 s and user CPU from
43.510 s to 42.865 s.
Chromium debug link, 32 threads, 10 interleaved run medians:
wall: 1.975 s -> 1.900 s (-3.8%)
user: 43.125 s -> 42.600 s (-1.2%)
system: 9.740 s -> 9.770 s (+0.3%)
peak RSS: 24,817,662 -> 24,889,408 KiB (+70 MiB)
Use the ELF section header for the stored input size and keep only the
data pointer in InputSection. Decompressed sections use sh_size because
their replacement buffer has the uncompressed size.
This reduces InputSection<X86_64> from 88 to 80 bytes. Relaxed RISC-V
and LoongArch sections continue to use the original section-header size
when copying their input bytes.
Chromium debug link, 32 threads, 10 interleaved run medians:
wall: 1.960 s -> 1.950 s (-0.5%)
user: 42.565 s -> 42.560 s
system: 9.775 s -> 9.615 s (-1.6%)
peak RSS: 24,834,922 -> 24,816,480 KiB (-18 MiB)
Parse each reachable object and record its COMDAT candidates in one
parallel traversal. Reuse the signature name while parsing instead of
recovering it from the selected Symbol for the GCC debug-macro check.
Chromium debug link, 32 threads, 10 interleaved run medians:
wall: 1.965 s -> 1.960 s (-0.3%)
user: 43.160 s -> 42.615 s (-1.3%)
system: 9.820 s -> 9.845 s (+0.3%)
Reserve a sparse address-space arena for symbols and input files. Objects in
that arena can refer to each other with four-byte self-relative pointers,
which halves the size of symbol-reference arrays and reduces Symbol from 48
to 40 bytes. On 32-bit hosts, reserve 256 MiB instead of 8 GiB; this places a
practical limit on linker metadata without affecting 32-bit targets linked on
a 64-bit host.
Keep the standard per-shard hash tables, but allocate their Symbol values in
stable arena blocks. Store global names beside those values, derive local
names from their owner files, and represent each COMDAT group in eight bytes.
Context still destroys file objects normally so their separately allocated
data is released before the arena is unmapped.
On the Chromium x86-64 debug benchmark with 32 threads, median wall time over
eight runs falls from 1.990 to 1.970 seconds. Median user time changes from
42.880 to 43.015 seconds, while system time falls from 9.965 to 9.890 seconds.
Median peak RSS falls from 25,625,100 to 24,841,684 KiB, a reduction of
783,416 KiB.
The generated output was byte-identical.
Chromium has 41.96 million ELF section-table slots but only 5.86 million
regular input sections. Store a 32-bit value per slot instead of an
InputSection pointer. Zero denotes an absent section, the high bit denotes a
mergeable section, and the remaining bits index stable per-file pools.
InputSectionTable owns both pools and presents the same indexed and range
interfaces used by linker passes. Moving a section to the mergeable pool is
now explicit instead of relying on a constructor that clears a pointer passed
by reference.
Chromium debug link, 32 threads, eight interleaved run medians:
wall: 2.005 s -> 2.000 s (-0.2%)
user: 43.595 s -> 43.375 s (-0.5%)
system: 10.160 s -> 9.865 s (-2.9%)
peak RSS: 26,145,360 -> 25,598,862 KiB (-534 MiB)
All section contents matched apart from .comment and the fast build-ID note.
GDB index type vectors contain fixed-width 32-bit records. Sort lists with at
least 256 records using four byte-wise radix passes, while retaining comparison
sorting for smaller lists. Reuse worker-local scratch storage so the linear
path does not allocate for every name.
Chromium debug link, 32 threads, eight interleaved run medians:
wall: 1.965 s -> 1.955 s (-0.5%)
user: 44.720 s -> 43.890 s (-1.9%)
system: 9.940 s -> 10.185 s (+2.5%)
The complete 4.86 GiB output was byte-identical.
Chromium debug has 18.39 million local symbols, and 9.71 million belong to
discarded COMDAT sections. After COMDAT elimination, allocate Symbol objects
only for retained local symbols and map every discarded local symbol to one
global zero-valued placeholder. The ELF gABI requires local symbols in a
discarded COMDAT member to be discarded and forbids references to them from
outside the group, so the placeholder does not preserve per-symbol metadata.
Chromium debug link, 32 threads, six interleaved run medians:
wall: 2.095 s -> 2.060 s (-1.7%)
user: 48.095 s -> 46.735 s (-2.8%)
system: 10.110 s -> 10.280 s (+1.7%)
peak RSS: 26,946,944 -> 26,241,790 KiB (-0.67 GiB)
Chromium sections matched apart from version and fast build-ID metadata.
Chromium debug contains 93.8 million relocations in nonallocated sections.
Its 230 MiB CREL input expands to about 2.1 GiB of ElfRel records. Decode
and apply those relocations in one pass instead of retaining the expanded
arrays. Keep exact arrays for allocated sections, whose relocations are
consumed later.
for_each_reloc calls a target-specific callback for every relocation. GCC
15 does not inline these large callbacks on its own, leaving a function call
for each ordinary REL or RELA relocation. Mark the callbacks ALWAYS_INLINE;
this has no effect on CREL's representation but avoids that regression.
Chromium debug link, 32 threads, six-run medians:
wall: 2.215 s -> 2.115 s (-4.5%)
user: 50.145 s -> 48.415 s (-3.5%)
system: 11.065 s -> 10.215 s (-7.7%)
peak RSS: 27.96 GiB -> 25.64 GiB (-2.32 GiB)
On an Asahi machine, adding ALWAYS_INLINE to the streaming implementation
changes the native clang-debug-arm64 link at 16 threads as follows:
wall: 2.36 s -> 1.85 s (-21.6%)
user: 35.3 s -> 27.1 s (-23.2%)
Chromium output matched apart from the fast build-ID payload. The two Asahi
outputs were byte-identical.
Compilation-unit headers and GNU pubnames do not depend on relocated
debug contents. Read them from per-file input sections and build the name
map in a low-priority TBB arena while foreground passes continue. Address
ranges and final serialization still wait until debug relocations are applied.
Chromium debug link, 32 threads, six-run medians:
wall: 2.255 s -> 2.175 s (-3.5%)
user: 49.670 s -> 49.485 s (-0.4%)
system: 11.460 s -> 11.260 s (-1.7%)
peak RSS: 27.95 GiB -> 27.98 GiB (+36 MiB)
The generated .gdb_index was byte-identical.
Most regular COMDAT signatures already name a global symbol. Reuse that
Symbol as the group identity instead of interning 14.8 million signatures in
a second map. Local, section, and versioned signatures use the same symbol
table through a name-based fallback.
Symbol resolution has been cleared when COMDAT owners are selected, so
sym_idx is temporarily available. Record the lowest file priority there, mark
each group whose file has that priority as the owner, and restore sym_idx with
one symbol-map traversal before normal resolution resumes. This keeps
ownership on the signature itself and needs no dense group IDs or auxiliary
side arrays.
On the Chromium x86-64 debug benchmark with 32 threads, median wall time over
eight runs falls from 2.310 to 2.250 seconds. Median user time falls from
49.520 to 47.390 seconds, while system time changes from 11.720 to 11.935
seconds. Median peak RSS falls from 29,485,754 to 29,103,198 KiB, a reduction
of 382,556 KiB.
The generated output was byte-identical.
ELF symbols already contain their string-table offsets. Cache only a one-byte
NameLen instead of retaining a 16-byte string_view for each input symbol.
Longer lengths use logarithmic lower bounds, so lookups scan only the uncached
suffix. Chromium has 37.8 million input symbols, making the per-symbol
reduction substantial.
On the Chromium x86-64 debug benchmark with 32 threads, median wall time over
eight runs changes from 2.320 to 2.315 seconds. Median user and system time
fall from 49.800 and 11.625 seconds to 49.645 and 11.580 seconds. Median peak
RSS falls from 30,132,552 to 29,503,344 KiB, a reduction of 629,208 KiB.
The generated output was byte-identical.
Store short name lengths exactly in one byte and use the remaining values as
logarithmic lower bounds. Looking up a long name then scans only its suffix.
Copy a long version-prefix name into the string pool when its original storage
is not NUL-terminated. This reduces Symbol<X86_64> from 56 to 48 bytes.
On the Chromium x86-64 debug benchmark with 32 threads, median wall time over
eight runs falls from 2.355 to 2.340 seconds. Median user time falls from
50.475 to 49.875 seconds, while system time is unchanged at about 11.85
seconds. Median peak RSS falls from 30,330,582 to 30,146,730 KiB, a reduction
of 183,852 KiB.
The generated output was byte-identical.
An input section's object and section header already identify its name in
the section-header string table. Store only the 16-bit length in InputSection
and recover the pointer from that existing metadata. This reduces
InputSection<X86_64> from 104 to 88 bytes.
On the Chromium x86-64 debug benchmark with 32 threads, median wall time over
eight runs changes from 2.365 to 2.360 seconds. Median user and system time
fall from 50.940 and 12.005 seconds to 50.495 and 11.700 seconds. Median peak
RSS falls from 30,470,228 to 30,335,754 KiB, a reduction of 134,474 KiB.
The generated output was byte-identical.
A CREL header gives the relocation count before decoding starts. Add
ExactArray, which owns an uninitialized array of a fixed size and provides
indexed access, and fill it directly. Existing relocation consumers receive a
span over the array.
This avoids vector capacity slack and initialization that the decoder
immediately overwrites.
On the Chromium x86-64 debug benchmark with 32 threads, median wall time over
eight runs falls from 2.400 to 2.370 seconds. Median user and system time fall
from 51.655 and 12.115 seconds to 50.855 and 11.965 seconds. Median peak RSS
falls from 31,006,116 to 30,470,208 KiB, a reduction of 535,908 KiB.
The generated output was byte-identical.
Chromium contributes 35.8 million GDB name records. Keep each temporary
record at 16 bytes, replace its parsed form with its indexed form after
interning, and deduplicate names within each compilation unit with a small
linear hash table instead of sorting them.
Compute each string hash and length in one scan, assign constant-pool ranges
with a parallel prefix sum, and record type-vector slots while interning. Keep
the slot-claiming protocol shared by both ConcurrentMap insertion interfaces.
On the Chromium x86-64 debug benchmark with 32 threads, median wall time
over eight runs falls from 2.550 to 2.390 seconds. Median user and system time
fall from 54.735 and 12.390 seconds to 51.275 and 12.095 seconds. Median peak
RSS falls from 32,086,034 to 30,999,878 KiB, a reduction of 1,086,156 KiB.
The generated output was byte-identical.
The cardinality estimators used shared atomic registers even though their
callers already partition work across threads. Give each worker a local sketch
and merge the registers after the parallel scan. Apply the same scheme to GDB
index names and mergeable-section fragments.
Chromium debug link, 32 threads, five interleaved run medians:
wall: 2.60 s -> 2.57 s (-1.2%)
user: 56.22 s -> 55.95 s (-0.5%)
The generated .gdb_index was byte-identical.
Section construction repeatedly recovered names from .shstrtab even when the
caller had just looked up the same name. Pass known names into constructors and
defer other lookups until a name is actually needed.
Chromium debug link, 32 threads, five interleaved run medians:
wall: 2.61 s -> 2.60 s (-0.4%)
user: 57.38 s -> 56.22 s (-2.0%)
The generated output was byte-identical.
Duplicate COMDAT groups account for millions of sections in large debug
links. Read the symbol table and group membership first, choose the winning
groups after archive extraction, and only then construct input sections and
local symbols for retained members.
Let ShardedMap report whether gather created each symbol so new symbols can
be initialized in the same pass. An empty section table means that a file has
not reached section construction, avoiding a separate parsing-state flag.
Chromium debug link, 32 threads, five interleaved run medians:
wall: 2.77 s -> 2.61 s (-5.8%)
peak RSS: 33.2 GiB -> 30.9 GiB (-2.3 GiB)
The generated output was byte-identical.
Canonical PLTs and copy relocations are alternative ways to give an
imported symbol an address in the output. Which one is needed follows
from the symbol type, so recording separate scan flags duplicates
information.
Use one CANONICAL action and NEEDS_CANONICAL flag, then choose the table
to populate when dynamic symbol entries are assigned.
We choose between the two PPC64 ABIs by endianness because ELFv1 is
normally big-endian and ELFv2 little-endian. However, that
correspondence is a convention rather than a rule. In particular, musl
uses ELFv2 on big-endian too, so linking for example Alpine ppc64
objects silently produced an ELFv1-style executable with a synthesized
.opd whose startup code crashes immediately.
We support only the usual combinations, so make get_elf_type() treat the
others as unrecognizable, just like an ELF file for an unsupported
machine. Also give unrecognizable ELF files a proper error message; we
used to print "incompatible file type: ppc64 is expected but got " with
a trailing empty string.
Fixes https://github.com/rui314/mold/issues/1498
Co-Authored-By: Claude Fable 5 <[email protected]>
We computed the binding of a symbol in the static symbol table from its
dynamic import/export status, so in an ordinary executable, every global
symbol was emitted as STB_LOCAL. That misleads all symbol table
consumers; nm prints "t main" instead of "T main", and gdb had to work
around it for a feature that depends on symbol bindings.
Only symbols hidden by visibility or localized by a version script
should be demoted. GNU ld keeps hidden symbols STB_GLOBAL with hidden
visibility while lld converts them to STB_LOCAL; we follow lld, which
also matches our previous output for such symbols.
One wrinkle: claim_unresolved_symbols() assigned version index 0
(VER_NDX_LOCAL) to imported symbols so that their .gnu.version entries
would be 0, which would make the new is_local() misclassify them as
version-script-localized. Leave ver_idx unassigned for imported symbols
and have the .gnu.version writer emit 0 for unversioned undefined
symbols directly, so that VER_NDX_LOCAL on a symbol means only
"localized by a version script".
Fixes https://github.com/rui314/mold/issues/906
Co-Authored-By: Claude Fable 5 <[email protected]>
A default-versioned symbol `foo@@VER` can be referred to not only as
`foo` but also as `foo@VER`, so defining both `foo@@VER` and `foo@VER`
is a duplicate definition of the versioned name. We didn't diagnose it
and silently emitted a dynamic symbol table containing two definitions
of `foo@VER`, leaving it up to the dynamic loader which one a versioned
reference binds to. GNU ld and lld reject this; now we do too.
We check for duplicates by scanning the dynamic symbol table once it's
populated, because the problem manifests if and only if both symbols
are exported, and the exported set is much smaller than the set of all
symbols. GNU ld additionally reports the error for non-exported
symbols; lld doesn't, and we behave like lld.
Fixes https://github.com/rui314/mold/issues/828
Co-Authored-By: Claude Fable 5 <[email protected]>
We ignored defined dynamic symbols whose .gnu.version entry is 0
(VER_NDX_LOCAL) on the assumption that such symbols are bound locally.
That assumption is wrong; a symbol that's actually local to a DSO
doesn't appear in .dynsym at all, so such an entry is ill-formed. GNU
ld briefly emitted version index 0 for unversioned defined symbols
(https://sourceware.org/bugzilla/show_bug.cgi?id=33577), and linking
against a DSO created by such ld made us report confusing "undefined
symbol" errors for symbols that plainly exist in the DSO.
lld rejects such files as corrupted. Follow suit and report an error
pointing at the offending file and symbol instead of silently ignoring
it.
Fixes https://github.com/rui314/mold/issues/1534
Co-Authored-By: Claude Fable 5 <[email protected]>
We silently accepted a shared object file given on the command line even
in the static mode and simply ignored it, producing an executable whose
references to the shared object's symbols remain unresolved. Such a
binary crashes at runtime. GNU ld and lld reject this at link time, and
so should we.
Fixes https://github.com/rui314/mold/issues/1616
Co-Authored-By: Claude Fable 5 <[email protected]>
We used to read files under .git directly to avoid a build-time dependency
on git, but the on-disk repository format is not stable. The hash lookup
broke for repositories using the new reftable format, and it never worked
in git worktrees, where .git is a file rather than a directory.
Just ask git instead. Building from a git checkout now requires git, which
is not much to ask; building from a source tarball still works and simply
omits the hash, as before.
Fixes https://github.com/rui314/mold/issues/1621
Co-Authored-By: Claude Fable 5 <[email protected]>
After LTO, we reset the reachability of archive members and redo symbol
resolution to re-derive which members are actually needed, because the
LTO-generated code may no longer use some of the members that were
extracted to satisfy references from IR files. However, the results of
the pre-LTO COMDAT group selection survived into the second resolution:
ComdatGroup::owner kept the first winner's priority, and the losers'
member sections remained dead.
Therefore, if a group's winner was an archive member that is not
re-extracted after LTO, no remaining file could provide the group's
sections, and we reported a false "refers to a discarded COMDAT section"
error for a perfectly valid program.
Reset the COMDAT group state in do_lto() so that the post-LTO symbol
resolution selects groups from a clean slate among the files that
actually remain in the link.
Fixes https://github.com/rui314/mold/issues/1565
Co-Authored-By: Claude Fable 5 <[email protected]>
Like the previous commit did for comdat groups, symbol interning no
longer goes through a single concurrent hash table. Object file
parsing only records each global symbol name along with which slot of
the file's symbol vector it belongs to; gather_symbols() then
deduplicates the names into Symbol objects and fills in the files'
symbol pointers, processing each hash shard on a single thread with
no synchronization. Besides avoiding concurrent hash table
insertions, this hashes each name only once and no longer constructs
a temporary Symbol per lookup, which the concurrent map's insert
interface required. Linker-synthesized symbols, shared libraries and
linker scripts keep interning through a locked slow path in
get_symbol().
Linking a Chromium debug build on a Threadripper 7980X saves ~12
CPU-seconds (83 -> 71) and about 0.25 seconds of wall time at 32
threads. Output files are bit-identical.
Co-Authored-By: Claude Fable 5 <[email protected]>
We used to intern comdat group signatures into a single concurrent
hash table as object files are parsed. The concurrent map is costly
under 32-way insertion, and every file redundantly hashed the
signature strings, which are long mangled names for C++ programs.
Signatures are now handled by a new ShardedMap class: parsing only
appends them to thread-local buffers binned by hash, and once all
files have been read, each bin is deduplicated by a single thread
with no synchronization, filling in the files' ComdatGroupRefs. A
signature is hashed once, when it's recorded. Deduplication runs in
resolve_symbols() right before comdat elimination rather than at the
end of read_input_files() so that files parsed later, such as ELF
files produced by the LTO plugin, are covered when we redo symbol
resolution. LTO comdats otherwise take a mutex-protected slow path.
Linking a Chromium debug build on a Threadripper 7980X is about 4%
faster (3.22s -> 3.06s at 32 threads), saving ~4 CPU-seconds. Output
files are bit-identical.
Co-Authored-By: Claude Fable 5 <[email protected]>
We used to read input files one at a time in the command line order,
with a single loop opening and mmap'ing each file and walking archive
files. That loop, not parsing, is the bottleneck of the read phase;
for a Chromium debug build with ~45,000 input files, it takes ~1.3
seconds, about 40% of the total link time.
We now open and read files with a parallel for-each loop with the
feeder pattern; an archive file feeds one job per member. The command
line parser records the reader state (--as-needed etc.) and a
hierarchical position for each input file argument, and files are
sorted back into the command line order afterwards to assign
priorities. Linker scripts, which can mutate the linker context, are
parsed after the parallel loop, one at a time in the command line
order. LTO claims are serialized because the plugin interface has
shared state.
Linking Chromium debug on a Threadripper 7980X is ~2% faster at 32
threads and ~14% faster at 64 threads, where the serial loop
previously made additional threads a net loss. Output files are
bit-identical to before.
Co-Authored-By: Claude Fable 5 <[email protected]>
mremap, which we used to grow the output file mapping for
.gdb_index, is Linux-specific, so commit 9b7c5267 broke the build on
the BSDs and macOS. Instead of growing a mapping that was created
with the exact file size, we now map the output file with twice as
much address space as its size upfront. The mapping beyond the end
of the file may not be touched, but growing the file with ftruncate
makes the tail accessible, so extend() no longer needs any mmap
call, and the output buffer never moves. Reserving address space
does not consume any physical memory; pages materialize only when
they are first touched.
This also fixes the madvise(MADV_HUGEPAGE) call in extend(), which
had always failed with EINVAL because its address was not
page-aligned. That went unnoticed because an mremap'ed mapping
inherits the huge-page flag from the original mapping.
Co-Authored-By: Claude Fable 5 <[email protected]>
A large link creates tens of millions of InputSection objects, one
heap allocation each, and the profiler attributed 5% of all cycles
to their construction. Allocate them from a per-file std::deque
instead and refer to them through plain pointers. Section death is
represented by a null entry as before; the object itself stays in
the pool until the file is destroyed.
This removes the InputSection constructor from the profile and
cuts the allocation count by 4x, but the end-to-end link time is
unchanged within noise on a Chromium debug build; the constructor
cost was evidently not on the critical path. The change still pays
its way by making `sections` a plain pointer vector, which drops
the unique_ptr indirection and the .get() noise throughout.
Co-Authored-By: Claude Fable 5 <[email protected]>
Compilers emit numerous temporary local symbols such as .L.str.42
for unnamed program elements. They are compiler-internal: nothing
references them through the symbol table, and DWARF refers to their
data by section offset. Nevertheless we kept them in the output by
default, following GNU ld. A Chromium debug build contains 2.26
million of them, and they added ~180 MiB of .symtab and .strtab to
the 4.9 GiB output.
Discard them by default, as lld does. The new --discard-none option
restores the previous behavior. -r and --emit-relocs continue to
keep all local symbols, since emitted relocations may refer to
them, and RISC-V and LoongArch no longer need their special case,
as the default now covers them.
Link time is unchanged within noise; the benefit is a ~4% smaller
output for large debug builds and a symbol table that no longer
resolves addresses to compiler-internal labels.
Co-Authored-By: Claude Fable 5 <[email protected]>
Previously, data appended to the output file, such as .gdb_index,
was staged in a heap buffer or a separate memory mapping. We can
instead just grow the existing mapping with mremap(MREMAP_MAYMOVE).
Consumers of the output buffer derive pointers from ctx.buf, so the
mapping is free to move, and since filesize now includes appended
data, the build ID and the .gnu_debuglink CRC naturally cover the
entire file.
Co-Authored-By: Claude Fable 5 <[email protected]>
When a live section refers to a __start_<name> or __stop_<name>
symbol, every section named <name> must be kept alive. The garbage
collector marked those sections one by one in the thread that
found the reference. A single reference can fan out to an enormous
number of sections: ClickHouse is built with XRay instrumentation,
which emits one xray_instr_map section per function, and the XRay
runtime references __start_xray_instr_map, so one thread marked
and visited a couple hundred thousand sections while the rest of
the machine waited for the mark phase to finish.
This was a regression introduced by 384527fd: before it,
C-identifier-named sections were unconditional GC roots and
entered the root set through a parallel loop. That commit
correctly made their retention conditional on a reference to
their marker symbols but expanded the fanout serially. Keep the
precise semantics and spread the fanout over threads with a
nested parallel loop, storing the sections of each name as a
vector instead of entries of a multimap.
This shortens ClickHouse's debug link from 1.98s to 1.45s at the
default 32 threads; its gc pass drops from 0.56s to 0.09s.
Co-Authored-By: Claude Fable 5 <[email protected]>
ConcurrentMap allocated its hash table with MAP_POPULATE, which
zeroes every page of the mapping inside the mmap system call, on
one thread. The string-merge map for a Clang debug build spans
several gigabytes, so every other thread stalled for ~170ms at the
barrier between splitting and inserting strings while one thread
pre-faulted the table.
Map the table lazily and advise MADV_HUGEPAGE instead. Populating
the table on demand from concurrent inserts spreads the fault cost
over all threads, huge pages make each fault populate 2MiB rather
than a base page, and they also cut the TLB miss rate of the
random probes into the table. Explicit parallel pre-faulting was
measured to add nothing on top of this. This shortens a Clang
debug link from 2.13s to 1.85s and a Chromium debug link from
3.38s to 3.27s at the default 32 threads.
Co-Authored-By: Claude Fable 5 <[email protected]>
.gdb_index was constructed in a malloc'ed buffer that close()
appended to the output with a single buffered write, copying the
entire section (534 MiB for a Chromium debug build) through the
page cache on one thread, into disk blocks that ext4 had to
allocate inside the write path.
Add OutputFile::extend, which returns a buffer for data appended
past the mapped image. A memory-mapped output file grows with
ftruncate, preallocates the new disk blocks, and memory-maps the
appended range, so write_gdb_index constructs the section directly
in the output file, in parallel. The range gets its own mapping
because growing the existing one with mremap could move it,
invalidating pointers into it held by the caller. Output files
that cannot be mapped (special files such as stdout, the separate
debug file, and Windows) keep buffering appended data on the heap,
now private to each implementation; the shared buf2 field is gone.
The fallocate calls are now guarded by a configure-time
HAVE_FALLOCATE check rather than #ifdef __linux__.
This shortens a Chromium debug link from 3.31s to 3.18s and makes
close_file effectively free.
Co-Authored-By: Claude Fable 5 <[email protected]>
RelDynSection::update_shdr collected dynamic relocations by walking
all chunks serially, and a single output section such as
.data.rel.ro can account for most of an output's dynamic
relocations (~1.5 million for a Chromium debug build, mostly
R_RELATIVE for vtables). The walk computes each relocation's offset
and addend with Symbol::get_addr, which made it a ~0.1s
single-threaded stretch between parallel passes.
Process an output section's absolute relocations in fixed-size
parallel shards and flatten the per-shard results, so the resulting
section contents are identical to the serial version. This shortens
a Chromium debug link from 3.58s to 3.49s at the default 32
threads.
Co-Authored-By: Claude Fable 5 <[email protected]>
parse_nonpositional_args tested each command line argument against
every option name, and each test allocated a few heap strings in
add_dashes. A command line for a large program consists almost
entirely of input file paths (a Chromium debug link passes ~19,000
paths but fewer than 100 options), so we were spending several
hundred milliseconds of serial time on failed option matches before
opening the first input file.
An argument that does not start with '-' cannot match any option,
so such arguments now go straight to the output list. This shortens
a Chromium debug link from 3.98s to 3.59s on a 64-core machine
because option processing delayed the start of the parallel parsing
phase.
Co-Authored-By: Claude Fable 5 <[email protected]>
SH-4 object files store relocation addends in the relocated places
rather than in r_addend, and mold assumed that dynamic relocations
work the same way, emitting them with zero r_addend and the addend
written to the relocated place. The glibc dynamic linker, however,
follows the usual RELA convention: it reads the addend of a symbolic
dynamic relocation only from r_addend, and it reads the relocated
place only for R_SH_RELATIVE relocations whose r_addend is zero. Any
symbolic dynamic relocation with a non-zero addend thus lost its
addend at load time.
A typical victim is the vtable pointer of a typeinfo object in a
position-independent executable. It must point into libstdc++'s
vtable (e.g. _ZTVN10__cxxabiv117__class_type_infoE + 8), so its
dynamic relocation carries a non-zero addend. With the addend
dropped, virtual calls on such a typeinfo dispatch to wrong vtable
slots, which crashes the C++ exception machinery as soon as a
personality routine tries to match a catch clause. The icf-lsda test
caught this on sh4aeb because its CI toolchain creates PIEs by
default; the little-endian sh4 toolchain defaults to
position-dependent executables, in which the vtable is copy-relocated
and no dynamic addend is involved, so the same test passed there.
Remove the ElfRel specializations that zeroed addends for symbolic
relocations; the generic RELA record types have the identical layout
and keep addends. Relocation records emitted for -r or --emit-relocs
keep following the object-file convention, since their consumers
expect addends in the relocated places.
Co-Authored-By: Claude Fable 5 <[email protected]>
Creating a .gdb_index was slower than it should have been for three
reasons. read_pubnames_cu looked up compunits with a linear scan,
which made it quadratic in the number of compunits; the compunit
vector is sorted by offset, so a binary search suffices. gdb_hash
was computed for every name occurrence even though the same name
typically appears in many compunits; we now compute it only when a
name is inserted into the map for the first time. Finally, nametypes
were sorted with a stable sort, but their ordering is total, so a
plain sort produces the same result without the extra buffer.
On a Chromium debug build, this makes the write_gdb_index pass 2x
faster with --threads=1 (10.2s to 5.2s) and 28% faster at the
default 32 threads (0.69s to 0.50s). The output is bit-identical.
Co-Authored-By: Claude Fable 5 <[email protected]>
Symbols are matched against version script patterns with an
Aho-Corasick automaton (for single-substring patterns) or an
NFA-based glob matcher, both of which scan the entire symbol name on
every query. But almost all patterns in real version scripts are a
match-all ("local: *;"), an exact name, or a name with one trailing
or leading "*", all of which can be matched with a plain string
comparison.
Handle such patterns directly and reserve the automatons for the
ones that actually need scanning, such as "*foo*" or "a*b".
Co-Authored-By: Claude Fable 5 <[email protected]>
The `README` gives users commands to copy, but `install-build-deps.sh`
is not ran as root, even though the CMake install step is indicated to
run with `sudo`. FreeBSD seems not to come with `sudo` by default, so
modifying the install script might be unwise. Simply place it in front
of the script invocation itself in the `README`.
The affinity partitioner replayed each round's chunk-to-thread
placement so that a thread would re-read the digests it wrote in the
previous round from its own cache. That no longer pays off: after the
recent ICF rework, the propagation loop streams over the digest and
edge arrays and is bound by memory bandwidth rather than cache reuse.
Linking Chromium on a Threadripper 7980X (64 cores, 8 CCDs), the
propagate timer is unchanged at 16 and 128 threads and loses only
about 2 ms (4%) at 32 threads, which is invisible in the total link
time. In exchange, the default auto partitioner retains full work
stealing, which balances better against the skewed per-section edge
counts, and the partitioner no longer needs to be threaded through
two function signatures.
Co-Authored-By: Claude Fable 5 <[email protected]>
compute_digest hashes an eligible relocation target without identity,
relying on a graph edge to keep different targets distinguishable
through the propagation rounds. But gather_edges only created edges
for a section's own relocations, not for the relocations in its FDEs,
so the reference from an FDE to its LSDA never became an edge. As a
result, two functions with identical machine code whose exception
tables catch different types were considered identical, and folding
them changes which exceptions the survivor catches. The bug is as old
as mold's ICF and also affects --icf=safe, since such functions are
usually not address-taken.
Create edges for FDE relocations, mirroring exactly what
compute_digest hashes. The included test, two byte-identical
functions catching different exception types, aborts with an uncaught
exception without this fix.
On 32-bit ARM the equivalent bug still exists through a different
mechanism: exception tables are .ARM.extab sections tied to functions
via .ARM.exidx rather than FDE references, which ICF does not yet
track. The test skips ARM for now.
A Chromium debug build is unaffected (same rounds, same number of
folded sections) since Chromium builds with -fno-exceptions.
Co-Authored-By: Claude Fable 5 <[email protected]>
gather_sections and gather_edges each kept a per-element count array
next to a starting-index array and combined the two at the end.
Counting directly into the index array shifted by one and running the
prefix sum in place removes the count arrays, and the extra entry at
the end means edge_indices[i + 1] is valid for every vertex, so the
propagation loop no longer needs to special-case the last vertex to
find where its edge list ends. The empty-input early return in
gather_edges falls out as well.
No behavior change.
Co-Authored-By: Claude Fable 5 <[email protected]>
A propagation round used to compute a vertex's next digest by hashing
its initial digest together with its neighbors' current digests,
which required keeping three digest arrays: the initial one and two
that alternate between current and next.
Hash the vertex's current digest instead of its initial one. This is
the textbook formulation of iterated partition refinement, and it
distinguishes exactly the same vertex pairs in every round: a current
digest determines the initial digest injectively (up to hash
collisions, which the whole algorithm already assumes away), so the
per-round partitions, the number of rounds and the final equivalence
classes are all unchanged.
The initial digest array disappears, saving 16 bytes per vertex, and
the current/next arrays are now two plainly named vectors that swap
after each round instead of an indexed triple with a flip-flop slot
index.
Co-Authored-By: Claude Fable 5 <[email protected]>
The propagation rounds used to record which vertices' digests had
stopped changing in a `converged` bitmap and skip rehashing them in
later rounds. That was worthwhile when rounds were expensive, but by
now a round is a cheap, uniform data-parallel pass, and the
bookkeeping costs as much as it saves: reading and comparing two
digests per vertex per round just to skip a short hash computation.
Rehash every vertex on every round instead. The convergence check was
the last user of Digest's equality operator, so that is gone too.
For a Chromium release build, the whole ICF pass got slightly faster
(0.166s to 0.152s wall, 4.7s to 4.3s of CPU time).
Co-Authored-By: Claude Fable 5 <[email protected]>
Leaf sections, those with no outgoing references, used to be merged
upfront so that they don't have to participate in the propagation
rounds as graph vertices. That was worth the code when the rounds and
the convergence check were expensive, but both have since become
cheap, and the propagation machinery handles leaves naturally: a
leaf's digest never changes, so it is marked as converged after the
second round and costs almost nothing from then on.
This deletes merge_leaf_nodes, is_leaf and the icf_leaf flag, and
moves eligibility classification to gather_sections. The leader
branch in hash_symbol is gone too because no section has a leader
anymore by the time digests are computed.
Also tidy up the neighborhood: the counting pass of gather_edges
skipped symbols that point to a section fragment before asking for
their input section, but a symbol's origin is a tagged pointer that
is either a fragment or an input section, so get_input_section()
already returns nullptr for fragment symbols. Dropping the redundant
check makes the counting pass and the filling pass identical in
shape.
For a Chromium release build, the ICF pass takes the same time as
before; the propagation rounds absorb roughly what merge_leaf_nodes
used to cost. The graph gets about 30% more vertices, which costs
some transient memory.
Co-Authored-By: Claude Fable 5 <[email protected]>
A digest used to be std::array<uint8_t, 16>, so DigestMap had to
memcpy the bytes into 64-bit words before deriving the slot index and
tag from them. Defining Digest as a struct of two u64 members gives
the words names and makes the copies disappear.
icf.cc is compiled once per target, so the struct is defined in every
one of those translation units. The definitions are all identical, so
this is fine under the one-definition rule, just as the old type
alias was.
No behavior change.
Co-Authored-By: Claude Fable 5 <[email protected]>
Leaf sections used to be merged with a dedicated hash map keyed by
raw section contents, with its own hasher and equality functor. That
equality was subtly weaker than the one the propagation rounds use:
it compared FDE bodies but ignored which CIE each FDE points to, so
two identical functions whose CIEs recorded different personality
routines could be merged incorrectly. It also ignored section flags.
Instead, compute the same digest for leaves that compute_digest
produces for graph sections and merge leaves through DigestMap, the
same structure the propagation rounds use. One definition of section
equality now covers both paths, which fixes the personality bug, and
LeafHasher, LeafEq and the last digest-keyed use of tbb's hash map in
this file disappear.
The trade-off is that leaf merging now relies on 128-bit digest
uniqueness instead of comparing contents exactly, but that is the
same assumption the rest of ICF is already built on.
For a Chromium debug build linked with --icf=all, merge_leaf_nodes
went from 0.07s to 0.10s because a leaf's contents are now hashed by
SipHash twice, once to insert and once to look up. 152 of 3.6M folded
sections are no longer merged; those are the sections the weaker
equality was conflating.
Co-Authored-By: Claude Fable 5 <[email protected]>
To group sections at the end of ICF, we used to build a concurrent
hash map from digest to the lowest-priority section holding it, which
duplicated most of what the digest-counting hash set already does.
That map was also slow enough to construct and destruct that we
deferred freeing it until exit.
This patch generalizes the counting set into DigestMap by adding a
section pointer to each slot. The counting rounds now elect the
lowest-priority section for each digest as a side effect, so by the
time the propagation loop converges, the final round has already
elected the leader of every equivalence class, and the grouping step
reduces to a single lookup pass. The digest-keyed tbb map and its
std::hash specialization disappear, and so does ctx.on_exit, whose
only purpose was to defer freeing that map.
For a Chromium debug build linked with --icf=all, the grouping pass
went from 0.23s to 0.01s and the propagation loop got 0.11s slower
for maintaining the leaders, making the whole ICF pass about 10%
faster (0.89s to 0.79s). The ICF fold decisions are unchanged.
Co-Authored-By: Claude Fable 5 <[email protected]>
To detect convergence of the ICF propagation rounds, we count the
number of distinct digests and stop when it ceases to increase. We
previously gated the count on a cheaper necessary-looking condition:
as long as the number of changed digests per round was still moving,
we assumed the graph could not have converged and skipped counting.
That assumption is wrong. A changing digest does not imply a changing
partition, because sections in the same equivalence class change
their digests in lockstep. For example, in a chain of sections that
are all distinct from the start, the partition is final after the
first round, but the changed-digest count keeps moving until the
propagation depth reaches the chain length, so the gate would delay
termination by the depth of the chain.
Since counting became cheap, we can simply count on every round and
stop as soon as the count repeats, which is the earliest round at
which convergence is provable. propagate() no longer needs to count
changed digests and report them to the caller.
For a Chromium debug build linked with --icf=all, this reduces the
number of propagation rounds from 40 to 28. The 12 saved rounds are
ones in which most sections had already converged, so the wall clock
time is unchanged. The output file is bit-identical.
Co-Authored-By: Claude Fable 5 <[email protected]>
To detect convergence of the ICF propagation rounds, we count the
number of distinct digests and stop when it ceases to increase. We
previously obtained the count by copying all digests and running a
parallel sort, which was expensive enough that we ran it only once
per 10 propagation rounds, overshooting the convergence point by up
to 9 rounds plus a full confirmation batch.
This patch replaces the sort with a concurrent open-addressing hash
set. Since the number of distinct digests cannot exceed the number of
digests, the table is allocated once at full size and never grows.
Slots are stamped with the round number, so the table needs no
clearing between rounds; and a slot match effectively compares an
entire 128-bit digest, so the count is exact under the same
hash-collision assumption the algorithm is already built on. Counting
is now cheap enough to run on every round that passes the existing
changed-digest gate, so the loop exits at the earliest round at which
convergence is provable, and the batching heuristic is gone.
For a Chromium debug build linked with --icf=all, the propagation
loop got 2.2x faster (0.73s to 0.34s), the number of propagation
rounds dropped from 59 to 40, and the whole ICF pass went from 1.29s
to 0.90s. The output file is unchanged.
Co-Authored-By: Claude Fable 5 <[email protected]>
This changes the bundled mimalloc.
mimalloc calls madvise(MADV_HUGEPAGE) on suitable memory regions only
when the caller also allows large OS pages, i.e. hugetlbfs pages.
That condition makes sense for hugetlbfs, whose pages are physically
committed at map time and thus shouldn't back lazily-committed
segments, but transparent huge pages have no such cost because their
pages materialize on fault. Since both were gated on the same flag,
huge mimalloc objects, which are allocated through a code path that
never sets the flag, were never eligible for THP under the madvise
THP mode that Linux distributions ship by default. mold's largest
data structures, such as the symbol and relocation arrays, are
exactly such objects; several gigabytes of them were backed by 4 KiB
pages.
This patch makes the THP advise depend only on the allow_thp option
(enabled by default except on Android) and leaves the hugetlbfs gate
as is. mimalloc v3 effectively behaves this way already; only the v2
line has this problem.
Linking a Chromium debug build gets about 2% faster in the default
fork mode (3.37s to 3.29s) and 15% faster with --no-fork (4.05s to
3.43s) on our machine, as the heap becomes almost fully backed by
huge pages, which also makes the process exit much cheaper.
Co-Authored-By: Claude Fable 5 <[email protected]>
The kernel reclaims an exiting process's address space in a single
thread, which takes an appreciable amount of time for a process that
maps tens of gigabytes of input files. We now drop the page table
entries for input file mappings with madvise(MADV_DONTNEED) on all
threads before exiting. File contents stay in the page cache, so warm
rebuilds are unaffected.
This makes linking a Chromium debug build with --no-fork about 11%
faster (4.55s to 4.05s) on our machine. Without --no-fork, the
teardown happens in the detached child process, which now releases
its memory sooner.
Co-Authored-By: Claude Fable 5 <[email protected]>
The PR_SET_THP_DISABLE prctl flag is inherited across fork and exec,
so a linker spawned from a process that has disabled transparent huge
pages for itself also runs without them, no matter what the system
THP setting says. mold allocates on the order of 10 GiB of heap for a
large link, and backing it with 4 KiB pages instead of 2 MiB pages
makes linking nearly twice as slow (6.2s vs 3.4s for a Chromium debug
build on our machine), so clear the inherited flag at startup.
Co-Authored-By: Claude Fable 5 <[email protected]>
lld does this by default. See: https://crbug.com/529102889
If a DSO is only referenced by sections that are garbage-collected, the
DSO's DT_NEEDED entry will be omitted.
These changes broke cross builds for ppc64le and s390x. I couldn't fix
it easily. Every fix attempt also takes a long time because of slow
qemu-user emulation.
I'll revert CMakeLists.txt and dist.sh to the original state. I don't
think we need to upgrade distros used by dist.sh as long as the binaries
produced using these images work.
Now our tar version is recent enough to use the `--sort` option.
We still need to run gzip explicitly in the pipeline because we want
to pass -n (--no-name) to not save the current time as the file
timestamp in a gzip header.
* update x86-64 to Debian 10. This has glibc 2.28 which matches the oldest actually used distro these days (rhel 8) and gcc-8.5 is new enough to bootstrap clang and libc++, which saves the whole gcc build step
* set the riscv64 target to use debian Trixie, which was the first stable release for that arch
* bullseye (arm,ppac,s390), trixie (riscv), and the loongarch sid snapshot all have clang-19 available
* make sure all images install the libc++-19-dev package to get libc++ static version
* MOLD_USE_LIBCXX now configures the build to look for and use llvm's libc++. This is already the default on macOS and BSD
* Added MOLD_STATIC_* flags for several libs to give more config beyond the overall MOLD_MOSTLY_STATIC
When mold relaxes an instruction, the relocation attached to it no
longer matches the rewritten code. With --emit-relocs that mismatch is
written to the output and confuses consumers such as BOLT. This change
addresses it by rewriting each emitted relocation to the type that
matches the relaxed instruction.
RISC-V and LoongArch already did this in a separate rewrite_reloc_types()
pass that could drift from apply_reloc_alloc; fold it into the latter and
delete the pass. ARM64, x86-64, i386 and s390x did not rewrite types
and now do, e.g. x86-64 GOTPCRELX => PC32, i386 GOT32X => GOTOFF, s390x
GOTENT => PC32DBL, ARM64 GOT loads => ADR_PREL_PG_HI21 and TLSDESC =>
IE/LE. Markers that GNU ld also keeps, such as x86-64 TLS and sparc64
GOTDATA_OP, are left unchanged.
Fixes https://github.com/rui314/mold/issues/1610
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
A symbol exported with an empty default version (foo@@) was treated as a
plain unversioned symbol, so a version script's "local: *" wildcard
localized it and dropped it from the dynamic symbol table; GNU ld keeps
it exported. Route foo@@ through parse_symbol_version() and assign it
VER_NDX_GLOBAL so it overrides the wildcard.
Signed-off-by: Sun Yuechi <[email protected]>
SFrame is a compact alternative to .eh_frame used for asynchronous stack
unwinding by profilers and debuggers. As of binutils 2.46, gas emits
.sframe (Version 3) by default with -Wa,--gsframe, so object files now
routinely carry it. Like .eh_frame, .sframe can't be treated as opaque
bytes: an output section is a single header followed by a PC-sorted index
of Function Descriptor Entries (FDEs) and a blob of Frame Row Entries
(FREs), so the linker has to parse the inputs and rebuild it.
This adds SFrame Version 3 support. We parse each input .sframe into its
FDEs, drop the entries for garbage-collected or ICF-folded functions,
concatenate the survivors, sort the index by function address and rewrite
the header. The FRE data carries no relocations and is position
independent, so it is copied verbatim; only each FDE's func_start is
relocated, which we resolve to a PC-relative offset (the output always
sets SFRAME_F_FDE_FUNC_START_PCREL). A PT_GNU_SFRAME segment is emitted so
the runtime can find the section.
For a relocatable output we can't resolve addresses yet, so the merged
index is left unsorted and each func_start is emitted as a relocation in
.rela.sframe for the final link to resolve, matching GNU ld -r.
We support only Version 3. A .sframe with the wrong magic is reported as
an error, whereas a section written in an older version is ignored.
Fixes https://github.com/rui314/mold/issues/1511
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Large C++ debug libraries on ARM64 (e.g. deal.II's libdeal_II.g.so) can
have a single batch reference enough distinct PLT-routed DSO imports that
the first-pass thunk group exceeds the old fixed 1 MiB max_thunk_size,
tripping the assertion in create_range_extension_thunks().
Replace the branch_distance/5 formula with explicit per-target batch
sizes (32/8/16 MiB for ARM64/ARM32/PPC) and derive max_thunk_size from
the batch as batch_size/2 (16/4/8 MiB). The reservation in d_thunk_end()
already accounts for max_thunk_size, so a larger cap is safe by
construction, and batch_size/2 is about the largest reservation ARM32's
16 MiB branch range can accommodate without throttling the batch.
Add a regression test that calls 50,000 imported functions from a single
function, which on ARM64 produces a first-pass thunk group well over the
old 1 MiB limit.
Fixes https://github.com/rui314/mold/issues/1538
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
actions/upload-artifact rejects names with a forward slash or colon, so
the test-results upload failed for the opensuse/tumbleweed matrix entry
(and would have failed for gentoo/stage3, debian:11, and the ubuntu:*
entries on a test failure). Sanitize matrix.distro by mapping / and :
to - before using it as the artifact name.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
After relaxation, the relocations attached to the relaxed instructions no
longer match the new, shorter instructions. With --emit-relocs we copied the
input relocations verbatim, emitting stale types at stale offsets.
Rewrite each relocation's offset and type in RelocSection::copy_buf to match
the relaxed code, recomputing the outcome the same way apply_reloc_alloc
recomputes its instruction rewrites. -r output is not relaxed, so its
relocations are passed through unchanged.
Fixes https://github.com/rui314/mold/issues/1584
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
The Android packed relocation section (SHT_ANDROID_RELA/REL) holds a
variable-length SLEB128-encoded delta stream, not a table of fixed-size
entries, so sh_entsize should be 0 and the byte stream needs no word
alignment. We were inheriting sizeof(ElfRel) and sizeof(Word) from the
plain .rela.dyn path. These fields aren't consulted at runtime (bionic
locates the blob via DT_ANDROID_RELA/RELASZ), so this only corrects the
metadata and lets the section pack without spurious padding.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
When folding identical sections, ICF was discarding the alignment of the
folded sections and only keeping the alignment of the chosen leader. If
a section with stricter alignment (e.g. 4-byte) was folded into a
section with looser alignment (e.g. 1-byte), the folded function could
be placed at an odd address.
On x86/x86_64, member function pointers use the lowest bit of the
address for virtual/non-virtual distinction. If a non-virtual member
function is folded and placed at an odd address, it is incorrectly
interpreted as virtual, leading to crashes. This fix ensures that we
update the leader's alignment to be the maximum alignment of all folded
sections. We do this in a global pass over all sections (both leaf and
non-leaf merged sections) before the sweep phase.
Fixes#1596
If a relocation points to a preemptible (imported) symbol, it should not
be considered identical to a relocation pointing to a local symbol, even
if their targets currently have identical bodies. Preemptible symbols
can be overridden at runtime, so we must preserve their unique identity.
Fixes#1598
With a PT_LOAD segment, we need to make sure p_offset % p_align ==
p_vaddr % p_align. In set_file_offsets, we try to assign contiguous file
offsets when sections are contiguous in memory. But when the alignment
of section changes and it's bigger than page size, we cannot do that.
The offset might need to be adjusted to follow the alignment rule.
A test binary with
__attribute__((section(".sum"))) __attribute__((aligned(8192))) void sum() {}
Section Headers:
[16] .plt.got PROGBITS 00000000000015c0 0005c0 000008 00 AX 0 0 16
[17] .sum PROGBITS 0000000000002000 001000 00000b 00 AX 0 0 8192
.sum section is assigned contiguous offset to its previous section. As a
result, in create_phdr, .sum is mapped to the same PT_LOAD segment as
previous sections.
Program Headers:
LOAD 0x000588 0x0000000000001588 0x0000000000001588 0x000b80 0x000b80 R E 0x2000
p_offset = 0x588
p_vaddr = 0x1588
p_align = 0x2000
So p_offset % p_align != p_vaddr % p_align, it violates the alignment
rule. As a result, if this scenario happens to a shared library, it
cannot be dlopen() on a system with glibc < 2.35 which is before this
commit:
https://github.com/bminor/glibc/commit/163f625cf9becbb82dfec63a29e566324129c0cd.
Fix it by not assigning contiguous offset when section's alignment is
different and it's bigger than page size.
Several parse-time sites converted `symbol_strtab.data() + st_name`
implicitly to string_view, running strlen on each call. Precompute
a string_view vector parallel to elf_syms and read from it.
InputSection::name() implicitly converts a `const char *` to
string_view, which calls strlen on the shstrtab entry at every call.
Precompute the name as a string_view once in the constructor.
Linking clang Debug is ~1.3% faster; mlir-opt Debug is ~2.6% faster.
ConcurrentMap::insert reads ent.key before writing, so on an empty
slot the read first installs a PTE pointing at the system zero page,
then the CAS triggers wp_page_copy and a cross-CPU TLB shootdown.
Under heavy parallel insertion this shows up as ~3% of link time in
smp_call_function_many_cond plus a much larger inclusive cost in
asm_exc_page_fault.
Pass MAP_POPULATE to pre-fault the whole map in the mmap() thread.
Reads no longer install zero-page PTEs and writes no longer trigger
CoW. Linking clang-23 Debug is ~5% faster, llvm-tblgen ~7%,
clang-tblgen ~9%; max RSS rises by 0.1-0.6%. Falls back cleanly on
platforms that don't define MAP_POPULATE.
Several parse-time sites converted `symbol_strtab.data() + st_name`
implicitly to string_view, running strlen on each call. Precompute
a string_view vector parallel to elf_syms and read from it.
InputSection::name() implicitly converts a `const char *` to
string_view, which calls strlen on the shstrtab entry at every call.
Precompute the name as a string_view once in the constructor.
Linking clang Debug is ~1.3% faster; mlir-opt Debug is ~2.6% faster.
Commit 6d6a128b moved the -C chdir(2) into parse_nonpositional_args(),
which is invoked by both the speculative X86_64 mold_main() and the
re-run via redo_main(). When -C is in effect, the second invocation
re-entered expand_response_files() from the post-chdir directory, so
@rsp with a relative path could no longer be opened.
Save the cwd before parsing and restore it before redo_main() so the
re-run resolves relative paths from the original directory. Also
switch the chdir call itself to std::filesystem::current_path().
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
src/mold.h (HANDLE in MappedFile) and lib/perf.cc (FILETIME /
GetProcessTimes / GetCurrentProcess) both use Windows APIs inside
_WIN32 blocks but neither included <windows.h>. The MSYS2 UCRT64 build
worked because <tbb/enumerable_thread_specific.h> (pulled in via
lib/lib.h) used to #include <windows.h> for its TLS code.
oneTBB 2023.0.0 replaced that bulk include with forward declarations
of just the TLS functions ("declare prototypes of just the needed TLS
functions, instead of including whole windows.h"). When MSYS2 picked
up 2023.0.0, build-msys started failing with "'HANDLE' does not name
a type" at src/mold.h:224 (and the same shape of error in lib/perf.cc
after the mold.h fix landed).
Include <windows.h> explicitly in both translation units so the build
doesn't rely on transitive paths through third-party headers.
Last passing run: https://github.com/rui314/mold/actions/runs/25585325507/job/75112599227
First failing run: https://github.com/rui314/mold/actions/runs/25767054228/job/75681837767
A compile unit's DW_AT_ranges attribute in DW_FORM_rnglistx form holds an
index into the offset table at DW_AT_rnglists_base; the corresponding
entry locates the range list for that DIE. mold was ignoring the index
and iterating every offset, picking up range lists belonging to child
DIEs (subprograms, lexical_blocks, inlined_subroutines) that use
DW_FORM_rnglistx as well.
The resulting .gdb_index address table contained child-DIE ranges nested
inside the CU's range. GDB 17 detects the overlap, prints "warning:
.gdb_index address table has a range (...) that overlaps with an earlier
range, ignoring .gdb_index", and discards the index. Older GDBs accept
the bloated table without complaint -- the duplicate entries all point
to the same CU, so lookups still resolve correctly -- which is why this
went undetected for years.
The bug only triggers when a CU contains a non-contiguous lexical_block,
which clang emits for scopes with EH landing pads from throwing RAII
destructors. Replace the loop with offsets[ranges.value], matching the
DW_FORM_sec_offset branch and GDB's own read_rnglist_index.
The new test invokes clang++ directly since DW_FORM_rnglistx is a
clang-specific behavior; gcc through 13 uses DW_FORM_sec_offset, which
mold has always handled correctly. The test skips cleanly if clang++
isn't installed or doesn't emit rnglistx for the source.
Reported by @MikeWang000000.
Fixes https://github.com/rui314/mold/issues/1578
When --dynamic-linker is passed alongside -shared, mold currently emits
a .interp section and a PT_INTERP program header. Both lld and BFD ld
silently drop --dynamic-linker for -shared outputs because shared
libraries are loaded by the dynamic linker rather than bootstrapped by
the kernel, so PT_INTERP serves no purpose there.
Gate .interp creation on (!shared || pie) so we keep it for regular
executables, -pie executables, and -shared -pie runnable shared
libraries (the libc.so.6-style case), and drop it only for the pure
shared-library case. The PT_INTERP program header at
output-chunks.cc:219 is conditional on ctx.interp being non-null, so
the segment vanishes automatically with no second edit.
Fixes https://github.com/rui314/mold/issues/1572
The Warn() machinery has consulted ctx.arg.suppress_warnings since
6d2dbd42 ("Preparation to support Apple ld64's -w option", March
2023), but no cmdline flag ever set it. Wire -w (the GNU/BFD
spelling) and --no-warnings (the lld long form) to set the field.
Because Warn::Warn early-returns when the flag is true, before the
ctx.arg.fatal_warnings branch sets has_error, -w also cancels
--fatal-warnings — matching lld's documented behavior.
The "warnings only" semantics match GCC, clang, and lld: real errors
still print and still fail the link. BFD ld's -w is broader and
suppresses error messages too, which would hide the cause of link
failures.
Fixes https://github.com/rui314/mold/issues/1493
Linker scripts produced for lld (e.g., the version script Chrome's
build emits) use the POSIX/shell convention `[!...]` to negate a
bracket character class. mold previously accepted only `[^...]`, so
those scripts failed to match. Accept both, treating `!` and `^`
identically at the start of a bracket expression.
Fixes https://github.com/rui314/mold/issues/1575
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Commit 6b8293bf changed the early `ver` computation in
SharedFile::parse so that undef symbols use the raw versym instead
of being forced to VER_NDX_GLOBAL. Some BFD ld versions (notably
binutils 2.35 on Debian 11) emit VER_NDX_LOCAL (0) as the versym
for unversioned undefined dynamic symbols, so the subsequent
`if (ver == VER_NDX_LOCAL) continue;` check silently dropped those
undef references. With the DSO's reference invisible to mold, the
matching object-file definition was never marked as exported, and
at runtime the dynamic loader failed with "undefined symbol".
Restore the old behavior: defined symbols with VER_NDX_LOCAL are
still skipped (they aren't really exposed for dynamic linking),
but undef references are always processed.
Also fix the verdef/verneed merge in the same path. vd_ndx and
vna_other share a single versym index space (a producing linker
assigns them from one counter), so a single vector covers both.
read_verdef and read_verneed are folded into one read_version_strings
that populates that shared-index vector directly, replacing the
buggy `append` concatenation.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
For an undefined symbol in a shared library, mold treated the versym
as VER_NDX_GLOBAL and registered the symbol under its bare name. That
meant an undef foo@VER_1.0 in libbar.so was looked up as just foo, so
a non-default foo@VER_1.0 in another DSO did not satisfy it and
--no-allow-shlib-undefined wrongly reported "undefined symbol: foo".
Parse .gnu.version_r and, when an undef has a versym indexing into it,
register the symbol as name@VERSION so the existing versioned-symbol
resolution path picks up matching definitions.
Fixes https://github.com/rui314/mold/issues/1571
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
`readelf | grep -q` plus `set -o pipefail` is fragile: when grep -q
matches it stops reading immediately, readelf gets SIGPIPE on its next
write, and pipefail propagates the resulting 141. Alpine (musl) hits
this where glibc happens to escape it. Drop -q so grep drains stdin.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
The new StartStopMap parameter contains std::string_view, so the
unqualified call to visit() inside mark() and the recursive call inside
visit() itself were resolved to std::visit by ADL on gcc 10 (Debian 11),
breaking the build.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
A section whose name is a valid C identifier was treated as a GC root,
keeping it (and everything reachable from it) alive even when nothing
referenced its synthesized __start_/__stop_ marker symbols. That could
pull in unrelated code with undefined references and cause spurious
"undefined symbol" errors that lld and ld.bfd don't produce.
Build a multimap of c-identifier-named sections up front and, during
marking, when a relocation targets __start_<name> or __stop_<name>,
mark every section named <name> alive.
Fixes https://github.com/rui314/mold/issues/1571
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Pre-Android-12 (API 30 and earlier) bionic loaders predate the official
ELF gABI assignment of DT_RELR/DT_RELRSZ/DT_RELRENT and use legacy
OS-specific tag values DT_ANDROID_RELR (0x6fffe000), DT_ANDROID_RELRSZ
(0x6fffe001), DT_ANDROID_RELRENT (0x6fffe003), and section type
SHT_ANDROID_RELR (0x6fffff00). With --use-android-relr-tags, mold emits
those instead of the standard ones so binaries linked with
--pack-dyn-relocs=relr can be loaded on those older devices. The flag
is undocumented (not listed in --help) since it's only relevant to
shipping to legacy Android targets.
https://github.com/rui314/mold/issues/1544
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Implements the Android Packed Relocations Format (APS2) used by Android
binaries to compress dynamic relocations. Two new modes are accepted:
`--pack-dyn-relocs=android` encodes all dynamic relocations as APS2 in
.rela.dyn (SHT_ANDROID_RELA) and emits DT_ANDROID_RELA(SZ) instead of
DT_RELA(SZ); `--pack-dyn-relocs=android+relr` is the same but with
R_RELATIVE relocations diverted to .relr.dyn via the existing RELR path.
The encoder groups consecutive relocations sharing offset_delta and
r_info to amortize SLEB128 overhead. The encoded bytes are decoded by
Android's bionic loader and by llvm-readelf, which the new test uses to
verify correctness.
main.cc gains a final set_osec_offsets() call after fix_synthetic_symbols.
Symbol values assigned there change addends for relocations that
reference synthetic symbols, which can shift the SLEB128-encoded section
size; re-running the layout convergence keeps sh_size consistent with
the final encoding.
Fixes https://github.com/rui314/mold/issues/1544
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Previously each chunk wrote its dynamic relocations into a magic offset
in .rel.dyn (tracked via per-chunk reldyn_offset), with a separate
construct_relr pass building per-chunk relr vectors. Now each chunk
returns its dynrels via a virtual collect_dynrels(), and RelDynSection
aggregates them into a single relocs vector that it owns and writes.
RELR-eligibility is determined by checking r_offset alignment of the
collected ElfRel entries (replacing the ABS_REL_RELR kind).
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Generalize the zstd:N syntax added in the previous commit so that the
zlib compression level can also be specified. zlib levels 0 through 9
are accepted, where 0 means no compression. Plain `zlib` remains
equivalent to `zlib:1` (the previous hardcoded default).
Also drop the objcopy/zstdcat round-trip from the zstd-level test in
favor of a simpler readelf check, matching the new zlib-level test, and
add error-message coverage for out-of-range levels.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Change the compression level syntax from zstd-<level> to zstd:<level>
as requested by @rui314 for consistency with lld.
Signed-off-by: Pierluigi Lenoci <[email protected]>
Allow users to specify a zstd compression level (1-22) for debug section
compression via --compress-debug-sections=zstd-N, where N is the desired
level. Higher levels achieve better compression ratios at the cost of
compression speed. The plain 'zstd' option remains equivalent to 'zstd-3'
(the previous hardcoded default).
Closes: https://github.com/rui314/mold/issues/1290
Signed-off-by: Pierluigi Lenoci <[email protected]>
pcaddi computes PC + (imm << 2), so the relaxation from a GOT load
(pcalau12i + ld.d) to pcaddi is valid only when the symbol's address
is 4-byte aligned. Without this check, a reference to a misaligned
symbol was relaxed to a pcaddi that resolves to the address with its
low two bits cleared, producing a wrong pointer.
Fixes https://github.com/rui314/mold/issues/1567
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
The parallel writer that fills (type, cu_idx) tuples for each name
appended entries in thread-scheduling order, so names referenced from
multiple compilation units produced different .gdb_index bytes across
runs. Sort each name's tuple array after the parallel append so the
output is reproducible while preserving parallelism.
Fixes https://github.com/rui314/mold/issues/1566
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Previously, --omagic only flipped segment permissions to RWX but
still page-aligned PT_LOAD segments and emitted PT_GNU_RELRO, so
text and data ended up in separate page-padded segments. This
mismatched the documented behavior ("makes the first data segment
not aligned to a page boundary") and GNU ld's --omagic, which also
disables shared linking.
Make --omagic imply --Bstatic, disable RELRO, and stop forcing
PT_LOAD's p_align to the page size. Also gate chunk->is_relro on
z_relro so a disabled RELRO no longer leaks into address layout.
Fixes https://github.com/rui314/mold/issues/1562
When linking with LTO, archive members extracted pre-LTO to satisfy
references from IR objects could cause false duplicate symbol errors.
This happened in two ways:
1. The pre-LTO check_duplicate_symbols flagged conflicts between LTO IR
objects and regular objects. These conflicts are handled by the LTO
backend and should not be reported.
2. After LTO compilation, archive members that were extracted pre-LTO
retained stale is_reachable flags even when the LTO output superseded
all their symbols. The post-LTO duplicate check then saw both the
archive member and the LTO output defining the same symbol.
Fix both issues:
- In check_duplicate_symbols, skip errors where one side is an LTO IR
object and the other is not. Only IR-vs-IR duplicates need catching.
- In do_lto, reset reachability for archive members before the post-LTO
resolve_symbols so that only genuinely needed members are re-extracted.
Fixes https://github.com/rui314/mold/issues/1421
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
This relocation is used by clang's -fexperimental-relative-c++-abi-vtables
to emit 32-bit GOT-relative PC-relative offsets in vtable entries (e.g. for
RTTI pointers). It resolves to G + GOT + A - P as a 32-bit value.
Fixes https://github.com/rui314/mold/issues/1561
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
This patch fixes two distinct blind spots in how `mold` handles
--no-allow-shlib-undefined checks during the final link phase:
1. The Hidden Static Provider Bug (False Negative)
Previously, if a shared library requested a symbol that was provided by a
static object file with STV_HIDDEN, `mold` would silently overwrite the
global symbol visibility to STV_DEFAULT and rubber-stamp the binary,
resulting in a guaranteed `symbol lookup error` at runtime.
Fix: We now interrogate the raw `elf_syms` array of the static object
provider. If the original visibility is hidden/internal, we flag it as an
invalid provider and throw the error, matching GNU ld/lld behavior.
2. The LTO Shadowing Bug (False Positive)
When LTO is heavily utilized, GCC's plugin may optimize away symbols that
are theoretically provided by static objects if they aren't used internally.
If a shared library still needs that symbol, `mold`'s static state
registered the provider as NULL and threw an error, ignoring the fact
that a loaded DSO (like libc.so) offered a valid public fallback.
Fix: Introduced `has_public_dso_fallback()`. Before throwing an undefined
symbol error, `mold` performs a rapid scan of `ctx.dsos`. If a public
fallback exists dynamically, the error is safely suppressed.
Originally discussed in #1535, the debug data generated by
--separate-debug-file is being corrupted. In the best case scenario,
there's many blank/nameless undefined symbols in the symtab, but in
most production scenario I've encountered, the file is corrupt / gdb
refuses to load it:
$ gdb main
Reading symbols from $PWD/main.dbg...
BFD: /$PWD/main.dbg: invalid string offset 4130582 >= 1345 for section `.strtab'
BFD: /$PWD/main.dbg: invalid string offset 16777228 >= 1345 for section `.strtab'
$ readelf -Ws main.dbg
Symbol table '.symtab' contains 139 entries:
[...]
38: 0000000000000000 0 SECTION LOCAL DEFAULT 38 .strtab
39: 0000000000000000 0 SECTION LOCAL DEFAULT 39 .symtab
40: 0018a80200010000 0xb10000003f071500 NOTYPE LOCAL DEFAULT bad section index[3232] <corrupt>
41: 0000003f07140000 0xfd02000100000cc2 NOTYPE LOCAL HIDDEN bad section index[11500] <corrupt>
42: 02000100000cd300 0x3f0712000008a3 <unknown>: 7 LOCAL PROTECTED [<other>: 3c] UND <corrupt>
43: 3f07110000056202 0x100000cf5000000 NOTYPE LOCAL DEFAULT 1 <corrupt>
This appears to be happening because the strtab size is set to be larger
than its true value, causing garbage data to be placed in it, which
breaks the symtab.
This regression occurred in 5b71029, which fixed a different form of
corruption (the PHDR symbol escaping into the section headers, shifting
the section offsets), but created this strtab corruption. The root cause
is that `compute_section_headers` was moved prior to the wiping of
irrelevant chunks. `compute_section_headers` computes the strtab size by
adding up individual chunk strtab contributions. It needs to occur _after_
the wiping so that the strtab doesn't account for the data before the
wipe.
The core of this patch is really just a partial revert of 5b71029--the
compute_section_headers call appears to have been in the correct place
prior to this change; I've added a test that catches the issue by
intentionally bloating the size of the `.plt` section with many external
symbols. This section is wiped in the dbg data, so by bloating it, the
test can inspect for corruption.
Fixes#1535
Signed-off-by: Alex Karle <[email protected]>
When mold auto-detects an unsupported target from ELF headers,
redo_main() previously called abort() with no context. This replaces
it with a descriptive error message matching the style used by the
-m flag path in cmdline.cc.
Fixes#1553
Interestingly, calling fallocate on tmpfs is very slow. It took
almost a second for a fresh ~5 GiB file, even though intuititvely
it should finish very quickly.
Make Script<E>::get_script_output_type support AS_NEEDED inside an INPUT
or GROUP group, so that the libatomic_asneeded.so linker script used by
GCC 16 works.
Fixes#1545
Signed-off-by: Jonathan Wakely <[email protected]>
This change eliminates the cost of zero-initialization on vector
resizing by using malloc/realloc instead. We also parallelize the
loops to write adddress areas and types to .gdb_index.
We read version script files in cmdline.cc, so we need to handle -C
and --chroot early. Otherwise, a version script passed by command line
can cause a "file not found" error.
By popular request, this commit adds a new cmake variable that allows
building mold with a limited set of supported targets, which can help
reduce build time.
The use of this flag is discouraged unless you build mold frequently
for personal use. Otherwise, build mold with the default settings for
simplicity and maximum compatibility. In particular, distributions
should not build mold with a limited set of supported targets.
Fixes https://github.com/rui314/mold/issues/1364
Read-only data sections containing uninitialized variables are essentially
useless because reading from them always returns 0 at any time, so if they
exist, they are there for a reason. We do not want to automatically
convert it to a BSS.
https://github.com/rui314/mold/issues/1514
Currently, when using --static inside --push-state and --pop-state, we
incorrectly mark the global state as static. This commit changes to the
correct behavior where global state is marked as static only when
--static is used at the outer most (outside of any --push-state,
--pop-state).
Quote from SH-4 CPU Core Architecture manual:
The opcode 0xFFFD is guaranteed to be defined in any SH-4
architecture revision. Other unused opcodes may be treated as
reserved in any particular SH-4 implementation.
The only undefined opcode which the architecture guarantees to cause
a General Illegal Instruction Exception is 0xFFFD.
On 64-bit ARM, mimalloc is built with the compiler flag
`-march=armv8.1-a` by default. This flag advises GCC to make use of
ARMv8.1-A-specific instructions such as `ldaddal`. Machines based on the
older ARMv8-A instruction set - for example the Raspberry Pi 4 -
experience 'Illegal instruction (core dumped)' crashes as a result.
To ensure compatibility with machines that do not support ARMv8.1-A,
disable architecture-specific optimisations by setting the CMake option
`MI_NO_OPT_ARCH` to `ON`.
As of mimalloc 2.2.3, 64-bit ARM is the only architecture affected by
the `MI_NO_OPT_ARCH` option.
It turns out that at least clang-18 fails to compile our code to load
a 32-bit native-endian value from memory using shift + bitwise OR into
a single instruction when compiled with `-march=native`. It instead
compiles the code to complicated vector instructions.
It seems that the good old bswap and memcpy are the best option to
prevent the compiler from overthinking.
I asked ChatGPT how to match multiple glob patterns simultaneously with a
given input string, while avoiding the textbook-style NFA-to-DFA conversion.
Then it suggested that I implement a bitvector-based NFA simulation
algorithm that I wasn't aware. I don't think I could have come up with it
myself easily. This is impressive. ChatGPT is so good at programming and
sometimes much better than me! It may not be oto long before AI writes all
the code for me.
Our glob pattern matcher was an exponential-time implementation.
This can easily be improved by avoiding recursion as explained at
https://research.swtch.com/glob.
I learned that tools supporting compressed sections generally support
concatenated zlib streams within a single compressed debug section.
Examples include GNU/LLVM binutils and libbacktrace. So let's emit
compressed data that way, because it's much easier to do.
It is actually desirable to emit multiple small compressed blocks for
extremely large debug sections, because zlib doesn't support compressed
streams larger than 4 GiB on machines where the long type is 32 bits.
Context: https://sourceware.org/pipermail/binutils/2025-June/141514.html
Chunk<E>::uncompressed_data can be very large as it contains an entire
output section contents. std::vector<u8>::resize() zero-initializes the
contents. We want to avoid that cost.
On my AMD Threadripper 7980X machine, zero-initializing 1 GiB of
memory takes ~200 ms. I can confirm that this change shaves off ~1.2s
for an executable with 5 GiB of debug info sections in uncompressed
form.
Unlike .symtab, .dynsym cannot have an extended symbol table, so a
section index that is larger than 65279 is not representable in an
output.
Fixes https://github.com/rui314/mold/issues/1468
Recent versions of BLAKE3 library can be built with OneTBB for parallelization.
If that is enabled, we don't want to build and link our bundled OneTBB to avoid
conflict.
Fixes https://github.com/rui314/mold/issues/1466
I believe it is unlikely that sections such as .debug_line grow beyond
4 GiB to cause relocation overflows, but doing some extra work is
trivial, so just do it.
Previously, if the combined size of DWARF32 and DWARF64 debug
information exceeded 2^32 bytes, a relocation overflow could occur.
Since not all static libraries are built with -gdwarf64, this overflow
issue prevented some very large programs from using static libraries
built without -gdwarf64. Recompiling everything with -gdwarf64 is the
"right" way to fix it, but that's not always feasible for various
reasons.
This commit mitigates the issue.
By sorting the input .debug_info sections so that DWARF32 debug info
comes before DWARF64, a relocation overflow can occur only if the size
of DWARF32 debug info alone exceeds 2^32 bytes.
A call to weak undefined symbol could be relaxed as long as the symbol
could be promoted to a dynamic symbol, where the relaxation is done
against the known PLT address and the address of the symbol itself
doesn't really matter.
Signed-off-by: Yao Zi <[email protected]>
Previously, symbols specified with the `global:` label were unversioned
even if --default-symver was given. Now, such symbols are versioned with
the output file's soname.
Fixes https://github.com/rui314/mold/issues/1448
It looks like `podman build` command does not push the newly created
image with the `latest` tag to ghcr.io, so the following `podman run`
pulled an older image. That caused an issue that the scirpt created
a different tarball than the one on my local machine.
I don't know what is the "proper" fix, but it looks like adding
`--pull=never` to `podman run` forbids it from pulling an older image
from ghcr.io, solving the issue.
If a versioned symbol is a default one, it can be referred to with two
names, e.g. `foo` and `foo@VERSION`. Previously, we forward
`foo@VERSION` to `foo` if it is undefined, but it didn't always work
correctly. Now, we resolve both `foo` and `foo@VERSION` as usual and
then forward `foo@VERSION` to `foo` afterwards.
Fixes https://github.com/rui314/mold/issues/1169
mimalloc 2.2.2 started using __DATE__ and __TIME__ macros which make
the output non-reproducible. We can set the SOURCE_DATE_EPOCH environment
variable to fix the "current" timestamp.
Before 9306f69a61, we completely ignored
duplciate symbol errors in IR object files. After that commit, we've
been accidentally reporting duplicate comdat symbols as errors.
Fixes https://github.com/rui314/mold/issues/1434
It looks like Linux is going to support CET only on x86-64 (i.e. endbr64),
and it is very unlikely that x86-32 will get CET support. So I'll remove
the instruction from the code we generate for x86-32.
Removes the temporary file created by the compiler
from the dependency list created with --dependency-file.
Without this build systems will never report "Nothing to do",
but always do a re-link of the final object.
R_X86_64_CODE_5_{GOTPCRELX,GOTTPOFF,GOTPC32_TLSDESC} and
R_X86_64_CODE_6_{GOTPCRELX,GOTPC32_TLSDESC} are defined just for
completeness (*), so we've implemented all APX relocations with
this change.
* https://gitlab.com/x86-psABIs/x86-64-ABI/-/merge_requests/53
These relocations are for Intel APX (Advanced Performance Extension)
that extends the number of general-purpose registers from 16 to 32.
GNU assembler that comes with GCC 14 or newer can compile the test code.
I believe this is just due to the lower number of users on ARM64
runners compared to x86. However, based on my measurements, ARM64
builds all binaries in 1h30m, whereas x86 takes 2h30m.
We need to stop using `grep -q` because grep terminates immediately after
finding the first match with `-q`, causing SIGPIPE to the process on the
other side of the pipe.
Previously, these symbols were defined relative to a section. That is
logically correct, because the runtiem is supposed to apply IREL
relocs between `__rela_iplt_start` and `__rela_iplt_end`, so their
actual locations shouldn't matter if there's no IREL relocs (i.e. they
refer to the same location.)
However, under some circumstances, glibc reads `__rela_iplt_start`
before applying base relocations and `__rela_iplt_end` after applying
them due to compiler instruction reordering.
Therefore, we need to make them absolute symbols that don't require
base relocations.
Fixes https://github.com/rui314/mold/issues/1407
If both --separate-debug-info and --compress-debug-info are given, we want to
compress debug sections in background so that the delay caused by compression
is not visible to users.
I didn't know if Azure ARM64 servers support ARM32 instructions, but
apparently they do because running ARM32 code is much faster on ARM64
GitHub Actions runners.
Previously, 4 bytes were allocated for each relocation in a text
section to store the number of bytes removed before each relocation.
Now, only the locations where bytes are removed are stored.
This commit improves performance by ~4% when linking clang-19 for RISC-V.
Previously, we allocate 4 byte struct for each relocation to store a
reference to a thunk in case the relocation needs to branch to a thunk.
Most references are null because most relocations don't need thunks.
This commit changes how we manage references to range extension thunks.
Now, thunk addresses are stored to symbols rather than to relocations.
Linking clang-19 for ARM64 is now ~7% faster than before.
ELF Application Binary Interface s390x Supplement Version 1.6.1, p.58 says
that "The doublewords at _GLOBAL_OFFSET_TABLE_[1...2] are reserved for
system use."
We had previously reserved GOT[0] and GOT[1], but we did not reserve GOT[2].
It's most likely that GOT[2] is not actually in use, as it appears that
mold-linked programs work without it. But we need to adhere to the psABI.
On RHEL 10, GCC is configured with the `--with-arch_64=x86-64-v3`
option, which makes it implicitly use the `-march=x86-64-v3`
command-line flag by default.
This breaks the `arch-x86_64-ifunc-alias` test: since the `x86-64-v3`
microarchitecture level includes SSSE3 and AVX2, GCC appears to
interpret the `default` and `ssse3,avx2` targets as the same thing, and
omits generating a resolver function.
Fix the test by forcing GCC's target architecture to the `x86-64`
baseline.
Switch from Docker to Podman for container builds and remove unused
buildx steps. Also this commit updates GitHub Actions workflows to use
redhat-actions/podman-login and docker/setup-qemu-action@v3, removing
references to Docker Buildx.
Fixes: ec1d7120aa ("Use docker only on GitHub Actions")
Signed-off-by: Ruowen Qin <[email protected]>
Debian 11 ships GCC 10 and CMake 3.18, which happen to be precisely the
minimum versions required for building mold from source.
Add a CI pipeline to prevent future commits from inadvertently breaking
compatibility with older toolchains.
Linux upstream has reverted commit 2a010c412853 ("fs: don't block
i_writecount during exec") and the change is merged in the 6.13 cycle.
This means the executable overwriting behavior can be restored on Linux
kernel version 6.13 or later.
Note that there is probably not much we can do for 6.11 / 6.12 kernels.
Although the reversion is backported, many distros do not use upstream
sub-version numbers which means we cannot determine whether the kernel
has this reversion based on its version number.
Signed-off-by: Jinghao Jia <[email protected]>
Even though 16 KiB is the common page size, the Linux kernel for
LoongArch supports 64 KiB pages. Currently, mold-generated binaries
run only on systems with <=16 KiB page.
Note that unlike GNU ld, increasing the maximum page size doesn't
increase the on-disk binary size of generated binaries.
This is an alternative to https://github.com/rui314/mold/pull/1383
The upcoming version of GNU binutils's ld now treats arguments for
`--package-metadata` as percent-encoded strings and decodes them before
writing to .note.package.
Additionally, GNU ld will support `%[<charname>]`, where `<charname>` can be
`comma`, `lbrace`, `rbrace`, `quot`, or `space`. This feature could improve
readability when manually writing command-line arguments.
That said, I don't believe we need to support these metacharacters, for the
following reasons:
1. This option is specifically designed for distribution package maintainers
to embed metadata into binaries, and is not intended for general use.
2. Command-line arguments for this option are always generated by build
scripts, not written manually.
If we need these character names, we can add that later.
Furthermore, GNU ld does not appear to report errors for orphan '%'
characters that are not followed by valid hex values; instead, it simply
passes through such characters. I find this behavior fragile. We will report
an error for invalid '%' characters.
With these considerations in mind, I'll just rename the existing
`--encoded-package-metadata` option to `--package-metadata`.
In case ctest fails in the CI, no error logs are printed. This makes
debugging harder.
So run ctest with `--output-on-failure` to ease debugging in case of
failures.
Combining mimalloc and AddressSanitizer triggers a segmentation fault in
many tests.
Combining mimalloc and ThreadSanitizer leads to duplicate symbols for
the `new` and `delete`
operators, causing the build to fail.
Example message from a failed GitHub Actions build:
```
mold: error: duplicate symbol:
third-party/mimalloc/libmimalloc-debug.a(alloc.c.o):
/usr/lib/llvm-18/lib/clang/18/lib/linux/libclang_rt.tsan_cxx-x86_64.a(tsan_new_delete.cpp.o):
operator delete(void*, std::align_val_t)
```
This allows the compiler to make sure that the implementation of
`set_mimalloc_options()` matches its declaration.
More importantly, it indirectly pulls in `config.h`, where the macros
`MOLD_USE_SYSTEM_MIMALLOC` and `MOLD_USE_MIMALLOC` are conditionally
defined. Without these, the build configuration is ignored.
We don't need to create a copy of license files for third-party software
as we keep an original license file for each subdirectory. It is probably
a bit risky to create copies of license files because it is easy to become
out-of-sync. So I'll just remove this file.
If running in big-endian mode, SH4 processors expects both data and
instructions to be encoded in big-endian. Therefore, nop need to be
reversed for sh4be.
This commit adds experimental big-endian SH4 support. Since we already
support little-endian SH4, it wasn't too difficult to support its big-
endian variant.
Some tests related to C++ exceptions fail due to mysterious errors.
I don't have time to debug it, so I'll disable them for now.
Closes https://github.com/rui314/mold/issues/1273
Including mimalloc-new-delete.h overrides new and delete operators
with mimalloc. If a user choose not to use mimalloc, we shouldn't
include that file.
https://github.com/rui314/mold/issues/1355
RHEL 8.7's version of dwarfdump (2018-01-29) is too old to recognize the
SHF_COMPRESSED flag in debug info, so the compress-debug-sections test
fails despite the debug sections being compressed as intended.
To fix this, we will use readelf to dump information about the
executable. The COMPRESSED flag can be verified using this.
It looks like only the presence of the entry is important, but by
convention, GNU ld and lld set 0 to a DT_AARCH64_VARIANT_PCS dynamic
entry, so we should follow suit.
Keeping DEC Alpha support is difficult because there are no Alpha
machines available for toolchain developers, and even if there were,
no modern Linux distros still support Alpha. For example, Debian
dropped Alpha support in Debian 6.0. As a result, I cannot test my
linker with real-world programs.
In fact, I believe mold has never been able to link real-world large
programs. Object files for Alpha are compiled with the small code
model by default, so it is not easy to support it in the linker.
There's no means to verify that my implementation is correct for Alpha
because Alpha's psABI has never been published by DEC. The most
"reliable" source of correctness is GNU ld's source code and comments.
Last but not least, I believe there are literally zero mold/Alpha
users. Unlike some other retro computers like the m68k, Alpha doesn't
seem to get much love from the community, perhaps because the
availability of Alpha machines was limited in the first place, even in
the 90s.
Therefore, I'll remove Alpha support now. If there's someone who wants
to keep it, we can resurrect and re-test it.
This change should make the mold binary size a little smaller.
This commit reverts 0612ea41f1 and
instead solves the original issue with a different approach.
Now, if a hidden symbol was resolved to a DSO symbol, we'll redo
symbol resolution so that the symbol won't be resolved to the same one
next time.
This should fix our build-all buildbot.
Assume both `foo.a` and `bar.so` define the same symbol `baz`.
If `baz`'s symbol visibility is hidden, it needs to be resolved within
the output file, i.e., from `foo.a`. However, previously, such symbol
was resolved to the one in `bar.so`.
To fix the problem, we'll lower the symbol priority for DSOs.
Fixes https://github.com/rui314/mold/issues/1342
Word-size absolute relocations, such as R_X86_64_64, are special from
the linker's point of view because only such symbols can be promoted
to dynamic symbols. This patch handles them separately.
* Fix infinite loop in `split_by_comma_or_colon()`.
The mere presence of a `,` or a `:` in the input string would make the
function enter an infinite loop, effectively freezing the program until
all available memory was exhausted. The string position in the loop was
not incremented past the `,` or the `;` character that had just been
read.
The function is currently used only in the `--exclude-libs` option. The
freeze is reproducible with:
```bash
$ mold --exclude-libs foo,bar
```
The longer the string after the first `,` or `:` is, the faster memory
exhaustion is reached.
* Skip empty parts in `split_by_comma_or_colon()`.
The LoongArch extreme code model uses the following instructions to
materialize a 64-bit value in a regiter.
pcalau12i $t1, %pc_hi20(g2)
addi.d $t0, $zero, %pc_lo12(g2)
lu32i.d $t0, %pc64_lo20(g2)
lu52i.d $t0, $t0, %pc64_hi12(g2)
The previous relocation formula expected that `page(pc)` would be
consistent for all the instructions, but this assumption is incorrect
if the instruction sequence crosses a 4 KiB boundary.
Now, the LoongArch psABI requires that the machine instructions must be
consecutive, and the relocations for lu32.d and lu52i.d uses
`page(pc - 8)` and `page(pc - 16)` instead of `page(pc)`.
This psABI change gave me an impression that the LoongArch's extreme code
model was poorly designed and inadequately tested. If these instructions
must be consecutive, only a single relocation referring to the beginning
of the instructions would suffice, which relocates all the following four
instructions at once.
Maybe we do not need to support the relocations for the extreme code model
because the code model was buggy at the spec level, which suggests that no
one is using them. But I'm not sure if it is safe to remove them, so let's
just follow the psABI change.
Old zlib does not support crc32_z. The difference between the two is
that crc32() takes an input size as an unsigned int while crc32_z()
takes as size_t. For our use case, size is guranteed to be short, so
crc32() is safe to use.
It looks like emitting PCADDU18I + JIRL instead of just BL for function
calls is a way to go, so we don't want to bother to maintain range
extension thunks for the psABI.
Since no other linkers support range extension thunks for LoongArch other
than us, removing the support should be OK.
Previously, functions that are referred to only by section symbols were
not considered address-taken, and we always rewrote endbr64s with NOPs for
such functions. That caused a runtime fault when such function was called
indirectly.
Now, endbr64s for such symbols are retained.
I tried to build clang-19 as a release build with and without `-z
rewrite-endbr` and counted the number of endbr64 instructions in each
binary. Here is the result:
Before: 110,615
After: 91,799
So the feature reduces the number of gadgets by 17%. Both binaries worked
fine with Intel SDE CPU emulator with `-cet 1 -cet_raise 1`, so I think
it's finally working as expected.
I also tried to build mold itself with and without the feature. Here is
the result:
Before: 27,430
After: 17,725
This is a 35% reduction. I confirmed that mold built with `-z rewrite-endbr`
can self-host under Intel SDE.
The rewrite_endbr pass is extremely fast. It took only 7 milliseconds
for a ~210 MiB clang-19 binary on my Threadripper 7990X machine. We
may want to consider enabling it by default at some point.
glibc's `__register_frame_info_bases` expect that it can read a terminator
if a .eh_frame is empty. Removing the section completely caused a crash bug
on Alpine Linux with musl as libc.
Specifically, mold-generated binaries crash if all the following
condition were met:
1. musl is used as a libc
2. .eh_frame is empty
3. libgcc_s.so is linked
Prior to 06b592683c, condition (3) was not
usually satisfied, so we didn't notice this issue. Now, the shared library
is linked against almost all output files because the file is referenced by
`__deregister_frame_info`.
https://github.com/bminor/glibc/blob/910aae6e5a2196938fc30fa54dd1e96f16774ce7/sysdeps/generic/unwind-dw2-fde.c#L105-L107
Fixes https://github.com/rui314/mold/issues/1309
Specifying the compiler flag `-Wl,--package-metadata=<JSON>` might not
work, because the shells might eat the quotation marks and the compiler
splits the JSON at the commas.
Ubuntu tried to using a specs file to set `--package-metadata` but that
turned out to be too fragile. autopkgtests might use the compiler flags
but the needed environment variables are not set in the test
environment. Debugging a crash of an application build with the -spec
parameter lacks the environment variables. People like to iteratively
continue building the software in the build directory while hacking on
the package and then have no environment variable set.
So introduce a `--encoded-package-metadata` linker flag that takes a
percent-encoded JSON. Percent-encoding is used because it is a
standard and simple to implement.
Bug-Ubutru: https://bugs.launchpad.net/bugs/2071468
Signed-off-by: Benjamin Drung <[email protected]>
Previously, we find a string in a mergeable string section by offset
after adding a relocation addend to a symbol value. Now, we find a
string just by a symbol value and then add a relocation addend later.
Assume a mergeable section containing two strings as an example.
foo\0bar\0
^ ^
.L1 .L2
Previously, a relocation referring to .L2 - 1 referred to the end of
.L1. Now, it refers to one byte before .L2.
Section symbol is an exception to this rule, because it seems it's
what the compiler is assuming. The rules seem arbitrary, but it looks
like we need them for the compatibility with existing object files.
Fixes https://github.com/rui314/mold/issues/1300
This option is to separate debug info to a different file. The debug
info file's filename is stored to the main output file's .gnu_debuglink
section. gdb can read the section contents and followg the link to
find debug info in another file.
Fixes https://github.com/rui314/mold/issues/1294
It is not clearly defined when undefined weak symbols are resolved.
It looks like there are two possible approaches:
1. Promote all weak undefined symbols to dynamic ones so that they'll
have another chance to be resolved at load-time, or
2. Promote weak undefined symbols to dynamic ones only when there are
definitions in other DSOs at link-time.
(1) provides the maximum flexibility. For example, consider a main program
that has a weak undefined symbol `foo` and there's no DSO that defines it
at link-time. In (1), `foo` gets promoted to a dynamic symbol, so that one
of its depending DSO is upgraded to define `foo`, the main executable's
`foo` is resolved to that symbol at load-time. On the other hand, in (2),
`foo` would have already been converted to an absolute symbol at address
zero at link-time, so you need to rebuild the main executable to use the
new definition of `foo` in the shared library.
However, (1) is not compatible with copy relocations. This is because we
need to know the size of the symbol when creating a copy relocation, but
that information is not available unless we have a definition. It's also
not compatible with canonical PLTs because canonical PLTs have non-zero
addresses and therefore weak undefined symbols would always be resolved to
non-zero addresses.
As a workaround, GNU ld promotes weak undefs to dynamic symbols only when
they don't need copy relocations or canonical PLTs. In other words, weak
undef's behavior is different between -fPIC and -fno-PIC. In the former
case, they become dynamic symbols, and vice versa.
I don't think that workaround is a good one. So, mold took the second
approach.
There is, however, another thing to consider. What if we can find a
defined symbol in a DSO that is specified as `-as-needed`? Previously,
mold did not mark the library as "needed" and converted the weak undef
into an absolute symbol.
However, libstdc++ assumes that if weak undef symbol
`__pthread_key_create` is not resolved, it assumes that multi-threading is
not used in the executable, which resulted in a mis-detection with mold.
Therefore, this patch changes the mold's behavior so that it makes weak
undefs to keep DSOs "needed".
Fixes https://github.com/rui314/mold/issues/1286
Use $CXX instead of $CC to detect whether C++ programs could be built
statically. Add function test_cxxflags for convenience.
Fix test error in an environment where C++ programs cannot be built
statically, with error like:
mold: fatal: library not found: c++
Signed-off-by: Yao Zi <[email protected]>
The interaction between these flags are unnecessarily complicated,
but it looks like `--undefined=ignore-in-object-files` needs to
share the same internal flag as `-z defs` so that they override
each other.
Fixes https://github.com/rui314/mold/issues/1270
Teaching the compiler that `buflen` cannot be negative and also doesn't
take a large value allows it to optimize code a little bit more than
it currently does.
I couldn't find any report that the reduced round version is broken
despite being used at a lot of places (e.g. Rust's hash table uses
SipHash-1-3). So I assume it's secure.
`which` isn't in POSIX and several Linux distributions are trying to
remove it from their base system, see e.g. https://lwn.net/Articles/874049/.
Just use `command -v` which is POSIX.
Signed-off-by: Sam James <[email protected]>
We use Aho-Corasick string search algorithm for version script.
If all symbols in a version script do not start with '*', we can
terminate the algorithm early.
I believe some version of objcopy corrupts an object file when
renaming a section. In this change, I use sed instead of objcopy
as a workaround.
Fixes https://github.com/rui314/mold/issues/1244
It looks like Clang recognize `#pragma GCC unroll`. Instead of adding
more C preprocessor directives here, I'd like to rely on the default
compiler optimizations.
If a synthesized symbol overrides an imported symbol, the symbol's
`is_imported` flag must be reset.
Technically, `compute_import_export` is called too early, and this
function call should be moved after `create_synthetic_sections`.
However, fixing the issue that way was very hard because until we
create output sections, we don't know what __start_/__stop_ symbols
need to be synthesized, but in order to create output sections,
we need to compute import/export bits.
So, in this commit, we simply reset `is_imported` flag for
synthesized symbols.
https://github.com/rui314/mold/pull/1236
Previously, .so and .a were of the same priority and therefore symbols
in those were resolved based on their positions in the command line;
whichever file appears first in the command line took precedence.
Here is the problem we are trying to solve with this change: KiCad
passes unnecessary .a files to the linker along with some .so files.
Some symbols are defined both by .a and by .so.
If a symbol is resolved from .a, the linker pulls out the file from
the archive, but because the .a file does not really provide a
complete set of object files, it ended with "undefined symbol" error.
If a symbol is resolved from .so, everything is fine.
This is arguably a bug in KiCad, or at least depending on the order of
files in the command line is very fragile. But maybe this change could
fix the issue without too much side effects. So let's see how it goes.
Fixes https://github.com/rui314/mold/discussions/1234
RISC-V psABI requires that symbol to be exported from an executable
if there's a GP-relative reference. For simplicity, we always export
it from executable as long as it has a .dynamic section.
https://github.com/rui314/mold/issues/1222
Runtime doesn't care where mergeable sections are, but it's reported
that Valgrind does. So, grouping mergeable sections just doesn't seem
to make sense.
https://github.com/rui314/mold/pull/1223
libtool mistakes mold 2.4.1 as GNU ld 2.4 and wrongly concludes that our
linker does not support anonymous versioning and suppresses some uses of
version scripts. That has been causing subtle compatibility issues with
programs that use libtool to create their .so files.
Here is the code that mistakes our linker as GNU ld:
https://git.savannah.gnu.org/cgit/libtool.git/tree/m4/libtool.m4?h=v2.4.7#n5066
As a workaround, I decided to bump our linker version so that the version
number is sufficiently large. This is admittedly ugly but I think it's the
simplest solution for the problem.
Our ConcurrentMap uses linear probing to find unused hash table entry.
It gives up if 128 consective slots are occupied, and the whole process
dies with the "ConcurrentMap is full" error message. So our hash
function's quality must be high.
For .gdb_index, we used to use gdb_index() to compute keys for the
ConcurrentMap. It turned out that the function's quality is poor,
generating very similar output for short strings.
This commit changes the hash function to xxhash.
Fixes https://issues.chromium.org/issues/40276991#comment5
Previously, mold marked an imported symbol as a strong one if the
symbol came from a DSO and was exported as a strong symbol by the DSO.
This logic resulted in a miscomputation of the weakness bit, causing a
compatibility issue with other linkers.
Now, an imported symbol is marked as strong only when there's at least
one strong reference to it. In other words, if all references to an
imported symbol are weak, the symbol will be imported as a weak one.
Fixes https://github.com/llvm/llvm-project/issues/83080
Prior to this change, links on Windows were failing with:
mold: fatal: <input file>: CreateFileMapping failed: 5
"5" is the Windows error code for ERROR_ACCESS_DENIED.
Use native Windows file API in MappedFile. Create read only mapping with
FILE_MAP_COPY view because the file mapping contents may be modified in
process but should not be written back.
On Windows, it is important that files are opened in "binary" mode to
avoid automatic "text" mode conversions which can result in "corrupted"
input/output.
Previously, mold didn't merge sections with the same name but different
section flags into a single section. This behavior is different from other
linkers and caused a compatibility issue.
Now, mold merges input sections by their names and types.
Fixes https://github.com/rui314/mold/issues/1196
std::string is a non-trivial type, this change ensures its
copy-ctor and dtor don't get called.
Found by Clazy (range-loop-reference)
Signed-off-by: Christoph Grüninger <[email protected]>
illumos is a descendent of Solaris/OpenSolaris, and treats
symbols with shndx=SHN_ABS and st_value=0 specially, such that
they should be marked imported. This change adds a special
case for this behavior, wrapped up in a function that #ifdef's
out on other platforms. With this patch, executables linked
with mold on illumos run; without it, they do not.
The original behavior was spotted by Luqman Aden (@luqmana).
This also patches a small bogon in the third-party `mimalloc`
code. I've sent that same fix upstream, but so far the
maintainer has not resonded, so replicate here.
Fixes#1183
Signed-off-by: Dan Cross <[email protected]>
When removing symbols, the indices in .llvm_addrsig also needs to be
updated, but binutils is not aware of this and leaves the table in a
corrupt state.
Use the same test as what LLD uses to reject potentially corrupted addrsig
sections.
Unfortunately, testing this seems tricky. While producing a file with OOB
addrsig index is not hard, it's hard to consistently trigger a segfault
with that access.
Signed-off-by: Tatsuyuki Ishi <[email protected]>
Previously, if two or more VERSION clauses match to the same symbol,
the first one took precedence. This was incompatible with GNU ld, which
gives the last one the highesth priority.
This change inverted the priority so that the last one will take
precedence other the others.
Fixes https://github.com/rui314/mold/issues/1158
Using ftruncate() creates holes inside files, that on first (write) page
fault gets allocated an actual disk block. There are several
disadvantages with this:
- Allocating block one-by-one is slower than doing it all at once.
- As we write to the output in parallel the write order is not exactly
sequential. The allocated blocks can also be out-of-order and make the
on-disk representation fragmented.
Instead, use posix_fallocate() which allocates a contiguous on-disk block
(if possible). It's arguably a better fit for linker output, since holes
are unlikely to be present after writing all the output (any contiguous
zeroes should just go into .bss).
I benchmarked this by linking mold with debug symbols. On an 8-core
desktop, it is 5% (1.0s -> 0.95s) faster, but on a 64-core workstation it
is actually 40% slower (0.37s -> 0.52s). The total amount of System time
is reduced (6.7s -> 5.2s), however. I suspect that the reduced page fault
work ends up causing more lock contention.
Despite potential regression on high-core systems I still think this
change is justified because of the better on-disk layout.
Signed-off-by: Tatsuyuki Ishi <[email protected]>
Previously, we always tried to demangle a symbol name as a Rust
symbol first and then attempted to demangle it as a C++ symbol.
This resulted in an incorrect demangled result, as the Rust legacy
mangling scheme is not distinguishable from C++.
This patch fix the issue by adding the "is_rust_obj" flag to the
ObjectFile. We try to demangle a symbol as a Rust one before as a C++
only if the flag is true.
Fixes https://github.com/rui314/mold/issues/1159
A `.eh_frame` section contains data for exception handling. Usually,
an object file contains only one `.eh_frame` section, which explains
how to handle exceptions for all text sections in the same object file.
However, it appears that, in rare cases, we need to handle object
files containing multiple `.eh_frame` sections. An example of this is
the `/usr/lib/clang/17/lib/x86_64-redhat-linux-gnu/clang_rt.crtbegin.o`
file, which is provided by the `compiler-rt` package of Fedora 39.
Specifically, I'm using the `quay.io/fedora/fedora:39` Docker image.
The file contains two `.eh_frame` sections.
One `.eh_frame` in the file is of type `STT_X86_64_UNWIND` and the
other is of `STT_PROGBITS`. It's possible that the file was created
with `ld -r`, and the linker failed to merge the two incoming
`.eh_frame` sections into one output section due to the difference in
section types.
We did not expect such inputs and consequently produced corrupted
output files.
This commit improves our linker so that mold can handle multiple
`.eh_frame` sections in a single object file.
Fixes https://github.com/rui314/mold/issues/1157
Previously, symbol names or wildcard patterns in version script files
were processed strictly from top to bottom. When a single symbol
matches two or more patterns, the first one was given precedence.
However, GNU ld appears to treat exact matches differently, assigning
them higher precedence over wildcard patterns. This discrepancy led to
programs linked with Qt 6.6.1 using mold failing to launch.
Fixes https://github.com/rui314/mold/issues/1158
It had been assumed that the TLSDESC code seqeunce looks like this:
lea 0(%rip), %rax
R_X86_64_GOTPC32_TLSDESC foo
call *(%rax)
R_X86_64_TLSDESC_CALL foo
However, LLVM seems to also emit something like this:
lea 0(%rip), %reg
R_X86_64_GOTPC32_TLSDESC foo
...
mov %reg, %rax
...
call *(%rax)
R_X86_64_TLSDESC_CALL foo
That means when we rewrite the LEA instruction to relax the code
sequence, we need to handle destination registers other than %rax.
The wrong assumption caused a program crash as reported as
https://bugs.gentoo.org/914849.
This is a new experimental flag to make room at the end of PHDR
so that post-processing tools can add more entries as needed.
Fixes https://github.com/rui314/mold/issues/1148
Debian 8 was released in April 2015, which is old enough for
binary distribution. Previously, we used to use Debian 10 (Bullseye),
which was released in July 2019.
--dynamic-list, --export-dynamic-symbol and --export-dynamic-symbol-list
have different semantics for executables and DSOs. If the output is an
executable, they specify a list of symbols that are to be exported.
If the output is a shared object, they specify the list of symbols that
are to be interposable.
mold havne't implemented the latter semantics. This commit fixes that
issue.
Fixes https://github.com/rui314/mold/issues/1071
.init_array/.fini_array haven't completely replace .ctors/.dtors, so
we need to convert .ctors/.dtors to .init_array/.fini_array.
Fixes https://github.com/rui314/mold/issues/1113
Previously, we didn't emit a dynamic relocation for a statically-
initialized function pointer pointing to an ifunc on POWER10, e.g.
int fn() __attribute__((ifunc("resolve_fn")));
void *ptr = fn;
Fixes https://github.com/rui314/mold/issues/1142
For --relocatable output, we need to pass through these attributes
for correctness. With this change, we'll have more .rodata sections
such as .rodata.str1.1, rodata.str1.4, etc. in the final executable,
but that should be fine because the runtime doesn't care about
section tables at all. The table isn't even loaded to memory at runtime.
IFUNC symbols are resolved at process startup by executing the function
that the symbol points to. This is used to select the "best" function at
runtime; for instance, the runtime may choose a faster version of memcpy
that uses SIMD instructions if they are available on the current system.
Thus, an IFUNC symbol has two addresses: the initial address (or the
resolver's address) and the resolved address, which is the return value of
the resolver.
In position-independent executables (PIEs), function pointers are loaded
from the GOT indirectly, and symbols are not directly referenced. In such
executables, the initial value of the GOT slot for an IFUNC symbol
contains the resolver address, and this is overwritten at runtime to the
resolved address upon process startup. When user code takes a pointer to
an IFUNC, it always reads the resolved address from GOT.
In contrast, position-dependent executables (PDEs) may have instructions
that directly refer to an IFUNC symbol, such as movabs on x86-64. The GOT
entry for an IFUNC holds the resolved address, so any direct reference
must also produce the resolved address to maintain pointer equality.
(C/C++ standards require that two pointers must be equal if and only if
they are taken for the same symbol.)
Previously, we emitted text relocations to modify instruction operands.
However, text relocations are undesirable and not always reliable. For
example, on ARM64, multiple instructions are used to materialize a
symbol's address, and it's not feasible to issue a dynamic relocation to
alter those instructions since the dynamic loader generally can only
modify 32-bit or 64-bit words.
In this commit, I have adopted a different strategy. An IFUNC symbol now
occupies two consecutive GOT slots in a PDE. The first slot holds the
symbol's PLT address, and the second slot holds the resolved address. The
PLT address is consistently used as the symbol's address throughout the
process, while the second slot is used only by the PLT entry to jump to
the resolved address.
This method ensures pointer equality without the need to emit text
relocations for IFUNC symbols in PDEs.
It seems R_LARCH_PCALA_LO12 is sometimes used against JIRL even though
such use case is incorrect as per the psABI.
At least, `/usr/lib/loongarch64-linux-gnu/libc_nonshared.a(atexit.oS)`
on `cfarm400.cfarm.net` contains such relocation.
We need this patch to self-host mold on that cfarm machine.
Specifically, .llvm.call-graph-profile is always REL even on x86-64.
We can safely ignore the relocation table because it contains only
R_NONE relocations.
Fixes https://github.com/rui314/mold/issues/653
The test expects the output format given by `llvm-readelf`:
```
5: 0000000000010698 0 FUNC GLOBAL DEFAULT [VARIANT_PCS] 13 foo
```
However, in GNU's `readelf` implementation the symbol name comes first:
```
5: 0000000000010698 0 FUNC GLOBAL DEFAULT 13 foo [VARIANT_PCS]
```
Replace the regular expression with two `grep` calls piped together to
support both output formats.
Signed-off-by: Christoph Erhardt <[email protected]>
`test_cflags` uses $CC to test if a given flag is supported, so
we should use $CC instead of $GCC for the rest of the file.
Fixes https://github.com/rui314/mold/issues/1128
This reverts commit 967006479f because
it made the linker to crash. TLSDESC doesn't seem to work even with
GNU ld on ARM32 musl systems, so I'll disable the test.
Fixes https://github.com/rui314/mold/issues/1116
Despite the extern template items Clang 14 seems to insist on instantiating
elf_main(), with the hilarious consequence of the function being duplicated
quadratically to the number of archs.
Move this to a different CU so that the compiler cannot possibly
instantiate it more than once. This significantly speeds up compilation
related to main.cc.
Signed-off-by: Tatsuyuki Ishi <[email protected]>
Despite the extern template items Clang 14 seems to insist on instantiating
elf_main(), with the hilarious consequence of the function being duplicated
quadratically to the number of archs.
Move this to a different CU so that the compiler cannot possibly
instantiate it more than once. This significantly speeds up compilation
related to main.cc.
Signed-off-by: Tatsuyuki Ishi <[email protected]>
Compiler warnings have the potential to uncover real bugs before they
make it into a commit. For example, the accidental fallthrough
introduced by commit 713d81947a and fixed
by commit 49f38828d1 could have been
detected by GCC.
Remove `-Wunused-variable` since it is implied by `-Wall`.
Add `-Wno-unused-parameter` to suppress warning spam regarding unused
parameters.
Signed-off-by: Christoph Erhardt <[email protected]>
This fixes the following compiler warning:
> enumerated and non-enumerated type in conditional expression [-Wextra]
Signed-off-by: Christoph Erhardt <[email protected]>
endbr64 is a relatively new x86 instruction to mark a location to which an
indirect jump instruction can transfer control. If the control-flow
integrity is turned on (i.e., endbr64 is effective), an indirect jump can
only target an endbr64 or raise a runtime exception. This mechanism makes
some types of control hijacking attacks, such as ROP or JOP, very hard
because an attacker can no longer jump to an arbitrary location.
If `-fcf-protection` is given, GCC emits an endbr64 at the beginning of
each global function conservatively because the address of that function
might be used by other translation units.
In reality, most function addresses aren't taken, so the conservative
assumption ends up emitting lots of unnecessary endbr64 instructions.
This not only bloats the code but also weakens security because an
attacker would have more locations to attack.
The new linker option, `-z rewrite-endbr`, is designed to mitigate this
situation. The linker is in a position to conduct whole-program analysis
on the input files and determine functions whose addresses have never been
taken. If `-z rewrite-endbr` is given, mold performs that analysis and
rewrites the leading endbr64 with a nop if the function's address wasn't
taken.
For evaluation, I built the mold executable itself both with and without
this option. Before using the `-z rewrite-endbr` option, the mold
executable contained 25,994 endbr64 instructions. With the option, the
number becomes 18,035, a 30% reduction.
The new option is exprerimental; we may remove it in a future versions of
the mold linker.
For example, when the leader's instruction is removed and a follower is right
next to it, the follower's relocation can point to the follower's address
itself. In this case the leader's relocation may lie before or after the
follower, so just search for both.
Signed-off-by: Tatsuyuki Ishi <[email protected]>
I made a fair amount of effort to try to support MIPS, but it turned out
that it is much harder than expected. I punted it instead of making
further efforts.
The problem is the MIPS ABI is hostile to the linker in the modern
environment. MIPS object files are still compiled for the small code
model in which GOT entries and data in the small data area are expected
to be accessible with a single machine instruction with a 16-bit
displacement. In other words, .got/.sdata/.sbss are expected to be
smaller than 64 KiB. This might have been a reasonable assumption in the
90s, but it's not suitable for modern applications that can be 1000x
larger than the binaries in the 90s.
MIPS requires the linker to implement tons of workarounds for its legacy
ABI assumptions. Our incomplete MIPS support can build binaries that
pass all our unit tests. However, it needed more effort to support
real-world programs that are larger than our test cases.
At this point, I don't think it is productive to implement workarounds
for the old ABI that is stuck in the 90s. It is honestly annoying to
think about workarounds for the code that is intentionally compiled to
be hostile to the linker. The situation is unfortunate, but if the
Open Source community is still serious about MIPS, they should improve
the ABI and the compiler instead of asking us to implement the legacy
ABI.
Closes https://github.com/rui314/mold/issues/1040
Quote from Arm Cortex-A75 Software Optimization Guide
(PJDOC-466751330-205) p.59:
Consider aligning subroutine entry points and branch targets to
16-byte boundaries, within the bounds of the code-density requirements
of the program. This ensures that the subsequent fetch can maximize
bandwidth following the taken branch by bringing in all useful
instructions.
`CMakeLists.txt` declares a minimum CMake dependency on 3.14.
```
cmake_minimum_required(VERSION 3.13)
```
But the `--install` switch is only available since CMake 3.15, see
<https://stackoverflow.com/a/56457739>.
This commit updates the installation instructions in `README.md` so that
they work with CMake 3.14.
If an as-needed DSO is referenced only by other DSOs and not by
the output file we are creating, we won't mark it as "needed".
Fixes https://github.com/rui314/mold/issues/1036
Although we do not support TLSDESC on any target on which
ctx.tls_begin != ctx.dtp_addr, my understanding is that the origin
of the TLSDESC base relocation is tls_begin.
Object files have to be compiled with `-fPIE` or `-fPIC` for a
position-independent executable, and such object files shouldn't
contain relocations that directly refers to data objects without
GOT.
ARM64's crt1.o may refer to `main` with the following four instructions
instead of with the usual `R_AARCH64_ABS64` relocation:
18: d2e00000 movz x0, #0x0, lsl #48
18: R_AARCH64_MOVW_UABS_G3 main
1c: f2c00000 movk x0, #0x0, lsl #32
1c: R_AARCH64_MOVW_UABS_G2_NC main
20: f2a00000 movk x0, #0x0, lsl #16
20: R_AARCH64_MOVW_UABS_G1_NC main
24: f2800000 movk x0, #0x0
24: R_AARCH64_MOVW_UABS_G0_NC main
28: d2e00003 movz x3, #0x0, lsl #48
So we need to create a PLT if a symbol is referred to by this way.
Fixes https://github.com/rui314/mold/issues/1074
Although incomplete, this implementation is capable of passing the
following tests:
TRIPLE=mips64el-linux-gnuabi64 bash -x ../test/elf/hello-dynamic.sh
TRIPLE=mips64-linux-gnuabi64 bash -x ../test/elf/hello-dynamic.sh
Undefined versioned symbols are handled specially; they are resolved
at the very end of name resolution stage. When they are resolved, we
need to make sure that they are marked as imported symbols.
Fixes https://github.com/rui314/mold/issues/1057
Convenient if you have configured to use mold for a particular project but want
to test a new local build.
Signed-off-by: Tatsuyuki Ishi <[email protected]>
open() can fail with transient reasons, such as running out of file descriptor
limit. Previously, this was treated the same as file not being found, which
caused confusing error messages [1].
With this change anything but ENOENT will be treated as a fatal error. This will
also include e.g. permission errors, but if there's something within the search
path that is not accessible, it's likely better to tell the user instead of
silently skipping it.
[1]: https://github.com/rui314/mold/issues/1026
Signed-off-by: Tatsuyuki Ishi <[email protected]>
STT_GNU_IFUNC is significant only for exported symbols. Setting
non-exported symbol type to STT_GNU_IFUNC actually causes an issue
if the symbol is used as a canonical PLT because the runtime would
try to run the canonical PLT as an IFUNC.
Fixes https://github.com/rui314/mold/issues/1028
I made this change by asking GPT-4-based ChatGPT to improve this
document with an additional note that this document was written by a
non-native English speaker and may have wrong or unnatural phrases.
Fixes the following error:
/elf/thunks.cc:81:32: warning: enum constant in boolean context [-Wint-in-bool-context]
return ty == R_PPC64_REL24 || R_PPC64_REL24_NOTOC;
Signed-off-by: Martin Liska <[email protected]>
Handle .eh_frame in a similar way as mergeable sections.
This seems to be required for GCC CRT compiled with LTO.
Signed-off-by: Tatsuyuki Ishi <[email protected]>
When LTO is enabled for CRT it seems symbols like __EH_FRAME_BEGIN__.lto_priv.0
can be created. Treat it as the same as the non-LTO variant.
Signed-off-by: Tatsuyuki Ishi <[email protected]>
Previously, we clear liveness bits for all input files after compiling
IR object files to real object files so that we can do name resolution
completely from scratch. However, that can result in inconsistent name
resolution results before and after LTO.
Consider a weak defined symbol X in an object file. A weak undef doesn't
pull files out of an archive file. However, if symbol X is transformed
to an undefined symbol as a result of LTO, it could pull out a file from
an archive. That can have cascading effects and result in a link failure.
With this fix, all object files that are alive at the time when LTO kicks
in are guaranteed to be kept after LTO.
Fixes https://github.com/rui314/mold/issues/991
Previously, we failed to report an undefined error if we have two
undefined symbols, a strong and a weak one, for the same symbol.
It is an undefined symbol error if an undefined symbol is resolved
to a weak undefined symbol.
Fixes https://github.com/rui314/mold/issues/967
Ideally we wanted to use __builtin_assume() or [[assume]], but the former
is available only in clang and the latter is in C++23.
Fixes https://github.com/rui314/mold/issues/966
Previously, we created section symbols only for memory-allocated secitons.
That resulted in symbols with invalid section indices for the --relocatable
output.
https://github.com/rui314/mold/issues/957
Since the maximum size of RangeExtensionThunk is 100 KiB, it can't contain
too many entries, so the contents is addressable with a 16-bit value.
We don't create more than 32768 range extension thunks (unless the output
is hundreds gibibytes in size), so we can also use i16 to specify the
index of RangeExtensionThunk.
We used to have `--print-dependencies` and `--print-dependencies=full`
options. I think this distinction is necesssary. Now, `=full` is renamed
`--print-dependencies`.
These aren't bug, but I decided to repeat the same code instead of
adding [[fallthrough]] because the code duplication is very small.
Fixes https://github.com/rui314/mold/issues/952
We used to try to avoid using identifiers such as S, A or P more than
once because they were defined as macros expaneded to function calls.
Now they are just variables, so we can use them as many times as we
want without any performance penalty.
Unknown relocations will be reported by scan_relocations(). We don't
want to execute unreachable() statement in get_addend() because it
triggers undefined behavior.
https://github.com/rui314/mold/issues/947
It looks like R_ARM_PLT32 can be applied to non-BL/non-BLX instructions,
so we can't handle it as an alias for R_ARM_CALL.
https://github.com/rui314/mold/issues/934
Delete this message and describe the bug here. Note that we only fix bugs in the most recent release of mold, so please make sure that you are using the most recent version.
Loaded 100 of 3803 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.