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.
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]>
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]>
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]>
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]>
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]>
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]>
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]>
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]>
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]>
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]>
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]>
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]>
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]>
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]>
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]>
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]>
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]>
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]>
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.
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.
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
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
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
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
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
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
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
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
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
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
2026-09-01 22:06:22 +09:00
1645 changed files with 88705 additions and 4698 deletions
Loaded 100 of 1645 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.