Compare commits

...
3066 Commits
Author SHA1 Message Date
Rui Ueyama 331ac66366 Fix CI 2026-10-22 14:06:23 +09:00
Rui UeyamaandClaude Opus 5.5 11fa24033d Keep symbols versioned with .symver preemptible
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]>
2026-10-21 13:18:54 +09:00
Rui UeyamaandClaude Opus 5.5 605749d515 Keep IFUNCs exported from a shared object preemptible
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]>
2026-10-20 11:17:49 +09:00
Rui Ueyama 77d78b9a0e Fix CI 2026-10-19 10:54:16 +09:00
Rui Ueyama 72f728c20b Check for read-only copyrel buffers without catching SIGSEGV
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.
2026-10-19 09:27:55 +09:00
Rui Ueyama 6b93c0e18e Put .openbsd.randomdata into RELRO
.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.
2026-10-19 09:27:55 +09:00
Rui Ueyama c12b4144fd Create .dynamic for static executables that export symbols
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.
2026-10-19 09:27:55 +09:00
Rui UeyamaandClaude Opus 5.5 c522789231 Mark the Mach-O build ID as a version-3 UUID
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]>
2026-10-11 15:00:34 +09:00
Rui UeyamaandClaude Opus 5.5 f298606d15 Rename the Mach-O linker's compute_uuid to write_build_id
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]>
2026-10-11 14:58:59 +09:00
Rui UeyamaandClaude Opus 5.5 8692acde9d Update comments
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-11 14:43:17 +09:00
Rui UeyamaandClaude Opus 5.5 743e318fc3 Deduplicate Mach-O literals the way the ELF linker does
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]>
2026-10-11 13:40:30 +09:00
Rui UeyamaandClaude Opus 5.5 65665ee122 Return just the target name from the ELF linker's link functions
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]>
2026-10-11 13:40:30 +09:00
Rui UeyamaandClaude Opus 5.5 4e43149228 Parse the Mach-O linker's options once
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]>
2026-10-11 13:40:30 +09:00
Rui UeyamaandClaude Opus 5.5 55137f5427 Replace roundabout code for fixed data in the Mach-O linker
- 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]>
2026-10-11 13:40:30 +09:00
Rui UeyamaandClaude Opus 5.5 449de61f53 Move the little-endian push helpers from endian.rs to macho.rs
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]>
2026-10-11 13:40:30 +09:00
Rui UeyamaandClaude Opus 5.5 2d266c1fef Name the Mach-O linker mold-macho in its -v banner and list its archs
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-11 13:40:30 +09:00
Rui Ueyama 91181fd32b Update mold.1 (automated commit) 2026-10-11 01:03:11 +00:00
Rui Ueyama d04c073d0a Improve man page 2026-10-11 10:02:43 +09:00
Rui Ueyama 2248758575 Update mold.1 (automated commit) 2026-10-11 00:03:34 +00:00
Rui Ueyama 246390167b Accept GNU ld's -t, -n and -i
They are GNU ld's short names for --trace, --nmagic and -r.

https://github.com/rui314/mold/pull/1731.
2026-10-11 09:01:06 +09:00
Rui UeyamaandClaude Opus 5.5 1247d53089 Rewrite the Mach-O linker's file comments for readers new to macOS
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-10 13:41:26 +09:00
Rui UeyamaandClaude Opus 5.5 bc95c8af79 Rewrite bundle_hook.rs's file comment for readers new to macOS
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]>
2026-10-10 12:21:05 +09:00
Rui UeyamaandClaude Opus 5.5 d66a911074 Merge the ELF and Mach-O test runners into one crate
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]>
2026-10-09 22:10:53 +09:00
Rui UeyamaandClaude Opus 5.5 eb21f960fd Expand response files in mold-common for both linkers
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]>
2026-10-09 22:10:53 +09:00
Rui UeyamaandClaude Opus 5.5 ad4102263d Stop holding back the Mach-O linker's warnings while it reads options
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]>
2026-10-09 21:47:25 +09:00
Rui UeyamaandClaude Opus 5.5 b6c19ba5d3 Call the diagnostic macros by their bare names in the Mach-O linker
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]>
2026-10-09 21:35:39 +09:00
Rui UeyamaandClaude Opus 5.5 0b5236e5d5 Use mold-common's diagnostics in the Mach-O linker
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]>
2026-10-09 21:35:39 +09:00
Rui Ueyama 9fde7131a6 Use mold-common's archive reader in the Mach-O linker
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)".
2026-10-09 21:34:09 +09:00
Rui UeyamaandClaude Opus 5.5 8e253902ca Define Mach-O records as the ELF linker defines its records
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]>
2026-10-09 21:09:09 +09:00
Rui UeyamaandClaude Opus 5.5 9e4f5fc672 Remove macho/util.rs
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]>
2026-10-09 21:09:09 +09:00
Rui Ueyama 0dd2e85f78 Merge the ELF and Mach-O integration tests 2026-10-09 21:02:29 +09:00
Rui UeyamaandClaude Opus 5.5 bc9416662a Use the uuid crate for the Mach-O linker's UUIDs
-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]>
2026-10-09 20:06:17 +09:00
Rui UeyamaandClaude Opus 5.5 c90df98626 Split common/util.rs into modules by topic
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]>
2026-10-09 20:06:04 +09:00
Rui UeyamaandClaude Opus 5.5 f9b53613be Move display_name to macho/error.rs and macho/util/ to macho/util.rs
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]>
2026-10-09 19:52:04 +09:00
Rui UeyamaandClaude Opus 5.5 419f941917 Move cityhash.rs to mold-common
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]>
2026-10-09 19:51:19 +09:00
Rui UeyamaandClaude Opus 5.5 90b3b2e7c3 Use mold-common's copies of the Mach-O linker's utilities
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]>
2026-10-09 19:50:54 +09:00
Rui UeyamaandClaude Opus 5.5 a53d23e4ef Base CityHash64 on google/cityhash instead of libc++
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]>
2026-10-09 19:49:38 +09:00
Rui UeyamaandClaude Opus 5.5 0d94989d9c Move macho/arch/ into macho/
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]>
2026-10-09 19:41:52 +09:00
Rui Ueyama 23e64f1602 Remove an unnecessary file 2026-10-09 19:38:51 +09:00
Rui UeyamaandClaude Opus 5.5 08bbeb7b9a Silence Apple ld's __eh_frame warning in debug builds
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]>
2026-10-09 19:38:00 +09:00
Rui UeyamaandClaude Opus 5.5 ada2e80cc5 Name the toolchain copy of mold ld64.mold in lto-library-default
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]>
2026-10-09 19:37:15 +09:00
Rui UeyamaandClaude Opus 5.5 5aa155003d Don't run the ELF tests on macOS
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]>
2026-10-09 19:37:15 +09:00
Rui Ueyama 042b9cf06c Set e_shnum to 0 from SHN_LORESERVE sections up
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.
2026-10-09 19:10:28 +09:00
Rui Ueyama a1eaf5ff76 Print "mold <version> (<commit>)" for ld64.mold -v
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.
2026-10-09 18:37:41 +09:00
Rui Ueyama 96406a27e1 Move the git hash lookup to mold-common
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.
2026-10-09 18:36:53 +09:00
Rui Ueyama afd7b70851 Build the Mach-O linker on Windows
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.
2026-10-09 17:06:42 +09:00
Rui Ueyama cfcd7ee129 Use mold-common's strerror in the Mach-O linker
It was a copy of mold-common's, which also builds on Windows.
2026-10-09 17:03:41 +09:00
Rui Ueyama a51db2cb47 Use mold-common's fork and crash cleanup in the Mach-O linker
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.
2026-10-09 17:03:01 +09:00
Rui Ueyama 4f76492963 Use mold-common's timers in the Mach-O linker
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.
2026-10-09 17:01:51 +09:00
Rui Ueyama 0d3ec92304 Stop using Unix-only OsStr conversions in the Mach-O linker
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.
2026-10-09 17:00:38 +09:00
Rui Ueyama 8ca12c6442 Fix a Clippy warning 2026-10-09 16:55:07 +09:00
Rui Ueyama 5d7ba5d627 Install ld64.mold
mold acts as the Mach-O linker under that name. This replaces
mold-macho's install.sh, which installed only that symlink.
2026-10-09 16:54:38 +09:00
Rui Ueyama 227af62689 Run the Mach-O CI on mold's packages
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.
2026-10-09 16:53:32 +09:00
Rui Ueyama b0302fbcaf Run the Mach-O tests through an ld64.mold symlink
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.
2026-10-09 16:53:07 +09:00
Rui Ueyama a662cede04 Run the Mach-O linker if invoked as ld64.mold
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.
2026-10-09 16:52:12 +09:00
Rui Ueyama dba9aa127e Build the Mach-O test harness only on macOS
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.
2026-10-09 16:50:39 +09:00
Rui Ueyama 5d05ee37c7 Merge https://github.com/rui314/mold-macho
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.
2026-10-09 16:50:20 +09:00
Rui UeyamaandClaude Opus 5.5 953ca90b63 Rename the cli's features to macho-arm64 and macho-x86_64
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]>
2026-10-09 16:34:14 +09:00
Rui UeyamaandClaude Opus 5.5 d2e5695302 Update cpp_demangle to 0.5.1
mold uses 0.5.1. Its Symbol::demangle no longer takes options.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-09 16:30:45 +09:00
Rui UeyamaandClaude Opus 5.5 d8caed2a76 Move the tests to tests/macho
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]>
2026-10-09 16:30:20 +09:00
Rui UeyamaandClaude Opus 5.5 83edba7a8d Explain Mach-O's TLS in a new tls.rs
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-09 16:20:34 +09:00
Rui UeyamaandClaude Opus 5.5 f8dbc1c147 Expand objc.rs's file comment
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-09 15:33:42 +09:00
Rui Ueyama 9fcf4a97d9 Fix comments
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.
2026-10-09 15:19:28 +09:00
Rui UeyamaandClaude Opus 5.5 222a192f0a Pin the macOS version where the tests' outputs depend on it
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]>
2026-10-09 14:55:34 +09:00
Rui UeyamaandClaude Opus 5.5 962911566b Expect macOS 27's dyld to catch a static initializer's exception
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]>
2026-10-09 14:55:34 +09:00
Rui Ueyama 725874568b Remove an unnecessary file 2026-10-09 14:48:22 +09:00
Rui Ueyama 4988b79cb2 Fix the Windows build 2026-10-09 14:08:03 +09:00
Rui Ueyama 4d367e231c Remove unnecessary reborrows and mutable parameters 2026-10-09 13:59:15 +09:00
Rui Ueyama b2ca095547 Remove unnecessary shared reborrows of ctx
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.
2026-10-09 13:35:02 +09:00
Rui Ueyama 93dadfe956 Update mold.1 (automated commit) 2026-10-09 03:46:19 +00:00
Rui Ueyama bcc453917a Start x86-64 .plt.got entries of ifuncs in non-PIE executables with endbr64
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.
2026-10-09 12:37:02 +09:00
Rui Ueyama c00226bed7 Fix Clippy warnings
Closes https://github.com/rui314/mold/pull/1727
2026-10-09 12:37:02 +09:00
Rui Ueyama ff6a03bc69 Read ARM64 build attributes for BTI, PAC and GCS
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
2026-10-09 12:37:02 +09:00
Rui Ueyama 7866fb4799 Start the ARM64 PLT header with bti c regardless of BTI
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.
2026-10-09 12:37:02 +09:00
Rui Ueyama 7f6d60fb80 Support -z gcs-report-dynamic
-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.
2026-10-09 12:37:02 +09:00
Rui Ueyama 55c2971772 Add BTI landing pads for range extension thunks
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.
2026-10-09 12:11:36 +09:00
Rui Ueyama 822b2eb202 Support -z pac-plt
-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.
2026-10-09 10:50:00 +09:00
Rui Ueyama 358b0ca290 Support -z gcs and -z gcs-report
-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.
2026-10-09 10:49:20 +09:00
Rui Ueyama eec9e37a95 Support -z force-bti and -z bti-report
-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.
2026-10-09 10:49:20 +09:00
Rui Ueyama 5252daa5d8 Emit BTI PLTs on ARM64
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.
2026-10-09 10:49:20 +09:00
Rui Ueyama 353668da81 Replace PLT_SIZE and PLT_HDR_SIZE with Target::plt_entry_offset
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.
2026-10-09 10:47:31 +09:00
Rui Ueyama 38b10b4391 Merge GNU_PROPERTY_AARCH64_FEATURE_1_AND
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.
2026-10-09 10:15:15 +09:00
Rui Ueyama 442f2d57a2 Release the old output mapping before remapping it in extend()
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.
2026-10-09 10:15:15 +09:00
Rui Ueyama cfa3d7d58a Merge pull request #1726 from ancieg/tests/support-altlinux-objdump-rel-output
Support ALT Linux objdump relocation output in testsuite
2026-10-08 21:49:13 +09:00
Anton Zhukharev e3ea2b773f test: support ALT Linux objdump relocation output
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.
2026-10-08 13:56:44 +03:00
Rui UeyamaandClaude Opus 5.5 6a1eabb9b3 Append a table's little-endian fields with util's push16, push32 and push64
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]>
2026-10-08 18:45:50 +09:00
Rui UeyamaandClaude Opus 5.5 50074775e4 Build a -r output's LC_LINKER_OPTION commands with the other load commands
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]>
2026-10-08 18:43:55 +09:00
Rui UeyamaandClaude Opus 5.5 720b2c5776 Let the stub helper chunk choose to write legacy LINKEDIT's entries
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]>
2026-10-08 18:42:42 +09:00
Rui UeyamaandClaude Opus 5.5 b99464037c Place a symbol at its stub and GOT slot as Symbol::addr finds them
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]>
2026-10-08 18:41:26 +09:00
Rui UeyamaandClaude Opus 5.5 ed3be5663b Let each chunk list the pointers it rebases and the references it splits
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]>
2026-10-08 18:38:01 +09:00
Rui UeyamaandClaude Opus 5.5 d980bc4b1e Keep the mergeable record's chunk in chunks/mergeable_record.rs
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]>
2026-10-08 18:37:02 +09:00
Rui UeyamaandClaude Opus 5.5 e2903c0e98 Look a GOT load's helper up in the chunk that holds it
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]>
2026-10-08 18:37:02 +09:00
Rui UeyamaandClaude Opus 5.5 ec1030cef0 Give the Objective-C chunks the code that writes and places their data
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]>
2026-10-08 18:36:33 +09:00
Rui UeyamaandClaude Opus 5.5 950f7d012d List an object's local symbols in ObjectFile::populate_symtab, as mold does
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]>
2026-10-08 18:35:30 +09:00
Rui UeyamaandClaude Opus 5.5 068cb2d807 Let each chunk list the local symbols it synthesizes, as mold does
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]>
2026-10-08 18:35:03 +09:00
Rui UeyamaandClaude Opus 5.5 88c77c502e Put check_weak_imports after report_undef_errors, where the driver runs it
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]>
2026-10-08 18:28:43 +09:00
Rui UeyamaandClaude Opus 5.5 f069ff203b Keep the version sets in cmdline.rs and the hook's classes in bundle_hook.rs
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]>
2026-10-08 18:28:43 +09:00
Rui UeyamaandClaude Opus 5.5 156508238e Keep Context's last helpers where mold has them
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]>
2026-10-08 18:28:18 +09:00
Rui UeyamaandClaude Opus 5.5 0d4ec2d7d0 Write out the passes' one-line helpers where they are used
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]>
2026-10-08 18:27:31 +09:00
Rui UeyamaandClaude Opus 5.5 a9a45066e0 Run the dylib checks after dead_strip_dylibs from the driver
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]>
2026-10-08 18:27:31 +09:00
Rui UeyamaandClaude Opus 5.5 87958e9b77 Ask Args what the segment options make of a segment
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]>
2026-10-08 18:27:31 +09:00
Rui UeyamaandClaude Opus 5.5 b6418cfff5 Keep the chunks' layout helpers in their chunk modules
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]>
2026-10-08 18:27:31 +09:00
Rui UeyamaandClaude Opus 5.5 309b8cd8df Resolve each object's symbols in ObjectFile methods, as mold does
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]>
2026-10-08 18:27:31 +09:00
Rui UeyamaandClaude Opus 5.5 33cacc7e81 Rank a subsection's labels in input_sections.rs, beside label
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]>
2026-10-08 18:19:57 +09:00
Rui UeyamaandClaude Opus 5.5 4839893703 Ask a dylib for its bind ordinal, and a symbol for an export's
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]>
2026-10-08 18:19:57 +09:00
Rui UeyamaandClaude Opus 5.5 867ab0b2e3 Look for a dylib's dependencies in reader.rs
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]>
2026-10-08 18:19:57 +09:00
Rui UeyamaandClaude Opus 5.5 3b72e4e657 Find a relocation's section in input_files.rs and read integers in util
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]>
2026-10-08 18:19:57 +09:00
Rui UeyamaandClaude Opus 5.5 bc98b76f3b Keep the internal object's synthetic inputs beside ObjectFile::internal
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]>
2026-10-08 18:19:57 +09:00
Rui UeyamaandClaude Opus 5.5 9b9914967b Dissolve output_sections.rs into passes.rs and the chunks, as in mold
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]>
2026-10-08 18:18:43 +09:00
Rui UeyamaandClaude Opus 5.5 a64c091d7c Lay out __eh_frame in chunks/eh_frame.rs, as mold's construct
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]>
2026-10-08 18:18:43 +09:00
Rui UeyamaandClaude Opus 5.5 1b9e6ecae9 Split create_output_sections into passes, as mold's driver runs them
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]>
2026-10-08 18:18:43 +09:00
Rui UeyamaandClaude Opus 5.5 1a23ba49c3 Call functions and fields by mold's names
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]>
2026-10-08 17:56:27 +09:00
Rui UeyamaandClaude Opus 5.5 fcd1d6aecc Call an input section's contents, visit and kill by mold's names
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]>
2026-10-08 17:53:10 +09:00
Rui UeyamaandClaude Opus 5.5 26be971d07 Keep the CFI records in input_sections.rs, as mold does
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]>
2026-10-08 17:53:10 +09:00
Rui UeyamaandClaude Opus 5.5 c3d7a8986f Name a subsection and report its relocations through the subsection
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]>
2026-10-08 17:53:10 +09:00
Rui UeyamaandClaude Opus 5.5 9622c65259 Ask a relocation for its target, as sold does
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]>
2026-10-08 17:52:49 +09:00
Rui UeyamaandClaude Opus 5.5 be61fa4668 Ask an input section for its address, header and relocations
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]>
2026-10-08 17:51:25 +09:00
Rui UeyamaandClaude Opus 5.5 06b458f1bb Name the symbol flag setters and the name lookup as mold does
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]>
2026-10-08 17:48:37 +09:00
Rui UeyamaandClaude Opus 5.5 1107fc340e Make the symbol addresses Symbol methods, as mold has them
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]>
2026-10-08 17:48:37 +09:00
Rui UeyamaandClaude Opus 5.5 ce2f56bec8 Make the symbol predicates Symbol methods, as mold has them
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]>
2026-10-08 17:48:37 +09:00
Rui UeyamaandClaude Opus 5.5 a3f8b6275b Keep the symbols' aux table in SymbolTable, as mold does
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]>
2026-10-08 17:48:37 +09:00
Rui UeyamaandClaude Opus 5.5 426d6dff8f Refuse a legacy-LINKEDIT dylib bound for the shared cache
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]>
2026-10-08 17:29:46 +09:00
Rui UeyamaandClaude Opus 5.5 dd5bc4bd3c Drop Context's forwards to the stub chunks' sizes
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]>
2026-10-08 17:26:56 +09:00
Rui UeyamaandClaude Opus 5.5 5eb4fb99d7 Write the output from the driver, as mold does
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]>
2026-10-08 17:26:55 +09:00
Rui UeyamaandClaude Opus 5.5 a0ea339f6d Give the __LINKEDIT tables their alignment where their headers are made
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]>
2026-10-08 17:26:54 +09:00
Rui UeyamaandClaude Opus 5.5 689d15b263 Encode the unwind info, chain starts and relocations in their chunks
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]>
2026-10-08 17:26:53 +09:00
Rui UeyamaandClaude Opus 5.5 a33b979f3b Lay out an output section's members in its module, as mold does
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]>
2026-10-08 17:26:53 +09:00
Rui UeyamaandClaude Opus 5.5 a45881b068 Lay out the method lists and merge the image info in their chunks
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]>
2026-10-08 17:26:52 +09:00
Rui UeyamaandClaude Opus 5.5 acf30c10ef Size the stubs and the GOT in their own modules, as mold's update_shdr
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]>
2026-10-08 17:26:51 +09:00
Rui UeyamaandClaude Opus 5.5 2906fdd808 Read the API list in cmdline.rs and the $ld$ names in tapi.rs
-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]>
2026-10-08 17:16:04 +09:00
Rui UeyamaandClaude Opus 5.5 f486892e5e Share the LEB128 helpers and CityHash through util
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]>
2026-10-08 17:16:04 +09:00
Rui UeyamaandClaude Opus 5.5 4dba60f59b Keep thunks in thunks.rs and ask SymAux about stubs and thunks
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]>
2026-10-08 17:16:04 +09:00
Rui UeyamaandClaude Opus 5.5 7faba83f6d Make the relocation, unwind and Objective-C helpers their types' methods
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]>
2026-10-08 17:16:04 +09:00
Rui UeyamaandClaude Opus 5.5 a3e6735b07 Write -r's __objc_imageinfo with the chunk's copy_buf
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]>
2026-10-08 17:16:03 +09:00
Rui UeyamaandClaude Opus 5.5 c405be8e87 Keep the import ordinal encodings with the chunks that write them
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]>
2026-10-08 17:16:03 +09:00
Rui UeyamaandClaude Opus 5.5 ba8dc87c55 Let the stub chunks own their entries' offsets and addresses
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]>
2026-10-08 17:16:03 +09:00
Rui UeyamaandClaude Opus 5.5 a01adacb96 Move the tests from tests/macho/ to tests/ as in mold
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]>
2026-10-08 17:05:37 +09:00
Rui UeyamaandClaude Opus 5.5 a1a8957038 Look an object's subsections up through the object, as sold does
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]>
2026-10-08 17:03:18 +09:00
Rui UeyamaandClaude Opus 5.5 766bee2a74 Make the linker's own data words and imports on Context
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]>
2026-10-08 17:03:16 +09:00
Rui UeyamaandClaude Opus 5.5 a11509dbcf Redirect merged-away subsections' symbols from input_files.rs
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]>
2026-10-08 17:03:13 +09:00
Rui UeyamaandClaude Opus 5.5 202d4d2e7a Drop dead unwind records through one helper next to their ranges
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]>
2026-10-08 17:03:12 +09:00
Rui UeyamaandClaude Opus 5.5 01e2019577 Keep the input sections' predicates and flags in input_files.rs
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]>
2026-10-08 17:03:10 +09:00
Rui UeyamaandClaude Opus 5.5 fdb44da0a2 Rank and claim definitions in the object files, as mold does
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]>
2026-10-08 17:03:09 +09:00
Rui UeyamaandClaude Opus 5.5 faecaa77a3 Ask MachSyms whether they are undefined or weak as mold asks ElfSyms
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]>
2026-10-08 17:03:07 +09:00
Rui UeyamaandClaude Opus 5.5 9feb41d675 Move the LTO code to lto.rs as mold does
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]>
2026-10-08 17:00:02 +09:00
Rui UeyamaandClaude Opus 5.5 460a36e925 Put file types, target detection and search paths where mold has them
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]>
2026-10-08 17:00:02 +09:00
Rui UeyamaandClaude Opus 5.5 94914e6a1d Claim unresolved symbols in a pass of their own, as mold does
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]>
2026-10-08 17:00:02 +09:00
Rui UeyamaandClaude Opus 5.5 1e91b6e9c3 Rename macho/target/ to macho/arch/ as in mold
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]>
2026-10-08 17:00:01 +09:00
Rui UeyamaandClaude Opus 5.5 f7e37f7bee Inline scan_relocations' helpers as mold does
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]>
2026-10-08 17:00:01 +09:00
Rui UeyamaandClaude Opus 5.5 58653c205a Ask relocations what they are instead of classifying them
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]>
2026-10-08 17:00:01 +09:00
Rui UeyamaandClaude Opus 5.5 7b51536fa8 Give symbols GOT slots and stubs in the chunks' modules
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]>
2026-10-08 17:00:01 +09:00
Rui UeyamaandClaude Opus 5.5 df23ad144b Name the symbol and relocation records as sold does
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]>
2026-10-08 17:00:01 +09:00
Rui UeyamaandClaude Opus 5.5 ba60ade749 Scan relocations into symbol flags as mold does
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]>
2026-10-08 17:00:01 +09:00
Rui Ueyama c28bbdf7e9 Move tests/ to tests/elf/
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.
2026-10-08 15:07:50 +09:00
Rui UeyamaandClaude Opus 5.5 1df71db57b Read the tests' Mach-O files through macho.py
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]>
2026-10-08 13:28:25 +09:00
Rui UeyamaandClaude Opus 5.5 19d2b40941 Ignore Python's bytecode caches
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]>
2026-10-08 13:21:50 +09:00
Rui UeyamaandClaude Opus 5.5 531546fb9b Write unwind_lookup as a Python script on macho.py
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]>
2026-10-08 13:21:40 +09:00
Rui UeyamaandClaude Opus 5.5 cf17b2f8f8 Rewrite the tests' section flags through a shared macho.py
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]>
2026-10-08 13:19:29 +09:00
Rui UeyamaandClaude Opus 5.5 46fb1ef217 Give objc-section-types' arm64 object an __objc_selrefs
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]>
2026-10-08 13:18:22 +09:00
Rui UeyamaandClaude Opus 5.5 d552a20a96 Give the CI's shell tests twice the default timeout
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]>
2026-10-08 13:13:27 +09:00
Rui UeyamaandClaude Opus 5.5 622673616e Merge dtrace.inc into common.inc
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]>
2026-10-08 12:47:01 +09:00
Rui UeyamaandClaude Opus 5.5 5866d2b9e8 Write the DOF dumper as a Python script
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]>
2026-10-08 12:42:35 +09:00
Rui UeyamaandClaude Opus 5.5 451040034c Rename the CI workflow to macho.yml
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]>
2026-10-08 12:40:32 +09:00
Rui UeyamaandClaude Opus 5.5 de5e497ed9 Move the tests to tests/macho/
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]>
2026-10-08 12:40:32 +09:00
Rui Ueyama b378954c66 Fix CI 2026-10-08 11:54:19 +09:00
Rui UeyamaandClaude Opus 5.5 16a49aba20 Report outputs too large for a 32-bit host
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]>
2026-10-08 11:48:11 +09:00
Rui UeyamaandClaude Opus 5.5 e3d742513d Remove a partial output file on a panic
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]>
2026-10-08 11:43:36 +09:00
Rui UeyamaandClaude Opus 5.5 21983cef5e Report a failed link when SIGCHLD is ignored
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]>
2026-10-08 11:40:52 +09:00
Rui UeyamaandClaude Opus 5.5 d91672511a Version the crates 3.0.0 and give them mold's metadata
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]>
2026-10-08 11:38:32 +09:00
Rui UeyamaandClaude Opus 5.5 e6f02c5326 Take mimalloc from the mold-mimalloc crate
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]>
2026-10-08 11:38:27 +09:00
Rui UeyamaandClaude Opus 5.5 3b5894978a Build on i686 hosts
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]>
2026-10-08 11:33:27 +09:00
Rui UeyamaandClaude Opus 5.5 74b084a536 Build on hosts without 64-bit atomics
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]>
2026-10-08 11:32:53 +09:00
Rui UeyamaandClaude Opus 5.5 4be0b80ae4 Name the target crates mold-macho-arm64 and mold-macho-x86_64
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]>
2026-10-08 11:19:38 +09:00
Rui UeyamaandClaude Opus 5.5 810dcb98d2 Drop __llvm_addrsig from an image, as mold drops .llvm_addrsig
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]>
2026-10-08 11:11:06 +09:00
Rui UeyamaandClaude Opus 5.5 82fdb15bbf Drop the hash of the bundle hook's source
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]>
2026-10-08 11:06:25 +09:00
Rui UeyamaandClaude Opus 5.5 b4258ab431 Move the Mach-O linker to macho/
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]>
2026-10-08 11:05:09 +09:00
Rui Ueyama e67110eb56 Let ICF merge sections into an address-taken section
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.
2026-10-08 10:55:38 +09:00
Rui Ueyama 7955d1c331 Fix ICF merging references to different --defsym'd symbols
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.
2026-10-08 10:26:50 +09:00
Rui UeyamaandClaude Opus 5.5 260feeaeef Wait for a simulator device to finish booting before its tests run
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]>
2026-10-08 00:17:58 +09:00
Rui UeyamaandClaude Opus 5.5 dac32ae96b Connect lldb to the runner's simulator device before it loads a program
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]>
2026-10-08 00:17:58 +09:00
Rui UeyamaandClaude Opus 5.5 e0e9eb567d Decode bind-opcodes' binds with objdump, which Xcode 26 reads right
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]>
2026-10-08 00:17:52 +09:00
Rui UeyamaandClaude Opus 5.5 d7ce6b3938 Keep a weak definition weak in an object without subsections
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]>
2026-10-07 19:55:24 +09:00
Rui UeyamaandClaude Opus 5.5 e390633695 Merge tentative definitions into the greatest alignment, as mold does
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]>
2026-10-07 19:55:24 +09:00
Rui UeyamaandClaude Opus 5.5 fdaf2715ef Let any real definition beat a tentative one, as mold does
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]>
2026-10-07 19:55:24 +09:00
Rui UeyamaandClaude Opus 5.5 5cbedc2f9d Make an arm64 reference 4 GiB away an error, as mold does
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]>
2026-10-07 19:50:45 +09:00
Rui UeyamaandClaude Opus 5.5 60b8bec239 Run the tests' mold-only checks, which a stdout-only probe skipped
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]>
2026-10-07 19:42:55 +09:00
Rui UeyamaandClaude Opus 5.5 95bac2daed Report duplicate symbols in mold's one-line form again
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]>
2026-10-07 19:42:55 +09:00
Rui UeyamaandClaude Opus 5.5 d651824c7f Resolve the runtime routines LTO calls after LTO
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]>
2026-10-07 19:41:37 +09:00
Rui UeyamaandClaude Opus 5.5 48a94a15b1 Decide after LTO which dylibs bitcode needed
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]>
2026-10-07 19:31:00 +09:00
Rui UeyamaandClaude Opus 5.5 23d91bb0cb Rank a weak definition in an archive or a dylib after a strong one
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]>
2026-10-07 19:24:52 +09:00
Rui UeyamaandClaude Opus 5.5 5f4075084e Fold only functions that are unwound alike
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]>
2026-10-07 19:12:57 +09:00
Rui Ueyama 418dbb7734 Merge pull request #1714 from orielhaim/ci/modernize-workflows
Modernize GitHub Actions workflows
2026-10-07 17:03:54 +09:00
Rui Ueyama 3643dd32a9 Build the musl tarballs on the oldest supported Alpine
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.
2026-10-07 16:32:34 +09:00
Rui Ueyama 6cccf88a9a Move the format-independent code to common/
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.
2026-10-07 16:32:34 +09:00
Rui Ueyama 9d47119b28 Move -run to run.rs and stop passing Args to OutputFile::open
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.
2026-10-07 16:32:34 +09:00
Rui Ueyama 1502c1971f Fix the package name in build-windows.ps1
The mold-cli package was renamed mold in e4e0ad7c9.
2026-10-07 16:32:34 +09:00
Rui Ueyama adff957a3d Rename libmold to mold-elf and mold-arch-* to mold-elf-*
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.
2026-10-07 16:32:34 +09:00
Rui Ueyama 2505c77baa Move the ELF linker to elf/
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.
2026-10-07 16:32:34 +09:00
Rui UeyamaandClaude Opus 5.5 7571a83b8a Weigh only the loaded files' references to weak-import a symbol
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]>
2026-10-07 15:36:28 +09:00
Rui UeyamaandClaude Opus 5.5 ddcd230705 Load an auto-linked archive's ObjC members only by -all_load
-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]>
2026-10-07 15:29:23 +09:00
Rui UeyamaandClaude Opus 5.5 c09bdd451a Start no dead-strip walk in an archive member the link left out
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]>
2026-10-07 15:29:23 +09:00
Rui UeyamaandClaude Opus 5.5 933b010646 Run the arm64 iOS simulator by default, one simulator at a time
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]>
2026-10-07 13:56:00 +09:00
Rui UeyamaandClaude Opus 5.5 42dc28c8e8 Expect a simulator's -flat_namespace deprecation in an OS dylib
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]>
2026-10-07 13:32:16 +09:00
Rui UeyamaandClaude Opus 5.5 584d6cd6c8 Expect an x86_64 simulator's objects as clang builds them
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]>
2026-10-07 13:32:02 +09:00
Rui UeyamaandClaude Opus 5.5 27e51cfb47 Skip in an older simulator what came with a newer OS
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]>
2026-10-07 13:32:02 +09:00
Rui UeyamaandClaude Opus 5.5 c1f3e0488b Count /Library/Apple as the shared cache's on macOS alone
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]>
2026-10-07 13:32:02 +09:00
Rui UeyamaandClaude Opus 5.5 fa22f8bd41 Let off unused libraries by the start of their install names
-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]>
2026-10-07 13:32:02 +09:00
Rui Ueyama f56e1e3620 Merge pull request #1719 from joshuarli/musl-release-tarballs
release: tarballs for musl-based Linux distributions
2026-10-07 13:19:33 +09:00
Rui UeyamaandClaude Opus 5.5 87d0e027f9 Record references to a class reference slot as ld-prime does
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]>
2026-10-07 12:52:06 +09:00
Rui UeyamaandClaude Opus 5.5 c4a54aeff5 Say that firmware links lazy libraries as regular ones
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]>
2026-10-07 12:20:06 +09:00
Rui UeyamaandClaude Opus 5.5 834966497d Ignore -e start where dyld calls the entry point as main
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]>
2026-10-07 12:20:06 +09:00
Rui UeyamaandClaude Opus 5.5 02a97ebbe4 Run the new platform tests in the simulator configurations
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]>
2026-10-07 12:20:06 +09:00
Rui UeyamaandClaude Opus 5.5 988717deb6 Keep only the Objective-C image info flags that describe the code
__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]>
2026-10-07 12:12:48 +09:00
Rui UeyamaandClaude Opus 5.5 9fe69ccf70 Link a simulator's image against MH_SIM_SUPPORT macOS dylibs
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]>
2026-10-07 12:12:48 +09:00
Rui UeyamaandClaude Opus 5.5 ec127bfdcc Keep the platform-independent parts of the tests in a simulator
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]>
2026-10-07 11:56:27 +09:00
Rui UeyamaandClaude Opus 5.5 e97951094c Skip the tests of macOS's own rules in a simulator
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]>
2026-10-07 11:56:27 +09:00
Rui UeyamaandClaude Opus 5.5 ae5e4f1112 Build every input of a test for the configuration's platform
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]>
2026-10-07 11:56:27 +09:00
Rui UeyamaandClaude Opus 5.5 0316225138 Link for the configuration's platform where a script names macOS
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]>
2026-10-07 11:56:27 +09:00
Rui UeyamaandClaude Opus 5.5 965de32b55 Give the tests' programs absolute run paths
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]>
2026-10-07 11:56:27 +09:00
Rui UeyamaandClaude Opus 5.5 b48a539222 Write the tests' stubs for the configuration's platform
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]>
2026-10-07 11:56:26 +09:00
Rui UeyamaandClaude Opus 5.5 7954c66513 Link against the configuration's SDK, $SDK
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]>
2026-10-07 11:56:26 +09:00
Rui UeyamaandClaude Opus 5.5 6f75e266c2 Run the tests' programs with $RUN
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]>
2026-10-07 11:56:26 +09:00
Rui UeyamaandClaude Opus 5.5 7a130db22a Run the test suite for the iOS, tvOS and visionOS simulators
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]>
2026-10-07 11:56:25 +09:00
Rui UeyamaandClaude Opus 5.5 d59259e910 Take a platformless object for any platform's with a warning
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]>
2026-10-07 11:37:22 +09:00
Rui UeyamaandClaude Opus 5.5 1e443d05ef Deprecate flat lookups and -bind_at_load as ld-prime does per platform
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]>
2026-10-07 11:37:22 +09:00
Rui UeyamaandClaude Opus 5.5 04566dc829 Leave a simulator's image 16 more bytes of header padding
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]>
2026-10-07 11:37:22 +09:00
Rui UeyamaandClaude Opus 5.5 74bc7d4bfb Add the SHA-1 code directory for iOS and tvOS before 11 only
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]>
2026-10-07 11:29:00 +09:00
Rui UeyamaandClaude Opus 5.5 88e4ef6d62 Name iOS and tvOS before 12 by LC_VERSION_MIN_IPHONEOS and _TVOS
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]>
2026-10-07 11:25:05 +09:00
Rui UeyamaandClaude Opus 5.5 a8b8f426aa Default to chained fixups from each OS's first dyld that reads them
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]>
2026-10-07 11:25:05 +09:00
Rui UeyamaandClaude Opus 5.5 01bc94f15b Turn defaults on at ld64's version sets, for every OS
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]>
2026-10-07 11:25:05 +09:00
Rui UeyamaandClaude Opus 5.5 e738ae0d79 Refuse iOS deployment targets older than 4.3
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]>
2026-10-07 11:19:43 +09:00
Rui UeyamaandClaude Opus 5.5 86a1b5a812 Make a device's images encryptable by default
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]>
2026-10-07 11:19:43 +09:00
Rui UeyamaandClaude Opus 5.5 856e44b888 Check split info's kinds only where dyld_info prints them
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]>
2026-10-07 11:09:47 +09:00
Rui UeyamaandClaude Opus 5.5 a7cdba0f68 Make the firmware tests' objects without clang's firmware target
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]>
2026-10-07 11:09:47 +09:00
Rui UeyamaandClaude Opus 5.5 0ae38c7164 Say nothing of a missing /usr/local/lib, as ld-prime does
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]>
2026-10-07 11:09:47 +09:00
Rui UeyamaandClaude Opus 5.5 289f3788e8 Sign the simulators' arm64 images by default
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]>
2026-10-07 10:37:45 +09:00
Rui UeyamaandClaude Opus 5.5 7465a5c0e8 Link for iOS, tvOS and visionOS devices and simulators
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]>
2026-10-07 10:37:13 +09:00
Joshua Li 0fe4506e3b container-based verification and simplify 2026-10-07 00:52:01 +00:00
Joshua Li db1785dd28 Release tarballs for musl-based Linux distributions
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.
2026-10-07 00:37:26 +00:00
Rui Ueyama 2cb82cad8c Refactor 2026-10-07 08:53:20 +09:00
Rui Ueyama 75200f107e Refactor
This change hides the zstd context cache inside Compressor.
2026-10-07 08:02:40 +09:00
Rui Ueyama e211b08dde Build the embedded mold-wrapper.so without debug info
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
2026-10-07 07:30:19 +09:00
Rui Ueyama 37c0756898 Prepare the workspace crates for publishing on crates.io
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.
2026-10-07 07:30:19 +09:00
Rui Ueyama e4e0ad7c92 Rename the mold package to libmold and mold-cli to mold
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`.
2026-10-07 07:30:19 +09:00
Rui Ueyama 1347ca95c9 Merge pull request #1713 from orielhaim/perf/reuse-zstd-contexts
Reuse zstd compressors across debug shards
2026-10-07 07:30:15 +09:00
orielhaim 2321cb8943 Modernize GitHub Actions workflows 2026-10-06 16:23:30 +03:00
orielhaim dc98a0a69e Reuse zstd compressors across debug shards 2026-10-06 15:26:38 +03:00
Rui Ueyama b2e5a7084f Merge pull request #1712 from orielhaim/update-cpp-demangle-0.5.1
Update cpp_demangle to 0.5.1
2026-10-06 17:36:44 +09:00
orielhaim 4520bf5cbb Update cpp_demangle to 0.5.1 2026-10-06 11:08:00 +03:00
Rui Ueyama 1c333a65a6 Update mold.1 (automated commit) 2026-10-06 07:01:58 +00:00
Rui Ueyama 05c04008b2 Accumulate DT_FLAGS and DT_FLAGS_1 values in u32
The DF_ and DF_1_ constants are u32, so this removes the cast on each
flag.
2026-10-06 16:00:09 +09:00
Rui Ueyama 8f7cec5e9c Fix -z global's help message, documentation and test
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.
2026-10-06 16:00:09 +09:00
Rui Ueyama d45d909cf6 Embed mold-wrapper.so in the mold 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.
2026-10-06 15:49:32 +09:00
Rui Ueyama a870bfc4ec Fix typos and grammatical errors in test comments 2026-10-06 15:49:32 +09:00
Rui Ueyama 5ab8dce567 Merge pull request #1709 from orielhaim/z-global
Add support for -z global
2026-10-06 15:49:23 +09:00
Rui Ueyama 4a1a285c42 Update mold.1 (automated commit) 2026-10-06 03:53:49 +00:00
orielhaim 0da39addc4 Add support for -z global 2026-10-05 22:02:59 +03:00
Rui Ueyama 939893bf2b Fix typos and grammatical errors in comments
This also updates comments that referred to names that no longer exist
in the Rust tree, such as C++-era function names and src/target.
2026-10-06 00:34:18 +09:00
Rui Ueyama fbf10d9c31 Compile each crate as one codegen unit in release builds
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
2026-10-05 22:54:20 +09:00
Rui Ueyama c0bc7ed467 Implement --no-mmap-output-file
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
2026-10-05 22:54:20 +09:00
Rui Ueyama abff0d11bd Make installation directories configurable in install-mold.sh
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
2026-10-05 21:15:25 +09:00
Rui Ueyama 63174e9d72 Use zlib-rs instead of zlib
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
2026-10-05 21:15:15 +09:00
Rui Ueyama f353a4b6bd Rename src/target to src/arch
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.
2026-10-05 19:28:25 +09:00
Rui Ueyama 76c10b8683 Pass --target to clang in tests without a bash array
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.
2026-10-05 18:16:35 +09:00
Rui Ueyama 189ac7d0a1 Mark data referenced from files without .llvm_addrsig as address-taken
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.
2026-10-05 18:16:35 +09:00
Rui Ueyama 7bfacf1bde Format with rustfmt 2026-10-05 17:55:41 +09:00
Rui Ueyama fc888957c9 Hold the contents of .llvm_addrsig instead of its InputSection
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.
2026-10-05 17:51:17 +09:00
Rui Ueyama cb8a30cf80 Link the distributed mold executable with itself with ICF
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.
2026-10-05 17:30:36 +09:00
Rui Ueyama 7daa95e0fa Use ICF and address-significance tables in release builds
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.
2026-10-05 17:28:33 +09:00
Rui Ueyama c56d1a35e4 Ignore trailing DW_CFA_nops when comparing FDEs in ICF
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.
2026-10-05 17:28:29 +09:00
Rui Ueyama 82626c8d14 Identify references to mergeable sections by offset in ICF
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.
2026-10-05 17:28:25 +09:00
Rui Ueyama 931870ba6d Keep --as-needed DSOs that needed DSOs depend on
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.
2026-10-05 17:12:31 +09:00
Rui Ueyama 331e47c603 Accept --no-{warn,error}-rwx-segments and --no-error-execstack
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.
2026-10-05 17:12:31 +09:00
Rui Ueyama 8de38c35a2 Bump mold version to 3.0.0 2026-10-05 15:40:40 +09:00
Rui Ueyama 602d8b42ad Update mold.1 (automated commit) 2026-10-05 05:51:49 +00:00
Rui Ueyama 6e322219ca Fix tests that fail or are needlessly skipped on some hosts
Running the suite on GCC Compile Farm machines found test scripts that
don't work as intended on some hosts:

- on_qemu() took KVM guests on POWER for qemu-user
- the native POWER10 target ignored CPU and shared ppc64le's directory
- readelf 2.35 appends "[<localentry>: 1]" to POWER10 function names
- compressed debug sections hid .debug_str from `readelf -p`
- some tests assumed Linux, glibc, GCC-only flags or g++
- an x86-64 executable was run without $QEMU on a non-x86 host
- old binutils lack multiple .symver and SHF_LINK_ORDER
- GCC 8's `gcc -r` links start files into relocatable output
- debug-macro-section.sh grepped for an offset format objdump doesn't print
2026-10-05 14:41:30 +09:00
Rui Ueyama b4fe61fbf7 Preserve GCC's .debug_macro groups in -r output
With -g3, GCC emits a COMDAT group for the .debug_macro contents of each
header file, and other .debug_macro sections refer to it. mold doesn't
deduplicate these groups because it would resolve references to discarded
copies to 0 (https://github.com/rui314/mold/issues/438), but -r output
dropped the groups while keeping SHF_GROUP on their members. Now -r keeps
every copy as a COMDAT group. GNU ld -r deduplicates them and redirects
the references to the kept copies; without that redirection, mold leaves
deduplication to the final link, where GNU ld, lld and mold handle the
groups as they would in the original objects.
2026-10-05 14:41:30 +09:00
Rui Ueyama b575a42641 Stream relocations in --print-dependencies
--print-dependencies reads the relocations of every input section,
including non-allocated ones whose CREL tables are not decoded when the
file is read. It read them as an array, which reinterpreted such a table
as RELA records and failed an assertion. C++ mold's get_rels() decoded
the table on demand instead.

A read-only pass doesn't need the table materialized, so iterate with
the streaming relocation iterator as --stats does, and document when
the array accessor can be used.
2026-10-05 14:41:30 +09:00
Rui Ueyama 47bf6a030a Compile C code with the medium code model on LoongArch
rustc's LoongArch targets use the medium code model, whose calls reach
±128 GiB, but the cc crate passes no -mcmodel to the C compiler unless
-Ccode-model is given explicitly, so C code got the normal code model,
whose direct calls reach only ±128 MiB. A debug build of mold has more
than 128 MiB of text, so calls from C code such as zstd, mimalloc and
lto-message.c to the PLT failed to link with R_LARCH_B26 overflows on
a native LoongArch host. We now give -Ccode-model=medium explicitly.
2026-10-05 14:41:30 +09:00
Rui Ueyama e2b599c3c1 Preserve non-COMDAT section groups in -r output
A section group whose flag word is 0 is not a COMDAT group. It is never
deduplicated; it only ties its members together so that they are kept or
discarded as a unit. annobin emits such groups, e.g. into crt1.o on
AlmaLinux 8. mold ignored them, but -r output kept SHF_GROUP on their
members, leaving SHF_GROUP sections that no SHT_GROUP lists, which the
gABI forbids and objdump 2.30 rejects. -r output now keeps each such
group with flag word 0, as GNU ld and lld do. Other links still treat
its members as ordinary sections.
2026-10-05 14:41:30 +09:00
Rui Ueyama c4cd78d93a Decode a CREL table on demand when rewriting relocations
CREL tables of non-allocated sections are not decoded when an object
file is read; their relocations are streamed when they are applied.
On ppc64v1, rewrite_opd() also rewrites relocations of non-allocated
sections such as .debug_info, and rels_mut() reinterpreted such a table
as RELA records. Linking a CREL object built with -g failed an
assertion, and a table whose size was a multiple of 24 would be misread.

A CREL table can't be rewritten in its compressed form, so rels_mut()
now decodes it into the side table first, as get_rels() in C++ mold
did. relocation_iter() then returns the rewritten records from there.
2026-10-05 14:41:30 +09:00
Rui Ueyama 63a9f80d1c Drop -e with an address
This reverts aed5a38d1, which made -e accept a number as an entry
address if no symbol has that name, as GNU ld and lld do. New features
are deferred until after 3.0.0.
2026-10-05 14:41:30 +09:00
Rui Ueyama 6c0d688966 Drop ARM64 BTI, PAC and GCS support
This reverts f03b74595, 48428d389 and 3a1b9c6c2, which merged
GNU_PROPERTY_AARCH64_FEATURE_1_AND, emitted BTI PLTs and landing pads
for range extension thunks, and added -z force-bti, pac-plt, gcs,
bti-report and gcs-report.

GCC 16 no longer records -mbranch-protection in .note.gnu.property but
in AArch64 build attributes, which we don't read, so outputs silently
lost the BTI, PAC and GCS bits and -z force-bti warned about every
object file. We'll bring the feature back with build attribute support
after 3.0.0. Until then, ARM64 outputs have no .note.gnu.property, as
in 2.42.1.
2026-10-05 14:41:30 +09:00
Rui Ueyama 1e66190ff1 Use unstable sorts where the C++ version did
These sorts have keys that are unique among the elements, so a stable
sort produces the same order. Using an unstable sort, as C++ mold did,
avoids the stable sort's buffer and signals that ties don't matter.
2026-10-05 14:41:30 +09:00
Rui Ueyama 89148f0a36 Sort ARM32 mapping symbols stably
Two mapping symbols can share an offset, e.g. when `ld -r` places a
section ending with a mapping symbol before one starting with another.
The later one in the symbol table applies, as in the C++ version, which
used stable_sort. An unstable sort could leave BE8 code unswapped.
2026-10-05 14:41:30 +09:00
Rui Ueyama 3da398023e Use the uuid crate for --build-id=uuid
Default features are turned off because uuid's std feature refers to
optional wasm32-only crates, and Cargo puts those into Cargo.lock even
though nothing builds them. With them off, uuid is the only new package.
2026-10-05 14:41:30 +09:00
Rui Ueyama 596475ee28 Simplify 2026-10-05 14:41:30 +09:00
Rui Ueyama 59d20ed5ea Remove redundant instruction and section wrappers 2026-10-05 14:41:30 +09:00
Rui Ueyama e819287122 Remove comments from target instantiation crates 2026-10-05 14:41:30 +09:00
Rui Ueyama b3307426e8 Require a Git clone in dist.sh
When run in an extracted source archive, dist.sh cloned the release tag
as a bare repository into .git to get the commit hash and timestamp.
Cargo refuses to build in a directory whose .git is a bare repository,
and the embedded hash named the tag rather than the files actually
built. A worktree doesn't work either, because its .git file points
outside the build container and the hash is silently omitted. Stop with
an error unless .git is a directory.
2026-10-05 14:41:30 +09:00
Rui Ueyama 56f1250f77 Merge pull request #1702 from aokblast/fbsd/fix_checkl
Fix FreeBSD -B semantic
2026-10-04 09:37:46 +09:00
ShengYi Hung cb9ba2a6a5 Fix FreeBSD -B semantic
FreeBSD defaults to ld.lld. To use -B on cc with mold, we need to
replace link of ld.lld also.
2026-10-04 04:55:42 +11:00
Rui UeyamaandClaude Opus 5.5 4ccb085ad3 Take up a stub reached by two paths once in the prefetch
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]>
2026-10-04 00:00:00 +09:00
Rui UeyamaandClaude Opus 5.5 854ad9b72e Read the new objects' auto-link options in parallel
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]>
2026-10-03 23:47:45 +09:00
Rui UeyamaandClaude Opus 5.5 2c73f1b20e Leave room in the arenas for the auto-link rounds' objects
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]>
2026-10-03 23:41:36 +09:00
Rui UeyamaandClaude Opus 5.5 f7e1cbfa7d Keep the remembered file lookups in shards
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]>
2026-10-03 23:39:22 +09:00
Rui UeyamaandClaude Opus 5.5 a85114be59 Skip another platform's $ld$previous directive before reading its fields
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]>
2026-10-03 23:29:43 +09:00
Rui UeyamaandClaude Opus 5.5 995ead199a Look a path up once in a link's library searches
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]>
2026-10-03 23:21:23 +09:00
Rui UeyamaandClaude Opus 5.5 efbdcdc0c3 Read each stub as a task of its own as soon as it is found
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]>
2026-10-03 23:21:23 +09:00
Rui UeyamaandClaude Opus 5.5 431bf6927e Read stubs whole on all cores before the serial loop loads them
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]>
2026-10-03 23:19:21 +09:00
Rui UeyamaandClaude Opus 5.5 a2b93a9387 Leave room after the load commands for post-link dylib renames
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]>
2026-10-03 23:08:17 +09:00
Rui UeyamaandClaude Opus 5.5 1035783645 Resolve a library's directory only for an @loader_path name
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]>
2026-10-03 23:00:10 +09:00
Rui UeyamaandClaude Opus 5.5 13197b28ad Read a file reached by two paths once
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]>
2026-10-03 22:58:38 +09:00
Rui UeyamaandClaude Opus 5.5 daa96abaaf Walk only an object's global symbols for liveness
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]>
2026-10-03 21:55:10 +09:00
Rui UeyamaandClaude Opus 5.5 0aae85afcb Search for staged symbols' subsections through their addresses alone
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]>
2026-10-03 21:52:03 +09:00
Rui UeyamaandClaude Opus 5.5 fe2bedaaa3 Search for auto-linked libraries once, in parallel
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]>
2026-10-03 21:39:37 +09:00
Rui UeyamaandClaude Opus 5.5 1e224c7ef1 Find a stub's linker directives as it is parsed, not as it loads
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]>
2026-10-03 21:36:38 +09:00
Rui UeyamaandClaude Opus 5.5 35368b0dbd Record a round's references and its dylibs' symbols in parallel
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]>
2026-10-03 21:32:17 +09:00
Rui UeyamaandClaude Opus 5.5 b42b574078 Time the resolution rounds, the liveness walk and auto-link loading
-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]>
2026-10-03 21:31:15 +09:00
Rui UeyamaandClaude Opus 5.5 ade6d11d10 Run resolution again for a new tentative definition only if it loads
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]>
2026-10-03 21:31:15 +09:00
Rui UeyamaandClaude Opus 5.5 36e683cfdb Load auto-linked libraries between liveness and the final round
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]>
2026-10-03 21:24:20 +09:00
Rui UeyamaandClaude Opus 5.5 155eecf97d Find the method lists' and selector references' sections by header
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]>
2026-10-03 21:11:51 +09:00
Rui UeyamaandClaude Opus 5.5 04c687d19f Let each dylib claim the symbols it exports, by search order
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]>
2026-10-03 21:10:58 +09:00
Rui UeyamaandClaude Opus 5.5 3d377a784b Coalesce Objective-C references found through their section headers
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]>
2026-10-03 21:10:25 +09:00
Rui UeyamaandClaude Opus 5.5 b87350e3a6 Find each symbol's subsection once, as objects are staged
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]>
2026-10-03 20:59:18 +09:00
Rui UeyamaandClaude Opus 5.5 59b4090e27 Read the Objective-C class lists in parallel
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]>
2026-10-03 20:49:24 +09:00
Rui UeyamaandClaude Opus 5.5 32c58abf49 Find the Objective-C list subsections through their section headers
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]>
2026-10-03 20:45:17 +09:00
Rui Ueyama 8041014c3e Resolve non-allocated merged sections in the foreground
Non-allocated strings were merged in a background Rayon task, but Rayon
has no task priorities: a thread waiting for a foreground parallel pass
could pick up a long piece of the merge, and the pass waited up to
200 ms for it. Resolve these sections in compute_section_sizes as before
2316c952a. Within noise on large debug builds (0.94-1.04).
2026-10-03 20:36:52 +09:00
Rui Ueyama ffad459a57 Share dummy symbols between relocation pairs to the same fragment
Instruction pairs such as ADRP+ADD on ARM64 refer to the same string
with two consecutive relocations, and each got a dummy symbol of its own.
Reuse the previous relocation's dummy symbol if it refers to the same
section symbol with the same addend. Since both relocations of a pair now
refer to one symbol, ADRP+ADD relaxation applies to them too. On
chrome-release-arm64, reattach_section_pieces 39 -> 30 ms and peak RSS
5.86 -> 5.56 GB.
2026-10-03 20:36:52 +09:00
Rui UeyamaandClaude Opus 5.5 5fb64f4afb Find and drop merged categories' method lists without a linear scan
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]>
2026-10-03 20:36:48 +09:00
Rui Ueyama 6a44075396 Lay out thunk-bearing sections over arrays of member attributes
Range extension thunk creation and removal lay out input sections in a
serial loop that looked up each member through its file, costing a few
dependent cache misses per section. Read sizes and alignments into an
array in parallel first, and publish the offsets in parallel. On
chrome-release-arm64, compute_section_sizes 34 -> 27 ms and
remove_redundant_thunks 25 -> 15 ms; on chrome-debug-arm64, 87 -> 81 ms
and 72 -> 50 ms.
2026-10-03 20:36:44 +09:00
Rui Ueyama fb20353ea4 Group identical CIEs by their digests in parallel
CIE deduplication, which both ICF and .eh_frame layout run, compared each
CIE with every distinct CIE found so far in a serial loop. Compute their
SipHash digests in parallel with a random key and number the classes
through a hash table instead, assuming that digests never collide as ICF
does. FDE offsets are now assigned in parallel too. uniquify_cies 9 -> 2
ms on chrome-release-arm64; .eh_frame layout 15 -> 8 ms on
chrome-debug-x86_64.
2026-10-03 20:36:43 +09:00
Rui Ueyama 93d75ca0ee Set symbol visibility without an atomic read-modify-write
set_visibility() is only called on exclusively owned symbols, but it
updated the bits with a compare-and-swap loop. Take &mut self and write
them directly.
2026-10-03 20:36:43 +09:00
Rui Ueyama a0795de60c Collect merged-section members in parallel
merged_resolve_members visited every file on one thread. Fold the
members of each file in parallel and append the per-thread lists in
file order. About 5 ms on chrome-release-arm64.
2026-10-03 20:36:35 +09:00
Rui Ueyama 06893555e7 Size symbol table shards by estimated distinct names
gather reserved each shard for every recorded occurrence of a name, about
three times the number of distinct names, so the sparse table touched far
more pages than it used. Estimate the distinct names with HyperLogLog
instead. chrome-debug-arm64 peak RSS 26.76 -> 25.91 GB.
2026-10-03 20:36:35 +09:00
Rui Ueyama 78753b0fe4 Build glob literal searchers once per pattern
Pattern::matches called memchr::memmem::find for the literal after each
star, which builds a searcher on every call. Build them when compiling
the pattern instead. apply_version_script on tensorflow-debug 260 -> 220
ms.
2026-10-03 20:36:35 +09:00
Rui Ueyama d088db65b1 Set import/export bits and parse symbol versions in parallel
compute_import_export collected updates in parallel and applied them on
one thread because Symbol's attribute bits needed &mut. Make the bits an
AtomicU16 so that file-parallel passes can set them through shared
references, testing a bit before writing it as mark() does.
parse_symbol_version now finds versioned symbols in parallel; it still
applies them in input order because one may read a version that another
has just set. compute_import_export on tensorflow-debug 167 -> 9 ms.
2026-10-03 20:36:35 +09:00
Rui UeyamaandClaude Opus 5.5 1909a91567 Find a code subsection's unwind records by its range
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]>
2026-10-03 20:30:15 +09:00
Rui UeyamaandClaude Opus 5.5 40d9a192cb Fold __objc_classrefs into __got from macOS 15 on
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]>
2026-10-03 19:47:31 +09:00
Rui UeyamaandClaude Opus 5.5 b0c5d1f8ee Find an input GOT section's slot relocations in one pass
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]>
2026-10-03 19:05:38 +09:00
Rui UeyamaandClaude Opus 5.5 8f0c9b2eb3 Follow the Objective-C list sections a block at a time
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]>
2026-10-03 18:05:09 +09:00
Rui UeyamaandClaude Opus 5.5 fb1b2115ad Rewrite the Objective-C method lists in parallel
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]>
2026-10-03 17:46:23 +09:00
Rui UeyamaandClaude Opus 5.5 6110474f92 Assign input sections to output sections in parallel
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]>
2026-10-03 17:35:09 +09:00
Rui UeyamaandClaude Opus 5.5 106e5c72b2 Find an Objective-C pointer's relocation by binary search
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]>
2026-10-03 17:26:07 +09:00
Rui UeyamaandClaude Opus 5.5 0638addb22 Make subsections with one constructor
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]>
2026-10-03 17:13:46 +09:00
Rui UeyamaandClaude Opus 5.5 29fb75dd30 Find each subsection's first entry by its predecessor
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]>
2026-10-03 16:59:20 +09:00
Rui UeyamaandClaude Opus 5.5 c3ff0b800d Fix the mergeable modules' stale comments
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]>
2026-10-03 16:59:20 +09:00
Rui UeyamaandClaude Opus 5.5 54c3812ed6 Split mergeable.rs into the record's format and the object it makes
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]>
2026-10-03 16:59:20 +09:00
Rui UeyamaandClaude Opus 5.5 b5c71ff931 Find a mergeable dylib's LC_ATOM_INFO with input_files' iterator
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]>
2026-10-03 16:59:20 +09:00
Rui UeyamaandClaude Opus 5.5 23086a2f33 Share the lazy and delay-init passes' use scan and helper names
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]>
2026-10-03 16:59:20 +09:00
Rui UeyamaandClaude Opus 5.5 966e2baaaa Name the symbol table's entry-with-name triple once
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]>
2026-10-03 16:57:55 +09:00
Rui UeyamaandClaude Opus 5.5 00d27559e0 Write the merged library's object header from its layout
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]>
2026-10-03 16:57:55 +09:00
Rui UeyamaandClaude Opus 5.5 ff3116fbae Port libc++'s CityHash64 function by function
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]>
2026-10-03 16:57:55 +09:00
Rui UeyamaandClaude Opus 5.5 c240e2d4a0 Place mergeable entries with util's align_to_mod
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]>
2026-10-03 16:57:55 +09:00
Rui UeyamaandClaude Opus 5.5 0b514034f7 Read and write a mergeable record's entries with one type
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]>
2026-10-03 16:57:55 +09:00
Rui UeyamaandClaude Opus 5.5 4d1f1d1e30 Read and write a mergeable record's fixups with one type
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]>
2026-10-03 16:57:55 +09:00
Rui UeyamaandClaude Opus 5.5 239ea90121 Ask a subsection whether it is emitted, in one method
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]>
2026-10-03 16:55:44 +09:00
Rui UeyamaandClaude Opus 5.5 7483dd240c Keep the predicates that classify input sections in input_files.rs
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]>
2026-10-03 16:55:44 +09:00
Rui UeyamaandClaude Opus 5.5 825ccde337 Name the object once in the bad LC_LINKER_OPTION error
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]>
2026-10-03 16:55:44 +09:00
Rui UeyamaandClaude Opus 5.5 9c21e4f7e6 Name the .tbd stub reader parse_tbd, beside parse_dylib_binary
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]>
2026-10-03 16:55:44 +09:00
Rui UeyamaandClaude Opus 5.5 1b4c082098 Put the first passes in the order the driver runs them
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]>
2026-10-03 16:43:44 +09:00
Rui UeyamaandClaude Opus 5.5 421a1b32d0 Say what read_input_files' two middle steps are for
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]>
2026-10-03 16:43:44 +09:00
Rui UeyamaandClaude Opus 5.5 4f1a160af1 Group reader.rs's functions by what they do
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]>
2026-10-03 16:43:44 +09:00
Rui UeyamaandClaude Opus 5.5 492d2b7945 Check a dylib's clients and UUID in load_dylib, not in match guards
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]>
2026-10-03 16:43:44 +09:00
Rui UeyamaandClaude Opus 5.5 5f225ee90d Order error.rs as mold-rust's, with the byte strings last
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]>
2026-10-03 16:43:44 +09:00
Rui UeyamaandClaude Opus 5.5 144465127d Put the dylib ranking after the claims that use it
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]>
2026-10-03 16:43:44 +09:00
Rui UeyamaandClaude Opus 5.5 90f7b50845 Time the driver's passes one way and mark its phases
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]>
2026-10-03 16:43:44 +09:00
Rui UeyamaandClaude Opus 5.5 0b08bd9cd6 Fold pick_label into subsec_label_index and fix isec_addr's comment
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]>
2026-10-03 16:43:44 +09:00
Rui UeyamaandClaude Opus 5.5 3e8b10b965 Split collect_file's dylib and archive cases into functions
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]>
2026-10-03 16:43:44 +09:00
Rui UeyamaandClaude Opus 5.5 8d275de7f1 Name the resolution rounds as mold-rust does and split out a claim
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]>
2026-10-03 16:43:44 +09:00
Rui UeyamaandClaude Opus 5.5 2a6988dd10 Move reading the input files out of passes.rs into reader.rs
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]>
2026-10-03 16:43:44 +09:00
Rui UeyamaandClaude Opus 5.5 6e51de7894 Let load_autolink_deps and do_lto redo resolution themselves
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]>
2026-10-03 16:43:44 +09:00
Rui UeyamaandClaude Opus 5.5 cf71e2d6d8 Name the mergeable record's header fields once
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]>
2026-10-03 16:42:57 +09:00
Rui UeyamaandClaude Opus 5.5 6e4f1c1c77 Keep the mergeable record's content types in one table
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]>
2026-10-03 16:42:57 +09:00
Rui UeyamaandClaude Opus 5.5 a55a67cda6 Say what two comments' code does instead of how it once failed
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]>
2026-10-03 16:33:54 +09:00
Rui UeyamaandClaude Opus 5.5 537615e7e0 Name a segment's fixups and a rebase's chain word
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]>
2026-10-03 16:31:32 +09:00
Rui UeyamaandClaude Opus 5.5 01e5850fcd Fill LC_DYLD_INFO_ONLY from one rule for its five tables
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]>
2026-10-03 16:31:32 +09:00
Rui UeyamaandClaude Opus 5.5 f6ec0e3aca Rewrap the thunks module comment's last paragraph
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]>
2026-10-03 16:27:30 +09:00
Rui UeyamaandClaude Opus 5.5 fa485705d0 Test for a weak definition's nlist in one place
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]>
2026-10-03 16:27:30 +09:00
Rui UeyamaandClaude Opus 5.5 07bab8974a Lay the segments out until __unwind_info fits in one helper
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]>
2026-10-03 16:27:30 +09:00
Rui UeyamaandClaude Opus 5.5 2ee21ee85f Put bind_dyld_lazy_load first in lazy_load.rs, as the driver runs it
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]>
2026-10-03 16:27:30 +09:00
Rui UeyamaandClaude Opus 5.5 d1739fe798 Count the batches left in a section with a function of their own
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]>
2026-10-03 16:27:30 +09:00
Rui Ueyama 4165d0e82e Redirect branches to section offsets to symbols
Assemblers emit a call to a static function in another section as a
branch to a section symbol plus an offset, but a range extension thunk
jumps to the start of a symbol. On RELA targets, give such a branch a
symbol at its destination and a zero addend, as we do for references to
section fragments. This fixes Chromium's ARM64 debug build, which
9b9162146 made an error and 2.42.1 linked into calls to wrong addresses.
2026-10-03 16:22:19 +09:00
Rui UeyamaandClaude Opus 5.5 a1bda924e9 Read an object's unwind info in a step of its own
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]>
2026-10-03 16:13:30 +09:00
Rui UeyamaandClaude Opus 5.5 2b9217ba5e Keep input-file helpers that only their own module uses private
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]>
2026-10-03 16:13:30 +09:00
Rui UeyamaandClaude Opus 5.5 3fe268366d Drop an unused setter and a pass-through wrapper
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]>
2026-10-03 16:13:30 +09:00
Rui UeyamaandClaude Opus 5.5 37b17c2976 Put set_platform and add_linker_option with the option readers
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]>
2026-10-03 16:10:25 +09:00
Rui UeyamaandClaude Opus 5.5 d548fe38d5 Say what Args::is_kext tells, as its sibling predicates do
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]>
2026-10-03 16:10:25 +09:00
Rui UeyamaandClaude Opus 5.5 8ffbad9d06 Keep -fixup_chains for kexts and kernels in a local of resolve_fixups
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]>
2026-10-03 16:10:25 +09:00
Rui UeyamaandClaude Opus 5.5 784af75009 Make a bitcode file's placeholder object from a plain empty one
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]>
2026-10-03 15:59:57 +09:00
Rui UeyamaandClaude Opus 5.5 410b5068a0 Split integrate_objects into its steps, and fill arenas without raw pointers
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]>
2026-10-03 15:59:57 +09:00
Rui UeyamaandClaude Opus 5.5 01d02b3b5a Split the .tbd readers into a function per format and per library
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]>
2026-10-03 15:59:57 +09:00
Rui UeyamaandClaude Opus 5.5 db7235ce0f Put stray doc comments back on the items they describe
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]>
2026-10-03 15:59:57 +09:00
Rui UeyamaandClaude Opus 5.5 1c6ec28755 Walk an input's load commands through one iterator
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]>
2026-10-03 15:59:57 +09:00
Rui UeyamaandClaude Opus 5.5 bee175a177 Drop the mapped file flag for LTO's objects that no one reads
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]>
2026-10-03 15:59:57 +09:00
Rui UeyamaandClaude Opus 5.5 d52dd3124c Name a dylib binary's platforms plainly in the platform diagnostic
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]>
2026-10-03 15:59:57 +09:00
Rui UeyamaandClaude Opus 5.5 57e33a60b4 Add stub and binary dylibs through one path with their re-exports
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]>
2026-10-03 15:59:57 +09:00
Rui UeyamaandClaude Opus 5.5 b3927eb41f Build every dylib from one constructor
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]>
2026-10-03 15:59:57 +09:00
Rui UeyamaandClaude Opus 5.5 4b40b9ca2c Put scan_relocations' comment on GOT loads where GOT loads are handled
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]>
2026-10-03 15:57:29 +09:00
Rui UeyamaandClaude Opus 5.5 8466af3bea Check a moved symbol's eligibility in one condition in subsec_named
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]>
2026-10-03 15:57:29 +09:00
Rui UeyamaandClaude Opus 5.5 fcfa44dce3 Say which dylibs dead_strip_dylibs may drop, and rewrap a doc comment
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]>
2026-10-03 15:57:29 +09:00
Rui UeyamaandClaude Opus 5.5 968846b90a Name what passes.rs imports by its imported name in its second half
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]>
2026-10-03 15:57:28 +09:00
Rui UeyamaandClaude Opus 5.5 5f0bd7e15b Move the lazy-dylib passes into a module of their own
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]>
2026-10-03 15:57:28 +09:00
Rui UeyamaandClaude Opus 5.5 233fb21faa Resolve a traced file's real path in the trace writer
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]>
2026-10-03 15:57:28 +09:00
Rui UeyamaandClaude Opus 5.5 6c154ce460 Gather thunk addresses from every output section, as mold-rust does
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]>
2026-10-03 15:57:28 +09:00
Rui UeyamaandClaude Opus 5.5 3665e1ec01 Name the command line's initial undefines in one function
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]>
2026-10-03 15:57:28 +09:00
Rui UeyamaandClaude Opus 5.5 ab54a37630 Tell an object's own references apart by its index in print_dependencies
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]>
2026-10-03 15:57:28 +09:00
Rui UeyamaandClaude Opus 5.5 6994e4465a Name an object in diagnostics by its path, without file_display
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]>
2026-10-03 15:57:28 +09:00
Rui UeyamaandClaude Opus 5.5 cda706bc9e Add the -alias names and claim the boundary symbols in their own functions
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]>
2026-10-03 15:57:28 +09:00
Rui UeyamaandClaude Opus 5.5 54b9ee7b8f Find weak definitions' losing copies and merge literal shards in helpers
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]>
2026-10-03 15:57:28 +09:00
Rui UeyamaandClaude Opus 5.5 922db42c2f Drop the unused dylibs in a function of their own
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]>
2026-10-03 15:57:28 +09:00
Rui UeyamaandClaude Opus 5.5 984689bf9f Give imports their stubs and linker imports their binding in one place
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]>
2026-10-03 15:57:28 +09:00
Rui UeyamaandClaude Opus 5.5 5914f2db16 Lay out a segment's chunks with their alignment and unwind encoding apart
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]>
2026-10-03 15:57:28 +09:00
Rui UeyamaandClaude Opus 5.5 372cc4de93 Build the __LINKEDIT fixups and sorted globals in functions of their own
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]>
2026-10-03 15:57:28 +09:00
Rui UeyamaandClaude Opus 5.5 110dfb2c42 Compute the UUID apart from copying the chunks
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]>
2026-10-03 15:57:28 +09:00
Rui UeyamaandClaude Opus 5.5 129914e2f2 Report undefined symbols after deciding which ones dyld looks up
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]>
2026-10-03 15:57:28 +09:00
Rui UeyamaandClaude Opus 5.5 4fa02a5104 Copy the tables built during layout with one helper
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]>
2026-10-03 15:52:19 +09:00
Rui UeyamaandClaude Opus 5.5 b84136d2b2 Copy an output section's members through mold's for_each_member
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]>
2026-10-03 15:52:19 +09:00
Rui UeyamaandClaude Opus 5.5 e3c8243cb0 Keep the layout helpers only their file uses private
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]>
2026-10-03 15:52:19 +09:00
Rui UeyamaandClaude Opus 5.5 060ab33975 Look up a split-info site's helper without an emptiness guard
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]>
2026-10-03 15:52:19 +09:00
Rui UeyamaandClaude Opus 5.5 94b7d76cc7 Lay out and write the -r output in steps of their own
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]>
2026-10-03 15:52:19 +09:00
Rui UeyamaandClaude Opus 5.5 6983182133 Encode the export trie in named passes
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]>
2026-10-03 15:52:19 +09:00
Rui UeyamaandClaude Opus 5.5 256fe02da1 Write the chained fixups payload table by table
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]>
2026-10-03 15:52:19 +09:00
Rui UeyamaandClaude Opus 5.5 e6eb548c7a Encode __unwind_info in named phases
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]>
2026-10-03 15:52:19 +09:00
Rui UeyamaandClaude Opus 5.5 019d88efbc Give -force_symbols_*weak_list and -move_to_r*_segment an arm each
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]>
2026-10-03 15:48:17 +09:00
Rui UeyamaandClaude Opus 5.5 70e49eb5a7 Name the command line and link result types, as mold-rust does
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]>
2026-10-03 15:48:17 +09:00
Rui UeyamaandClaude Opus 5.5 f31faa2457 Settle the source version in resolve_defaults, environment included
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]>
2026-10-03 15:48:17 +09:00
Rui UeyamaandClaude Opus 5.5 2800bd85ae Pass the fork pipe's byte through File, as mold-rust does
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]>
2026-10-03 15:48:17 +09:00
Rui UeyamaandClaude Opus 5.5 020d4da0a7 Write an x86-64 stub's displacement as the range check computed it
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]>
2026-10-03 15:43:45 +09:00
Rui UeyamaandClaude Opus 5.5 40471e73a1 Encode x86-64's "movq slot(%rip), %reg" in one place
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]>
2026-10-03 15:43:45 +09:00
Rui UeyamaandClaude Opus 5.5 be1ec10537 Build -trace_symbols_file's dylib entries with one closure
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]>
2026-10-03 15:23:37 +09:00
Rui UeyamaandClaude Opus 5.5 121f652fac Walk all of an NFA's state words when matching, as mold-rust does
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]>
2026-10-03 15:23:37 +09:00
Rui UeyamaandClaude Opus 5.5 b3f86249a4 Take the -sdk_imports report's symbols from dylib_imports
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]>
2026-10-03 15:23:37 +09:00
Rui UeyamaandClaude Opus 5.5 f583a7e807 Read a Mach-O header in one place in filetype
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]>
2026-10-03 15:23:37 +09:00
Rui UeyamaandClaude Opus 5.5 299302302d Read hexadecimal and decimal option arguments through ArgCursor
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]>
2026-10-03 15:23:37 +09:00
Rui UeyamaandClaude Opus 5.5 1f5e308cdc Keep each option's resolution in one place in resolve_options
-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]>
2026-10-03 15:23:37 +09:00
Rui UeyamaandClaude Opus 5.5 fb498381bc Test Args::shared_region in check_dylib_use instead of recomputing it
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]>
2026-10-03 15:23:37 +09:00
Rui UeyamaandClaude Opus 5.5 61feaa1f18 Share the deployment target check of lazy-load and delay-init dylibs
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]>
2026-10-03 15:23:37 +09:00
Rui UeyamaandClaude Opus 5.5 596c892370 Put the -preload paragraph of resolve_defaults over its code
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]>
2026-10-03 15:23:37 +09:00
Rui UeyamaandClaude Opus 5.5 f9c8d49408 Put the option resolution helpers in the order parse_args calls them
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]>
2026-10-03 15:23:37 +09:00
Rui UeyamaandClaude Opus 5.5 39d1feb570 Drop add_patterns' value, which every caller gave as 0
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]>
2026-10-03 15:23:37 +09:00
Rui UeyamaandClaude Opus 5.5 315327c5c2 Resolve -pagezero_size from the option, not a flag beside it
-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]>
2026-10-03 15:23:37 +09:00
Rui UeyamaandClaude Opus 5.5 70c71019a8 List only the option defaults that aren't zero for Args
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]>
2026-10-03 15:23:37 +09:00
Rui UeyamaandClaude Opus 5.5 6b7a0d5888 Read DWARF's fixed-size fields through one take()
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]>
2026-10-03 15:05:53 +09:00
Rui Ueyama 100987dd0e Mention the minimum Rust version in README 2026-10-03 15:01:31 +09:00
Rui UeyamaandClaude Opus 5.5 71fc3f6d30 Put Symbol's constructor with its other basic accessors
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]>
2026-10-03 15:01:00 +09:00
Rui UeyamaandClaude Opus 5.5 0b9352c972 Read a classic method list's header in one place
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]>
2026-10-03 15:01:00 +09:00
Rui UeyamaandClaude Opus 5.5 2d21869bab Name a CPU type's target by the targets' own constants
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]>
2026-10-03 15:01:00 +09:00
Rui UeyamaandClaude Opus 5.5 abb96d920f Reflow two comments broken mid-paragraph
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]>
2026-10-03 15:01:00 +09:00
Rui UeyamaandClaude Opus 5.5 f1c30bb457 Drop NList::is_undef, which nothing calls
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]>
2026-10-03 15:01:00 +09:00
Rui UeyamaandClaude Opus 5.5 d64d72bc0e Define the targets' 32- and 64-bit field writers once
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]>
2026-10-03 15:01:00 +09:00
Rui UeyamaandClaude Opus 5.5 916d41bba2 Default-construct ICF's digest slots, as mold-rust's DigestSlot does
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]>
2026-10-03 15:01:00 +09:00
Rui UeyamaandClaude Opus 5.5 ed122c7de0 Look symbols up by a borrowed Query, as mold-rust does
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]>
2026-10-03 15:00:59 +09:00
Rui UeyamaandClaude Opus 5.5 3a976beac4 Compute an Objective-C reference record's key in a function of its own
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]>
2026-10-03 15:00:59 +09:00
Rui UeyamaandClaude Opus 5.5 827aa7b8ad Find the compile unit's abbreviation with mold's find_abbrev
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]>
2026-10-03 15:00:59 +09:00
Rui UeyamaandClaude Opus 5.5 19eb7dacbe Relax arm64's GOT and TLV loads in one pair of match arms
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]>
2026-10-03 15:00:59 +09:00
Rui UeyamaandClaude Opus 5.5 ed34e8a5e2 Resolve a non-extern relocation's section in one place
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]>
2026-10-03 15:00:59 +09:00
Rui UeyamaandClaude Opus 5.5 28be56b33e Encode arm64's lazy and delay-init helpers with one instruction encoder
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]>
2026-10-03 15:00:59 +09:00
Rui UeyamaandClaude Opus 5.5 c22549cae1 Share the lookup of a GOT load's lazy or delay-init helper
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]>
2026-10-03 15:00:59 +09:00
Rui UeyamaandClaude Opus 5.5 9beb62b891 Compare bundle paths with symlinks resolved in merged-libraries-hook.sh
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]>
2026-10-03 14:56:24 +09:00
Rui Ueyama 6bdd637973 Declare the minimum supported Rust version
mold needs Rust 1.95 for std::hint::cold_path and the atomic update
methods, while its dependencies need only 1.85. Declaring it makes Cargo
reject older toolchains with a clear message before building, and lets
the resolver avoid dependency versions that need a newer rustc.
2026-10-03 14:47:19 +09:00
Rui Ueyama e10a25f478 Link libatomic from mold instead of from the mimalloc crate
This reverts 6f8dbb64b. mimalloc uses 64-bit atomics, which GCC
implements as calls into libatomic on hosts such as 32-bit PowerPC.
The need comes from linking mimalloc statically into mold, so the
executable is the right place to add libatomic. Our mimalloc_rust fork
is pinned to the commit before its libatomic change, so the fork
differs from upstream only by the bundled mimalloc version.
2026-10-03 14:32:23 +09:00
Rui UeyamaandClaude Opus 5.5 767abbd054 Compute the mach header's flags in a function of their own
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]>
2026-10-03 14:31:27 +09:00
Rui UeyamaandClaude Opus 5.5 bfd9237969 Drop the load-command wrappers that only forward
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]>
2026-10-03 14:31:27 +09:00
Rui UeyamaandClaude Opus 5.5 a9c6cba926 Let append_string finish a string load command
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]>
2026-10-03 14:31:27 +09:00
Rui UeyamaandClaude Opus 5.5 81cb7f2077 Look up an input section's output section in one table
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]>
2026-10-03 14:31:27 +09:00
Rui UeyamaandClaude Opus 5.5 74c797529e Encode every bind opcode in one place
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]>
2026-10-03 14:31:27 +09:00
Rui UeyamaandClaude Opus 5.5 0829aa55fe Find the pointers dyld fixes up with one helper
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]>
2026-10-03 14:31:27 +09:00
Rui UeyamaandClaude Opus 5.5 c3ffe7e996 Record each arm64 instruction's fixup of a mergeable dylib on its own
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]>
2026-10-03 13:56:52 +09:00
Rui UeyamaandClaude Opus 5.5 958a73e889 Make the DOFs after dead stripping, of the live probe sites
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]>
2026-10-03 13:56:52 +09:00
Rui UeyamaandClaude Opus 5.5 ba7fc393a5 Let the bundle hook's initializer keep its own name
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]>
2026-10-03 13:48:03 +09:00
Rui UeyamaandClaude Opus 5.5 ba238f729b Lay delay-init helpers out in the order of their first uses
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]>
2026-10-03 13:48:03 +09:00
Rui UeyamaandClaude Opus 5.5 240536bc9a Have a delay-init stub jump through its symbol's own GOT slot
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]>
2026-10-03 13:48:03 +09:00
Rui UeyamaandClaude Opus 5.5 6ab2221bdd Leave __dyld_private where the stub helper made it
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]>
2026-10-03 13:48:03 +09:00
Rui UeyamaandClaude Opus 5.5 af3023f920 Keep a delay-init dylib's install name string apart from the inputs'
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]>
2026-10-03 13:48:03 +09:00
Rui UeyamaandClaude Opus 5.5 966c5bdcbf Report a delay-init use in mold's words alone
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]>
2026-10-03 13:48:03 +09:00
Rui UeyamaandClaude Opus 5.5 623001d38e Order a DOF's probes and instances by their first sites
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]>
2026-10-03 13:47:25 +09:00
Rui UeyamaandClaude Opus 5.5 7b2c5e9e7f Name a DTrace probe's argument types as dtrace -h spelled them
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]>
2026-10-03 13:47:25 +09:00
Rui UeyamaandClaude Opus 5.5 13e526db7c Make a DOF without libdtrace's checks of the D script it rebuilds
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]>
2026-10-03 13:47:25 +09:00
Rui UeyamaandClaude Opus 5.5 ab565ddcdc Name a DTrace probe site's function by its subsection's label
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]>
2026-10-03 13:47:25 +09:00
Rui UeyamaandClaude Opus 5.5 49cc020cb9 Take a branch to a DTrace probe from any section for a probe site
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]>
2026-10-03 13:47:25 +09:00
Rui UeyamaandClaude Opus 5.5 4c845d3e88 Record in a mergeable dylib only the imports its code refers to
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]>
2026-10-03 13:47:25 +09:00
Rui UeyamaandClaude Opus 5.5 05f3e3f4cc Set only the mergeable record flags a merging link reads
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]>
2026-10-03 13:46:37 +09:00
Rui UeyamaandClaude Opus 5.5 32eabae1db Keep a mergeable dylib's compact unwind records anonymous
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]>
2026-10-03 13:46:37 +09:00
Rui UeyamaandClaude Opus 5.5 72dcce2933 Write a mergeable record's entries in the order they are made
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]>
2026-10-03 13:46:37 +09:00
Rui UeyamaandClaude Opus 5.5 909940a27b Record ___dso_handle in a mergeable dylib as a plain undefine
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]>
2026-10-03 13:46:37 +09:00
Rui UeyamaandClaude Opus 5.5 76cea0c509 Note every named subsection of a merged library's objects
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]>
2026-10-03 13:46:37 +09:00
Rui UeyamaandClaude Opus 5.5 b5c7eb1dff Record the Objective-C lists the link made without names
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]>
2026-10-03 13:46:37 +09:00
Rui UeyamaandClaude Opus 5.5 cf187aa3e8 Stop checking that a mergeable record's fixup targets have symbols
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]>
2026-10-03 13:46:37 +09:00
Rui UeyamaandClaude Opus 5.5 b8733b28cd Lay a merged library's entries out in the record's order
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]>
2026-10-03 13:46:37 +09:00
Rui UeyamaandClaude Opus 5.5 1942604c6f Drop the DOF section's own subsection from what dtrace records
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]>
2026-10-03 13:42:22 +09:00
Rui UeyamaandClaude Opus 5.5 ca0a5b06ec Report every reference to dropped Swift metadata after dead stripping
-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]>
2026-10-03 13:42:22 +09:00
Rui UeyamaandClaude Opus 5.5 965a077c05 List every text relocation and 32-bit pointer by address
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]>
2026-10-03 13:42:22 +09:00
Rui UeyamaandClaude Opus 5.5 ea652a919b Check segment overlaps on the placed segments
-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]>
2026-10-03 13:42:22 +09:00
Rui UeyamaandClaude Opus 5.5 a01cd26cfe Report layout errors as any error, without printing the layout
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]>
2026-10-03 13:42:22 +09:00
Rui UeyamaandClaude Opus 5.5 ad24c4e2d2 Drop the dylib load order field no one reads
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]>
2026-10-03 13:42:22 +09:00
Rui UeyamaandClaude Opus 5.5 ef580586fb Give dylibs their load commands in the order they are first named
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]>
2026-10-03 13:42:22 +09:00
Rui UeyamaandClaude Opus 5.5 3783a15e6f Name a fixup error's place by file, subsection and offset
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]>
2026-10-03 13:42:22 +09:00
Rui UeyamaandClaude Opus 5.5 212764293c Bind Swift's __swift_FORCE_LOAD_$_ references as any weak import
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]>
2026-10-03 13:42:22 +09:00
Rui UeyamaandClaude Opus 5.5 77e4059421 Lay out common symbols where they are first declared
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]>
2026-10-03 13:42:22 +09:00
Rui UeyamaandClaude Opus 5.5 2a3889a994 Say an undefined name the command line insists on is wanted by it
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]>
2026-10-03 13:42:22 +09:00
Rui UeyamaandClaude Opus 5.5 c087d3eb0d Keep stubs and GOT slots in the order relocations reach them
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]>
2026-10-03 13:42:22 +09:00
Rui UeyamaandClaude Opus 5.5 4b8dca80e0 Lay out the lazy-load tables in the order of first use
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]>
2026-10-03 13:42:22 +09:00
Rui UeyamaandClaude Opus 5.5 fd97b1826f Call __dyld_lazy_load only from the lazy-load helpers
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]>
2026-10-03 13:42:22 +09:00
Rui UeyamaandClaude Opus 5.5 393ab7a206 Mention a disallowed auto-linked library as a hint not found is
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]>
2026-10-03 13:42:22 +09:00
Rui UeyamaandClaude Opus 5.5 98c225c2a0 Name an unreadable input or one without LC_UUID once
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]>
2026-10-03 13:42:22 +09:00
Rui UeyamaandClaude Opus 5.5 f77ddfa62b Stop at the first missing library or framework in command-line order
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]>
2026-10-03 13:42:22 +09:00
Rui UeyamaandClaude Opus 5.5 e9cd528079 Look for dyld_stub_binder without checking for libSystem first
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]>
2026-10-03 13:42:22 +09:00
Rui UeyamaandClaude Opus 5.5 fa01a24ba5 Bind dyld_stub_binder only once a stub needs it
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]>
2026-10-03 13:42:22 +09:00
Rui UeyamaandClaude Opus 5.5 d2d243fa3e Leave a zero-fill segment's file offset where it starts
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]>
2026-10-03 13:42:22 +09:00
Rui UeyamaandClaude Opus 5.5 d068457076 Refuse -assert-weak-l's strong imports in one plain message
-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]>
2026-10-03 13:42:22 +09:00
Rui UeyamaandClaude Opus 5.5 10e2b4407b Check weak imports in the objects' own order
-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]>
2026-10-03 13:42:22 +09:00
Rui UeyamaandClaude Opus 5.5 486f8c9edf Name files in diagnostics by their paths
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]>
2026-10-03 13:42:22 +09:00
Rui UeyamaandClaude Opus 5.5 f578faa0ae Give every code section range-extension thunks, in any segment
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]>
2026-10-03 13:42:22 +09:00
Rui UeyamaandClaude Opus 5.5 7f964e15cc Drop the warning about code too large for ld-prime's island clusters
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]>
2026-10-03 13:42:22 +09:00
Rui UeyamaandClaude Opus 5.5 d015f6baa3 Warn of unmovable symbols as found, naming their objects by path
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]>
2026-10-03 13:42:21 +09:00
Rui UeyamaandClaude Opus 5.5 4199b076b1 Report -commons conflicts and redundant re-exports by path
-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]>
2026-10-03 13:42:21 +09:00
Rui UeyamaandClaude Opus 5.5 156ea78206 List each function referring to a poisoned symbol once
-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]>
2026-10-03 13:42:21 +09:00
Rui UeyamaandClaude Opus 5.5 6a6a8e79c1 Trace the libraries the link brings in on its own in one pass
-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]>
2026-10-03 13:42:21 +09:00
Rui UeyamaandClaude Opus 5.5 61f011c839 Say why each archive member loaded once resolution is done
-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]>
2026-10-03 13:42:21 +09:00
Rui UeyamaandClaude Opus 5.5 808e577017 List each file -t read once, in the order read
-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]>
2026-10-03 13:42:21 +09:00
Rui UeyamaandClaude Opus 5.5 2f07ae63e6 Print one parent chain per symbol for -why_live
-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]>
2026-10-03 13:42:21 +09:00
Rui UeyamaandClaude Opus 5.5 10ee1158f8 Report duplicates among bitcode files before LTO
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]>
2026-10-03 13:42:21 +09:00
Rui UeyamaandClaude Opus 5.5 b6ccde5cff Warn of ignored auto-link options once, as they are read
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]>
2026-10-03 13:42:21 +09:00
Rui UeyamaandClaude Opus 5.5 fadf541505 Check each input's version once, with no ordering between them
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]>
2026-10-03 13:42:21 +09:00
Rui UeyamaandClaude Opus 5.5 1de2179ffa Warn of a merged dylib built for a newer macOS once
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]>
2026-10-03 13:42:21 +09:00
Rui UeyamaandClaude Opus 5.5 2914751591 Warn of an object of another CPU subtype once
-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]>
2026-10-03 13:42:21 +09:00
Rui UeyamaandClaude Opus 5.5 0ba444135e Warn of an ignored library once, however often it is named
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]>
2026-10-03 13:42:21 +09:00
Rui UeyamaandClaude Opus 5.5 87d389d94f Drop the literal table nothing reads
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]>
2026-10-03 13:42:21 +09:00
Rui UeyamaandClaude Opus 5.5 3f4d713533 Read the -sdk_imports API list next to the report that uses it
-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]>
2026-10-03 13:40:03 +09:00
Rui UeyamaandClaude Opus 5.5 33cc30a5d2 Merge categories only into classes the image defines
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]>
2026-10-03 12:52:10 +09:00
Rui UeyamaandClaude Opus 5.5 9c1a233f67 Drop the MOLD_OBJC_DEBUG trace of skipped category merges
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]>
2026-10-03 12:52:10 +09:00
Rui UeyamaandClaude Opus 5.5 2f64aac111 Give each objc stub a selector reference and name of its own
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]>
2026-10-03 12:52:10 +09:00
Rui UeyamaandClaude Opus 5.5 04027cabde Let the objc stubs share _objc_msgSend's GOT slot
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]>
2026-10-03 12:52:10 +09:00
Rui UeyamaandClaude Opus 5.5 4c2334b67c Keep the weak-lookup GOT slots in __got
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]>
2026-10-03 12:52:10 +09:00
Rui UeyamaandClaude Opus 5.5 b44fcc1652 Leave an input __DATA,__got where it is
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]>
2026-10-03 12:52:10 +09:00
Rui UeyamaandClaude Opus 5.5 50e9d0d82f Keep class references in __objc_classrefs at every deployment target
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]>
2026-10-03 12:52:10 +09:00
Rui UeyamaandClaude Opus 5.5 d0e148a406 Name a .tbd file's platforms plainly in the platform diagnostic
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]>
2026-10-03 12:52:10 +09:00
Rui UeyamaandClaude Opus 5.5 6bc8cdc8e6 Name x86-64 lazy load helpers after rsp and rbp too
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]>
2026-10-03 12:52:10 +09:00
Rui UeyamaandClaude Opus 5.5 656d5dc763 Pad every x86-64 stub helper entry to 12 bytes
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]>
2026-10-03 12:50:13 +09:00
Rui UeyamaandClaude Opus 5.5 e0bbae282f Let an arm64 ADRP reach the page 4 GiB below
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]>
2026-10-03 12:50:13 +09:00
Rui UeyamaandClaude Opus 5.5 fe991417ba Refuse a late-bound reference without naming ld-prime's fixup kind
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]>
2026-10-03 12:50:13 +09:00
Rui UeyamaandClaude Opus 5.5 efe7a2e14b Take a non-extern relocation's section without a warning
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]>
2026-10-03 12:50:13 +09:00
Rui UeyamaandClaude Opus 5.5 ecc09e1fac Refuse an absolute symbol as an initializer offset
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]>
2026-10-03 12:50:13 +09:00
Rui UeyamaandClaude Opus 5.5 93849b7951 Write LC_VERSION_MIN_MACOSX only for macOS before 10.14
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]>
2026-10-03 12:50:13 +09:00
Rui UeyamaandClaude Opus 5.5 d2c4c09bbf Encode rebase and bind opcodes as sold does
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]>
2026-10-03 12:50:13 +09:00
Rui UeyamaandClaude Opus 5.5 a8347958bf List local relocations by address and refuse only unreachable ones
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]>
2026-10-03 12:50:13 +09:00
Rui UeyamaandClaude Opus 5.5 a16605ec78 Record no split-info entries that adjust by nothing
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]>
2026-10-03 12:50:13 +09:00
Rui UeyamaandClaude Opus 5.5 31725dfea0 Encode __unwind_info as sold does: compressed pages, plain entries
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]>
2026-10-03 12:50:13 +09:00
Rui UeyamaandClaude Opus 5.5 a61a62a312 Check fixups' alignment and chain pages in one plain pass
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]>
2026-10-03 12:50:13 +09:00
Rui UeyamaandClaude Opus 5.5 34785e9a88 Size __chain_starts by the chains it lists
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]>
2026-10-03 12:43:46 +09:00
Rui UeyamaandClaude Opus 5.5 fcecfac8e0 Write the LC_LAZY_LOAD_DYLIB_INFO commands in load order
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]>
2026-10-03 12:43:46 +09:00
Rui UeyamaandClaude Opus 5.5 6eca74f490 Flag no segment SG_NORELOC
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]>
2026-10-03 12:43:46 +09:00
Rui UeyamaandClaude Opus 5.5 6b085a2ae2 Leave -headerpad free after the load commands, not ld-prime's estimate
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]>
2026-10-03 12:43:46 +09:00
Rui UeyamaandClaude Opus 5.5 9ab2e9667d Test that an arm64 empty literal section with an ltmp label stays
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]>
2026-10-03 12:37:09 +09:00
Rui UeyamaandClaude Opus 5.5 7f21fec237 List both files of a re-exported dylib named again at another path
-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]>
2026-10-03 12:37:09 +09:00
Rui UeyamaandClaude Opus 5.5 7f7a87b012 Trace a stub's re-exports once, whatever its platforms
-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]>
2026-10-03 12:37:09 +09:00
Rui UeyamaandClaude Opus 5.5 53d201b19a Link the bytes after a C-string section's last NUL as they are
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]>
2026-10-03 12:37:09 +09:00
Rui UeyamaandClaude Opus 5.5 e5d22024b6 Carry an FDE of a function outside code, and warn of it as found
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]>
2026-10-03 12:37:09 +09:00
Rui UeyamaandClaude Opus 5.5 8ae2fd83ca Keep an empty section with a label at a record section's end
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]>
2026-10-03 12:37:09 +09:00
Rui UeyamaandClaude Opus 5.5 f9d8eff3df Read $ld$ directives' versions as X[.Y[.Z]], skipping bad ones
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]>
2026-10-03 12:37:09 +09:00
Rui UeyamaandClaude Opus 5.5 ac815749ca Read -sdk_imports_api_list as plain JSON
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]>
2026-10-03 12:37:09 +09:00
Rui UeyamaandClaude Opus 5.5 b0f7102a08 Leave libLTO's version out of the ThinLTO failure message
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]>
2026-10-03 12:37:09 +09:00
Rui UeyamaandClaude Opus 5.5 006649bc36 Name a library given twice by its name or path
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]>
2026-10-03 12:37:09 +09:00
Rui UeyamaandClaude Opus 5.5 afb58a07ee Refuse each lazy-load or delay-init use in an error of its own
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]>
2026-10-03 12:37:09 +09:00
Rui UeyamaandClaude Opus 5.5 5086331be4 Report the errors in the input files as they are found
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]>
2026-10-03 12:37:09 +09:00
Rui UeyamaandClaude Opus 5.5 c31daf6205 Create the output file by removing the old one, then creating it
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]>
2026-10-03 12:37:09 +09:00
Rui UeyamaandClaude Opus 5.5 98c2fea1d4 Word I/O errors with strerror's text, as mold-rust does
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]>
2026-10-03 12:37:09 +09:00
Rui UeyamaandClaude Opus 5.5 c9e34b3034 Make -fatal_warnings turn each warning into an error, as mold-rust does
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]>
2026-10-03 12:37:09 +09:00
Rui UeyamaandClaude Opus 5.5 79ef95e66f Drop ld-prime's conditions on deprecation warnings, and two warnings
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]>
2026-10-03 12:37:09 +09:00
Rui UeyamaandClaude Opus 5.5 debbad395c Test the shared cache's option refusals one at a time
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]>
2026-10-03 12:37:09 +09:00
Rui UeyamaandClaude Opus 5.5 9403320259 Drop the Apple-internal SDK's dirty data and order files
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]>
2026-10-03 12:37:09 +09:00
Rui UeyamaandClaude Opus 5.5 d05c595cf2 Take any -lto_library path, the last one given
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]>
2026-10-03 12:37:09 +09:00
Rui UeyamaandClaude Opus 5.5 dcf38e2c81 Tell an architecture name's CPU type by its prefix
-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]>
2026-10-03 12:37:09 +09:00
Rui UeyamaandClaude Opus 5.5 b94e0eb6f4 Drop $LD_DYLIB_ARCH_FALLBACK, and name only the arm64 and x86_64 CPUs
$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]>
2026-10-03 12:37:09 +09:00
Rui UeyamaandClaude Opus 5.5 95bcd03e9e Warn of each ignored lazy-load or delay-init library once, by name
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]>
2026-10-03 12:37:09 +09:00
Rui UeyamaandClaude Opus 5.5 7db78e9ac6 Resolve the options first, then check them, in no promised order
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]>
2026-10-03 12:37:09 +09:00
Rui UeyamaandClaude Opus 5.5 164cdd6981 Refuse an unknown option, and warn of an obsolete one, as read
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]>
2026-10-03 12:37:09 +09:00
Rui UeyamaandClaude Opus 5.5 18ab53017e Let -w silence every option warning, wherever it is
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]>
2026-10-03 12:37:09 +09:00
Rui UeyamaandClaude Opus 5.5 80296d83b8 Read response files as mold-rust does
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]>
2026-10-03 12:37:09 +09:00
Rui UeyamaandClaude Opus 5.5 cca36e7146 Read -source_version as up to five plain decimal numbers
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]>
2026-10-03 12:37:09 +09:00
Rui UeyamaandClaude Opus 5.5 8948b9a872 Read numeric option arguments as plain numbers
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]>
2026-10-03 12:37:09 +09:00
Rui UeyamaandClaude Opus 5.5 d2dc83a48d Refuse mixed export choices in one message
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]>
2026-10-03 12:37:09 +09:00
Rui UeyamaandClaude Opus 5.5 951b3db4b6 Refuse an unknown platform or architecture in one message
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]>
2026-10-03 12:37:09 +09:00
Rui UeyamaandClaude Opus 5.5 61cbde9b07 Report a missing option argument in one message for every option
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]>
2026-10-03 12:37:09 +09:00
Rui UeyamaandClaude Opus 5.5 4d98de8b4c Give each x86-64 stack-indirect unwind entry its own __unwind_info entry
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]>
2026-10-03 11:32:02 +09:00
Rui UeyamaandClaude Opus 5.5 5360831581 Bring the -r and symbol table comments up to date
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]>
2026-10-03 01:49:27 +09:00
Rui UeyamaandClaude Opus 5.5 67dadc3181 Leave a -r output's zero-fill sections where they come
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]>
2026-10-03 01:48:49 +09:00
Rui UeyamaandClaude Opus 5.5 4625c7c3d7 Keep only the names of folded functions, not ld-prime's scope rule
-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]>
2026-10-03 01:44:55 +09:00
Rui UeyamaandClaude Opus 5.5 8e77417a0a List the imports live code uses and those the image indexes
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]>
2026-10-03 01:44:06 +09:00
Rui UeyamaandClaude Opus 5.5 d38b9352d2 Point an N_INDR alias at its import's own string
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]>
2026-10-03 01:38:41 +09:00
Rui UeyamaandClaude Opus 5.5 3ba18b90f1 Put a -static image's absolute __mh_execute_header in no section
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]>
2026-10-03 01:38:02 +09:00
Rui UeyamaandClaude Opus 5.5 0aad603ffa List a final image's locals object by object in symbol table order
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]>
2026-10-03 01:35:42 +09:00
Rui UeyamaandClaude Opus 5.5 6ba59bbcc5 List every folded function's local name at its survivor
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]>
2026-10-03 01:35:30 +09:00
Rui UeyamaandClaude Opus 5.5 de7e13f9c8 List a rewritten method list's demoted name as any other
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]>
2026-10-03 01:35:13 +09:00
Rui UeyamaandClaude Opus 5.5 8e94a90d69 List every local of a live subsection but assembler temporaries
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]>
2026-10-03 01:35:13 +09:00
Rui UeyamaandClaude Opus 5.5 3c7b5954c9 Keep the debug unit of an object nothing of which is left
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]>
2026-10-03 01:34:57 +09:00
Rui UeyamaandClaude Opus 5.5 3e10a3b42a Note a tentative definition in each object that declares it
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]>
2026-10-03 01:34:57 +09:00
Rui UeyamaandClaude Opus 5.5 e8a4e7d539 Write a debug note for every symbol of a live subsection
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]>
2026-10-03 01:34:57 +09:00
Rui UeyamaandClaude Opus 5.5 4c568f4908 Lay out the symbol table's strings in entry order
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]>
2026-10-03 01:34:57 +09:00
Rui UeyamaandClaude Opus 5.5 3dab27bfe2 Share a note's string with its symbol's whenever the names are equal
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]>
2026-10-03 01:34:57 +09:00
Rui UeyamaandClaude Opus 5.5 60676bd6a9 Drop the opening N_SO of the stabs and the N_OSO at string offset 0
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]>
2026-10-03 01:34:57 +09:00
Rui UeyamaandClaude Opus 5.5 b3496e6db4 Carry -r auto-link options through as the inputs have them
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]>
2026-10-03 01:34:57 +09:00
Rui UeyamaandClaude Opus 5.5 d24eee6582 Write no -r LC_LINKER_OPTIMIZATION_HINT when no hint survives
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]>
2026-10-03 01:34:57 +09:00
Rui UeyamaandClaude Opus 5.5 f5cb2a1c94 Place a -r output's contents right after its load commands
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]>
2026-10-03 01:34:57 +09:00
Rui UeyamaandClaude Opus 5.5 2101a82abd Keep an object's bytes under a -r GOT reference
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]>
2026-10-03 01:34:17 +09:00
Rui UeyamaandClaude Opus 5.5 b84415d12d Write -r __compact_unwind records 8-aligned with 8-byte relocations
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]>
2026-10-03 01:34:05 +09:00
Rui UeyamaandClaude Opus 5.5 bdb9df9e92 Write a -r section's relocations in the order the reader has them
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]>
2026-10-03 01:33:18 +09:00
Rui UeyamaandClaude Opus 5.5 d3e042574c Keep every undefined symbol of a -r output's inputs, in input order
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]>
2026-10-03 01:32:50 +09:00
Rui UeyamaandClaude Opus 5.5 ba6a933924 List a -r output's locals in each object's symbol table order
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]>
2026-10-03 01:32:50 +09:00
Rui UeyamaandClaude Opus 5.5 0ec288c9f1 Copy the n_desc of -r symbols as their inputs have them
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]>
2026-10-03 01:32:50 +09:00
Rui UeyamaandClaude Opus 5.5 76ba4288d3 Carry -r input labels and relocation targets through as they are
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]>
2026-10-03 01:32:50 +09:00
Rui UeyamaandClaude Opus 5.5 7f4dcacaaf Keep only the section flags a final image's readers act on
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]>
2026-10-03 01:12:16 +09:00
Rui UeyamaandClaude Opus 5.5 1e2f1cabb9 Make a section that mixes zero fill and contents file-backed
-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]>
2026-10-03 01:12:16 +09:00
Rui UeyamaandClaude Opus 5.5 700e332d14 Type a boundary symbol's empty section by the standard table alone
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]>
2026-10-03 01:12:16 +09:00
Rui UeyamaandClaude Opus 5.5 fd9c0adacb Give the stub sections one alignment per target
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]>
2026-10-03 01:12:16 +09:00
Rui UeyamaandClaude Opus 5.5 90cb093890 Lay __thread_bss out in input order like other sections
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]>
2026-10-03 01:12:16 +09:00
Rui UeyamaandClaude Opus 5.5 36339a8c4e Keep the names of old compilers' coalesced sections
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]>
2026-10-03 01:12:16 +09:00
Rui UeyamaandClaude Opus 5.5 dd37ef649d Merge __StaticInit and the literal pools into their sections by name
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]>
2026-10-03 01:11:49 +09:00
Rui UeyamaandClaude Opus 5.5 6a4b9cb505 Lay relative method lists out in the order they were made
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]>
2026-10-03 01:11:49 +09:00
Rui UeyamaandClaude Opus 5.5 f9cb596f13 Write __eh_frame as the CIEs of the kept FDEs, then the FDEs
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]>
2026-10-03 01:11:49 +09:00
Rui UeyamaandClaude Opus 5.5 4d165d752e Lay -sectcreate data out in command-line order, after the inputs
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]>
2026-10-03 01:11:15 +09:00
Rui UeyamaandClaude Opus 5.5 5cafa9cd5b Order sections within a segment by kind and creation order
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]>
2026-10-03 01:10:38 +09:00
Rui UeyamaandClaude Opus 5.5 aebd1e50d1 Keep the linker's sections apart from same-named input sections
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]>
2026-10-03 01:09:58 +09:00
Rui UeyamaandClaude Opus 5.5 e0de1ec983 Read __compact_unwind without ld-prime's fixup diagnostics
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]>
2026-10-03 00:42:34 +09:00
Rui UeyamaandClaude Opus 5.5 837008a0db Read __eh_frame at mold's level of checking
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]>
2026-10-03 00:42:34 +09:00
Rui UeyamaandClaude Opus 5.5 832ef8de81 Check sections of fixed-size records only where mold would miscompile
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]>
2026-10-03 00:42:34 +09:00
Rui UeyamaandClaude Opus 5.5 5d73627ccf Check input relocations only where mold would miscompile
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]>
2026-10-03 00:42:34 +09:00
Rui UeyamaandClaude Opus 5.5 b68bd0ef34 Drop what only the old -map read
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]>
2026-10-03 00:28:34 +09:00
Rui UeyamaandClaude Opus 5.5 b93e7d8d18 Write -map as rows of subsections, in mold-rust's shape
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]>
2026-10-03 00:28:34 +09:00
Rui UeyamaandClaude Opus 5.5 d65206fa17 Make no __text section for an image without code
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]>
2026-10-03 00:28:34 +09:00
Rui UeyamaandClaude Opus 5.5 66b1ba821d Order -order_file lines by any live symbol of the name
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]>
2026-10-03 00:28:34 +09:00
Rui UeyamaandClaude Opus 5.5 a3b3fda9db Report each symbol's output section with -trace_symbol_layout
-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]>
2026-10-03 00:28:33 +09:00
Rui UeyamaandClaude Opus 5.5 d281f6053e Name -dependency_info's files by absolute paths, each once
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]>
2026-10-03 00:28:33 +09:00
Rui UeyamaandClaude Opus 5.5 d30d81dcbd Read a mergeable dylib's record without validating it
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]>
2026-10-03 00:24:18 +09:00
Rui UeyamaandClaude Opus 5.5 a513d71e81 Read .tbd stubs without checking them against TAPI's schemas
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]>
2026-10-03 00:24:11 +09:00
Rui UeyamaandClaude Opus 5.5 840563a80b Link and run a program against each version of .tbd stub
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]>
2026-10-03 00:24:00 +09:00
Rui UeyamaandClaude Opus 5.5 6c53cf98ba Print the paths and names diagnostics take from inputs as bytes
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]>
2026-10-02 21:07:53 +09:00
Rui UeyamaandClaude Opus 5.5 e3a4a30311 Read the symbols the command line names as bytes
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]>
2026-10-02 21:07:53 +09:00
Rui UeyamaandClaude Opus 5.5 bd74a1b11a Carry symbol names as bytes
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]>
2026-10-02 21:07:53 +09:00
Rui UeyamaandClaude Opus 5.5 f7d5592584 Keep a merged module's losing copy reaching the ThinLTO copy that won
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]>
2026-10-02 20:55:45 +09:00
Rui UeyamaandClaude Opus 5.5 9226a14672 Refuse a PC-relative reference to an import that takes no stub or GOT
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]>
2026-10-02 20:40:27 +09:00
Rui UeyamaandClaude Opus 5.5 02887a5bde Refuse a mergeable dylib's lazy or delayed dylib only when it is used
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]>
2026-10-02 20:40:27 +09:00
Rui UeyamaandClaude Opus 5.5 c3e6f38951 Record a mergeable dylib's initial undefines that are imports
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]>
2026-10-02 20:40:27 +09:00
Rui UeyamaandClaude Opus 5.5 0ba3e1c851 List the map's rows at one address in ld-prime's order
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]>
2026-10-02 20:40:27 +09:00
Rui UeyamaandClaude Opus 5.5 506b893031 Count an adrp and the load completing it as one poisoned reference
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]>
2026-10-02 20:40:27 +09:00
Rui UeyamaandClaude Opus 5.5 e081dbbe8c Override a tentative definition only with an archive member's data
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]>
2026-10-02 20:40:27 +09:00
Rui UeyamaandClaude Opus 5.5 12925af9d8 Write the JSON reports with serde_json
-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]>
2026-10-02 20:22:18 +09:00
Rui UeyamaandClaude Opus 5.5 cd40392d0a Read version 5 stubs with serde_json
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]>
2026-10-02 20:22:18 +09:00
Rui UeyamaandClaude Opus 5.5 64bc387a60 Read -sdk_imports_api_list with serde_json
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]>
2026-10-02 20:22:18 +09:00
Rui UeyamaandClaude Opus 5.5 8ecf824414 Stop checking an object's layout before reading it
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]>
2026-10-02 19:58:19 +09:00
Rui UeyamaandClaude Opus 5.5 387e4e81cb Stop checking symbols for what only a corrupt object has
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]>
2026-10-02 19:56:35 +09:00
Rui UeyamaandClaude Opus 5.5 d614143748 Drop the fixes for objects fuzzing corrupted
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]>
2026-10-02 19:53:14 +09:00
Rui UeyamaandClaude Opus 5.5 1d8fba6021 Take an object section's alignment as it is again
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]>
2026-10-02 19:52:56 +09:00
Rui UeyamaandClaude Opus 5.5 1f02bb3c24 Gather auto-link hints for -trace_implicit_libraries only when asked
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]>
2026-10-02 01:56:48 +09:00
Rui UeyamaandClaude Opus 5.5 c1030c70f7 Split and trim a stub's sequence items byte by byte
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]>
2026-10-02 01:56:48 +09:00
Rui UeyamaandClaude Opus 5.5 c16fedd568 Hash ICF digests with mold-rust's SipHash
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]>
2026-10-02 01:56:48 +09:00
Rui UeyamaandClaude Opus 5.5 990ad92959 Sort -dependency_info's missing files as bytes where that is the same
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]>
2026-10-02 01:56:48 +09:00
Rui UeyamaandClaude Opus 5.5 9b17ca8c83 Parse the stubs re-exported at any depth ahead of loading them
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]>
2026-10-02 01:56:48 +09:00
Rui UeyamaandClaude Opus 5.5 1ec54c576f Resolve each directory of -dependency_info's relative paths once
-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]>
2026-10-02 01:56:48 +09:00
Rui UeyamaandClaude Opus 5.5 9203a68b7b Rank only the labels of address-taken functions for Swift folding
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]>
2026-10-02 01:56:48 +09:00
Rui UeyamaandClaude Opus 5.5 c2cd75f290 Parse auto-linked libraries' stubs on all cores ahead of loading them
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]>
2026-10-02 01:56:48 +09:00
Rui UeyamaandClaude Opus 5.5 6bed379f9b Deduplicate the objects' auto-link options before sorting them
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]>
2026-10-02 01:56:48 +09:00
Rui UeyamaandClaude Opus 5.5 42355775b4 Group parse_args's option arms by what the options are about
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]>
2026-10-02 01:26:37 +09:00
Rui UeyamaandClaude Opus 5.5 2b5fa2c445 Take the environment's defaults for options in env_defaults
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]>
2026-10-02 01:26:37 +09:00
Rui UeyamaandClaude Opus 5.5 eada16b7ee Move the bodies of the longer option arms into helpers
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]>
2026-10-02 01:26:37 +09:00
Rui UeyamaandClaude Opus 5.5 f87bcbd67b Read the symbol list options' files through the cursor
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]>
2026-10-02 01:26:37 +09:00
Rui UeyamaandClaude Opus 5.5 3d4f173430 Split what parse_args does after the options into phases
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]>
2026-10-02 01:26:37 +09:00
Rui UeyamaandClaude Opus 5.5 49e0b96ff5 Gather the options parse_args resolves later in ParseState
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]>
2026-10-02 01:10:07 +09:00
Rui UeyamaandClaude Opus 5.5 32356b8f48 Read the command line through an argument cursor
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]>
2026-10-02 01:09:09 +09:00
Rui UeyamaandClaude Opus 5.5 41951462d6 Take the names of segment and section options as bytes
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]>
2026-10-02 00:53:28 +09:00
Rui UeyamaandClaude Opus 5.5 2abc4d2f88 Carry segment and section names as bytes
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]>
2026-10-02 00:53:28 +09:00
Rui UeyamaandClaude Opus 5.5 dcdd6924f6 Print the bytes a diagnostic names as they are
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]>
2026-10-02 00:52:27 +09:00
Rui UeyamaandClaude Opus 5.5 09b97d22f8 Refuse an image without its one LC_UUID
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]>
2026-10-02 00:48:14 +09:00
Rui UeyamaandClaude Opus 5.5 8793c53b4e Take a section's alignment as ld-prime's 32-bit shift
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]>
2026-10-02 00:48:14 +09:00
Rui UeyamaandClaude Opus 5.5 20a6668f23 Survive the garbage objects fuzzing found
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]>
2026-10-02 00:48:14 +09:00
Rui UeyamaandClaude Opus 5.5 248d89ea23 Read the symbols ld-prime ignores as it does
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]>
2026-10-02 00:48:14 +09:00
Rui UeyamaandClaude Opus 5.5 c08d18f1a1 Check an object's layout as ld-prime does before reading it
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]>
2026-10-02 00:42:44 +09:00
Rui UeyamaandClaude Opus 5.5 922e032784 Name undefined symbols' referencing files and fat dylibs as ld-prime does
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]>
2026-10-02 00:42:44 +09:00
Rui UeyamaandClaude Opus 5.5 ba4a05181e List a dylib in the map once for each input naming it
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]>
2026-10-02 00:42:44 +09:00
Rui UeyamaandClaude Opus 5.5 5c7ae2cefd Ignore bitcode for another architecture as ld-prime does
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]>
2026-10-02 00:42:44 +09:00
Rui UeyamaandClaude Opus 5.5 571464b919 Keep ld-prime's placeholder of an empty __text in the map
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]>
2026-10-02 00:42:44 +09:00
Rui UeyamaandClaude Opus 5.5 5a83869ecd List the map's labels of a place as ld-prime ranks them
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]>
2026-10-02 00:42:44 +09:00
Rui UeyamaandClaude Opus 5.5 52a77e17ff Warn of unused dylibs before the weak exports
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]>
2026-10-02 00:42:44 +09:00
Rui UeyamaandClaude Opus 5.5 b5bbd7a394 Report an unresolved re-export list name as an initial undefine
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]>
2026-10-02 00:42:44 +09:00
Rui UeyamaandClaude Opus 5.5 68b7fed0a7 Name the folded functions of an object in one pass over its symbols
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]>
2026-10-02 00:16:30 +09:00
Rui UeyamaandClaude Opus 5.5 ff389a57cf Look for ThinLTO's suffix in local symbol names with one searcher
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]>
2026-10-02 00:16:30 +09:00
Rui UeyamaandClaude Opus 5.5 b59ba9c102 Compare section names in place in the checks made of every subsection
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]>
2026-10-02 00:16:30 +09:00
Rui UeyamaandClaude Opus 5.5 352ba7404f Name an object for its unwind warnings only if it has one to give
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]>
2026-10-02 00:16:30 +09:00
Rui UeyamaandClaude Opus 5.5 781f1bc78e Look up a library's moved exports by name as its directives are read
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]>
2026-10-02 00:16:30 +09:00
Rui UeyamaandClaude Opus 5.5 b35944ff1d Refuse input sections of contents only the linker makes
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]>
2026-10-02 00:08:19 +09:00
Rui UeyamaandClaude Opus 5.5 f38bb202c1 Trace the lists category merging makes in -trace_symbol_layout
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]>
2026-10-01 23:58:37 +09:00
Rui UeyamaandClaude Opus 5.5 1065dce38b Merge the linker's contents into a -sectcreate section of their name
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]>
2026-10-01 23:52:25 +09:00
Rui UeyamaandClaude Opus 5.5 79bad86afe Optimize no Objective-C of an image dyld doesn't load
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]>
2026-10-01 23:46:32 +09:00
Rui UeyamaandClaude Opus 5.5 1dc815de87 Refuse a listed Objective-C class with no class data
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]>
2026-10-01 23:46:32 +09:00
Rui UeyamaandClaude Opus 5.5 363067cba7 Merge the objects' ObjC image info as ld-prime checks each object
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]>
2026-10-01 23:46:32 +09:00
Rui UeyamaandClaude Opus 5.5 8890ea9fab Know the Objective-C image info in __DATA alone
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]>
2026-10-01 23:45:21 +09:00
Rui UeyamaandClaude Opus 5.5 9bb0bf9604 Merge a synthesized section into an input section of its name
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]>
2026-10-01 23:45:21 +09:00
Rui UeyamaandClaude Opus 5.5 7f184a3844 Warn about an option's name given for -rpath's path
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]>
2026-10-01 23:45:21 +09:00
Rui UeyamaandClaude Opus 5.5 2960a7ab6d Skip empty arguments as ld-prime does
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]>
2026-10-01 23:45:21 +09:00
Rui UeyamaandClaude Opus 5.5 2d6737a53d Read response files by ld-prime's rules
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]>
2026-10-01 23:45:21 +09:00
Rui UeyamaandClaude Opus 5.5 b37e0c2a17 Disassemble only the function the thunk test checks
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]>
2026-10-01 23:44:19 +09:00
Rui UeyamaandClaude Opus 5.5 6567063254 Check a mergeable record's largest kinds as ld-prime does
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]>
2026-10-01 23:35:39 +09:00
Rui UeyamaandClaude Opus 5.5 aa46c5f5da Name the GOT slot a mergeable reference reads, not its neighbor's
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]>
2026-10-01 23:35:39 +09:00
Rui UeyamaandClaude Opus 5.5 a694dea59b Bind a data pointer to a DTrace symbol by flat lookup
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]>
2026-10-01 23:35:39 +09:00
Rui UeyamaandClaude Opus 5.5 9c75731478 Refuse -make_mergeable with lazy-load and delay-init dylibs
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]>
2026-10-01 23:34:52 +09:00
Rui UeyamaandClaude Opus 5.5 7acc832b36 Warn of a merged dylib built for a newer macOS as ld-prime does a dylib
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]>
2026-10-01 23:34:52 +09:00
Rui UeyamaandClaude Opus 5.5 4bf0e873e5 Put -sectcreate's sections before sections of the linker's own content
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]>
2026-10-01 23:34:52 +09:00
Rui UeyamaandClaude Opus 5.5 644240ac51 Link x86-64 images for macOS before 10.6 with legacy LINKEDIT
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]>
2026-10-01 23:23:50 +09:00
Rui UeyamaandClaude Opus 5.5 c16b4bddac Make x86-64 executables for macOS before 10.6 non-PIE by default
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]>
2026-10-01 23:23:29 +09:00
Rui UeyamaandClaude Opus 5.5 772d4e715e Describe the tests in subsections, not ld64's units
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]>
2026-10-01 23:08:20 +09:00
Rui UeyamaandClaude Opus 5.5 48b6900ff0 Call the mergeable record's elements entries
-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]>
2026-10-01 23:08:15 +09:00
Rui UeyamaandClaude Opus 5.5 0168e0940c Name -why_live's graph nodes and the moved subsections plainly
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]>
2026-10-01 23:08:09 +09:00
Rui UeyamaandClaude Opus 5.5 07221d4bac Call the literals ld64 names in a -r output literals
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]>
2026-10-01 23:08:09 +09:00
Rui UeyamaandClaude Opus 5.5 ae09bca2e7 Name the output's pieces as subsections and records
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]>
2026-10-01 23:07:20 +09:00
Rui UeyamaandClaude Opus 5.5 826f0effcb Describe ld-prime's splitting of objects in subsections
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]>
2026-10-01 23:07:14 +09:00
Rui Ueyama 9e320c4012 Use mimalloc on sparc64 hosts again
This reverts bd97ebc90. mimalloc's page map still doesn't cover the
sign-extended high half of the sparc64 address space, but mimalloc maps
its arenas at hinted addresses in the low half, and since v3.5.0 a page
outside the map fails to register instead of corrupting memory. On an
UltraSPARC T5 running Linux, mold with mimalloc 3.5.3 passes the native
test suite.
2026-10-01 22:57:43 +09:00
Rui Ueyama 10b162491a Assemble the R_SPARC_OLO10 test input as non-PIC
GNU as rejects %lo(sym) + addend with -K PIC, which GCC passes to it
when it defaults to PIE, as Debian's sparc64 GCC does.
2026-10-01 22:57:43 +09:00
Rui UeyamaandClaude Opus 5.5 79183fe91f Name shared items after subsections, not ld64's units
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]>
2026-10-01 22:43:50 +09:00
Rui UeyamaandClaude Opus 5.5 56f51a5b0d Link a hook that places mergeable libraries' classes in their bundles
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]>
2026-10-01 22:29:48 +09:00
Rui UeyamaandClaude Opus 5.5 0b6f8ac0b7 Zero the modification times of a -r input's notes under -reproducible
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]>
2026-10-01 22:22:38 +09:00
Rui UeyamaandClaude Opus 5.5 aff3de4ada Keep a category list with merged entries in its place
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]>
2026-10-01 22:22:38 +09:00
Rui UeyamaandClaude Opus 5.5 181786f29f Put the GOT slots of the image's own weak definitions with the weak ones
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]>
2026-10-01 22:13:32 +09:00
Rui UeyamaandClaude Opus 5.5 87e521e9ab Note a common from -r inputs once, in the first object noting it
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]>
2026-10-01 22:13:32 +09:00
Rui UeyamaandClaude Opus 5.5 07868558d2 Credit the names -alias gives to the linker in -map
-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]>
2026-10-01 22:13:32 +09:00
Rui UeyamaandClaude Opus 5.5 adc505ed5e Fold an @objc thunk only as a C function, not as a Swift one
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]>
2026-10-01 22:13:32 +09:00
Rui UeyamaandClaude Opus 5.5 b9965170da Load no archive member for _main into an image without an entry point
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]>
2026-10-01 22:13:32 +09:00
Rui UeyamaandClaude Opus 5.5 70dba84e36 Tell a public framework by its binary's name, as ld64 does
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]>
2026-10-01 22:13:32 +09:00
Rui UeyamaandClaude Opus 5.5 66e56107b7 Write no debug note for a global with an assembler-local name
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]>
2026-10-01 22:13:32 +09:00
Rui UeyamaandClaude Opus 5.5 3407298b63 Keep a private-extern common one in a -r output
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]>
2026-10-01 22:13:32 +09:00
Rui UeyamaandClaude Opus 5.5 46b9ad2f10 Merge tentative definitions into the largest, alignment and all
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]>
2026-10-01 22:12:58 +09:00
Rui UeyamaandClaude Opus 5.5 1b58352bff List a -r output's __objc_clsrolist relocations by ascending pointer
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]>
2026-10-01 22:12:58 +09:00
Rui UeyamaandClaude Opus 5.5 60c501ddf9 Rename the locals -x or a symbol list strips in a -r output
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]>
2026-10-01 22:12:58 +09:00
Rui UeyamaandClaude Opus 5.5 94134860e3 Write -add_ast_path's N_AST entries in a -r output, and none under -S
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]>
2026-10-01 22:12:58 +09:00
Rui UeyamaandClaude Opus 5.5 8b47c01d80 Look a bare path ending in .a up as an archive library's
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]>
2026-10-01 22:12:58 +09:00
Rui UeyamaandClaude Opus 5.5 a2faccc8f2 Drop the objc_msgSend stubs that only dead code calls
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]>
2026-10-01 22:12:58 +09:00
Rui UeyamaandClaude Opus 5.5 8615ac3743 Version a moved-to library by the first library whose moved exports bind
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]>
2026-10-01 22:12:58 +09:00
Rui UeyamaandClaude Opus 5.5 859069285f Bind to the re-exporter an auto-linked @rpath library it re-exports
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]>
2026-10-01 22:12:58 +09:00
Rui UeyamaandClaude Opus 5.5 842c2a720e Write no modification times in the stabs under -reproducible
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]>
2026-10-01 22:12:58 +09:00
Rui UeyamaandClaude Opus 5.5 5b1bf078bf Keep the FDEs of same-named copies a fold merges in one object
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]>
2026-10-01 22:12:58 +09:00
Rui UeyamaandClaude Opus 5.5 2a6f6d67f2 Report fixups too far apart to chain before a bad chain page size
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]>
2026-10-01 22:12:58 +09:00
Rui UeyamaandClaude Opus 5.5 b76fd36581 Encode the weak-bind stream as ld-prime encodes the bind stream
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]>
2026-10-01 22:12:58 +09:00
Rui UeyamaandClaude Opus 5.5 bbd1922b83 Drop a -r input's empty debug units and notes of rewritten lists
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]>
2026-10-01 22:12:58 +09:00
Rui UeyamaandClaude Opus 5.5 b1dc50fd57 List a subsection's name before the labels that alias it in -map
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]>
2026-10-01 22:12:58 +09:00
Rui UeyamaandClaude Opus 5.5 ed85cb1a56 List the names at one address in ld-prime's symbol table order
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]>
2026-10-01 22:12:58 +09:00
Rui UeyamaandClaude Opus 5.5 86a496ef4c Credit a folded function's other labels to their own file in -map
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]>
2026-10-01 22:12:58 +09:00
Rui UeyamaandClaude Opus 5.5 b64932cb72 List a folded function named as its survivor only if first of its scope
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]>
2026-10-01 22:12:58 +09:00
Rui UeyamaandClaude Opus 5.5 7b9e7e2190 Look symbols up in re-exported libraries after the named ones
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]>
2026-10-01 22:12:58 +09:00
Rui UeyamaandClaude Opus 5.5 89671121a3 Re-export only the inlined documents a stub lists
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]>
2026-10-01 22:12:24 +09:00
Rui UeyamaandClaude Opus 5.5 dbfb205951 Refuse an ADRP to the page exactly 4 GiB below
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]>
2026-10-01 21:49:30 +09:00
Rui UeyamaandClaude Opus 5.5 884b18b990 Describe DTrace probe sites in DOF sections
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]>
2026-10-01 21:49:30 +09:00
Rui UeyamaandClaude Opus 5.5 eb60531653 Keep a -r input's DTrace symbols in its output
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]>
2026-10-01 21:44:02 +09:00
Rui UeyamaandClaude Opus 5.5 14e3ce9f2b Trace a move before both renames in -rename_section's segment
-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]>
2026-10-01 21:39:46 +09:00
Rui UeyamaandClaude Opus 5.5 aa2d78681e Take dylibs of $LD_DYLIB_ARCH_FALLBACK's architecture where named
$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]>
2026-10-01 21:39:46 +09:00
Rui UeyamaandClaude Opus 5.5 3b84c0777d Take the library over its stub in an SDK, unless $LD_PREFER_TAPI_FILE
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]>
2026-10-01 21:39:46 +09:00
Rui UeyamaandClaude Opus 5.5 e7dc5021c9 Report Swift ABI version mismatches as ld-prime does
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]>
2026-10-01 21:39:46 +09:00
Rui UeyamaandClaude Opus 5.5 8082b1750e Hash $RC_UUID_SALT into the content UUID, as ld-prime does
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]>
2026-10-01 21:39:46 +09:00
Rui UeyamaandClaude Opus 5.5 b93c6829f6 Mark a dylib app-extension safe under $LD_APPLICATION_EXTENSION_SAFE
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]>
2026-10-01 21:39:46 +09:00
Rui UeyamaandClaude Opus 5.5 f0c2c802ba Mark only a dylib MH_APP_EXTENSION_SAFE, as ld-prime does
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]>
2026-10-01 21:39:46 +09:00
Rui UeyamaandClaude Opus 5.5 0385bdf577 Take $LD_TREAT_WARNINGS_AS_ERRORS for -fatal_warnings
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]>
2026-10-01 21:39:46 +09:00
Rui UeyamaandClaude Opus 5.5 c7dd6ebfca Take the trace files Apple's build system names in the environment
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]>
2026-10-01 21:39:46 +09:00
Rui UeyamaandClaude Opus 5.5 7ba78c8223 Lay -sdk_imports out as ld-prime does, libraries in load command order
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]>
2026-10-01 21:39:46 +09:00
Rui UeyamaandClaude Opus 5.5 b48d9cc8e8 Look a command-line archive path up under -syslibroot as ld-prime does
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]>
2026-10-01 21:39:46 +09:00
Rui UeyamaandClaude Opus 5.5 ad3bf8ff7d List the files -dependency_info's searches missed, as ld-prime does
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]>
2026-10-01 21:39:46 +09:00
Rui UeyamaandClaude Opus 5.5 a758f6cd34 Keep -sectcreate sections in a -r output
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]>
2026-10-01 21:39:46 +09:00
Rui UeyamaandClaude Opus 5.5 c74a376522 Make each -sectcreate option a file of one section, as ld-prime does
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]>
2026-10-01 21:39:46 +09:00
Rui UeyamaandClaude Opus 5.5 5fbc2b6052 Pack a -r output's sections in the file as ld-prime does
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]>
2026-10-01 21:39:46 +09:00
Rui Ueyama 6f8dbb64b2 Let mimalloc_rust link libatomic
mimalloc_rust now links libatomic itself on targets without 64-bit
atomics, so cli/build.rs, which did that for 32-bit PowerPC hosts, is
no longer needed.
2026-10-01 21:31:34 +09:00
Rui UeyamaandClaude Opus 5.5 abfcb0c99e Test merging a mergeable dylib that calls through thunks
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]>
2026-10-01 21:27:44 +09:00
Rui UeyamaandClaude Opus 5.5 7cd9968ef7 Give a losing weak copy of a folded function no mergeable alias
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]>
2026-10-01 21:26:14 +09:00
Rui UeyamaandClaude Opus 5.5 e65e16852b Record an object's GOT slots in a mergeable dylib as GOT slots
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]>
2026-10-01 21:24:47 +09:00
Rui UeyamaandClaude Opus 5.5 ae8bc55e49 Set a mergeable record's Swift-or-Objective-C flag as ld-prime does
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]>
2026-10-01 21:17:24 +09:00
Rui UeyamaandClaude Opus 5.5 9ad280f5ab Give a merged library no load command when another merged one links it
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]>
2026-10-01 21:11:59 +09:00
Rui Ueyama b7d67b020e Make two tests independent of the system toolchain
readelf before binutils 2.39 can't decode version 9 indices and exits
with an error, so gdb-index-dwarf5-types ignores its exit status as
check_index already does. arch-x86_64-eh-frame-hdr-order links without
the C runtime, whose objects may have FDEs of their own.
2026-10-01 21:09:56 +09:00
Rui Ueyama a247ca27d3 Preserve SHF_LINK_ORDER only for -r
mold handles .ARM.exidx and other users of SHF_LINK_ORDER directly
rather than implementing the flag in general. This reverts 49129fbd5,
which kept SHF_LINK_ORDER sections alive with their linked sections, and
restores the ARM-specific GC edge to .ARM.exidx. 9aacb62c0 now sets
sh_link only in relocatable output, which needs it to preserve the flag.
2026-10-01 21:09:56 +09:00
Rui UeyamaandClaude Opus 5.5 fe8d4dde49 Keep a mergeable dylib's superclass references from dead stripping
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]>
2026-10-01 21:09:53 +09:00
Rui UeyamaandClaude Opus 5.5 06864e040d Record folded functions in a mergeable dylib as aliases
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]>
2026-10-01 21:02:44 +09:00
Rui UeyamaandClaude Opus 5.5 5e2d72a787 Check no static initializers of a build for profiling
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]>
2026-10-01 20:20:00 +09:00
Rui UeyamaandClaude Opus 5.5 6d2f0431e6 Merge no categories that would give a class a second +load
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]>
2026-10-01 20:20:00 +09:00
Rui UeyamaandClaude Opus 5.5 4cc7a296e8 Order an image by the Apple-internal SDK's order file for it
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]>
2026-10-01 20:20:00 +09:00
Rui UeyamaandClaude Opus 5.5 9e6bd9be0d Warn about an OS dylib installed by run path
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]>
2026-10-01 20:20:00 +09:00
Rui UeyamaandClaude Opus 5.5 97341ba140 Give an image no dyld loads no __objc_imageinfo
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]>
2026-10-01 20:20:00 +09:00
Rui UeyamaandClaude Opus 5.5 257c334531 Keep a kext's initializers in __mod_init_func
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]>
2026-10-01 20:20:00 +09:00
Rui UeyamaandClaude Opus 5.5 f702e9d537 Read symbol list patterns as ld-prime does
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]>
2026-10-01 20:20:00 +09:00
Rui UeyamaandClaude Opus 5.5 8906161997 Read an OS dylib's dirty data list from the Apple-internal SDK
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]>
2026-10-01 20:20:00 +09:00
Rui UeyamaandClaude Opus 5.5 5e442d571c Move an -alias name's atom when a symbol move list names it
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]>
2026-10-01 20:20:00 +09:00
Rui UeyamaandClaude Opus 5.5 98db49e551 Warn about unmovable symbols in ld-prime's order of atoms
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]>
2026-10-01 20:20:00 +09:00
Rui UeyamaandClaude Opus 5.5 95eea6a536 Move the ObjC records the linker rewrites with -move_to_*_segment
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]>
2026-10-01 20:20:00 +09:00
Rui UeyamaandClaude Opus 5.5 496a9c8a4e Order a -static image's other segments by first appearance
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]>
2026-10-01 20:20:00 +09:00
Rui UeyamaandClaude Opus 5.5 90e77b916c Rank a section by its first member's name before any rename
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]>
2026-10-01 20:20:00 +09:00
Rui UeyamaandClaude Opus 5.5 9c09e18fdf Move initializer lists of any name to __DATA_CONST
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]>
2026-10-01 20:20:00 +09:00
Rui UeyamaandClaude Opus 5.5 96bd3752e0 Rank data sections by one order with or without __DATA_CONST
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]>
2026-10-01 20:20:00 +09:00
Rui UeyamaandClaude Opus 5.5 319ee0c6b9 Write LC_ATOM_INFO for -make_mergeable
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]>
2026-10-01 20:04:14 +09:00
Rui UeyamaandClaude Opus 5.5 08e61b58ef Merge a dylib -make_mergeable made
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]>
2026-10-01 20:02:49 +09:00
Rui UeyamaandClaude Opus 5.5 0f94cf4f55 List the mach header's names only stripped code used as dead
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]>
2026-10-01 19:49:00 +09:00
Rui UeyamaandClaude Opus 5.5 ce34dab197 Keep -alias bases through dead stripping, as ld-prime does
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]>
2026-10-01 19:49:00 +09:00
Rui UeyamaandClaude Opus 5.5 a711aa2977 List the imports only bitcode referred to, unbound, as ld-prime does
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]>
2026-10-01 19:49:00 +09:00
Rui UeyamaandClaude Opus 5.5 108d4e2227 Preserve no bitcode definition the image auto-hides
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]>
2026-10-01 19:49:00 +09:00
Rui UeyamaandClaude Opus 5.5 5e6dc85364 Order output sections by where LTO-compiled atoms are laid out
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]>
2026-10-01 19:49:00 +09:00
Rui UeyamaandClaude Opus 5.5 2c994390f6 Credit an LTO-compiled external to the bitcode file that won it
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]>
2026-10-01 19:49:00 +09:00
Rui UeyamaandClaude Opus 5.5 256ecb54fa Report duplicates among bitcode files after LTO, as ld-prime does
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]>
2026-10-01 19:49:00 +09:00
Rui UeyamaandClaude Opus 5.5 6937b8e92a Point a nameless ThinLTO object's N_OSO at the string table's start
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]>
2026-10-01 19:49:00 +09:00
Rui UeyamaandClaude Opus 5.5 1433c566bf Cut non-external symbol names at ".llvm." as ld-prime does
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]>
2026-10-01 19:49:00 +09:00
Rui UeyamaandClaude Opus 5.5 2e6d5ad75a Give no debug stabs to an object dead stripping left no symbol of
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]>
2026-10-01 19:49:00 +09:00
Rui UeyamaandClaude Opus 5.5 6d972e4cfa Dead-strip final images with LTO code unasked, as ld-prime does
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]>
2026-10-01 19:49:00 +09:00
Rui UeyamaandClaude Opus 5.5 34eb8d70bc Tell libLTO to preserve the entry point only if bitcode defines it
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]>
2026-10-01 19:49:00 +09:00
Rui UeyamaandClaude Opus 5.5 ae07385ba9 Lay out LTO-compiled code where its bitcode files were named
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]>
2026-10-01 19:49:00 +09:00
Rui UeyamaandClaude Opus 5.5 60f2365e22 Take -flto-codegen-only, as ld-prime does
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]>
2026-10-01 19:49:00 +09:00
Rui UeyamaandClaude Opus 5.5 d439b202b4 Keep ThinLTO's intermediate files under -save-temps
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]>
2026-10-01 19:49:00 +09:00
Rui UeyamaandClaude Opus 5.5 14f93a968c Give ThinLTO the -cache_path_lto cache and its policy
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]>
2026-10-01 19:49:00 +09:00
Rui UeyamaandClaude Opus 5.5 75536b8dcd Name LTO objects in diagnostics as they were written
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]>
2026-10-01 19:49:00 +09:00
Rui UeyamaandClaude Opus 5.5 544c3fa4b2 Compile ThinLTO bitcode through libLTO's thinlto_* API, as ld-prime
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]>
2026-10-01 19:49:00 +09:00
Rui UeyamaandClaude Opus 5.5 8a04b76b79 Pass -mllvm options on to libLTO
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]>
2026-10-01 19:49:00 +09:00
Rui UeyamaandClaude Opus 5.5 e21a34e20b Write the LTO object into an -object_path_lto directory as lto.o
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]>
2026-10-01 19:49:00 +09:00
Rui UeyamaandClaude Opus 5.5 8d828682d9 Accept -branch_island_region_size and check its size
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]>
2026-10-01 19:48:18 +09:00
Rui UeyamaandClaude Opus 5.5 19d2eb941d Take -sdk_imports_api_list, the APIs an -sdk_imports report lists
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]>
2026-10-01 19:40:32 +09:00
Rui UeyamaandClaude Opus 5.5 a723495d95 Refuse -arch_variant as ld-prime does for arm64 and x86_64
-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]>
2026-10-01 19:40:32 +09:00
Rui UeyamaandClaude Opus 5.5 353e7a5189 Take -no_allow_dylib_sub_type_mismatches and its environment variable
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]>
2026-10-01 19:40:32 +09:00
Rui UeyamaandClaude Opus 5.5 b2edb9b346 List each label of a fixed-size literal in -map as ld-prime does
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]>
2026-10-01 19:40:32 +09:00
Rui UeyamaandClaude Opus 5.5 3c0a4ae63e List in -map what category merging adds to a class without a list
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]>
2026-10-01 19:40:32 +09:00
Rui UeyamaandClaude Opus 5.5 ee7dc48b59 Place rewritten class ro records in order when nothing else is left
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]>
2026-10-01 19:40:32 +09:00
Rui UeyamaandClaude Opus 5.5 26f64d0d4d Give an N_INDR symbol's target name a string of its own
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]>
2026-10-01 19:40:32 +09:00
Rui UeyamaandClaude Opus 5.5 4b39e8db52 Read $ld$ directives as loosely and as quietly as ld-prime does
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]>
2026-10-01 19:40:32 +09:00
Rui UeyamaandClaude Opus 5.5 e8acc7e537 Check a .tbd's values and a JSON one's sections as TAPI does
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]>
2026-10-01 19:40:32 +09:00
Rui UeyamaandClaude Opus 5.5 7d910a2605 Check a .tbd's YAML keys by the schema of its version, as TAPI does
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]>
2026-10-01 19:40:32 +09:00
Rui UeyamaandClaude Opus 5.5 5002819879 Read a .tbd's exports only, from flow and block sequences alike
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]>
2026-10-01 19:40:32 +09:00
Rui UeyamaandClaude Opus 5.5 88e924f3e4 Warn of re-exports a re-exported library makes redundant
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]>
2026-10-01 19:40:32 +09:00
Rui UeyamaandClaude Opus 5.5 3bc3c8c078 Create -sectcreate and -add_empty_section sections in command-line order
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]>
2026-10-01 19:40:32 +09:00
Rui UeyamaandClaude Opus 5.5 0249115d5c Write -trace_file, -trace_file_shared_cache and -trace_symbols_file
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]>
2026-10-01 19:09:17 +09:00
Rui UeyamaandClaude Opus 5.5 70207e42a6 Report every symbol's section mapping with -trace_symbol_layout
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]>
2026-10-01 18:59:17 +09:00
Rui UeyamaandClaude Opus 5.5 66c9aac8af Implement -trace_implicit_libraries and -trace_implicit_library
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]>
2026-10-01 18:52:58 +09:00
Rui UeyamaandClaude Opus 5.5 8176a2fdba Report -order_file lines that order nothing under -order_file_statistics
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]>
2026-10-01 18:52:19 +09:00
Rui UeyamaandClaude Opus 5.5 39192073da List -dependency_info's inputs and outputs as ld-prime does
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]>
2026-10-01 18:51:41 +09:00
Rui UeyamaandClaude Opus 5.5 cbc120a536 Write -map for -r links
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]>
2026-10-01 18:51:41 +09:00
Rui UeyamaandClaude Opus 5.5 a250f87e21 Write -map and -dependency_info for links that fail in layout
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]>
2026-10-01 18:51:41 +09:00
Rui UeyamaandClaude Opus 5.5 440dbb5540 Lead every segment with its code sections
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]>
2026-10-01 18:44:17 +09:00
Rui UeyamaandClaude Opus 5.5 578b76f244 Refuse a thread-local template split across the layout
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]>
2026-10-01 18:44:17 +09:00
Rui UeyamaandClaude Opus 5.5 271a2bb34a Report symbol moves with -trace_symbol_layout
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]>
2026-10-01 18:44:17 +09:00
Rui UeyamaandClaude Opus 5.5 af30d4e14f Move listed symbols to other segments with -move_to_*_segment
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]>
2026-10-01 18:44:17 +09:00
Rui UeyamaandClaude Opus 5.5 3bf15c7b31 List a folded function's name once per scope if it is the survivor's
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]>
2026-10-01 18:24:20 +09:00
Rui UeyamaandClaude Opus 5.5 69db0720d9 Build every segment's chain record before a page size error
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]>
2026-10-01 18:23:04 +09:00
Rui UeyamaandClaude Opus 5.5 b6c0ad94cf Make a non-PIE -static image's __got plain data
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]>
2026-10-01 18:22:05 +09:00
Rui UeyamaandClaude Opus 5.5 0fdd5e0d4b Reach code and data more than 4 GiB away as ld-prime does
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]>
2026-10-01 18:22:05 +09:00
Rui UeyamaandClaude Opus 5.5 66bfdb50cc Check segment overlaps on ld-prime's sequential layout
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]>
2026-10-01 18:22:05 +09:00
Rui UeyamaandClaude Opus 5.5 4ef21d2e9c Place __common by its first input section too
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]>
2026-10-01 18:22:05 +09:00
Rui UeyamaandClaude Opus 5.5 a705d31e96 Fold Swift functions even where their addresses are taken
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]>
2026-10-01 18:22:05 +09:00
Rui UeyamaandClaude Opus 5.5 1c8d4d3af0 Split collect_file's and load_pending's new checks out of them
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]>
2026-10-01 18:13:44 +09:00
Rui UeyamaandClaude Opus 5.5 fd52c92a7b Place __AUTH_CONST and __AUTH as ld-prime's standard segments
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]>
2026-10-01 18:13:44 +09:00
Rui UeyamaandClaude Opus 5.5 236b82640b Refuse an object cut short as ld-prime does, without crashing
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]>
2026-10-01 18:13:44 +09:00
Rui UeyamaandClaude Opus 5.5 29712d0ece Look dyld_stub_binder up dynamically where the image may
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]>
2026-10-01 18:13:44 +09:00
Rui UeyamaandClaude Opus 5.5 3adbe7796a Refuse a CFString constant without its two relocations
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]>
2026-10-01 18:10:20 +09:00
Rui UeyamaandClaude Opus 5.5 e335a4d907 Fail on a relocation before on unreadable chain pages
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]>
2026-10-01 18:10:20 +09:00
Rui UeyamaandClaude Opus 5.5 18e8da9bbc Walk the -u roots before the entry point for -why_live
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]>
2026-10-01 18:10:20 +09:00
Rui UeyamaandClaude Opus 5.5 65acb1d8e9 Look a path option's library up under no root after a last -syslibroot /
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]>
2026-10-01 18:10:20 +09:00
Rui UeyamaandClaude Opus 5.5 706a49025d Warn of a newer dylib once for each input that names it
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]>
2026-10-01 18:10:20 +09:00
Rui UeyamaandClaude Opus 5.5 b6e92f9b79 Warn of ignored auto-link options as ld-prime does, twice
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]>
2026-10-01 18:09:28 +09:00
Rui UeyamaandClaude Opus 5.5 5cfeada78e Re-target a -r reference before its section at the first symbol
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]>
2026-10-01 18:08:42 +09:00
Rui UeyamaandClaude Opus 5.5 a2093c099d Report an x86-64 32-bit pointer where and as ld-prime does
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]>
2026-10-01 18:08:42 +09:00
Rui UeyamaandClaude Opus 5.5 60ae277788 Check the options in ld-prime's order
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]>
2026-10-01 18:08:42 +09:00
Rui UeyamaandClaude Opus 5.5 56a37b2162 Take a fat file's slice of another subtype as ld-prime does
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]>
2026-10-01 18:08:42 +09:00
Rui UeyamaandClaude Opus 5.5 baf31338c9 Say why archive members load as the symbols are resolved
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]>
2026-10-01 18:07:30 +09:00
Rui UeyamaandClaude Opus 5.5 8d8397ee7e Force load an archive -force_load names after another option
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]>
2026-10-01 18:05:57 +09:00
Rui UeyamaandClaude Opus 5.5 92f307dd61 Check dylib platforms with the objects' once the inputs are read
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]>
2026-10-01 18:05:57 +09:00
Rui UeyamaandClaude Opus 5.5 194afc475d Warn of atoms too small for their pointers as the objects are read
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]>
2026-10-01 18:04:53 +09:00
Rui UeyamaandClaude Opus 5.5 b3d1c9cb45 Give the errors in the input files together as ld-prime does
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]>
2026-10-01 18:04:53 +09:00
Rui UeyamaandClaude Opus 5.5 87e5527335 Fail an arm64 chain at the last section's unaligned pointer
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]>
2026-10-01 18:04:53 +09:00
Rui UeyamaandClaude Opus 5.5 3c318ce30c Name the last __interpose section in the shared-region error
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]>
2026-10-01 18:04:53 +09:00
Rui UeyamaandClaude Opus 5.5 64feb298fe Name an unfolded class reference anon-N in text-relocation errors
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]>
2026-10-01 18:04:53 +09:00
Rui Ueyama f20f7bdfa7 Index the types that DWARF 5 type units define
GCC emits no pubnames for type units, so the index listed a type only
for the compilation units referring to it by signature. GDB 18 doesn't
follow such a reference when it looks a type up through the index, so
ptype failed for types defined in type units. Read the name of the type
each type unit defines from its DIEs, resolving their section offsets
with input relocations, and add it to the index for the type unit.
2026-10-01 17:57:23 +09:00
Rui UeyamaandClaude Opus 5.5 c625fea583 Split the label scan and common symbols out of dead_entries_of
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]>
2026-10-01 17:55:00 +09:00
Rui UeyamaandClaude Opus 5.5 e9046d2edc List -map's auto-linked files in the order ld-prime loads them
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]>
2026-10-01 17:55:00 +09:00
Rui UeyamaandClaude Opus 5.5 de068cfd0d Give the libraries exports moved to the last load commands
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]>
2026-10-01 17:53:23 +09:00
Rui UeyamaandClaude Opus 5.5 65b7dde273 Name a fat file's slices by the file's path in -map and diagnostics
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]>
2026-10-01 17:53:23 +09:00
Rui UeyamaandClaude Opus 5.5 5c1a27d592 List the linker's referenced symbols and created sections in -map
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]>
2026-10-01 17:53:23 +09:00
Rui UeyamaandClaude Opus 5.5 05e40cc399 List every atom the output lacks in -map's dead-stripped list
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]>
2026-10-01 17:53:23 +09:00
Rui UeyamaandClaude Opus 5.5 ea78e6fe00 Name -map atoms after their best label and list records one by one
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]>
2026-10-01 17:53:23 +09:00
Rui UeyamaandClaude Opus 5.5 118a711901 Give no unwind entry to a label inside a function's record range
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]>
2026-10-01 17:52:08 +09:00
Rui UeyamaandClaude Opus 5.5 127a96ca12 Split the __eh_frame record layout out of lay_out_eh_frame
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]>
2026-10-01 17:52:08 +09:00
Rui UeyamaandClaude Opus 5.5 16fcec2691 Leave out FDE offsets past 16 MiB of __eh_frame, and warn as ld-prime
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]>
2026-10-01 17:52:08 +09:00
Rui UeyamaandClaude Opus 5.5 3f633aa06d Keep the CIEs no FDE points at, as ld-prime does
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]>
2026-10-01 17:52:08 +09:00
Rui UeyamaandClaude Opus 5.5 1707930686 Lay out __eh_frame in input order, as ld-prime does
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]>
2026-10-01 17:52:08 +09:00
Rui UeyamaandClaude Opus 5.5 75757157d7 Point a function with several FDEs at the last one, as ld-prime does
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]>
2026-10-01 17:52:08 +09:00
Rui UeyamaandClaude Opus 5.5 577506da1b Check an empty __eh_frame record's next word as an FDE's CIE pointer
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]>
2026-10-01 17:52:08 +09:00
Rui UeyamaandClaude Opus 5.5 e722b54e13 Order __unwind_info entries of one address by encoding
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]>
2026-10-01 17:52:08 +09:00
Rui UeyamaandClaude Opus 5.5 a21969e88f Find a compact unwind record's function in its relocation's section
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]>
2026-10-01 17:52:08 +09:00
Rui UeyamaandClaude Opus 5.5 d31370bbdc Give each label of an unsplit code section its own unwind entry
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]>
2026-10-01 17:52:08 +09:00
Rui UeyamaandClaude Opus 5.5 5ea610d74f Default to the libLTO of the toolchain the linker runs from
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]>
2026-10-01 17:37:55 +09:00
Rui UeyamaandClaude Opus 5.5 c469d0f4d5 Vet -lto_library as ld-prime does
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]>
2026-10-01 17:37:55 +09:00
Rui UeyamaandClaude Opus 5.5 35c02b1248 Keep -why_load reasons across resolution rounds
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]>
2026-10-01 17:37:55 +09:00
Rui UeyamaandClaude Opus 5.5 c09e0a2bb0 Softload LTO runtime routines as ld-prime does
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]>
2026-10-01 17:37:55 +09:00
Rui UeyamaandClaude Opus 5.5 0c92d886df Match order-file lines for LTO symbols by their bitcode files
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]>
2026-10-01 17:37:55 +09:00
Rui UeyamaandClaude Opus 5.5 2ff4196d6e Match order-file object qualifiers by leaf name only
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]>
2026-10-01 17:37:55 +09:00
Rui UeyamaandClaude Opus 5.5 988d58f484 List bitcode files in -map and credit LTO symbols to them
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]>
2026-10-01 17:37:55 +09:00
Rui UeyamaandClaude Opus 5.5 3aa593a1aa Report duplicate symbols that involve bitcode as ld-prime does
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]>
2026-10-01 17:37:55 +09:00
Rui UeyamaandClaude Opus 5.5 b8ee35a62c Check each bitcode file's deployment target before LTO
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]>
2026-10-01 17:37:55 +09:00
Rui UeyamaandClaude Opus 5.5 e41ad59775 Name the LTO object /tmp/lto.o, or the -object_path_lto path
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]>
2026-10-01 17:37:55 +09:00
Rui UeyamaandClaude Opus 5.5 0da09c2e99 Preserve bitcode definitions that a native object also defines
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]>
2026-10-01 17:37:55 +09:00
Rui UeyamaandClaude Opus 5.5 7af0f56017 Link bitcode inputs in -r as ld-prime does
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]>
2026-10-01 17:37:55 +09:00
Rui UeyamaandClaude Opus 5.5 d5d0bf1528 Only warn about a report file that can't be created
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]>
2026-10-01 17:37:12 +09:00
Rui UeyamaandClaude Opus 5.5 af5de493f6 Keep a -r literal record's other labels as aliases
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]>
2026-10-01 17:37:12 +09:00
Rui UeyamaandClaude Opus 5.5 509d536802 Treat a regular-typed __objc_selrefs as data
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]>
2026-10-01 17:37:12 +09:00
Rui UeyamaandClaude Opus 5.5 e7d0e388e5 Load lazily named dylibs last below macOS 27
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]>
2026-10-01 17:37:12 +09:00
Rui UeyamaandClaude Opus 5.5 72b07a17bb Name the code signature after the install name
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]>
2026-10-01 17:37:12 +09:00
Rui UeyamaandClaude Opus 5.5 7e63b05716 Keep __mh_execute_header's section number in a -static image
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]>
2026-10-01 17:37:12 +09:00
Rui UeyamaandClaude Opus 5.5 78f7b7c0ed Keep a kext's section alignment under a small -segalign
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]>
2026-10-01 17:37:12 +09:00
Rui UeyamaandClaude Opus 5.5 d88ee3e74d Align __LINKEDIT's tables to file offsets
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]>
2026-10-01 17:37:12 +09:00
Rui UeyamaandClaude Opus 5.5 7a09eced7b Write LC_VERSION_MIN_MACOSX for x86-64 macOS 10.14.4+ and 10.15.4+
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]>
2026-10-01 17:37:12 +09:00
Rui UeyamaandClaude Opus 5.5 2829c04019 Leave an empty relocation table's LC_DYSYMTAB offset 0
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]>
2026-10-01 17:37:12 +09:00
Rui UeyamaandClaude Opus 5.5 73483ce0dd Round a -r output's segment size up to 8 bytes
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]>
2026-10-01 17:37:12 +09:00
Rui UeyamaandClaude Opus 5.5 c63b13b5ce Place -r relocations section by section in section order
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]>
2026-10-01 17:37:12 +09:00
Rui UeyamaandClaude Opus 5.5 4bb391bc6d Create the output file as ld-prime does
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]>
2026-10-01 17:37:12 +09:00
Rui UeyamaandClaude Opus 5.5 a4058043a7 Bind imports from private re-exports to the image itself
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]>
2026-10-01 16:59:07 +09:00
Rui UeyamaandClaude Opus 5.5 719a135614 Refuse -no_merge_* only for a library that exports a class
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]>
2026-10-01 16:59:07 +09:00
Rui UeyamaandClaude Opus 5.5 5b5c3fd8ee Export a stub's exception-type class as a class too
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]>
2026-10-01 16:59:07 +09:00
Rui Ueyama 56ee0f8c1b Update mimalloc to v3.5.3 2026-10-01 16:57:38 +09:00
Rui Ueyama 17b7b85a88 Suggest -fPIC for PC-relative references to absolute symbols
A non-PIC object referring to an absolute or undefined weak symbol with
a PC-relative relocation, such as AArch64's ADRP, can't be linked into
a position-independent output, but recompiling it with -fPIC fixes that
by going through the GOT. The error message lost that hint when it was
reworded, which broke abs-error on aarch64_be.
2026-10-01 16:57:38 +09:00
Rui Ueyama 97acaca6d2 Make new tests portable to FreeBSD
od -w is a GNU extension, so arch-x86_64-eh-frame-hdr-order failed on
FreeBSD. head and grep -q exit before reading all of their input, so
under pipefail the producer may die of SIGPIPE and fail the pipeline,
which happened to mergeable-entsize-order on FreeBSD.
2026-10-01 16:57:38 +09:00
Rui UeyamaandClaude Opus 5.5 1b23dbf5d8 Add -remove_swift_reflection_metadata_sections
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]>
2026-10-01 16:25:11 +09:00
Rui UeyamaandClaude Opus 5.5 0dc47bb453 Add -fixup_chains_section, -fixup_chains_section_vm and -rebase_section
-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]>
2026-10-01 16:25:11 +09:00
Rui UeyamaandClaude Opus 5.5 ad0e126b59 Add -sectorder
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]>
2026-10-01 16:24:48 +09:00
Rui UeyamaandClaude Opus 5.5 8b03f26a59 Add -merge_zero_fill_sections
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]>
2026-10-01 16:24:48 +09:00
Rui UeyamaandClaude Opus 5.5 29bbbdcfcc Order __bss and __common with the other zero-fill sections
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]>
2026-10-01 16:24:48 +09:00
Rui UeyamaandClaude Opus 5.5 9123fe0905 Add -add_linker_option, -ignore_auto_link and -force_load_swift_libs
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]>
2026-10-01 16:24:37 +09:00
Rui UeyamaandClaude Opus 5.5 73cdf8f93d Add -no_dwarf_unwind
-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]>
2026-10-01 16:24:22 +09:00
Rui UeyamaandClaude Opus 5.5 3f612935cd Accept -dtrace and -no_dtrace_dof
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]>
2026-10-01 16:24:22 +09:00
Rui UeyamaandClaude Opus 5.5 dfd475cf3d Accept -no_eh_labels, -no_order_inits and -no_huge
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]>
2026-10-01 16:24:21 +09:00
Rui UeyamaandClaude Opus 5.5 b8aa1cc0a5 Add -const_selrefs and -no_const_selrefs
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]>
2026-10-01 16:24:21 +09:00
Rui UeyamaandClaude Opus 5.5 eed2bfa937 Move the objc stubs to __TEXT_EXEC with -text_exec
-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]>
2026-10-01 16:24:21 +09:00
Rui UeyamaandClaude Opus 5.5 6fad1e63a7 Add -objc_stubs_fast and -objc_stubs_small
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]>
2026-10-01 16:24:21 +09:00
Rui UeyamaandClaude Opus 5.5 7d3ef72d0d Warn about a shared-cache dylib's run paths before refusing -U
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]>
2026-10-01 16:23:54 +09:00
Rui UeyamaandClaude Opus 5.5 3bf0943782 Pack x86-64's objc stubs back to back, 13 bytes each
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]>
2026-10-01 16:23:54 +09:00
Rui UeyamaandClaude Opus 5.5 7e070eea93 Accept -encryptable and -no_encryption
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]>
2026-10-01 16:22:17 +09:00
Rui UeyamaandClaude Opus 5.5 9d34d23ff5 Accept -image_suffix, and -framework Name,suffix
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]>
2026-10-01 16:22:17 +09:00
Rui UeyamaandClaude Opus 5.5 6c45858a93 Accept -sub_umbrella, and re-export by -sub_library as ld-prime does
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]>
2026-10-01 16:21:42 +09:00
Rui UeyamaandClaude Opus 5.5 365124bd24 Accept -interposable and -interposable_list
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]>
2026-10-01 16:20:45 +09:00
Rui UeyamaandClaude Opus 5.5 7673932406 Accept -unaligned_pointers
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]>
2026-10-01 16:20:44 +09:00
Rui UeyamaandClaude Opus 5.5 766c8265fc Accept -objc_class_ro_signing_mismatch
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]>
2026-10-01 16:20:44 +09:00
Rui UeyamaandClaude Opus 5.5 b3b879a2f8 Accept -deployment_target_mismatches
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]>
2026-10-01 16:20:36 +09:00
Rui UeyamaandClaude Opus 5.5 07680d5158 Accept -poison_symbol and -poison_symbols_list
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]>
2026-10-01 16:20:19 +09:00
Rui UeyamaandClaude Opus 5.5 a9c2e988e2 Report duplicate symbols as ld-prime does, and accept -allow_dead_duplicates
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]>
2026-10-01 16:20:19 +09:00
Rui UeyamaandClaude Opus 5.5 fdb4247cd8 Take warn for warning in -read_only_relocs
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]>
2026-10-01 16:20:02 +09:00
Rui UeyamaandClaude Opus 5.5 a091b02277 Accept -keep_duplicate and -keep_duplicates_list
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]>
2026-10-01 16:20:02 +09:00
Rui UeyamaandClaude Opus 5.5 534a1169bf Accept -force_symbols_weak_list and -force_symbols_not_weak_list
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]>
2026-10-01 16:19:33 +09:00
Rui UeyamaandClaude Opus 5.5 20c4a43dc5 End the errors about a list file that won't open with a blank line
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]>
2026-10-01 16:19:17 +09:00
Rui UeyamaandClaude Opus 5.5 f6c5341f05 Accept -max_default_common_align
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]>
2026-10-01 16:19:17 +09:00
Rui UeyamaandClaude Opus 5.5 81f26507f3 Lay out common symbols by the objects' symbol tables
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]>
2026-10-01 16:19:01 +09:00
Rui UeyamaandClaude Opus 5.5 2f23cb970f Take the source version from $RC_ProjectSourceVersion
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]>
2026-10-01 16:19:01 +09:00
Rui UeyamaandClaude Opus 5.5 39f22254f6 Accept -commons and -warn_commons
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]>
2026-10-01 16:19:01 +09:00
Rui UeyamaandClaude Opus 5.5 4069ea1225 Accept -no_weak_imports and -weak_reference_mismatches
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]>
2026-10-01 16:18:49 +09:00
Rui UeyamaandClaude Opus 5.5 6b734eda9a Accept -warn_weak_exports and -no_weak_exports
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]>
2026-10-01 16:18:15 +09:00
Rui UeyamaandClaude Opus 5.5 72c5d78e7c Accept -no_shared_cache_eligible
-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]>
2026-10-01 16:18:15 +09:00
Rui UeyamaandClaude Opus 5.5 55cf79fde6 Accept -no_dynamic_access
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]>
2026-10-01 16:18:15 +09:00
Rui UeyamaandClaude Opus 5.5 898371118f Accept -random_uuid
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]>
2026-10-01 16:18:15 +09:00
Rui UeyamaandClaude Opus 5.5 e74ea8742c Accept -dyld_env
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]>
2026-10-01 16:18:15 +09:00
Rui UeyamaandClaude Opus 5.5 89a9db4487 Refuse a link's lazy-load references in one error as ld-prime does
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]>
2026-10-01 16:16:24 +09:00
Rui UeyamaandClaude Opus 5.5 9509e501f3 Implement -delay-l, -delay_library and -delay_framework
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]>
2026-10-01 16:16:24 +09:00
Rui UeyamaandClaude Opus 5.5 44ad81a241 Implement -assert-weak-l, -assert_weak_library and -assert_weak_framework
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]>
2026-10-01 16:16:08 +09:00
Rui UeyamaandClaude Opus 5.5 997969b8cb Implement -possible-l, -possible_framework and -possible_library
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]>
2026-10-01 16:16:08 +09:00
Rui UeyamaandClaude Opus 5.5 ffaafb80b4 Order auto-linked dylibs by install name as ld-prime does
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]>
2026-10-01 16:16:08 +09:00
Rui UeyamaandClaude Opus 5.5 9ff2ec0876 Implement -force-l, -hidden_framework and -load_hidden
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]>
2026-10-01 16:16:08 +09:00
Rui UeyamaandClaude Opus 5.5 d414af23d3 Represent a library option as its kind and its name
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]>
2026-10-01 16:16:08 +09:00
Rui UeyamaandClaude Opus 5.5 2452610749 Link objects of another CPU subtype under -allow_sub_type_mismatches
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]>
2026-10-01 13:49:07 +09:00
Rui UeyamaandClaude Opus 5.5 241777286d Call an x86-64 kext's imports through stubs under -kexts_use_stubs
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]>
2026-10-01 13:49:07 +09:00
Rui UeyamaandClaude Opus 5.5 11c155149c Take ld-prime's LTO tuning options
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]>
2026-10-01 13:49:07 +09:00
Rui UeyamaandClaude Opus 5.5 f70afaa23f Accept ld-prime's tracing and statistics options
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]>
2026-10-01 13:49:07 +09:00
Rui UeyamaandClaude Opus 5.5 1ca748f1a7 Sum up the folded functions under -verbose_deduplicate
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]>
2026-10-01 13:48:47 +09:00
Rui UeyamaandClaude Opus 5.5 8cf0c4705d Look in sparse frameworks' Versions/Current on request
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]>
2026-10-01 13:48:47 +09:00
Rui UeyamaandClaude Opus 5.5 67a40e9f08 Fail on input files of another architecture under -arch_errors_fatal
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]>
2026-10-01 13:48:20 +09:00
Rui UeyamaandClaude Opus 5.5 3433feb674 Mark a -simulator_support dylib MH_SIM_SUPPORT
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]>
2026-10-01 13:48:20 +09:00
Rui UeyamaandClaude Opus 5.5 13dcecfe93 Silence the reduced section alignment warning on request
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]>
2026-10-01 13:48:20 +09:00
Rui UeyamaandClaude Opus 5.5 cccd51e810 Take iOS and Mac Catalyst minimum versions for unsupported platforms
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]>
2026-10-01 13:48:20 +09:00
Rui UeyamaandClaude Opus 5.5 0827b46e96 Refuse -threaded_starts_section and old -objc_abi_versions as ld-prime does
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]>
2026-10-01 13:48:20 +09:00
Rui UeyamaandClaude Opus 5.5 595d4f98af Take -force_flat_namespace for -flat_namespace as ld-prime does
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]>
2026-10-01 13:47:50 +09:00
Rui UeyamaandClaude Opus 5.5 a41a615201 Ignore the bitcode and linker-choice options as ld-prime does
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]>
2026-10-01 13:47:50 +09:00
Rui UeyamaandClaude Opus 5.5 ada10dbb20 Ignore the obsolete ld64 options with ld-prime's warnings
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]>
2026-10-01 13:47:50 +09:00
Rui UeyamaandClaude Opus 5.5 f0e87595fc Take a joined library option's name from the next argument too
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]>
2026-10-01 13:44:47 +09:00
Rui UeyamaandClaude Opus 5.5 a514911929 Accept -make_mergeable and -add_mergeable_debug_hook
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]>
2026-10-01 13:44:47 +09:00
Rui UeyamaandClaude Opus 5.5 91043fad6d Accept -merge_framework, -merge_library and -merge-l
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]>
2026-10-01 13:44:47 +09:00
Rui UeyamaandClaude Opus 5.5 970e61afe0 Accept -no_merge_framework, -no_merge_library and -no_merge-l
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]>
2026-10-01 13:44:47 +09:00
Rui UeyamaandClaude Opus 5.5 b53c31bd58 Take a library path outside the syslibroot as the file itself
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]>
2026-10-01 13:44:47 +09:00
Rui UeyamaandClaude Opus 5.5 578f8f145d Refuse a flat namespace for the shared cache before other kind checks
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]>
2026-10-01 13:44:47 +09:00
Rui UeyamaandClaude Opus 5.5 9fa9ae4ba4 Keep REFERENCED_DYNAMICALLY and warn that it is deprecated
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]>
2026-10-01 13:44:37 +09:00
Rui UeyamaandClaude Opus 5.5 2b5624d322 Give arm64 images for macOS before 10.12 a SHA-1 code directory
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]>
2026-10-01 13:35:39 +09:00
Rui UeyamaandClaude Opus 5.5 45adfb0d33 Add -source_version
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]>
2026-10-01 13:35:39 +09:00
Rui UeyamaandClaude Opus 5.5 5f7e01053e Keep FDEs of compact-unwound functions for macOS before 10.9
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]>
2026-10-01 13:35:39 +09:00
Rui UeyamaandClaude Opus 5.5 9d2e97db13 Start x86-64 executables for macOS before 10.8 from LC_UNIXTHREAD
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]>
2026-10-01 13:35:39 +09:00
Rui UeyamaandClaude Opus 5.5 48827cf2f8 Leave LC_SOURCE_VERSION out of images for macOS before 10.8
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]>
2026-10-01 13:35:39 +09:00
Rui UeyamaandClaude Opus 5.5 7a458b0517 Lead __DATA with crt1.o's __dyld and __program_vars
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]>
2026-10-01 13:35:39 +09:00
Rui UeyamaandClaude Opus 5.5 6f6fae5591 Stop with "Missing -arch option" when no object names the target
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]>
2026-10-01 13:35:39 +09:00
Rui UeyamaandClaude Opus 5.5 e342b5cbc2 Check dylibs' minimum OS versions as ld-prime does
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]>
2026-10-01 13:35:39 +09:00
Rui UeyamaandClaude Opus 5.5 085d25033d Print the -v banner on stderr and link with -version_details
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]>
2026-10-01 13:35:39 +09:00
Rui UeyamaandClaude Opus 5.5 3098b2c41e Lay out __thread_bss by atom size, as ld-prime does
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]>
2026-10-01 13:34:59 +09:00
Rui UeyamaandClaude Opus 5.5 e72903baa8 Name a branch's stub '' in a fixup error, as ld-prime does
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]>
2026-10-01 13:20:54 +09:00
Rui UeyamaandClaude Opus 5.5 f636450bfc Report only the first fixup error in the output file, as ld-prime does
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]>
2026-10-01 13:16:00 +09:00
Rui UeyamaandClaude Opus 5.5 c8c9fd89c5 Check the layout before __LINKEDIT in ld-prime's one walk
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]>
2026-10-01 13:10:16 +09:00
Rui UeyamaandClaude Opus 5.5 9d2d69f824 Regenerate the -r relocations and copy the sections on all cores
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]>
2026-10-01 12:42:49 +09:00
Rui UeyamaandClaude Opus 5.5 7c1a447b2c Gather the -r output's symbols on all cores
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]>
2026-10-01 12:42:49 +09:00
Rui UeyamaandClaude Opus 5.5 26ce1aabe5 Write the -r symbol table and its stabs as a final image's are
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]>
2026-10-01 12:42:49 +09:00
Rui UeyamaandClaude Opus 5.5 045ee7a846 Time the -r link's steps under -print_statistics
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]>
2026-10-01 12:42:49 +09:00
Rui UeyamaandClaude Opus 5.5 cf41abcb93 Write partial output pages after the writers finish
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]>
2026-10-01 12:41:01 +09:00
Rui UeyamaandClaude Opus 5.5 ad63b79fa5 Read the segment alignment from Args directly
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]>
2026-10-01 12:33:50 +09:00
Rui UeyamaandClaude Opus 5.5 15e9e68a1c Settle -pagezero_size and -image_base when parsing options
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]>
2026-10-01 12:33:50 +09:00
Rui UeyamaandClaude Opus 5.5 ac1f1ffe22 Resolve the unused-dylib warning default when parsing options
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]>
2026-10-01 12:33:50 +09:00
Rui UeyamaandClaude Opus 5.5 31584baea3 Resolve the relative method list default when parsing options
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]>
2026-10-01 12:33:50 +09:00
Rui UeyamaandClaude Opus 5.5 db637f335f Resolve lazy binding when parsing options
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]>
2026-10-01 12:33:50 +09:00
Rui UeyamaandClaude Opus 5.5 3ed821c320 Resolve the chained-fixups default when parsing options
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]>
2026-10-01 12:33:50 +09:00
Rui UeyamaandClaude Opus 5.5 37f013bf84 Resolve the ad-hoc signing default when parsing options
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]>
2026-10-01 12:33:50 +09:00
Rui UeyamaandClaude Opus 5.5 d330aa9fd9 Look a -l name ending in .o up as a file name
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]>
2026-10-01 12:11:45 +09:00
Rui UeyamaandClaude Opus 5.5 2043dfff70 Look for lib<name>.so as a dylib as ld-prime does
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]>
2026-10-01 12:11:45 +09:00
Rui UeyamaandClaude Opus 5.5 ee84d97910 Search a -L or -F directory given twice only once
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]>
2026-10-01 12:11:44 +09:00
Rui UeyamaandClaude Opus 5.5 9ff699ebee Warn of and leave out search paths that are no directories
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]>
2026-10-01 12:11:44 +09:00
Rui UeyamaandClaude Opus 5.5 f9e30b5e8e Apply ld-prime's -syslibroot rules to the search paths
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]>
2026-10-01 12:11:44 +09:00
Rui UeyamaandClaude Opus 5.5 63b066bbd5 Print the search paths for -v as ld-prime does
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]>
2026-10-01 12:11:44 +09:00
Rui Ueyama d62424b202 Update mold.1 (automated commit) 2026-10-01 02:09:06 +00:00
Rui UeyamaandClaude Opus 5.5 72773a5a25 Count a personality routine as referenced when settling undefineds
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]>
2026-10-01 10:29:43 +09:00
Rui Ueyama c544bc7a81 Don't write STT_TLS labels in .text as TLS offsets
Clang gives the STT_TLS type to the labels of TLSDESC code sequences,
which are in .text. With --emit-relocs they are written to .symtab,
where their values were computed as offsets from the start of the TLS
segment, which underflowed. Write the address of an STT_TLS symbol
outside a TLS section as it is.
2026-10-01 10:09:13 +09:00
Rui Ueyama 9b91621465 Report branches to section offsets that need thunks
Assemblers emit a call to a static function as a relocation against a
section symbol plus an offset. A range extension thunk jumps to the
start of a symbol, so taking such a branch through a thunk landed at
the wrong address. Report it as an error instead.
2026-10-01 10:09:13 +09:00
Rui Ueyama 3717460027 Keep all symbols in -r output
Relocations in a relocatable output may refer to local symbols such as
RISC-V's .L labels, so removing them with -x, -X or -s left relocations
pointing to symbol 0. -r now ignores -x and -X, and -s strips only debug
info as -S does.
2026-10-01 10:09:13 +09:00
Rui Ueyama 2ffdd1256c Fix a syntax error in dt-init.sh
`[[ ... -o ... ]]` is a syntax error in bash, so the script aborted at
that line and the exit trap reported success without running any check.
2026-10-01 10:09:13 +09:00
Rui Ueyama 13e6cc0d59 Don't range-check LoongArch TLS_{GD,LD}_PC_HI20
In the extreme code model, the pcalau12i with R_LARCH_TLS_GD_PC_HI20 or
R_LARCH_TLS_LD_PC_HI20 begins a 64-bit sequence whose lu32i.d and lu52i.d
supply the bits beyond ±2 GiB, so the GOT may be farther away than that.
The other *_PC_HI20 relocations were already applied without a range
check, as in GNU ld and lld, but these two rejected such a GOT with an
out-of-range error.
2026-10-01 10:09:13 +09:00
Rui Ueyama 81339386c9 Compute LoongArch *64_PC_HI12 from the pcalau12i's page
In the extreme code model, lu32i.d and lu52i.d follow pcalau12i by 8 and
12 bytes, and the upper 32 bits they set are relative to the pcalau12i's
page. We used page(P - 8) for both, which is the next page for lu52i.d if
the pcalau12i is the last word of a page. The result was wrong whenever
the borrow reached bit 52; e.g. a pcalau12i at 0x100000ffc materialized
0xfff0000080000000 instead of 0x80000000.
2026-10-01 10:09:13 +09:00
Rui Ueyama bd97ebc907 Use the system allocator on sparc64 hosts
mold crashed at startup on sparc64 Linux, which maps user memory at
sign-extended high addresses. mimalloc v3 registers pages in a map that
covers only 48-bit addresses, so the first registration wrote out of
bounds. Until the bundled mimalloc handles that layout, sparc64 hosts use
the system allocator (https://github.com/rui314/mold/issues/1639).
2026-10-01 10:09:13 +09:00
Rui Ueyama 64325c31cc Make the mold-wrapper.so library directory configurable
mold -run looked for mold-wrapper.so in /usr/local/lib/mold unless it was
next to the executable or in ../lib/mold, and install-mold.sh always put
it in $PREFIX/lib/mold, so a package using lib64 or a multiarch libdir
could not make -run find it. The C++ version took the directory from
CMake's libdir. MOLD_LIBDIR now sets it at build time and install time.
2026-10-01 10:09:13 +09:00
Rui Ueyama ee6fb251da Don't emit PREFETCHT0 on x86 targets without SSE
util::prefetch assumed SSE on all x86 targets, but i586-unknown-linux-gnu
has none, so mold died with SIGILL on CPUs such as the Pentium, K6,
Vortex86 and Quark. The x86 prefetch is now compiled only when the target
has SSE. Such a target simply ignores the hint, as other targets without
a prefetch instruction do.
2026-10-01 10:09:13 +09:00
Rui Ueyama 24d32398f5 Make .dynsym and .eh_frame_hdr order independent of the host
Versions of a symbol have the same name, so they tied in the unstable sort
of exported dynamic symbols. Rayon breaks ties with a pseudo-random
generator whose width follows usize, so .dynsym and its hash and version
tables differed between 32-bit and 64-bit hosts. The input order now breaks
the tie. .eh_frame_hdr had the same problem with FDEs for one function,
which are now ordered by their addresses.
2026-10-01 10:09:13 +09:00
Rui Ueyama 99173586d6 Report outputs too large for a 32-bit host
On a 32-bit host, linking an output of 2 GiB or more panicked, because
the doubled mapping length wrapped around and sizes of 4 GiB or more were
truncated to usize. Each panic also left a multi-GiB temporary file. We
now check the size before creating the file and report a fatal error.
The limit is 2 GiB, as no Rust object can be larger than isize::MAX bytes.
2026-10-01 10:09:13 +09:00
Rui Ueyama 3a1b9c6c28 Add BTI landing pads for range extension thunks
A range extension thunk jumps to its destination with `br x16`, which
faults under BTI if the destination doesn't begin with a landing pad, as
is the case for a static function that is only called directly. For such
a function, we now put `bti c; b <function>` at the beginning of the first
thunk after the function's section, which is always within direct branch
range, and other thunks jump there instead, as lld's landing pad thunks do.
2026-10-01 10:09:13 +09:00
Rui Ueyama 48428d3890 Support -z force-bti, pac-plt, gcs, bti-report and gcs-report
-z force-bti and -z pac-plt force the BTI and PAC bits of the output's
GNU_PROPERTY_AARCH64_FEATURE_1_AND, and -z pac-plt also makes PLT entries
authenticate .got.plt entries with autia1716 and sets DT_AARCH64_PAC_PLT.
-z gcs=implicit|never|always controls the GCS bit. -z bti-report and
-z gcs-report diagnose input files lacking the features, and as in lld,
-z force-bti and -z gcs=always imply the warning level.
2026-10-01 10:09:13 +09:00
Rui Ueyama f03b745952 Merge ARM64 feature properties and emit BTI PLTs
We used to merge only x86 properties in .note.gnu.property, so an ARM64
output lost GNU_PROPERTY_AARCH64_FEATURE_1_AND even if all its inputs were
compiled with -mbranch-protection. We now AND-merge it as GNU ld and lld do.
If the output is BTI-enabled, the PLT header and the PLT entries that may be
reached by an indirect branch start with `bti c` as lld's do, which makes PLT
entries 24 bytes long, and DT_AARCH64_BTI_PLT is set.
2026-10-01 10:09:13 +09:00
Rui Ueyama 4ba7792e43 Keep addends of symbol-less relocations in -r output
R_RISCV_ALIGN has no symbol, and its addend is the size of the NOP
padding that a relaxing link shrinks to realign the code after it.
symidx_addend returned (0, 0) for any symbol not written to the output
symbol table, including the null symbol, so `mold -r` wrote R_RISCV_ALIGN
with addend 0 and a later link could no longer realign the code. We now
keep the addend of a relocation without a symbol, as GNU ld and lld do.
LoongArch's R_LARCH_ALIGN refers to a local symbol and was not affected.
2026-10-01 10:09:13 +09:00
Rui Ueyama a36a1611e6 Support LoongArch extreme code model TLSDESC relocations
GCC 14 uses R_LARCH_TLS_DESC64_PC_LO20 and R_LARCH_TLS_DESC64_PC_HI12
with -mcmodel=extreme -mtls-dialect=desc to extend the TLSDESC sequence's
GOT access to 64 bits, and we rejected them as unknown. We now apply them
like the other 64-bit PC-relative pairs and don't rewrite the sequence's
LO12 instruction with pcaddi. As GNU ld and lld do, we don't relax the
sequence to IE or LE, except in static executables, which can't use
TLSDESC and therefore get LE.
2026-10-01 10:09:13 +09:00
Rui Ueyama 1ac5ff77e7 Relax RISC-V GOT loads that follow shrunk instructions
is_got_load_pair checks that a GOT load's PCREL_LO12_I refers to the
label at its AUIPC by comparing the label's value with the AUIPC's
r_offset. Symbol values are adjusted for removed bytes before relocations
are applied, but r_offsets are not, so a GOT load after a shrunk
instruction was no longer rewritten to AUIPC+ADDI. We now compare with
the label's st_value, as find_paired_reloc does.
2026-10-01 10:09:13 +09:00
Rui Ueyama 85d55f3fa4 Check ranges of RISC-V 32-bit PC-relative relocations
R_RISCV_32_PCREL, R_RISCV_PLT32 and R_RISCV_GOT32_PCREL in allocated
sections were truncated to 32 bits without a range check, so a reference
to a symbol or a GOT slot more than 2 GiB away silently got a wrong
value. We now report an out-of-range error for them, as we already do
for R_RISCV_32_PCREL in .eh_frame and as lld does.
2026-10-01 10:09:13 +09:00
Rui Ueyama 767260f53d Skip unknown RISC-V attributes by their tag parity
The .riscv.attributes parser knew the value types of only the three tags
mold uses, so the value of any other tag was read as a tag. For example,
an unknown tag 40 with value 5 was mistaken for Tag_RISCV_arch. As per
the psABI, an even tag has a ULEB128 value and an odd tag has a
NUL-terminated string, so we now skip other attributes by that rule.
They are still dropped from the output, as lld does for unknown tags.
2026-10-01 10:09:13 +09:00
Rui Ueyama 6b60f75821 Merge EF_RISCV_TSO of RISC-V input files
The output's e_flags were the first input's plus EF_RISCV_RVC, so an
object compiled for the TSO memory model (-march=rv64gc_ztso) lost its
EF_RISCV_TSO bit if it came after an ordinary RVWMO object. RVWMO code
runs correctly on TSO hardware but not vice versa, so the output now has
the bit if any input has it. GNU ld does the same; lld still takes the
bit from the first file.
2026-10-01 10:09:13 +09:00
Rui Ueyama f94d51fd53 Match C++ mold's -Map and --print-dependencies output
-Map wrote address zero as "0x0" where C++ mold wrote "0", and
--print-dependencies omitted the blank line that separated the comment
header from the dependency lines. Both outputs are now byte-identical
to those of C++ mold.
2026-10-01 10:09:13 +09:00
Rui Ueyama 8a360ff453 Treat an empty XDG_RUNTIME_DIR as unset for MOLD_JOBS
With MOLD_JOBS=1 and an empty XDG_RUNTIME_DIR, mold joined "" with
"mold-lock" and created the lock file in the current directory, so
processes in different directories did not serialize. Like other XDG
variables, an empty value now means unset, and mold falls back to
/tmp/mold-lock-$USER.
2026-10-01 10:09:13 +09:00
Rui Ueyama db723dfbac Accept any bytes as values of ignored options
-rpath-link, -fuse-ld, -O, --sort-section and --max-cache-size are
accepted only for compatibility, and their values are never used. mold
nevertheless required the values to be UTF-8, so a non-UTF-8 directory
given to -rpath-link was a fatal error. Read the values as raw bytes.
2026-10-01 10:09:13 +09:00
Rui Ueyama 037d1d57e6 Document -z noseparate-code as the default
The manual said -z separate-code was the default, but mold defaults to
-z noseparate-code and adds no padding between segments unless
--section-order is given, which implies -z separate-loadable-segments.
2026-10-01 09:57:41 +09:00
Rui Ueyama 04b493edf1 Make documented option spellings match the parser
The manual documents --no-color-diagnostics, but mold rejected it as an
unknown option; accept it as lld does. Conversely, the manual and --help
listed -z nodefs and -z start_stop_visibility, which neither mold nor
GNU ld accepts; the options are -z undefs and -z start-stop-visibility.
--help also said --color-diagnostics means always, while it means auto.
2026-10-01 09:57:41 +09:00
Rui Ueyama 2ca893e9ab Don't let flag options consume the next argument
--dynamic-list-data, --lto-pseudo-probe-for-profiling and a bare
--thinlto-index-only take no argument in GNU ld and lld, but mold read
the next command line argument as their value, silently dropping an
input file placed there. GNU ld's --package-metadata takes an optional
argument, so mold now accepts its value only after an equal sign.
2026-10-01 09:57:41 +09:00
Rui Ueyama 033d127859 Report I/O errors with strerror text
Rust's io::Error appends " (os error N)" to the strerror text when it is
displayed, so mold printed "cannot open foo.o: No such file or directory
(os error 2)" where the C++ version printed just the text. Diagnostics
now format I/O errors with a strerror helper that returns the text alone
and falls back to the usual formatting for errors not from the OS.
2026-10-01 09:57:41 +09:00
Rui Ueyama 39e6a01e25 Explain why a PC-relative relocation can't be used
scan_pcrel, which also handles GOT-relative relocations, told the user
to recompile with -fPIC whenever it couldn't resolve a relocation, even
though code using GOTOFF is PIC already. It now reports the reason: a
symbol at a fixed address, such as an absolute or undefined weak one, in
a position-independent output, or a preemptible symbol defined in the
shared object being created, which -Bsymbolic or hidden visibility fixes.
2026-10-01 09:57:41 +09:00
Rui Ueyama aed5a38d1d Accept an address as the -e argument
`-e 0x401000` was looked up only as a symbol name, so the link warned
that the entry symbol was undefined and set e_entry to 0. GNU ld and lld
fall back to parsing the argument as a number (decimal, 0x hex or octal)
when no symbol has that name, and so does mold now.
2026-10-01 09:57:41 +09:00
Rui Ueyama b7048f555a Let the first version node win for duplicate names
When a version script named the same symbol in two version nodes, each
exact-name match overwrote the previous one, so the last node won.
GNU ld and lld give the symbol the first node's version (lld warns about
the reassignment, and GNU ld rejects a name that is both local and
global). Apply only the first exact match. Wildcards still let the last
matching node win, as in both linkers.
2026-10-01 09:57:41 +09:00
Rui Ueyama f10b0c40c8 Don't write version sections for outputs without .dynsym
A version script or --default-symver on a link that exports no dynamic
symbols, such as a static executable, made verdef::construct index the
empty .dynsym and panic. Symbol versions exist only for dynamic symbols,
so write no version sections in that case, as GNU ld does.
2026-10-01 09:57:41 +09:00
Rui Ueyama b3566fac1a Report undefined hidden symbols in shared objects
Undefined symbols are allowed by default with -shared or with
--unresolved-symbols=ignore-all, and a non-weak hidden one was then
silently resolved to 0. A hidden symbol can't be resolved at runtime,
so report it as an error regardless of those options, as GNU ld and lld
do. Weak undefined hidden symbols still resolve to 0.
2026-10-01 09:57:41 +09:00
Rui Ueyama 56b862b23c Reject --defsym aliases of shared library symbols
--defsym=foo=bar with bar defined by a shared library copied bar's
library-relative value into an absolute foo, so calling foo jumped to a
bogus address. Such a symbol has no link-time address, so report it the
same way as an undefined one. GNU ld and lld reject it as well.
2026-10-01 09:57:41 +09:00
Rui Ueyama d5781d582c Merge common symbols into the largest one
The first file on the command line owned a common symbol, so `int buf[4]`
in one file and `int buf[64]` in another gave a 16-byte buf, and writes
to it clobbered its neighbours. As GNU ld and lld do, let the largest
common own the symbol and align it to the largest alignment among them,
for .common, .tls_common and -r output alike.
2026-10-01 09:57:41 +09:00
Rui Ueyama abb7b4ef64 Don't create .dynsym in an executable without .dynamic
--export-dynamic and --dynamic-list export symbols even from an
executable that links against no DSO. Such an executable has no
.dynamic, but the symbols were still added to .dynsym, and .dynstr
lacked its leading empty string, which only the creation of .dynamic
adds, so entry 0 of .dynsym got a symbol's name. Like GNU ld and lld,
emit no dynamic symbol table without .dynamic.
2026-10-01 09:57:41 +09:00
Rui Ueyama 696ed71789 Order same-named merged sections by their first member
Mergeable sections that share a name, type and flags but differ in
entry size are merged into separate output sections. Their sort keys
tied, so their order depended on which one the parallel conversion of
input files happened to create first. Break the tie with the first
member's file priority and section index, as for output sections.
2026-10-01 09:57:41 +09:00
Rui Ueyama 0ee71d395c Don't define __start_ and __stop_ symbols for ELF headers
add_synthetic_symbols defined __start_ and __stop_ symbols for every
allocated chunk with a C identifier name, including the ELF and program
headers named EHDR and PHDR. fix_synthetic_symbols skips headers, so
the symbols were left in .symtab with value 0, and -z
start-stop-visibility=protected exported them. Exclude headers in
start_stop_name, which both functions use.
2026-10-01 09:57:41 +09:00
Rui Ueyama ceab12c56b Don't fold exported functions with --icf=safe
compute_address_significance marked exported symbols' sections as
address-taken by walking .dynsym, but .dynsym is not populated until
after ICF, so the loop did nothing and --icf=safe gave identical
exported functions of a DSO the same address. Walk each object file's
defined global symbols instead, whose is_exported bits are already set.
2026-10-01 09:57:41 +09:00
Rui Ueyama c9e4cc4b6e Keep --init and --fini functions under --gc-sections
DT_INIT and DT_FINI refer to the --init and --fini functions, but only
the entry point was a GC root. If such a function was not exported
because it was hidden or made local by a version script, --gc-sections
discarded its section, DT_INIT and DT_FINI were emitted with value 0,
and the DSO crashed on load. Make them GC roots as GNU ld and lld do.
2026-10-01 09:57:41 +09:00
Rui Ueyama a551ec5cc3 Make synthesized ARM32 mapping symbols STT_NOTYPE
The $a, $t and $d symbols that mold creates for PLT entries and thunks
were STT_FUNC, copied from the symbols naming the entries, while AAELF
requires mapping symbols to be STT_NOTYPE. push_mapping_symbol now
builds the symbol from a section index and an address itself.
2026-10-01 09:53:52 +09:00
Rui Ueyama 4c9aad3726 Don't mark big-endian ARM relocatable output as BE8
Only a final link converts big-endian ARM code to BE8, so relocatable
output keeps the input's BE32 code, but it was marked EF_ARM_BE8 anyway.
GNU ld rejected such an object as "already in final BE8 format", and
disassemblers decoded its code in the wrong byte order. -r also failed
with "--be32 is not supported" unless --be8 was given, which GCC and
Clang never pass with -r.
2026-10-01 09:53:52 +09:00
Rui Ueyama 4c26f1649b Write Thumb NOP.W as two halfwords on big-endian ARM
A 32-bit Thumb instruction is a pair of halfwords, each in the byte
order of the input's code, but NOP.W was written as a single 32-bit
word. On big-endian targets, that swapped its halfwords, so a Thumb call
to an undefined weak symbol and a TLSDESC call relaxed to local-exec
became garbage in BE8 output. NOP.W is now written a halfword at a time.
2026-10-01 09:53:52 +09:00
Rui Ueyama c0f9cd6665 Support ARM32 ADR, LDR literal and ABS8/ABS16 relocations
Clang assembles ADR and LDR (literal) referring to a symbol in another
section to R_ARM_ALU_PC_G0 and R_ARM_LDR_PC_G0 in ARM code and to
R_ARM_THM_ALU_PREL_11_0 and R_ARM_THM_PC12 in Thumb code, which failed
with "unknown relocation", as did 8- and 16-bit data. The offset's sign
picks ADD or SUB, or the U bit of LDR, and the Thumb forms are relative
to the PC aligned down to 4, producing the same bytes as GNU ld.
2026-10-01 09:53:52 +09:00
Rui Ueyama 96d02824ee Support R_AARCH64_TSTBR14, ABS16, ABS32 and GOT_LD_PREL19
TBZ/TBNZ to a label in another section, .2byte/.4byte data referring to
a symbol, and GOT loads compiled with -mcmodel=tiny -fPIC failed with
"unknown relocation". TSTBR14 reaches ±32 KiB and is range-checked like
GNU ld and lld do, without thunks. ABS16 and ABS32 are too narrow for
dynamic relocations, so they must be link-time constants in PIC output.
GOT_LD_PREL19 is an LDR literal that loads the symbol's GOT slot.
2026-10-01 09:53:52 +09:00
Rui Ueyama 8233a75df1 Reject --strip-all with --emit-relocs
Relocations copied by --emit-relocs refer to symbols in .symtab, which
--strip-all omits, so the combination crashed or produced relocation
sections pointing to a nonexistent symbol table. Report an error as
lld does. (GNU ld instead keeps the symbols the relocations need.) -r
sets --emit-relocs internally and keeps accepting --strip-all.
2026-10-01 09:53:52 +09:00
Rui Ueyama 9aacb62c03 Set sh_link of SHF_LINK_ORDER output sections
An SHF_LINK_ORDER section's sh_link refers to the section it describes,
but mold always wrote zero. Once passed through -r, such sections lost
their links, so relinking with --gc-sections dropped .ARM.exidx and
__patchable_function_entries, and C++ programs on ARM aborted when
throwing. Set sh_link from the members' linked-to sections. Since it
can name only one section, -r also separates SHF_LINK_ORDER sections
whose linked-to sections are in different output sections, as GNU ld
and lld do.
2026-10-01 09:53:52 +09:00
Rui Ueyama 49129fbd59 Keep SHF_LINK_ORDER sections alive with their linked sections
An SHF_LINK_ORDER section such as __patchable_function_entries holds
metadata for the section that its sh_link refers to, and usually
nothing else refers to it. --gc-sections therefore discarded all of
them except .ARM.exidx, for which there was an ARM-specific edge from
a section to its unwind index. Replace that edge with a generic one
from a section to the SHF_LINK_ORDER sections linked to it, as GNU ld
and lld do.
2026-10-01 09:53:52 +09:00
Rui Ueyama b2c3d1dc67 Ignore --gc-sections when creating a relocatable output
Allocated fragments of mergeable sections start out dead under
--gc-sections and are revived by the garbage collector, but mold -r
returns before running GC. As a result, `-r --gc-sections` discarded
every .rodata.str* and .rodata.cst* piece and left the relocations
referring to them with symbol index 0. Since mold doesn't collect
garbage for -r, ignore the option there. GNU ld and lld do collect
garbage with -r, taking roots only from -e and -u.
2026-10-01 09:53:52 +09:00
Rui Ueyama 6df3fd23bb Fix R_SPARC_WDISP16 encoding
R_SPARC_WDISP16 is used by branch-on-register instructions such as brz,
whose 16-bit word displacement is split into d16hi at bits 21:20 and
d16lo at bits 13:0. We ORed the fields into the upper halfword of the
instruction instead, corrupting its opcode, and limited the displacement
to 64 KiB instead of 128 KiB. Place the fields as GNU ld does.
2026-10-01 09:53:52 +09:00
Rui Ueyama 269fbb959c Keep R_SPARC_OLO10's second addend in -r output
R_SPARC_OLO10 carries a second addend in the upper 24 bits of r_info,
which is added after the low 10 bits of S + A are taken. RelocSection
built its output records with ElfRel::new(), which zeroes that field, so
`ld [%g1 + %lo(var) + 8]` lost its +8 in -r and --emit-relocs output.
Build each record from a copy of the input one instead.
2026-10-01 09:53:52 +09:00
Rui Ueyama 614e2a0326 Treat m68k GOT and TLS offsets as signed
The 8- and 16-bit GOT and TLS relocations fill the displacement of a
(d8,An,Xn) or (d16,An) operand, which the CPU sign-extends, but we checked
them against unsigned ranges. A GOT offset of 32 KiB or more was thus
silently encoded as a negative displacement, and a local-exec TLS offset,
which is usually negative because the thread pointer is biased by 0x7000,
was rejected. Check them as signed as GNU ld does.
2026-10-01 09:53:52 +09:00
Rui Ueyama 92161ddf08 Write SH4 .eh_frame addends in place for -r
SH4 object files keep relocation addends in the relocated places even
though the relocations are RELA, and RelocSection already follows that
convention. EhFrameRelocSection, however, put adjusted addends only into
r_addend, which SH4 readers ignore, so after `ld -r` every FDE's pc_begin
pointed at the start of its output .text and C++ exceptions terminated.
Write the addends into .eh_frame as we do for REL targets.

This enables the three -r exception tests that were skipped on SH4.
2026-10-01 09:53:52 +09:00
Rui Ueyama d9cf1f099e Preserve r2 in the SH4 lazy PLT header
On SH4, r2 carries the address of the buffer for a struct return value.
Our PLT header loaded the link map into r2 and set r0 to 0, which glibc's
resolver accepts, but the resolver then restores r2 to its entry value, so
a lazily bound function returning a struct overwrote the link map. The
header now passes the link map in r0 as GNU ld does, borrowing r2 only
until the jump's delay slot restores it from the stack.
2026-10-01 09:53:52 +09:00
Rui Ueyama 3dcd1df382 Refer to ICF leaders in emitted relocations
A section folded by ICF has no output section, so with --emit-relocs a
relocation against its section symbol was emitted with symbol 0, and one
in .rela.eh_frame, such as an LSDA pointer, hit an expect(). Symbols
defined in a folded section are not in .symtab either, so references to
them lost their symbol or, in .rela.eh_frame, got a wrong one. Emit both
relative to the leader's output section, as the relocated contents and lld
do.
2026-10-01 09:53:52 +09:00
Rui Ueyama fdf84222d8 Compute RISC-V TLSDESC IE offsets from the new AUIPC
When relaxing TLSDESC to initial-exec, we write an AUIPC to the
TLSDESC_ADD_LO12's slot and a load to the TLSDESC_CALL's slot, but we
computed their GOT-relative offsets from the TLSDESC_HI20's address. The
two coincide only if the instructions in between are deleted, which is not
the case for objects lacking R_RISCV_RELAX on TLSDESC relocations, such as
clang 18's, so the load read a wrong GOT entry. Use the AUIPC's address,
as lld does.
2026-10-01 09:53:52 +09:00
Rui Ueyama 7c8b80520a Rewrite relaxed TLSDESC_HI20 after pairing its LO12s
With --emit-relocs, a TLSDESC_HI20 relaxed to IE or LE was rewritten to
R_NONE as soon as it was applied. The TLSDESC_LOAD_LO12, TLSDESC_ADD_LO12
and TLSDESC_CALL that follow find it with find_paired_reloc(), which
matches by type, so such links failed with "paired relocation is missing".
Rewrite TLSDESC_HI20s after all relocations have been applied. LoongArch is
unaffected because its TLSDESC relocations refer to the symbol directly.
2026-10-01 09:53:52 +09:00
Rui Ueyama 52febe9fae Shift symbols in ICF-folded sections like their leader
shrink_sections adjusted each symbol by the r_deltas of its own input
section, but a section folded by ICF is never shrunk, so its symbols kept
their pre-relaxation offsets while being resolved against the shrunk
leader. With LoongArch GCC's leading R_LARCH_ALIGN nop, calls to a folded
function landed 4 bytes past the leader's entry. A folded section was
identical to its leader before relaxation, so use the leader's deltas for
both symbol values and st_size.
2026-10-01 09:53:52 +09:00
Rui Ueyama efda2fc149 Synthesize register save/restore functions for PPC64 ELFv1
GCC calls _savegpr0_N, _restgpr0_N, _savefpr_N and similar functions
from -Os prologues and epilogues and expects the linker to provide them.
We synthesized them only for ELFv2, so ELFv1 programs built with -Os
failed to link with undefined symbols. ELFv1 calls them with plain
R_PPC64_REL24 relocations, not through function descriptors, and
expects the same code, so emit the same section in target byte order
and move its instruction table from ppc64v2.rs to the section's module.
2026-10-01 09:53:52 +09:00
Rui Ueyama b34a3082e1 Compute PPC64 GOT16 relocations from the GOT slot address
R_PPC64_GOT16, _LO, _HI and _HA take the offset of a GOT slot from the
TOC base, but we subtracted the TOC base from the slot's offset within
.got rather than from its address, so the result was off by the address
of .got. GCC addresses data through .toc instead, so only hand-written
assembly using @got was affected.
2026-10-01 09:53:52 +09:00
Rui Ueyama acc8854e93 Check the range of PPC32 16-bit GOT offsets
With -fpic, PPC32 code loads a GOT slot with one instruction whose
R_PPC_GOT16 relocation is a signed 16-bit offset from the GOT, and
R_PPC_GOT_TLSGD16, R_PPC_GOT_TLSLD16 and R_PPC_GOT_TPREL16 are the
same. We truncated these offsets silently, so a program whose GOT grew
past 32 KiB linked but loaded the wrong slots. Report an error instead,
as the psABI requires overflow checks for these fields.
2026-10-01 09:53:52 +09:00
Rui Ueyama d3b2a74fa0 Include data references in the __rela_iplt range
A static executable's startup code applies the R_IRELATIVE relocations
between __rela_iplt_start and __rela_iplt_end, and we placed the start
by counting only the GOT slots of IFUNC symbols. On PPC64, a data
reference to an IFUNC symbol gets its own R_IRELATIVE instead of the
canonical PLT address, so the range began too late and skipped a GOT
slot, and a call through that slot ran the resolver. Count them too.
2026-10-01 09:53:52 +09:00
Rui Ueyama 80baa5dbac Make references to discarded COMDAT symbols fatal with --noinhibit-exec
A global symbol defined only in a member of a discarded COMDAT group has
no owning file, so it has nothing to resolve to. The relocation scan
reports a reference to it and skips the relocation, but --noinhibit-exec
downgraded the error to a warning and the relocation was applied anyway,
which panicked on a missing GOT or TLS entry on most targets. Report the
reference as a fatal error in that case.
2026-10-01 09:53:52 +09:00
Rui UeyamaandClaude Opus 5.5 f1245ed995 Settle section alignments in output order as ld-prime does
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]>
2026-10-01 08:27:39 +09:00
Rui UeyamaandClaude Opus 5.5 3ae582df22 Keep the labels of a __ustring section without subsections
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]>
2026-10-01 08:27:39 +09:00
Rui UeyamaandClaude Opus 5.5 68e8b402dd Obey the $ld$ directives of dylib binaries as ld-prime does
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]>
2026-10-01 08:27:39 +09:00
Rui UeyamaandClaude Opus 5.5 9270e9d27b Search ld-prime's default library and framework directories
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]>
2026-10-01 08:27:38 +09:00
Rui UeyamaandClaude Opus 5.5 d81750cd5c Lay out the thread-local template by section type as ld-prime does
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]>
2026-10-01 08:27:38 +09:00
Rui UeyamaandClaude Opus 5.5 746274f45f Make the symbols of __thread_bss plain locals as ld-prime does
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]>
2026-10-01 08:27:38 +09:00
Rui UeyamaandClaude Opus 5.5 8639105885 Drop the demoted private externals too under -x as ld-prime does
-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]>
2026-10-01 08:27:38 +09:00
Rui UeyamaandClaude Opus 5.5 0f582728cf Keep local absolute symbols as ld-prime does
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]>
2026-10-01 08:27:38 +09:00
Rui UeyamaandClaude Opus 5.5 05a874daa5 Fold only functions whose addresses no one can compare, as ld-prime does
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]>
2026-10-01 08:27:38 +09:00
Rui UeyamaandClaude Opus 5.5 3d7a14b860 Place segments around every pin under a pinned __TEXT as ld-prime does
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]>
2026-10-01 07:16:57 +09:00
Rui UeyamaandClaude Opus 5.5 1f2cda3d6a Reduce the linker's own sections' alignment too as ld-prime does
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]>
2026-10-01 07:16:57 +09:00
Rui UeyamaandClaude Opus 5.5 7979856255 Want __dyld_lazy_load as an ordinary reference as ld-prime does
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]>
2026-10-01 07:16:57 +09:00
Rui UeyamaandClaude Opus 5.5 1e6b0fb0a0 Take the library files options name as ld-prime does
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]>
2026-10-01 07:16:57 +09:00
Rui UeyamaandClaude Opus 5.5 05545e4a7f Define no symbol for a label of an unnamed atom as ld-prime does
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]>
2026-10-01 07:16:57 +09:00
Rui UeyamaandClaude Opus 5.5 689f4d9a2a Write debug notes for the symbols ld-prime notes
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]>
2026-10-01 07:16:56 +09:00
Rui UeyamaandClaude Opus 5.5 a632cfb1de Bind exports $ld$previous moves to the older library as ld-prime does
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]>
2026-10-01 07:16:56 +09:00
Rui UeyamaandClaude Opus 5.5 9ff4bcf081 Give messages from parallel passes in the same order in every run
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]>
2026-10-01 06:52:51 +09:00
Rui UeyamaandClaude Opus 5.5 28c28bd1b2 Import the types the -r output names throughout
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]>
2026-10-01 06:47:01 +09:00
Rui UeyamaandClaude Opus 5.5 8653211c41 Take -r symbol and target addresses from isec_addr
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]>
2026-10-01 06:47:01 +09:00
Rui UeyamaandClaude Opus 5.5 9f69efd4b3 Build a -r output's data-in-code entries as a final image's
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]>
2026-10-01 06:47:01 +09:00
Rui UeyamaandClaude Opus 5.5 4a4f5e1e5f Reach the moved section and symtab helpers from -r directly
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]>
2026-10-01 06:47:01 +09:00
Rui UeyamaandClaude Opus 5.5 cdd8339404 Share the FDE field rewriting between the final link and -r
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]>
2026-10-01 06:47:01 +09:00
Rui UeyamaandClaude Opus 5.5 4ac303b0bf Split the -r externals and undefined symbols out of build_symtab
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]>
2026-10-01 06:47:01 +09:00
Rui UeyamaandClaude Opus 5.5 afab2cfaf4 Gather the -r local symbols outside build_symtab
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]>
2026-10-01 06:47:01 +09:00
Rui UeyamaandClaude Opus 5.5 ce70670b73 Move the -r local symbol type out of build_symtab
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]>
2026-10-01 06:47:01 +09:00
Rui UeyamaandClaude Opus 5.5 d3e5dfa8c3 Split writing the -r output out of link
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]>
2026-10-01 06:47:01 +09:00
Rui UeyamaandClaude Opus 5.5 7bd6634904 Build the -r synthetic sections' contents in their own functions
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]>
2026-10-01 06:47:00 +09:00
Rui UeyamaandClaude Opus 5.5 318c51b9ab Name -r relocation targets through one lookup
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]>
2026-10-01 06:47:00 +09:00
Rui UeyamaandClaude Opus 5.5 0cab7216fe Split the -r section layout out of link
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]>
2026-10-01 06:47:00 +09:00
Rui UeyamaandClaude Opus 5.5 e26058efa6 Move the -r output's synthetic sections out of link
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]>
2026-10-01 06:47:00 +09:00
Rui UeyamaandClaude Opus 5.5 8fe0c8cf00 Take -sdk_imports with -r as ld-prime does
-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]>
2026-10-01 06:04:26 +09:00
Rui UeyamaandClaude Opus 5.5 46397efc64 Read a -dylib_file and inputs whole as ld-prime does
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]>
2026-10-01 06:04:26 +09:00
Rui UeyamaandClaude Opus 5.5 2b24853901 Warn of lazy libraries in firmware as ld-prime does
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]>
2026-10-01 06:04:26 +09:00
Rui UeyamaandClaude Opus 5.5 782c865213 Stop at the first library not found as ld-prime does
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]>
2026-10-01 06:04:26 +09:00
Rui UeyamaandClaude Opus 5.5 0409e156eb Report duplicate symbols after undefined ones as ld-prime does
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]>
2026-10-01 06:04:26 +09:00
Rui UeyamaandClaude Opus 5.5 d0442b62c6 Fail on a section past a 32-bit segment end as ld-prime does
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]>
2026-10-01 06:04:26 +09:00
Rui UeyamaandClaude Opus 5.5 38bb838192 Refuse -bundle_loader outside a bundle first as ld-prime does
-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]>
2026-10-01 06:04:26 +09:00
Rui UeyamaandClaude Opus 5.5 168aa58363 Take an absolute -init symbol's value for its offset as ld-prime does
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]>
2026-10-01 06:04:26 +09:00
Rui UeyamaandClaude Opus 5.5 58c07ae5a4 Name text relocations' targets and sections as ld-prime does
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]>
2026-10-01 06:04:26 +09:00
Rui UeyamaandClaude Opus 5.5 70d7eef279 Print the layout at an error before __LINKEDIT as ld-prime does
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]>
2026-10-01 06:04:26 +09:00
Rui UeyamaandClaude Opus 5.5 23b1e8499c Give an x86-64 one-byte branch no stub as ld-prime does
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]>
2026-10-01 05:53:02 +09:00
Rui UeyamaandClaude Opus 5.5 dd4995c1ac Make branch islands only for the code of __TEXT as ld-prime does
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]>
2026-10-01 05:53:02 +09:00
Rui UeyamaandClaude Opus 5.5 4df94ed237 Give layout errors in the same order in every run
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]>
2026-10-01 05:52:56 +09:00
Rui UeyamaandClaude Opus 5.5 1158ab6bd5 Build chained fixups of a layout that has failed without overflowing
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]>
2026-10-01 05:52:56 +09:00
Rui UeyamaandClaude Opus 5.5 fddba7c767 Keep the section flag rules together in output_sections.rs
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]>
2026-10-01 05:46:24 +09:00
Rui UeyamaandClaude Opus 5.5 c16df72188 Merge a private re-export in one place in load_reexports
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]>
2026-10-01 05:46:24 +09:00
Rui UeyamaandClaude Opus 5.5 94abb54e5b Split the symbol table's planning into its steps
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]>
2026-10-01 05:46:24 +09:00
Rui UeyamaandClaude Opus 5.5 3d4de45f89 Move the symbol table's construction into chunks/symtab.rs
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]>
2026-10-01 05:46:24 +09:00
Rui UeyamaandClaude Opus 5.5 a2780b4ea4 Split create_output_sections into its steps
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]>
2026-10-01 05:46:24 +09:00
Rui UeyamaandClaude Opus 5.5 c03e217ef9 Move the output-section pass into output_sections.rs
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]>
2026-10-01 05:46:24 +09:00
Rui UeyamaandClaude Opus 5.5 586d307289 Place a section by the atoms the output keeps
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]>
2026-10-01 04:51:32 +09:00
Rui UeyamaandClaude Opus 5.5 c468aa42b6 Name a relative method list as ld-prime's own atom
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]>
2026-10-01 04:51:32 +09:00
Rui UeyamaandClaude Opus 5.5 b1a4a36158 Move ObjC references to __DATA_CONST from macOS 14.4
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]>
2026-10-01 04:51:32 +09:00
Rui UeyamaandClaude Opus 5.5 95dd6a5c41 Fold a class reference to a class only a temporary label names
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]>
2026-10-01 04:51:32 +09:00
Rui UeyamaandClaude Opus 5.5 a5457fb384 Keep the aliases of an ObjC list entry as ld-prime does
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]>
2026-10-01 04:51:32 +09:00
Rui UeyamaandClaude Opus 5.5 25463272c2 Name superclass and protocol references as ld-prime does
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]>
2026-10-01 04:51:32 +09:00
Rui UeyamaandClaude Opus 5.5 0d70ae23de Refuse an __objc_clsrolist entry with no relocation
__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]>
2026-10-01 04:51:32 +09:00
Rui UeyamaandClaude Opus 5.5 6a7b20a831 Give no GOT slot to what only a lost weak copy loads
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]>
2026-10-01 04:51:32 +09:00
Rui UeyamaandClaude Opus 5.5 5ba08fcf8b Keep the most aligned copy of a merged C string
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]>
2026-10-01 04:51:32 +09:00
Rui UeyamaandClaude Opus 5.5 52eab655b0 Merge identical UTF-16 strings in __TEXT,__ustring
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]>
2026-10-01 04:51:32 +09:00
Rui UeyamaandClaude Opus 5.5 db6c9352bd Pick among weak definitions by ld64's rules
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]>
2026-10-01 04:51:31 +09:00
Rui UeyamaandClaude Opus 5.5 20ff39fe40 Keep terminators alive when dead stripping
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]>
2026-10-01 04:51:31 +09:00
Rui UeyamaandClaude Opus 5.5 08de2e7314 Load -lazy-l dylibs at their first use from macOS 27
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]>
2026-10-01 04:30:33 +09:00
Rui UeyamaandClaude Opus 5.5 76f140cf0c Number unnamed atoms as ld-prime does
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]>
2026-10-01 04:15:03 +09:00
Rui UeyamaandClaude Opus 5.5 500525622e Print the final section layout after a layout error as ld-prime does
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]>
2026-10-01 04:15:03 +09:00
Rui UeyamaandClaude Opus 5.5 e12dfe7399 Fail on an initializer dyld binds under init offsets
__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]>
2026-10-01 04:15:03 +09:00
Rui UeyamaandClaude Opus 5.5 d2c705aaf1 Keep an input __got slot with an addend in the GOT
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]>
2026-10-01 04:15:03 +09:00
Rui UeyamaandClaude Opus 5.5 161ed1be1d Keep an input's non-lazy symbol pointers typed as ld-prime does
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]>
2026-10-01 04:15:03 +09:00
Rui UeyamaandClaude Opus 5.5 f8db09820a Want dyld_stub_binder in every lazy-binding image, as ld-prime does
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]>
2026-10-01 03:44:46 +09:00
Rui UeyamaandClaude Opus 5.5 61ba088daa Credit a common symbol's GOT slot in -map to the winning object
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]>
2026-10-01 03:44:46 +09:00
Rui UeyamaandClaude Opus 5.5 6d1e6d2109 Refuse -reexported_symbols_list outside a dylib, as ld-prime does
-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]>
2026-10-01 03:44:46 +09:00
Rui UeyamaandClaude Opus 5.5 c6cd613be4 Make no branch islands under -no_branch_islands
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]>
2026-10-01 03:44:46 +09:00
Rui UeyamaandClaude Opus 5.5 f58f7286ca Run the -init function first, from __init_offsets or LC_ROUTINES_64
-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]>
2026-10-01 03:44:46 +09:00
Rui UeyamaandClaude Opus 5.5 89f6af4cb0 Refuse an LC_LINKER_OPTION of other than one or two strings
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]>
2026-10-01 03:26:11 +09:00
Rui UeyamaandClaude Opus 5.5 d098728524 Warn of libraries given twice by path as ld-prime does
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]>
2026-10-01 03:26:11 +09:00
Rui UeyamaandClaude Opus 5.5 49234c0b57 Warn of a -weak-l that finds an archive as ld-prime does
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]>
2026-10-01 03:26:10 +09:00
Rui UeyamaandClaude Opus 5.5 1391ba66e1 Look for a dylib only for -reexport-l as ld-prime does
-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]>
2026-10-01 03:26:10 +09:00
Rui UeyamaandClaude Opus 5.5 4b1d583980 Print every -why_live chain as ld-prime does
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]>
2026-10-01 03:26:10 +09:00
Rui UeyamaandClaude Opus 5.5 3edc3c1832 Drop unreferenced undefined symbols from -r output as ld-prime does
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]>
2026-10-01 03:26:10 +09:00
Rui UeyamaandClaude Opus 5.5 0a72ba1f41 Warn of an ignored library for each input naming it as ld-prime does
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]>
2026-10-01 03:26:10 +09:00
Rui UeyamaandClaude Opus 5.5 4a48816fa5 Ignore a re-exported fat file without the arch as ld-prime does
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]>
2026-10-01 03:26:10 +09:00
Rui UeyamaandClaude Opus 5.5 6ed4595ca2 Load an object named twice twice as ld-prime does
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]>
2026-10-01 03:26:10 +09:00
Rui UeyamaandClaude Opus 5.5 de9933f34c List a re-exported dylib named at another path once in -t
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]>
2026-10-01 03:26:10 +09:00
Rui UeyamaandClaude Opus 5.5 fae270bed7 Rewrite -r auto-link options as ld-prime does
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]>
2026-10-01 03:26:10 +09:00
Rui UeyamaandClaude Opus 5.5 90a52243ea Give option warnings before an option error as ld-prime does
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]>
2026-10-01 03:26:10 +09:00
Rui UeyamaandClaude Opus 5.5 6dc5cac84e Implement -dylib_file as ld-prime does
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]>
2026-10-01 03:26:10 +09:00
Rui UeyamaandClaude Opus 5.5 47cb992a20 Implement -segalign as ld-prime does
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]>
2026-10-01 03:26:10 +09:00
Rui UeyamaandClaude Opus 5.5 6bd8b9ab2b Refuse to re-export a weak or lazy library as ld-prime does
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]>
2026-10-01 03:26:10 +09:00
Rui UeyamaandClaude Opus 5.5 55acc975c5 Merge a library's namings as ld-prime does
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]>
2026-10-01 03:26:10 +09:00
Rui UeyamaandClaude Opus 5.5 838b749cd9 Ignore -mark_dead_strippable_dylib as ld-prime does
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]>
2026-10-01 03:26:10 +09:00
Rui UeyamaandClaude Opus 5.5 e61400beb5 Warn about code atoms too large for ld-prime's branch island clusters
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]>
2026-10-01 03:10:45 +09:00
Rui UeyamaandClaude Opus 5.5 d6c8609f16 Report undefined -e and -alias names where ld-prime says they are wanted
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]>
2026-10-01 03:10:45 +09:00
Rui UeyamaandClaude Opus 5.5 c035d39852 Warn about -segment_order's __DATA_CONST before refusing the option
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]>
2026-10-01 03:10:45 +09:00
Rui UeyamaandClaude Opus 5.5 0405cdb7b7 Name no literal after a linker-private label, as ld64 does
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]>
2026-10-01 03:10:45 +09:00
Rui UeyamaandClaude Opus 5.5 dcdcad4d60 Bind a -flat_namespace dylib's own exports by flat lookup
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]>
2026-10-01 03:10:45 +09:00
Rui UeyamaandClaude Opus 5.5 4aaa0dc51f Pack classic rebase opcodes as ld64's encodeV1 does
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]>
2026-10-01 03:10:45 +09:00
Rui UeyamaandClaude Opus 5.5 762a923223 Make each initializer pointer an atom of its own
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]>
2026-10-01 02:56:29 +09:00
Rui UeyamaandClaude Opus 5.5 58aa3219a1 Coalesce unnamed class and protocol references as ld-prime does
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]>
2026-10-01 02:56:29 +09:00
Rui UeyamaandClaude Opus 5.5 41f7fa0de3 Run only the added function of a difference initializer pointer
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]>
2026-10-01 02:56:29 +09:00
Rui UeyamaandClaude Opus 5.5 2cf7a9f5a0 Apply the ObjC section flag rules to __DATA's sections only
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]>
2026-10-01 02:56:29 +09:00
Rui UeyamaandClaude Opus 5.5 20900af7f7 Move an input __DATA,__got's slots into the GOT
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]>
2026-10-01 02:56:29 +09:00
Rui UeyamaandClaude Opus 5.5 bd52b59e5f Refuse thread-local data a rename moves out of the template
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]>
2026-10-01 02:56:29 +09:00
Rui UeyamaandClaude Opus 5.5 000cac0759 Merge fixed-size literals only in ld-prime's pools
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]>
2026-10-01 02:56:29 +09:00
Rui UeyamaandClaude Opus 5.5 799c24a5fe Define __mh_dylib_header and __mh_bundle_header as ld-prime does
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]>
2026-10-01 02:09:51 +09:00
Rui UeyamaandClaude Opus 5.5 c74050908d Link dyld with -dylinker as ld-prime does
-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]>
2026-10-01 02:09:51 +09:00
Rui UeyamaandClaude Opus 5.5 2091e630ea Jump from a range-extension thunk to a weak definition's stub
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]>
2026-10-01 01:48:56 +09:00
Rui UeyamaandClaude Opus 5.5 2a1706d807 Keep branches with an addend out of range-extension thunks
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]>
2026-10-01 01:48:56 +09:00
Rui UeyamaandClaude Opus 5.5 e4bac4c0b7 Place each batch's thunk a branch's reach ahead, as mold does
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]>
2026-10-01 01:48:56 +09:00
Rui UeyamaandClaude Opus 5.5 d9e8d31ffb Lay out code without thunks when every branch reaches
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]>
2026-10-01 01:48:56 +09:00
Rui UeyamaandClaude Opus 5.5 7bf77d3eb0 Read Objective-C sections by their names whatever their types
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]>
2026-10-01 01:47:05 +09:00
Rui UeyamaandClaude Opus 5.5 160a89e82a Keep __objc_selrefs literal pointers whatever its type
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]>
2026-10-01 01:47:05 +09:00
Rui UeyamaandClaude Opus 5.5 e8124f4d93 Write only the section types a final image lays out
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]>
2026-10-01 01:47:05 +09:00
Rui UeyamaandClaude Opus 5.5 674e4fdda1 Refuse a thread-local template section of another type
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]>
2026-10-01 01:47:05 +09:00
Rui UeyamaandClaude Opus 5.5 c447fc539e Read __TEXT,__constructor as a list of initializers
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]>
2026-10-01 01:47:05 +09:00
Rui UeyamaandClaude Opus 5.5 179d59319e Report unknown options together as ld-prime does
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]>
2026-10-01 01:37:31 +09:00
Rui UeyamaandClaude Opus 5.5 7b2e777ed4 Pad the header as ld-prime does for static images and small -headerpad
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]>
2026-10-01 01:37:31 +09:00
Rui UeyamaandClaude Opus 5.5 c8ce9f92ad List -map records, islands and dylibs as ld-prime does
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]>
2026-10-01 01:37:31 +09:00
Rui UeyamaandClaude Opus 5.5 2c4b84eeca Report missing arguments and unknown architectures as ld-prime does
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]>
2026-10-01 01:37:30 +09:00
Rui UeyamaandClaude Opus 5.5 7e5cf111df Read hexadecimal option values as ld-prime does
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]>
2026-10-01 01:37:30 +09:00
Rui UeyamaandClaude Opus 5.5 898aecba91 Implement the upward and lazy library options
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]>
2026-10-01 01:37:30 +09:00
Rui UeyamaandClaude Opus 5.5 08929361ab Warn about -pie and -no_pie overriding each other as ld-prime does
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]>
2026-10-01 01:37:30 +09:00
Rui UeyamaandClaude Opus 5.5 f6fc77b199 Read auto-link options as ld-prime does
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]>
2026-10-01 01:37:30 +09:00
Rui UeyamaandClaude Opus 5.5 922be537de Ignore -executable_path and @executable_path as ld-prime does
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]>
2026-10-01 01:37:30 +09:00
Rui UeyamaandClaude Opus 5.5 b3e10f8181 Note a renamed -macosx_version_min and repeated platforms as ld-prime does
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]>
2026-10-01 01:37:30 +09:00
Rui UeyamaandClaude Opus 5.5 a5cb1e9d10 Set -sectalign's alignment as ld-prime does
-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]>
2026-10-01 01:37:30 +09:00
Rui UeyamaandClaude Opus 5.5 985ed7a6ca Refuse a legacy .tbd's platform TAPI doesn't know
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]>
2026-10-01 01:16:45 +09:00
Rui UeyamaandClaude Opus 5.5 aa2e6942ae Ignore inputs for another architecture as ld-prime does
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]>
2026-10-01 01:16:45 +09:00
Rui UeyamaandClaude Opus 5.5 4b5a10fbf4 Ignore a .tbd without the link's architecture as ld-prime does
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]>
2026-10-01 01:16:45 +09:00
Rui UeyamaandClaude Opus 5.5 4ff3a7109f Check fixed-size record sections and __objc_imageinfo as ld-prime does
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]>
2026-10-01 01:16:45 +09:00
Rui UeyamaandClaude Opus 5.5 8e5f0a9e5c Diagnose unaligned pointers after text relocations as ld-prime does
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]>
2026-10-01 01:16:45 +09:00
Rui UeyamaandClaude Opus 5.5 d684beaa03 Report an LDR whose target its size doesn't divide as ld-prime does
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]>
2026-10-01 01:16:45 +09:00
Rui UeyamaandClaude Opus 5.5 0d9c9fb6f6 Test -r section-relative relocations of arm64 and pc-relative fields
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]>
2026-10-01 01:16:45 +09:00
Rui UeyamaandClaude Opus 5.5 129972ea59 Pack the chained-fixups tables as ld-prime does
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]>
2026-10-01 01:16:45 +09:00
Rui UeyamaandClaude Opus 5.5 7b57c9c34e Merge the categories of an imported class into the first of them
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]>
2026-10-01 00:52:47 +09:00
Rui UeyamaandClaude Opus 5.5 d7b6069ec7 Place __objc_protorefs between the selector and class references
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]>
2026-10-01 00:52:47 +09:00
Rui UeyamaandClaude Opus 5.5 5c7a260fdd Coalesce literals only within a section of one name
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]>
2026-10-01 00:52:47 +09:00
Rui UeyamaandClaude Opus 5.5 3708a715e5 Keep ObjC list labels a -r link demoted in a final image
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]>
2026-10-01 00:52:47 +09:00
Rui UeyamaandClaude Opus 5.5 fc8ee3ad1a Drop a merged Swift class's metaclass protocol list
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]>
2026-10-01 00:52:46 +09:00
Rui UeyamaandClaude Opus 5.5 f9721df4be Leave ld-prime's content-made literal atoms unnamed
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]>
2026-10-01 00:52:46 +09:00
Rui UeyamaandClaude Opus 5.5 83b30cf1d6 Rewrite the method lists of __objc_catlist2 categories
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]>
2026-10-01 00:52:46 +09:00
Rui UeyamaandClaude Opus 5.5 1f0512acea Keep the class references nothing refers to
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]>
2026-10-01 00:52:46 +09:00
Rui UeyamaandClaude Opus 5.5 751924d64f Name -r relocation targets re-derived from addresses as ld-prime does
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]>
2026-10-01 00:34:21 +09:00
Rui UeyamaandClaude Opus 5.5 faba8e5acf Name range-extension thunk entries as ld-prime names branch islands
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]>
2026-10-01 00:33:35 +09:00
Rui UeyamaandClaude Opus 5.5 a9d4be3b57 Keep every symbol-cut atom of an object without subsections alive
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]>
2026-10-01 00:33:35 +09:00
Rui UeyamaandClaude Opus 5.5 825da67a2c Keep a 4-byte __compact_unwind pointer 4 bytes in a -r output
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]>
2026-10-01 00:31:28 +09:00
Rui UeyamaandClaude Opus 5.5 1b953e91aa Write 4 into a -r CIE's personality cell on x86-64 too
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]>
2026-10-01 00:31:28 +09:00
Rui UeyamaandClaude Opus 5.5 1d4e9f2515 List an LSDA in __unwind_info only if the encoding says there is one
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]>
2026-10-01 00:31:28 +09:00
Rui UeyamaandClaude Opus 5.5 13f9a76274 Write __unwind_info for an image whose only unwind info is an FDE
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]>
2026-10-01 00:31:28 +09:00
Rui UeyamaandClaude Opus 5.5 5704dfb9d5 Encode __unwind_info entries of functions in data sections late
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]>
2026-10-01 00:31:28 +09:00
Rui UeyamaandClaude Opus 5.5 a1ea7eae09 Accept -no_compact_unwind and unwind by __eh_frame alone
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]>
2026-10-01 00:31:08 +09:00
Rui UeyamaandClaude Opus 5.5 530611a3ff Implement -stack_addr
-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]>
2026-10-01 00:18:11 +09:00
Rui UeyamaandClaude Opus 5.5 78f2eb2806 Report pc-relative fixups that can't reach their targets
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]>
2026-10-01 00:18:11 +09:00
Rui UeyamaandClaude Opus 5.5 4bba0ed7cf Write the -map file's atoms as ld-prime does
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]>
2026-10-01 00:18:11 +09:00
Rui UeyamaandClaude Opus 5.5 c921dc369e Implement -seg_page_size and -no_zero_fill_sections
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]>
2026-10-01 00:18:11 +09:00
Rui UeyamaandClaude Opus 5.5 e22f42f5bc Give a static executable's -stack_size a __UNIXSTACK segment
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]>
2026-10-01 00:18:11 +09:00
Rui UeyamaandClaude Opus 5.5 b2b6c3477b Round -image_base up before anything else reads it
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]>
2026-10-01 00:18:11 +09:00
Rui UeyamaandClaude Opus 5.5 a881b164df Read platform names and versions as ld-prime does
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]>
2026-10-01 00:18:11 +09:00
Rui UeyamaandClaude Opus 5.5 0127fb657b Ignore dylibs in kexts and -r links as ld-prime does
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]>
2026-10-01 00:18:11 +09:00
Rui UeyamaandClaude Opus 5.5 d55fd16bb6 Choose the output kind as ld64 does
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]>
2026-10-01 00:18:11 +09:00
Rui UeyamaandClaude Opus 5.5 b662df1430 Flag a boundary-made __LD,__compact_unwind as debug information
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]>
2026-10-01 00:15:54 +09:00
Rui UeyamaandClaude Opus 5.5 a44add40a2 Complete an unterminated string ending a C-string section
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]>
2026-10-01 00:15:54 +09:00
Rui UeyamaandClaude Opus 5.5 a02fdcd556 Drop input sections by segment, not by S_ATTR_DEBUG
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]>
2026-10-01 00:15:54 +09:00
Rui UeyamaandClaude Opus 5.5 9b95dde55c Give coalesced sections of old compilers their modern names
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]>
2026-10-01 00:15:54 +09:00
Rui UeyamaandClaude Opus 5.5 039b858399 Take a section for its standard one only by type or ObjC name
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]>
2026-10-01 00:15:54 +09:00
Rui UeyamaandClaude Opus 5.5 52883f9924 Let an empty first member decide a -r output section's flags
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]>
2026-10-01 00:15:54 +09:00
Rui UeyamaandClaude Opus 5.5 4087daf71a Take a final image section's flags from its first member
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]>
2026-10-01 00:15:12 +09:00
Rui UeyamaandClaude Opus 5.5 cf2d3c1b07 Size LC_MAIN before the entry point has an address
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]>
2026-10-01 00:14:35 +09:00
Rui UeyamaandClaude Opus 5.5 fc2b169052 Reject interposing tuples in an image bound for the shared region
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]>
2026-10-01 00:14:35 +09:00
Rui UeyamaandClaude Opus 5.5 3608068842 Move __interpose to __DATA_CONST in place of -rename_section
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]>
2026-10-01 00:14:35 +09:00
Rui UeyamaandClaude Opus 5.5 696ff201e1 Report a one-byte branch that can't reach as a fixup error
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]>
2026-09-30 23:53:01 +09:00
Rui UeyamaandClaude Opus 5.5 9a24f3d52d Resolve a section-relative target in the section it names
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]>
2026-09-30 23:53:01 +09:00
Rui UeyamaandClaude Opus 5.5 2b545f3cf7 Take 4-byte pointers in __compact_unwind and name a misplaced fixup
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]>
2026-09-30 23:53:01 +09:00
Rui UeyamaandClaude Opus 5.5 7ddef3f991 Refuse pointer fixups in read-only segments as ld-prime does
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]>
2026-09-30 23:53:01 +09:00
Rui UeyamaandClaude Opus 5.5 2ef9d6cceb Reject an x86-64 32-bit pointer in an image dyld loads
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]>
2026-09-30 23:53:01 +09:00
Rui UeyamaandClaude Opus 5.5 326f09ecd0 Relax an arm64 GOT load on a 64-bit add and refuse other instructions
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]>
2026-09-30 23:53:01 +09:00
Rui UeyamaandClaude Opus 5.5 88b3667747 Relax an x86-64 GOT load on leaq and refuse other instructions
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]>
2026-09-30 23:53:01 +09:00
Rui UeyamaandClaude Opus 5.5 147a75ae1c Don't crash on a symbol at a -r literal record
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]>
2026-09-30 23:50:21 +09:00
Rui UeyamaandClaude Opus 5.5 f9ca435281 Read a .tbd file for the link's platform, as ld-prime does
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]>
2026-09-30 23:50:21 +09:00
Rui UeyamaandClaude Opus 5.5 e265760192 Add a SHA-1 code directory where ld-prime does
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]>
2026-09-30 23:50:21 +09:00
Rui UeyamaandClaude Opus 5.5 dac51497d3 Check pointer alignment as ld-prime does
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]>
2026-09-30 23:50:21 +09:00
Rui UeyamaandClaude Opus 5.5 383149014d Align fixed-size records as ld-prime does
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]>
2026-09-30 23:50:21 +09:00
Rui UeyamaandClaude Opus 5.5 57c519e24f Reject fixed-size record sections of a partial record
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]>
2026-09-30 23:50:21 +09:00
Rui UeyamaandClaude Opus 5.5 6b0479272a Reject symbol pointer sections as ld-prime does
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]>
2026-09-30 23:50:21 +09:00
Rui UeyamaandClaude Opus 5.5 31fb986a2d Record no rebases for a non-PIE executable
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]>
2026-09-30 23:31:31 +09:00
Rui UeyamaandClaude Opus 5.5 7589d40b63 Align an unaligned common symbol as ld-prime does on x86-64 too
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]>
2026-09-30 23:31:31 +09:00
Rui UeyamaandClaude Opus 5.5 eb60902cd8 Give a segment of zero-fill sections alone file offset 0
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]>
2026-09-30 23:31:31 +09:00
Rui UeyamaandClaude Opus 5.5 1ab2b89e89 Give a -kernel image no __PAGEZERO unless asked for
__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]>
2026-09-30 23:31:31 +09:00
Rui UeyamaandClaude Opus 5.5 cfa3285ed4 Stamp LC_ID_DYLIB with time 1, as ld-prime does
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]>
2026-09-30 23:31:31 +09:00
Rui UeyamaandClaude Opus 5.5 91fe924b2b Give __DATA_CONST where ld-prime does by deployment target and -no_pie
__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]>
2026-09-30 23:31:31 +09:00
Rui UeyamaandClaude Opus 5.5 9e96b72a11 Take the deployment target from the first object, as ld-prime does
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]>
2026-09-30 23:31:31 +09:00
Rui UeyamaandClaude Opus 5.5 8c689bd365 Check an ICF class against its leader through the leaders' classes
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]>
2026-09-30 23:31:25 +09:00
Rui UeyamaandClaude Opus 5.5 2827aa7b4f Group each subsection's unwind records into one run
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]>
2026-09-30 23:26:28 +09:00
Rui UeyamaandClaude Opus 5.5 1e6c9e30c0 Keep __compact_unwind's input alignment in -r output
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]>
2026-09-30 23:26:28 +09:00
Rui UeyamaandClaude Opus 5.5 bb6354c8c2 Keep encoding-0 __unwind_info entries apart as ld-prime does
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]>
2026-09-30 23:26:28 +09:00
Rui UeyamaandClaude Opus 5.5 9f072c5c2c Give DWARF-mode __unwind_info entries their personality and LSDA
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]>
2026-09-30 23:26:27 +09:00
Rui UeyamaandClaude Opus 5.5 4fec45886f List every code atom in LC_FUNCTION_STARTS as ld-prime does
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]>
2026-09-30 23:26:27 +09:00
Rui Ueyama 8f17166297 Process undefined-symbol relocations with --noinhibit-exec
The relocation scan skips a relocation to an undefined symbol when that
is an error, because the link fails anyway. --noinhibit-exec reports the
error as a warning and writes the output, so relocations that needed a
GOT, PLT or TLS entry were applied without one, and mold panicked on the
missing index. Such relocations are now scanned as they are with
--warn-unresolved-symbols, so the symbol gets the entries it needs and
resolves to 0, as it does in GNU ld and lld.

Every target crashed on a -fPIC object reading an undefined variable,
an undefined TLS variable, or both.
2026-09-30 23:23:08 +09:00
Rui Ueyama 8b9b014847 Write -r addends in place only on REL targets and SH4
GNU as emits R_RISCV_ALIGN and R_LARCH_ALIGN for an alignment directive
in an executable NOBITS section. With -r, RelocSection sliced the output
section's bytes at each relocation before calling write_addend, but a
NOBITS section has no bytes in the file, so the slice was out of range.
Since only REL targets and SH4 keep addends in section contents, take
the slice only on those targets.
2026-09-30 23:23:08 +09:00
Rui Ueyama 5b22233017 Apply R_RISCV_64 and R_LARCH_64 in 32-bit objects
GAS and Clang emit R_RISCV_64 and R_LARCH_64 for `.quad sym` even in
ELF32 objects. On RV32 and LA32, they are not the word-size absolute
relocation that the output section applies, so they reached
apply_reloc_alloc, which had no case for them and panicked. Handle them
like other non-word-size absolute relocations, as lld does: scan them
with scan_absrel and write S + A as a 64-bit value.
2026-09-30 23:23:08 +09:00
Rui Ueyama a368d2233d Restart for old LTO plugins from the original directory
With a plugin that lacks get_symbols_v3, mold re-executes itself with
the same command line. The new process applies -C again, but it was
started in the directory the first one had already changed to, so a
relative -C failed with "chdir failed". Remember the directory before
-C and return to it before re-executing.
2026-09-30 23:18:33 +09:00
Rui Ueyama 04e7dd8f70 Apply --chroot only once to paths derived from inputs
Input file names already have --chroot applied. Relative thin archive
members and relative names in linker scripts are resolved against such
a name, but the result was opened with --chroot applied again, so the
link failed. Open these paths as they are. Also write the --repro
archive next to the output in the chroot rather than outside of it.
2026-09-30 23:18:33 +09:00
Rui Ueyama e3bc3490b7 Archive --repro inputs from memory
--repro reopened each input by its recorded name. Response files and
--retain-symbols-file are opened before -C changes the directory, so a
relative name no longer found them and the link failed, and a name with
--chroot applied got the prefix twice. Archive the contents already in
memory instead: no input has been rewritten in place at this point.
2026-09-30 23:18:32 +09:00
Rui Ueyama d2b88ab395 Don't write the output in place over a mapped input
An existing output file is reused by writing it in place. When that file
is also a memory-mapped input, as in `mold a.o -o a.o` with an a.o larger
than 32 KiB, its private mapping sees the truncation and the writes while
the input is still being read, so mold crashed with SIGBUS or produced a
broken output. Such a file is now replaced with a fresh one. Inputs are
matched by device and inode, which covers archives and thin members too.
2026-09-30 23:18:32 +09:00
Rui Ueyama fdab1c33b2 Open absolute script paths as is without --sysroot
An absolute path in a linker script is prefixed with the sysroot if
the script is in the sysroot. is_in_sysroot returned false for an
empty sysroot, so such a path fell through to the -L search, and an
incompatible file was skipped with a warning. C++ mold treats an empty
sysroot as containing every file and opens the path as is, reporting
an error if it cannot be used. GNU ld and lld don't search -L either.
2026-09-30 23:13:53 +09:00
Rui Ueyama bc4272f76d Open dynamic lists before changing directory for -C
--dynamic-list and --export-dynamic-symbol-list files were opened by
the driver after parse_args had already changed the directory for -C,
so a relative path was looked up in the new directory. C++ mold reads
them in the option loop, as we already do for --retain-symbols-file.
Open them there too and keep the mapped files in DynamicListSource;
the driver still parses them, since the parser needs a Context.
2026-09-30 23:13:53 +09:00
Rui Ueyama a0d62d97f7 Remove the temporary output file on a panic
A panic aborts the process in release builds, skipping the cleanup that
a fatal error or a SIGSEGV/SIGBUS does, so a failed link left its
.<output>.<pid> file behind. Install a panic hook that removes the file
and then calls the default hook, so the panic message, backtrace and
abort stay as they were. The hook runs on the panicking thread, so it
also covers rayon workers and the extern "C" LTO plugin callbacks.
2026-09-30 23:13:53 +09:00
Rui Ueyama 3d7bed0abd Reject a NUL byte in a response file
Arguments are passed on as C strings, so a NUL byte in the -plugin path,
a -plugin-opt or the -o name made loading the LTO plugin panic. A
response file is the only way to get a NUL byte into an argument, as
the OS passes arguments as C strings too, so reject one when reading a
response file instead of at each C string conversion.
2026-09-30 23:13:53 +09:00
Rui Ueyama 9685b424d8 Link libatomic on hosts without 64-bit atomics
mimalloc uses 64-bit atomics, which GCC implements as calls into
libatomic on hosts such as 32-bit PowerPC. The mimalloc crate links
libatomic only on ARM, so mold failed to link there, which CI's cargo
check did not catch. Link libatomic when the target lacks 64-bit
atomics, as the C++ version did. It is a link argument rather than a
library because rustc would place the latter before mimalloc, where
--as-needed drops it.
2026-09-30 23:08:41 +09:00
Rui Ueyama c925e791ce Combine Adler-32 checksums in Rust
libz-sys declares the length parameter of adler32_combine() as
libc::off_t, which is 32 bits on 32-bit glibc hosts, but Debian and
Ubuntu now compile C code for armhf and powerpc with 64-bit off_t, so
the bundled zlib reads a 64-bit length there. Every zlib-compressed
debug section larger than one 1 MiB shard got a wrong checksum, and
readelf, objcopy and gdb rejected it. Compute the combination in Rust
instead; it is a few lines of modular arithmetic.
2026-09-30 23:08:41 +09:00
Rui UeyamaandClaude Opus 5.5 d493672355 Reject non-lazy pointer sections named by indirect symbols
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]>
2026-09-30 23:02:37 +09:00
Rui UeyamaandClaude Opus 5.5 8b13d57bbc Put __interpose in __DATA_CONST from macOS 15 on
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]>
2026-09-30 23:02:37 +09:00
Rui UeyamaandClaude Opus 5.5 9c5a5fa397 Give a -r output's standard sections ld-prime's flags
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]>
2026-09-30 23:02:37 +09:00
Rui UeyamaandClaude Opus 5.5 d77ca53af7 Flag boundary sections of ld-prime's other standard names
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]>
2026-09-30 23:02:37 +09:00
Rui UeyamaandClaude Opus 5.5 9fe17fcbd4 Keep S_ATTR_SOME_INSTRUCTIONS only on code in a final image
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]>
2026-09-30 23:02:37 +09:00
Rui UeyamaandClaude Opus 5.5 4dc3266093 Resolve boundary symbols of ld-prime's own pointer sections
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]>
2026-09-30 23:02:37 +09:00
Rui UeyamaandClaude Opus 5.5 97c6daaf21 Move __auth_ptr and the rest of ld-prime's list to __DATA_CONST
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]>
2026-09-30 23:02:37 +09:00
Rui UeyamaandClaude Opus 5.5 a37eeb1121 Name the option whose argument the command line lacks, as ld-prime does
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]>
2026-09-30 22:58:55 +09:00
Rui UeyamaandClaude Opus 5.5 57ce4a1494 Fail a -fatal_warnings link at its end, with the warnings as warnings
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]>
2026-09-30 22:57:38 +09:00
Rui UeyamaandClaude Opus 5.5 afc955756c Check __eh_frame's relocations as ld-prime does, without panicking
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]>
2026-09-30 22:57:38 +09:00
Rui UeyamaandClaude Opus 5.5 c2f0b82591 Read LSDA and personality pointers in any encoding ld-prime reads
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]>
2026-09-30 22:57:38 +09:00
Rui UeyamaandClaude Opus 5.5 0c81e45e72 Read FDEs in the pointer encoding their CIE gives
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]>
2026-09-30 22:57:38 +09:00
Rui UeyamaandClaude Opus 5.5 3580b7bd6b Name an FDE pointer outside every section by its address
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]>
2026-09-30 22:57:38 +09:00
Rui UeyamaandClaude Opus 5.5 52db115061 Refuse an FDE for a function in a section of data
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]>
2026-09-30 22:57:38 +09:00
Rui UeyamaandClaude Opus 5.5 9c552307c5 Reject the relocation records ld-prime rejects
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]>
2026-09-30 22:32:27 +09:00
Rui UeyamaandClaude Opus 5.5 0bd30fc192 Check a method list's names before giving it selector references
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]>
2026-09-30 22:25:53 +09:00
Rui UeyamaandClaude Opus 5.5 92ad96521f Accept -target, the triple clang passes for firmware
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]>
2026-09-30 22:16:16 +09:00
Rui UeyamaandClaude Opus 5.5 3a1327abdf Link for -platform_version firmware as ld-prime does
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]>
2026-09-30 22:16:16 +09:00
Rui UeyamaandClaude Opus 5.5 5e836329c6 Order sections by -section_order as ld-prime does
-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]>
2026-09-30 22:16:16 +09:00
Rui UeyamaandClaude Opus 5.5 24261394c2 Implement -preload, the MH_PRELOAD output type
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]>
2026-09-30 22:16:16 +09:00
Rui UeyamaandClaude Opus 5.5 92774fe46f Give an empty code atom its own __unwind_info entry
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]>
2026-09-30 21:53:36 +09:00
Rui UeyamaandClaude Opus 5.5 9d2281cb79 Move every section of code to __TEXT_EXEC,__text in a kext
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]>
2026-09-30 21:53:36 +09:00
Rui UeyamaandClaude Opus 5.5 77a8cab0fc Count a common symbol as its object's in a zero-fill conflict
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]>
2026-09-30 21:53:36 +09:00
Rui UeyamaandClaude Opus 5.5 eed84ba2f8 Make an empty segment for a boundary symbol that names none
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]>
2026-09-30 21:53:36 +09:00
Rui UeyamaandClaude Opus 5.5 5882d94ea3 Type a section made for a boundary symbol by its name
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]>
2026-09-30 21:53:36 +09:00
Rui UeyamaandClaude Opus 5.5 2e46cf4525 Cut -sectcreate names to 16 bytes with ld-prime's warning
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]>
2026-09-30 21:53:36 +09:00
Rui UeyamaandClaude Opus 5.5 960afbf7ef Accept -text_exec
-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]>
2026-09-30 21:52:55 +09:00
Rui UeyamaandClaude Opus 5.5 ef65620af8 Encode __unwind_info again when code leaves __TEXT
__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]>
2026-09-30 21:50:36 +09:00
Rui UeyamaandClaude Opus 5.5 76dd1c2595 Let a -static -pie relocation address go below its base
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]>
2026-09-30 21:50:36 +09:00
Rui UeyamaandClaude Opus 5.5 1c6fa90288 Place segments around -segaddr pins as ld-prime does
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]>
2026-09-30 21:50:36 +09:00
Rui UeyamaandClaude Opus 5.5 58cc64a1a5 Round -pagezero_size up to a page as ld-prime does
__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]>
2026-09-30 21:50:36 +09:00
Rui UeyamaandClaude Opus 5.5 c59e8f9285 Accept -deduplicate
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]>
2026-09-30 21:47:16 +09:00
Rui UeyamaandClaude Opus 5.5 dcb32887f5 Give -segprot's and the obsolete-option warnings as ld-prime reads them
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]>
2026-09-30 21:39:30 +09:00
Rui UeyamaandClaude Opus 5.5 0bfebe7273 Refuse an image with no dylib before looking for dyld_stub_binder
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]>
2026-09-30 21:39:03 +09:00
Rui UeyamaandClaude Opus 5.5 49d0e15e63 Refuse an empty -segprot argument and check its letters bytewise
-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]>
2026-09-30 21:39:03 +09:00
Rui UeyamaandClaude Opus 5.5 2c315e198e Give -alias_list's warning once, silenced only by a -w before it
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]>
2026-09-30 21:39:02 +09:00
Rui UeyamaandClaude Opus 5.5 241e5b82ce Report a malformed __eh_frame in ld-prime's words
__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]>
2026-09-30 21:38:43 +09:00
Rui UeyamaandClaude Opus 5.5 2e140196b1 Take a non-extern subtraction pair in __eh_frame
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]>
2026-09-30 21:38:43 +09:00
Rui UeyamaandClaude Opus 5.5 042396599a Give a non-PIE executable no __DATA_CONST by default
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]>
2026-09-30 21:31:18 +09:00
Rui UeyamaandClaude Opus 5.5 74c2ffcce1 Count image offsets from the image's own address
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]>
2026-09-30 21:31:18 +09:00
Rui UeyamaandClaude Opus 5.5 6ae545943a Write library ordinal 0 into a flat-namespace image's imports
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]>
2026-09-30 21:31:18 +09:00
Rui UeyamaandClaude Opus 5.5 1a2a092037 Ignore -image_base of an executable with chained fixups
-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]>
2026-09-30 21:30:58 +09:00
Rui UeyamaandClaude Opus 5.5 7796c03517 Give a non-PIE executable classic dyld info by default
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]>
2026-09-30 21:30:58 +09:00
Rui UeyamaandClaude Opus 5.5 08cdeffcfb Treat -undefined options as ld-prime does
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]>
2026-09-30 21:30:58 +09:00
Rui UeyamaandClaude Opus 5.5 99e6bb5e8a Honor -no_fixup_chains under -static
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]>
2026-09-30 21:30:58 +09:00
Rui UeyamaandClaude Opus 5.5 c7afd158fc Warn when a rename mixes zero-fill and file-backed sections
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]>
2026-09-30 21:27:05 +09:00
Rui UeyamaandClaude Opus 5.5 a1cbcc2e65 Rename synthesized sections and the header's segment too
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]>
2026-09-30 21:27:05 +09:00
Rui UeyamaandClaude Opus 5.5 f2dc895993 Name output sections as ld-prime does under -rename_section
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]>
2026-09-30 21:27:05 +09:00
Rui UeyamaandClaude Opus 5.5 ad672cea6a Fill each symbol table range and its names in one parallel pass
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]>
2026-09-30 21:20:51 +09:00
Rui UeyamaandClaude Opus 5.5 ed1fc5d751 Inline the stab writers' helpers and reuse a function's address
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]>
2026-09-30 21:20:51 +09:00
Rui UeyamaandClaude Opus 5.5 293db008e0 Write each object's stabs straight into the output, as mold-rust does
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]>
2026-09-30 21:20:51 +09:00
Rui UeyamaandClaude Opus 5.5 86190d005b Compare section names in place when planning symbols and stabs
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]>
2026-09-30 21:20:51 +09:00
Rui UeyamaandClaude Opus 5.5 9144b3a556 Plan a symbol's stabs as the symbol, and expand them when written
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]>
2026-09-30 21:20:51 +09:00
Rui UeyamaandClaude Opus 5.5 2a2c15603f Look up only aliased addresses among the strong externals
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]>
2026-09-30 21:20:51 +09:00
Rui UeyamaandClaude Opus 5.5 0d34a140aa Plan every symbol table range before allocating its arrays
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]>
2026-09-30 21:20:51 +09:00
Rui UeyamaandClaude Opus 5.5 d02d6ffdc0 Fill the symbol table's locals, externals and imports in parallel
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]>
2026-09-30 21:20:50 +09:00
Rui UeyamaandClaude Opus 5.5 1d2025f8bb Write each symbol's string with its entry, without a string list
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]>
2026-09-30 21:20:50 +09:00
Rui UeyamaandClaude Opus 5.5 463766b2a2 List each unit's stabs in symbol-table order, not by address
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]>
2026-09-30 21:20:50 +09:00
Rui UeyamaandClaude Opus 5.5 29836c3f9c Record the symbol each stab names while writing the stabs
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]>
2026-09-30 21:20:50 +09:00
Rui UeyamaandClaude Opus 5.5 14208fd3bb Align initializer pointers and CFStrings to a pointer
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]>
2026-09-30 21:19:50 +09:00
Rui UeyamaandClaude Opus 5.5 bcaf632339 Drop a weak definition's other copies whatever their sizes
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]>
2026-09-30 21:19:30 +09:00
Rui UeyamaandClaude Opus 5.5 569342043f Write a coalesced-away weak copy's data-in-code entries nowhere
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]>
2026-09-30 21:19:30 +09:00
Rui UeyamaandClaude Opus 5.5 1f0d9238ee Reject options the output kind has no use for
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]>
2026-09-30 21:19:30 +09:00
Rui UeyamaandClaude Opus 5.5 1e7a8c9136 Split do_resolve into the steps of a resolution round
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]>
2026-09-30 21:13:30 +09:00
Rui UeyamaandClaude Opus 5.5 24cacae4d1 Split icf_sections into mold's ICF steps
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]>
2026-09-30 21:13:19 +09:00
Rui UeyamaandClaude Opus 5.5 a1bb46d2c3 Split dead_strip into mold's gc-sections passes
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]>
2026-09-30 21:13:07 +09:00
Rui UeyamaandClaude Opus 5.5 ab4ffce07d Leave SG_READ_ONLY off __DATA_CONST in the shared region
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]>
2026-09-30 21:11:52 +09:00
Rui UeyamaandClaude Opus 5.5 d6ab07f764 Link kernel extensions with -kext (MH_KEXT_BUNDLE)
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]>
2026-09-30 21:11:52 +09:00
Rui UeyamaandClaude Opus 5.5 41e1be8ef3 Order __DATA as ld-prime does when there is no __DATA_CONST
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]>
2026-09-30 21:09:57 +09:00
Rui UeyamaandClaude Opus 5.5 08c9801fc9 Accept -kernel, which makes a -static image a slidable kernel
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]>
2026-09-30 21:09:57 +09:00
Rui UeyamaandClaude Opus 5.5 64de2a9e23 Warn about static initializers in a dylib bound for the shared cache
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]>
2026-09-30 21:09:21 +09:00
Rui UeyamaandClaude Opus 5.5 55fe59f3b8 Lay out an image bound for the dyld shared cache as ld-prime does
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]>
2026-09-30 21:08:46 +09:00
Rui UeyamaandClaude Opus 5.5 1ea47c57a5 Record ld-prime's LC_SEGMENT_SPLIT_INFO entries on both targets
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]>
2026-09-30 21:05:48 +09:00
Rui UeyamaandClaude Opus 5.5 5cf637cc38 Spell the split-seg-info option -add_split_seg_info as ld64 does
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]>
2026-09-30 21:04:02 +09:00
Rui UeyamaandClaude Opus 5.5 46ba17f7d6 Refuse a dynamic image that links no dylib
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]>
2026-09-30 20:59:12 +09:00
Rui UeyamaandClaude Opus 5.5 035f675693 Ignore -image_base in a -r link without the PIE warning
-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]>
2026-09-30 20:59:12 +09:00
Rui UeyamaandClaude Opus 5.5 fbdce06587 Refuse -dead_strip with -r
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]>
2026-09-30 20:59:12 +09:00
Rui UeyamaandClaude Opus 5.5 5530c619d5 Take -no_dead_strip_inits_and_terms for -dead_strip
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]>
2026-09-30 20:59:12 +09:00
Rui UeyamaandClaude Opus 5.5 f2e0f1ee1f End __unwind_info's index at the last function's end
__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]>
2026-09-30 20:59:12 +09:00
Rui UeyamaandClaude Opus 5.5 fd05def00f Ignore CIE augmentation letters that carry no data
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]>
2026-09-30 20:59:12 +09:00
Rui UeyamaandClaude Opus 5.5 9a22dd5aed Take -segprot letters in either case and warn about unknown ones
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]>
2026-09-30 20:58:36 +09:00
Rui UeyamaandClaude Opus 5.5 b4bd872ee5 Let -w and -fatal_warnings govern the option checks' warnings
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]>
2026-09-30 20:58:36 +09:00
Rui UeyamaandClaude Opus 5.5 380f02ac20 Find a Swift class's metaclass at the class_t's own offset
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]>
2026-09-30 20:56:21 +09:00
Rui UeyamaandClaude Opus 5.5 aa7dba0c1e Say what integrate_objects' per-object symbol maps hold
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]>
2026-09-30 20:50:20 +09:00
Rui UeyamaandClaude Opus 5.5 2f44e342cc Document what a staged object's subsection and unwind fields hold
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]>
2026-09-30 20:50:20 +09:00
Rui UeyamaandClaude Opus 5.5 d26522e17c Rebase a staged object's indices in one place for both integrate paths
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]>
2026-09-30 20:50:20 +09:00
Rui UeyamaandClaude Opus 5.5 20fc1de073 Parse an object's unwind info in methods on the staged object
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]>
2026-09-30 20:50:20 +09:00
Rui UeyamaandClaude Opus 5.5 4333a8a951 Stage an object in phases, as methods on the object being built
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]>
2026-09-30 20:50:20 +09:00
Rui UeyamaandClaude Opus 5.5 a336be29e3 Read an object's load commands and symbol table in their own functions
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]>
2026-09-30 20:50:20 +09:00
Rui UeyamaandClaude Opus 5.5 e0f6ee0a5d Run the -O test's program once and compare the other links
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]>
2026-09-30 20:50:15 +09:00
Rui UeyamaandClaude Opus 5.5 5d830366f4 Resolve a rewritten method list's references with objc_ref_addr
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]>
2026-09-30 20:48:52 +09:00
Rui UeyamaandClaude Opus 5.5 212c48a11b Split the relative method-list conversion into its phases
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]>
2026-09-30 20:48:52 +09:00
Rui UeyamaandClaude Opus 5.5 592a62457f Split category merging into the phases it runs
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]>
2026-09-30 20:48:52 +09:00
Rui UeyamaandClaude Opus 5.5 d26484aab5 Make the Objective-C passes' synthetic subsections through helpers
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]>
2026-09-30 20:48:52 +09:00
Rui UeyamaandClaude Opus 5.5 467ec7ff68 Put the Objective-C metadata model ahead of the passes that use it
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]>
2026-09-30 20:48:52 +09:00
Rui UeyamaandClaude Opus 5.5 88b89b63b9 Move the Objective-C passes into their own module
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]>
2026-09-30 20:48:52 +09:00
Rui UeyamaandClaude Opus 5.5 33a0bd3521 Drop a hint that spans a symbol in an object without subsections
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]>
2026-09-30 20:48:52 +09:00
Rui UeyamaandClaude Opus 5.5 ba4ae15e47 Keep arm64 linker optimization hints in -r output
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]>
2026-09-30 20:36:08 +09:00
Rui Ueyama 1a694020fe Fix clippy warnings
Three of them came from a5511c1a7. CI doesn't run clippy.
2026-09-30 20:35:28 +09:00
Rui Ueyama fcd61c6e57 Don't read an instruction word at SPARC data relocations
apply_reloc_alloc read the 4-byte instruction at every relocation to
extract its register fields, so a 1- or 2-byte data relocation such as
R_SPARC_8 or R_SPARC_DISP16 in the last bytes of a section panicked.
Read the fields only for the relocations that rewrite an instruction.
2026-09-30 20:35:07 +09:00
Rui UeyamaandClaude Opus 5.5 db88c922db Reject flat lookups in a shared-cache-eligible dylib
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]>
2026-09-30 20:27:03 +09:00
Rui UeyamaandClaude Opus 5.5 aea7fa3fd5 Allow no undefined symbol in a -static image
-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]>
2026-09-30 20:27:03 +09:00
Rui UeyamaandClaude Opus 5.5 d2cebb4d95 Keep __mod_init_func in a -static image unless -init_offsets
__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]>
2026-09-30 20:27:03 +09:00
Rui UeyamaandClaude Opus 5.5 6763dafbfe Honor -fixup_chains under -static
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]>
2026-09-30 20:27:03 +09:00
Rui UeyamaandClaude Opus 5.5 d31ea906c1 Relax GOT loads of a -static image's weak definitions
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]>
2026-09-30 20:27:03 +09:00
Rui UeyamaandClaude Opus 5.5 aafb1bd031 Let Apple's ld leave x86_64 test binaries unsigned
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]>
2026-09-30 20:20:45 +09:00
Rui UeyamaandClaude Opus 5.5 a9ec4f0007 Collect LTO's native references in parallel
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]>
2026-09-30 20:20:45 +09:00
Rui UeyamaandClaude Opus 5.5 1991c41c92 Move dir_of's doc comment back above dir_of
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]>
2026-09-30 20:16:18 +09:00
Rui UeyamaandClaude Opus 5.5 bd86c00fdd Use ld64's shared-region eligibility for every shared-cache rule
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]>
2026-09-30 20:00:11 +09:00
Rui UeyamaandClaude Opus 5.5 88e78df63c Apply arm64 linker optimization hints as ld64 does
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]>
2026-09-30 19:54:51 +09:00
Rui UeyamaandClaude Opus 5.5 b8eca56320 Accept and ignore any -O option
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]>
2026-09-30 19:53:38 +09:00
Rui UeyamaandClaude Opus 5.5 5e6e87a54b Drop the -objc_category_merging option ld64 doesn't have
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]>
2026-09-30 19:49:51 +09:00
Rui UeyamaandClaude Opus 5.5 b692c263dd Keep a labeled literal apart and keep its label in -r output
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]>
2026-09-30 19:37:47 +09:00
Rui UeyamaandClaude Opus 5.5 29d93e33ca Mark only an init list atom's name no-dead-strip in -r output
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]>
2026-09-30 19:37:47 +09:00
Rui UeyamaandClaude Opus 5.5 c5a51caac9 List -r relocations atom by atom in descending offset order
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]>
2026-09-30 19:37:47 +09:00
Rui UeyamaandClaude Opus 5.5 7156c46397 Sort a merged relative method list by its name as ld-prime does
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]>
2026-09-30 19:24:28 +09:00
Rui UeyamaandClaude Opus 5.5 8a21d55893 Keep a class's rewritten ro record where the input had it
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]>
2026-09-30 19:24:28 +09:00
Rui UeyamaandClaude Opus 5.5 9c41e68a70 Warn about the __LD sections ld-prime doesn't know
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]>
2026-09-30 19:19:15 +09:00
Rui UeyamaandClaude Opus 5.5 2caa618da3 Warn about an unused bundle loader, in its command-line place
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]>
2026-09-30 19:19:15 +09:00
Rui UeyamaandClaude Opus 5.5 c503d99d7a Number chained imports and name them as ld-prime does
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]>
2026-09-30 19:18:22 +09:00
Rui UeyamaandClaude Opus 5.5 883a4bc3c6 Lay out the ad-hoc code signature as ld-prime does
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]>
2026-09-30 19:14:43 +09:00
Rui UeyamaandClaude Opus 5.5 521a3b1923 Refuse arm64 -static -pie links whose fixups ld-prime can't express
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]>
2026-09-30 19:14:43 +09:00
Rui UeyamaandClaude Opus 5.5 1d4545c0e9 Leave LC_UUID out under -no_uuid, as ld-prime does
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]>
2026-09-30 19:11:17 +09:00
Rui UeyamaandClaude Opus 5.5 e5fb25ad92 Spell the local-symbol keep list -non_global_symbols_no_strip_list
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]>
2026-09-30 18:08:31 +09:00
Rui UeyamaandClaude Opus 5.5 e2e09f540f Encode classic binds in ld64's order and compressed form
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]>
2026-09-30 18:07:20 +09:00
Rui UeyamaandClaude Opus 5.5 f437446aaa Write 4 under an arm64 GOT-relative pointer in -r output
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]>
2026-09-30 18:02:54 +09:00
Rui UeyamaandClaude Opus 5.5 cc024a5ea6 Write reloff 0 for a -r section without relocations
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]>
2026-09-30 17:58:57 +09:00
Rui UeyamaandClaude Opus 5.5 2b06c7c424 List every file the link loads under -t, as ld-prime does
-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]>
2026-09-30 17:27:06 +09:00
Rui UeyamaandClaude Opus 5.5 77bfc9bd02 Refuse a shared-cache dylib that links a library outside the cache
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]>
2026-09-30 17:21:58 +09:00
Rui UeyamaandClaude Opus 5.5 df245d3214 Name an arm64 -r output's literal records as ld-prime does
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]>
2026-09-30 16:55:54 +09:00
Rui UeyamaandClaude Opus 5.5 17825c7b3c Keep a -r output's linker-private labels as ld-prime does
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]>
2026-09-30 16:55:54 +09:00
Rui UeyamaandClaude Opus 5.5 a13a284d39 Mark -r symbols of initializer lists N_NO_DEAD_STRIP
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]>
2026-09-30 16:55:54 +09:00
Rui UeyamaandClaude Opus 5.5 6b3a413a55 Keep N_WEAK_DEF on a weak private external a -r output demotes
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]>
2026-09-30 16:55:54 +09:00
Rui UeyamaandClaude Opus 5.5 9381d0a48a Lay out a -r output's symbol table as ld-prime does
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]>
2026-09-30 16:55:54 +09:00
Rui UeyamaandClaude Opus 5.5 70f93f70a6 Write no stabs for exception tables' labels or ivar offsets
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]>
2026-09-30 16:55:54 +09:00
Rui UeyamaandClaude Opus 5.5 f6b10616a0 Keep N_WEAK_DEF on a weak private external made local
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]>
2026-09-30 16:55:54 +09:00
Rui UeyamaandClaude Opus 5.5 e6aca6c060 Lay the string table out in ld-prime's order
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]>
2026-09-30 16:55:54 +09:00
Rui UeyamaandClaude Opus 5.5 13f6357706 Stamp an archive member's N_OSO with its ar header's time
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]>
2026-09-30 16:55:54 +09:00
Rui UeyamaandClaude Opus 5.5 c304dac86d Order each unit's stabs by address, as ld-prime does
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]>
2026-09-30 16:55:54 +09:00
Rui UeyamaandClaude Opus 5.5 b308445199 List a final image's locals by address, before its stabs
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]>
2026-09-30 16:55:54 +09:00
Rui UeyamaandClaude Opus 5.5 50feb060a8 Lay out __unwind_info's pages and index as ld-prime does
__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]>
2026-09-30 16:34:49 +09:00
Rui UeyamaandClaude Opus 5.5 e18b24eeef Merge identical __unwind_info entries across padding
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]>
2026-09-30 16:34:49 +09:00
Rui UeyamaandClaude Opus 5.5 18eb7f003f Give code without unwind info its own __unwind_info entry
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]>
2026-09-30 16:34:49 +09:00
Rui UeyamaandClaude Opus 5.5 00f6b7bdae Warn about unused dylibs linked into a shared-cache dylib
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]>
2026-09-30 16:34:49 +09:00
Rui UeyamaandClaude Opus 5.5 69cafb5694 Name inputs by real path in -why_load, -why_live and version warnings
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]>
2026-09-30 16:34:49 +09:00
Rui UeyamaandClaude Opus 5.5 2bb4ae9b09 Link on past a missing -order_file or -alias_list with a warning
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]>
2026-09-30 16:34:49 +09:00
Rui UeyamaandClaude Opus 5.5 965405b17c Report missing libraries and unreadable inputs in ld-prime's words
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]>
2026-09-30 16:34:49 +09:00
Rui UeyamaandClaude Opus 5.5 12ff77994f Lay out the export trie root first, then the rest in post-order
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]>
2026-09-30 16:34:49 +09:00
Rui UeyamaandClaude Opus 5.5 f4815bad24 Leave ld-prime's free space after the load commands
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]>
2026-09-30 16:32:02 +09:00
Rui UeyamaandClaude Opus 5.5 841b4899a1 Rank a final image's sections the way ld-prime 27037 does
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]>
2026-09-30 16:32:02 +09:00
Rui UeyamaandClaude Opus 5.5 29ced21912 Order -r output sections the way ld-prime 27037 does
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]>
2026-09-30 16:32:02 +09:00
Rui UeyamaandClaude Opus 5.5 8a8fe30d1b Give the objc stubs a GOT slot of their own for _objc_msgSend
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]>
2026-09-30 16:20:07 +09:00
Rui UeyamaandClaude Opus 5.5 00487284b7 Give only lazily bound stubs a lazy pointer and helper entry
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]>
2026-09-30 16:20:07 +09:00
Rui UeyamaandClaude Opus 5.5 4c99cf0d71 Lay out stubs and GOT slots in ld-prime's order
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]>
2026-09-30 16:19:35 +09:00
Rui UeyamaandClaude Opus 5.5 e466560ad2 Align x86-64 stub helper entries to 4 bytes, as ld-prime does
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]>
2026-09-30 16:07:43 +09:00
Rui UeyamaandClaude Opus 5.5 97f13261df Write chained rebases as image offsets for macOS 12 targets
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]>
2026-09-30 16:07:43 +09:00
Rui UeyamaandClaude Opus 5.5 ba1af89ed0 Order segments by -segment_order as ld-prime does
-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]>
2026-09-30 15:58:50 +09:00
Rui UeyamaandClaude Opus 5.5 f6163ad325 Apply -segprot as ld-prime does: first wins, arm64 max is init
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]>
2026-09-30 15:58:50 +09:00
Rui UeyamaandClaude Opus 5.5 6ebe83eccb Honor -image_base only for images that stay where they are linked
-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]>
2026-09-30 15:58:50 +09:00
Rui UeyamaandClaude Opus 5.5 d7c787a39c Link no dylib into a -static image
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]>
2026-09-30 15:58:50 +09:00
Rui UeyamaandClaude Opus 5.5 1ba8245228 Keep a -static image's constant data in __DATA
__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]>
2026-09-30 15:58:38 +09:00
Rui UeyamaandClaude Opus 5.5 da424858f8 Unwind a -static image by its __eh_frame, without __unwind_info
__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]>
2026-09-30 15:58:38 +09:00
Rui UeyamaandClaude Opus 5.5 72f103ff4c Leave a -static image unsigned unless -adhoc_codesign asks
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]>
2026-09-30 15:58:38 +09:00
Rui UeyamaandClaude Opus 5.5 9885544891 Leave the code tables and build version out of a -static image
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]>
2026-09-30 15:58:38 +09:00
Rui UeyamaandClaude Opus 5.5 e97799bb6c Slide a -static -pie image by local relocations
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]>
2026-09-30 15:58:25 +09:00
Rui UeyamaandClaude Opus 5.5 5e87ef1f47 Write no dyld tables into a -static image
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]>
2026-09-30 15:58:06 +09:00
Rui UeyamaandClaude Opus 5.5 8b303ad905 Start a -static image from LC_UNIXTHREAD instead of LC_MAIN
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]>
2026-09-30 15:58:06 +09:00
Rui UeyamaandClaude Opus 5.5 77dc6913d7 Relax a folded classref only if its adrps pair with one use each
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]>
2026-09-30 15:53:44 +09:00
Rui UeyamaandClaude Opus 5.5 ad0ed3a907 Sort objc stubs by selector and let their slots absorb inputs'
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]>
2026-09-30 15:53:44 +09:00
Rui UeyamaandClaude Opus 5.5 6f5b050934 Point stub selector references at an input's name string
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]>
2026-09-30 15:53:44 +09:00
Rui UeyamaandClaude Opus 5.5 5cbfb35933 Drop the warning about app-extension-unsafe dylibs
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]>
2026-09-30 15:50:57 +09:00
Rui UeyamaandClaude Opus 5.5 74dd768c33 Honor a library's allowable clients as ld-prime does
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]>
2026-09-30 15:50:56 +09:00
Rui UeyamaandClaude Opus 5.5 42891aa443 Warn about a missing re-exported library in ld-prime's words
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]>
2026-09-30 15:50:56 +09:00
Rui UeyamaandClaude Opus 5.5 639abbb20b Look a re-exported library up by its leaf name in the search path
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]>
2026-09-30 15:50:56 +09:00
Rui UeyamaandClaude Opus 5.5 be073e5672 Bind to a private re-export's library when it is linked itself
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]>
2026-09-30 15:50:56 +09:00
Rui UeyamaandClaude Opus 5.5 d5d9489956 Reject mixing export lists, unexport lists and -no_exported_symbols
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]>
2026-09-30 15:40:08 +09:00
Rui UeyamaandClaude Opus 5.5 0ec4cab282 Re-export a dylib's symbols an export list names
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]>
2026-09-30 15:40:08 +09:00
Rui UeyamaandClaude Opus 5.5 884f8c4940 Treat an export list's plain names as initial undefines
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]>
2026-09-30 15:40:08 +09:00
Rui UeyamaandClaude Opus 5.5 96687168a1 Keep a live-support atom only if it references a live atom
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]>
2026-09-30 15:40:08 +09:00
Rui UeyamaandClaude Opus 5.5 7f6684a979 Warn about newer-OS inputs to -r against the first object's target
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]>
2026-09-30 15:30:27 +09:00
Rui UeyamaandClaude Opus 5.5 9d82bbacb5 Write LC_VERSION_MIN_MACOSX for x86-64 targets before macOS 10.14
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]>
2026-09-30 15:30:27 +09:00
Rui UeyamaandClaude Opus 5.5 bc90359088 Default x86-64 dylibs and bundles to chained fixups from macOS 12
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]>
2026-09-30 15:22:17 +09:00
Rui UeyamaandClaude Opus 5.5 8f66e863aa Warn once about repeated library options, each as spelled
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]>
2026-09-30 15:20:23 +09:00
Rui UeyamaandClaude Opus 5.5 5921d7d468 Let every naming of a dylib count, not only the first
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]>
2026-09-30 15:18:40 +09:00
Rui UeyamaandClaude Opus 5.5 06e65aa5a0 Pass an input's option flags to collect_file as a ReaderContext
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]>
2026-09-30 15:16:21 +09:00
Rui UeyamaandClaude Opus 5.5 0136323358 Keep only the split-info constants in use, with the other Mach-O ones
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]>
2026-09-30 15:00:58 +09:00
Rui UeyamaandClaude Opus 5.5 3af7fe4e9b Place a symbol by its own section, not by address alone
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]>
2026-09-30 14:59:45 +09:00
Rui UeyamaandClaude Opus 5.5 e32def557f Look for references to undefined symbols only when there are some
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]>
2026-09-30 14:56:31 +09:00
Rui UeyamaandClaude Opus 5.5 ee370a5f30 Keep arm64 executables PIE under -no_pie, as ld-prime does
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]>
2026-09-30 14:52:43 +09:00
Rui Ueyama 6c3d077149 Merge pull request #34 from opendarwin-project/main
Add kernel linking support
2026-09-30 14:46:04 +09:00
Rui Ueyama a2d545d456 Merge pull request #32 from bitemyapp/perf/header-once
Build the Mach-O header once and check its flags in parallel
2026-09-30 13:54:00 +09:00
Chris AllenandClaude Opus 5.5 2d53afa13c Build the Mach-O header once and check its flags in parallel
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]>
2026-09-30 13:51:09 +09:00
Rui Ueyama 655e4460ca Merge pull request #31 from bitemyapp/perf/sha2-0.11
Hash with the SHA-256 instructions on Apple silicon too
2026-09-30 13:48:56 +09:00
Rui Ueyama 36c3746cfd Merge pull request #30 from bitemyapp/parity/26-autolink-reexport
Treat an auto-linked library already loaded as a re-export as a hint
2026-09-30 13:44:45 +09:00
Rui Ueyama 3a8c2d7247 Merge pull request #29 from bitemyapp/parity/25-r-gcc-except-tab
Keep __gcc_except_tab after the other __TEXT sections in a -r output
2026-09-30 13:35:14 +09:00
Rui Ueyama f2ecd3aa87 Merge pull request #28 from bitemyapp/parity/24-x86-64-method-lists
Convert method lists in x86-64 dylibs and merge absolute lists into __objc_data
2026-09-30 13:30:43 +09:00
Rui UeyamaandClaude Opus 5.5 bdcda39db9 Byte-align x86-64 stubs only when every stub is lazily bound
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]>
2026-09-30 13:25:57 +09:00
Rui Ueyama be9cc04abf Merge pull request #27 from bitemyapp/parity/23-x86-64-stub-align
Align x86-64 stubs by whether the lazy-binding helper exists
2026-09-30 13:23:46 +09:00
Rui UeyamaandClaude Opus 5.5 cae7db068f Ignore an empty section with no atom-naming symbol per input section
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]>
2026-09-30 13:17:56 +09:00
Rui UeyamaandClaude Opus 5.5 9524d32a22 Resolve a non-extern relocation by its section ordinal
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]>
2026-09-30 13:17:56 +09:00
Rui Ueyama 412725532f Merge pull request #26 from bitemyapp/parity/22-empty-sections
Make no output section from an empty section that defines no symbol
2026-09-30 13:11:58 +09:00
Chris AllenandClaude Opus 5.5 1e65e0cd97 Make no output section from an empty section that defines no symbol
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]>
2026-09-30 13:09:25 +09:00
Rui UeyamaandClaude Opus 5.5 bc4ced43b8 Put the umbrella commands after LC_RPATH as ld-prime does
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]>
2026-09-30 13:07:18 +09:00
Rui UeyamaandClaude Opus 5.5 1a21f22357 Flag a segment of only -sectcreate contents SG_NORELOC
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]>
2026-09-30 13:06:18 +09:00
Rui Ueyama 78047514e8 Merge pull request #25 from bitemyapp/parity/21-umbrella-noreloc
Order the umbrella commands and flag an anchors-only segment as ld-prime does
2026-09-30 13:04:01 +09:00
Rui Ueyama acd91fb025 Merge pull request #24 from bitemyapp/parity/20-stub-compat-version
Carry a stub's compatibility version into the load command
2026-09-30 12:57:42 +09:00
Theo Paris bf31b3f345 Implement LC_SEGMENT_SPLIT_INFO v2 generation for kernel collection builder
Signed-off-by: Theo Paris <[email protected]>
Change-Id: Ib4e40380775c6ec1427e6c9efbd27d1e6a6a6964
2026-09-29 20:45:39 -07:00
Theo Paris c6eced6322 Add kernel linking support
Signed-off-by: Theo Paris <[email protected]>
Change-Id: I9435135cdcadf1f215e3feb0aabee8776a6a6964
2026-09-29 20:45:39 -07:00
Rui Ueyama 0e8a9c3e0b Merge pull request #23 from bitemyapp/parity/19-reexport-public
Bind to a re-exported public library directly, inlined stub or not
2026-09-30 12:42:17 +09:00
Rui Ueyama 0a0a7fd557 Merge pull request #22 from bitemyapp/parity/18-r-load-commands
Lay out a -r output's load commands as ld-prime does
2026-09-30 12:35:55 +09:00
Rui UeyamaandClaude Opus 5.5 3fe6d339dc Mark a -r output's symbols from no_dead_strip sections
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]>
2026-09-30 12:29:47 +09:00
Rui UeyamaandClaude Opus 5.5 12fd4ab752 Keep a -r output section's input flags as ld-prime does
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]>
2026-09-30 12:27:44 +09:00
Rui Ueyama 03c7a1efd1 Merge pull request #21 from bitemyapp/parity/17-r-objc-list-labels
Give the __objc_*list entries no symbol in a -r output
2026-09-30 12:21:36 +09:00
Rui Ueyama 8da9020a2c Merge pull request #20 from bitemyapp/parity/16-r-whole-section
Carry a whole-section object through -r as ld-prime does
2026-09-30 12:12:45 +09:00
Rui Ueyama 2e222a32e7 Merge pull request #19 from bitemyapp/parity/15-r-eh-frame
Write a -r output's __eh_frame as ld-prime does
2026-09-30 12:03:52 +09:00
Rui Ueyama 4d26beea7c Merge pull request #18 from bitemyapp/parity/14-section-layout
Lay sections out as ld-prime does
2026-09-30 11:59:37 +09:00
Rui Ueyama e694e4a8ea Merge pull request #17 from bitemyapp/parity/13-symtab
Match ld-prime's symbol table: live imports, absolute locals, export lists
2026-09-30 11:50:22 +09:00
Chris AllenandClaude Opus 5.5 4f5320d0a3 Match ld-prime's symbol table: live imports and absolute locals
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]>
2026-09-30 11:47:38 +09:00
Rui Ueyama c44a56adae Merge pull request #16 from bitemyapp/parity/12-x86-64-objc-stubs
Follow ld-prime's x86-64 Objective-C and stub layout
2026-09-30 11:39:59 +09:00
Rui UeyamaandClaude Opus 5.5 4716e17ed6 Load a weak-coalesced thread-local's descriptor through the GOT
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]>
2026-09-30 11:31:46 +09:00
Rui Ueyama c7d7687491 Merge pull request #15 from bitemyapp/parity/11-tlv-got
Give imported thread-locals a GOT slot instead of __thread_ptrs
2026-09-30 11:28:35 +09:00
Rui Ueyama 8776b4d68c Merge pull request #14 from bitemyapp/parity/10-swift-force-load
Write no fixup for Swift's __swift_FORCE_LOAD_$_ references
2026-09-30 11:24:18 +09:00
Rui UeyamaandClaude Opus 5.5 f678decba7 Never look up a relative -L or -F directory under the syslibroot
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]>
2026-09-30 11:18:30 +09:00
Rui Ueyama 01fc50c123 Merge pull request #13 from bitemyapp/parity/09-weak-framework-imports
Make every import from a weak-linked library a weak import
2026-09-30 11:16:00 +09:00
Rui UeyamaandClaude Opus 5.5 c86cf18352 Unweaken only the symbol that names a whole-section atom
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]>
2026-09-30 11:04:42 +09:00
Rui Ueyama 189749d254 Merge pull request #12 from bitemyapp/parity/08-whole-section-weak
Strip the weak flag from a whole-section atom's first symbol
2026-09-30 11:01:05 +09:00
Rui Ueyama 34d2471695 Merge pull request #11 from bitemyapp/parity/07-dyld-private
Name the stub helper's dyld word __dyld_private
2026-09-30 10:49:35 +09:00
Rui Ueyama cd387d6026 Merge pull request #10 from bitemyapp/parity/06-init-offsets
Lay out __init_offsets by deployment target, as ld-prime does
2026-09-30 10:44:10 +09:00
Rui UeyamaandClaude Opus 5.5 2a2146383a Keep LTO definitions that native commons or -alias bases reference
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]>
2026-09-30 10:40:47 +09:00
Rui Ueyama 8b5a5346ab Merge pull request #9 from bitemyapp/parity/05-lto-native-refs
Preserve LTO symbols only for references from native code
2026-09-30 10:39:02 +09:00
Rui Ueyama a1982ec46a Merge pull request #8 from bitemyapp/parity/04-libsystem-needed-all-load
Keep libSystem, need only the named dylibs, exempt libclang_rt from -all_load
2026-09-30 10:35:40 +09:00
Rui Ueyama d18d8aca41 Merge pull request #7 from bitemyapp/parity/03-dylib-version-codesign
Default dylib versions to 0.0.0 and sign arm64 output only
2026-09-30 10:09:39 +09:00
Rui UeyamaandClaude Opus 5.5 6670077219 Hide the symbols export lists leave out as private externs
-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]>
2026-09-30 09:44:25 +09:00
Rui UeyamaandClaude Opus 5.5 1a7b785d10 Import rayon's prelude once at the top of each file
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]>
2026-09-30 09:43:33 +09:00
Rui Ueyama a60b51eb78 Merge pull request #6 from bitemyapp/parity/02-header-flags
Match ld-prime's Mach-O header flags
2026-09-30 09:29:29 +09:00
Rui Ueyama abe11b94a8 Match print-dependencies' column separators with \s
Based on https://github.com/rui314/mold-macho/pull/5
2026-09-29 20:19:55 +09:00
Rui Ueyama f9828086ad Build on i686 hosts
i386 aligns 64-bit integers to 4 bytes, so Symbol and NameRecord are
smaller there than on other hosts, and the assertions that they are
exactly 48 and 16 bytes failed to compile. Assert upper bounds instead,
which still catch growth on 64-bit hosts, and check the i686 build in
CI.
2026-09-29 13:30:46 +09:00
Rui Ueyama 0ea3c18bc1 Build on hosts without 64-bit atomics
std has no AtomicU64 or AtomicI64 on hosts such as 32-bit PowerPC,
32-bit MIPS and pre-v6 ARM, so mold did not compile there, whereas the
C++ version fell back to libatomic. Take those two types from
portable-atomic, which uses the std types where they exist and falls
back to locks elsewhere. Also check the 32-bit PowerPC build in CI; the
existing armv7 check did not catch this because armv7 has 64-bit
atomics.

See https://github.com/rui314/mold/discussions/1643#discussioncomment-18603572.
2026-09-29 13:26:43 +09:00
Rui Ueyama a9c709b8a4 Merge pull request #1692 from bitemyapp/up/g23-deterministic-r-order
Order same-named output sections deterministically in -r output
2026-09-29 13:02:59 +09:00
Rui Ueyama 6b16418c6d Merge pull request #1691 from bitemyapp/up/g22-stale-comments
Remove stale comments and duplicated module docs
2026-09-29 12:59:19 +09:00
Rui Ueyama 04d6d800e0 Merge pull request #1690 from bitemyapp/up/g21-backend-endian
Use the shared endian helpers in the backends
2026-09-29 12:55:23 +09:00
Rui Ueyama 3e51fa1915 Merge pull request #1689 from bitemyapp/up/g19-test-runner
Give test scripts a closed stdin and return failure from run
2026-09-29 12:52:17 +09:00
Rui Ueyama ee6a2fa617 Merge pull request #1688 from bitemyapp/up/g18-link-entry-aliases
Name the link entry point's types once
2026-09-29 12:49:13 +09:00
Rui Ueyama 5d44b9f41d Restore the indexed loop in the glob NFA matcher 2026-09-29 12:45:05 +09:00
Rui Ueyama 427e9604ab Merge pull request #1687 from bitemyapp/up/g17-util-tidy
Tidy the utilities
2026-09-29 12:43:10 +09:00
Rui Ueyama 471142f238 Merge pull request #1686 from bitemyapp/up/g16-input-helpers
Remove duplicated helpers in the input layer
2026-09-29 12:28:17 +09:00
Rui Ueyama 8ea4678ba7 Revert two hunks from PR 1685
The .tbss layout loop gained a second end-of-list check next to the
existing one, for a case that cannot happen: .tbss is never the last
chunk. DigestMap::new was rewritten with repeat_with, which took five
lines to say what one line did. Restore both.

Follow-up to https://github.com/rui314/mold/pull/1685.
2026-09-29 12:00:33 +09:00
Rui Ueyama d947c52b3e Merge pull request #1685 from bitemyapp/up/g15-passes-loops
Simplify a few loops in the passes
2026-09-29 11:46:35 +09:00
Rui Ueyama 29d22ccdc7 Merge pull request #1684 from bitemyapp/up/g14-chunk-writers
Tidy the chunk writers
2026-09-29 11:34:03 +09:00
Rui Ueyama c1a509a3ff Drop FileRecord::size
FileRecord::size was size_of::<Self>(), and record sizes were written
both ways. Use size_of everywhere. Unlike the trait method, it is a
const fn, so it also works in the compile-time assertions.

https://github.com/rui314/mold/pull/1683 unified the spelling in the
other direction.
2026-09-29 11:06:35 +09:00
Rui Ueyama ee668f78a3 Use size_of and align_of from the prelude
Both have been in the prelude since Rust 1.80, so the std::mem:: prefix
is unnecessary.
2026-09-29 11:04:30 +09:00
Rui Ueyama 25d2a3ecf1 Report a failed link when SIGCHLD is ignored
An ignored SIGCHLD is inherited across exec, e.g. from `trap '' CHLD`
in a shell. The child that fork_child creates is then reaped
automatically, so the parent's waitpid fails with ECHILD. The parent
ignored that and read the untouched zero status as a successful exit,
so a link that failed exited with status 0. Report a failure instead.

Also read and write the completion byte through File, which removes
two raw libc calls.

Based on https://github.com/rui314/mold/pull/1682.
2026-09-28 23:09:54 +09:00
Rui Ueyama fd13e83077 Revert the from_hex and returns_etxtbsy changes from PR 1681
Both callers of from_hex already check that the byte is a hex digit,
so the new assertion was redundant. The utsname rewrite traded
MaybeUninit::assume_init for mem::zeroed without removing an unsafe
block, so restore the original code.
2026-09-28 22:27:50 +09:00
Rui Ueyama 9a4ad937d6 Merge pull request #1681 from bitemyapp/up/g11-cmdline-driver
Drop dead option arms and tighten two helpers
2026-09-28 20:14:08 +09:00
Rui Ueyama d17b876f6a Run abs-reloc-promotion.sh on targets other than x86-64
The fix it tests is target-independent, and only its last check was
x86-64-specific. Count the dynamic relocations against foo and bar
instead of looking for R_X86_64_64. PowerPC is skipped because GCC puts
the read-only pointers in writable sections there, so nothing is
promoted.
2026-09-28 20:04:42 +09:00
Rui Ueyama 3028b2c18c Accept a null symbol array from the LTO plugin
LLVMgold calls get_symbols with a null pointer and a zero count for an
IR file that has no symbols, e.g. one compiled from an empty source
file. slice::from_raw_parts does not accept a null pointer even for a
zero length, so the debug build aborted on its precondition check.
Use an empty slice for a zero count, in add_symbols as well.

Based on https://github.com/rui314/mold/pull/1680.
2026-09-28 20:01:17 +09:00
Chris AllenandClaude Opus 5.5 7f1b5647cc Report a thread pool failure as a fatal error
Building the thread pool fails when threads cannot be created, e.g.
under a low `ulimit -u`. That hit an expect, so mold aborted with a
panic message rather than reporting the error like other system
errors.

Taken from https://github.com/rui314/mold/pull/1678, which also
added checks for malformed inputs that are not included here.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-28 19:48:41 +09:00
Rui Ueyama 339e870f18 Merge pull request #1679 from bitemyapp/up/g09-map-symbol-atomics
Use atomics for the concurrent map's key length and Symbol's CAS loops
2026-09-28 19:48:36 +09:00
Rui Ueyama 3ce0175c15 Merge pull request #1675 from bitemyapp/up/g04-abs-reloc-hardening
Make absolute relocation classification deterministic and check record alignment at compile time
2026-09-28 18:43:19 +09:00
Rui Ueyama 516456132e Make --dependency-file and --repro output deterministic
Input files are opened in parallel, and each file is appended to the
file pool when it is opened, so the pool's order changed from run to
run. --dependency-file and --repro list files in that order. Return
the pool sorted by name instead.

The dependency-file tests expected a space after a.o, i.e. that a.o
was not the last dependency. That failed intermittently on Alpine,
where libc.so is not a linker script and no dependency reliably comes
after a.o.
2026-09-28 18:40:02 +09:00
Chris AllenandClaude Opus 5.5 00b398ffd2 Pass the relocation record to check_range
report_out_of_range looked the record up again through the file's
relocation table while apply_reloc_alloc held that table mutably, which
created a shared view of memory behind a live unique reference, and it
walked the CREL iterator from the start to do so. Every caller has the
record at hand, so pass it in. The loop indices that only served the
lookup are gone with it.

Cherry-picked from https://github.com/rui314/mold/pull/1674.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-28 18:07:27 +09:00
Chris AllenandClaude Opus 5.5 4eb5f294b1 Look up CIE relocations through the file on each use
CieRecord stored a shared slice over its .eh_frame relocation records,
but reattach_fragment_relocations later rewrites those records in place
through the private input mapping, and the FDE and CIE accessors read
through the stale slice afterwards. The record now keeps only the
relocation section index and reads the current table through
ObjectFile::relocations, as decoded CREL tables already did. The raw
relocation view no longer claims a 'static lifetime.

Cherry-picked from https://github.com/rui314/mold/pull/1674.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-28 18:07:27 +09:00
Rui Ueyama e5a4e9351a Merge pull request #1672 from bitemyapp/up/g01-parity-section-order
Match C++ mold's program header and synthetic symbol order
2026-09-28 17:26:14 +09:00
Rui Ueyama cb2004af8d Update mold.1 (automated commit) 2026-09-28 07:52:39 +00:00
Rui Ueyama 830a8609aa Read single-dash long options as GNU ld and lld do
mold accepts a multi-letter option with a single dash, but the option
chain is first-match, and some single-letter options were tried before
long options starting with the same letter. "-entry=main" was thus read
as "-e ntry=main", "-filter libf.so" as "-f ilter" followed by an input
file, and "-fix-cortex-a53-843419" left a bogus DT_AUXILIARY entry. GNU
ld and lld read all of these as the long option.

Move each single-letter option past the last long option it shadows.
Conversely, GNU ld and lld read "-execute-only" as "-e xecute-only" and
"-omagic" as "-o magic", because those names take double dashes only.
Spell such names with double dashes in the option table, which
match_option now honors, replacing the rule that all names starting
with "o" need two dashes. Where GNU ld and lld disagree, follow GNU ld.

Thanks to @lu-zero for investigating this issue and proposing a fix in
https://github.com/rui314/mold/pull/1670.

Fixes https://github.com/rui314/mold/issues/1671
2026-09-28 16:49:46 +09:00
Chris AllenandClaude Opus 5.5 95534c98a1 Carry a stub's compatibility version into the load command
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]>
2026-09-23 13:32:10 -05:00
Chris AllenandClaude Opus 5.5 2e4069b289 Give imported thread-locals a GOT slot instead of __thread_ptrs
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]>
2026-09-23 12:51:56 -05:00
Chris AllenandClaude Opus 5.5 632faf392e Treat an auto-linked library already loaded as a re-export as a hint
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]>
2026-09-23 12:10:01 -05:00
Chris AllenandClaude Opus 5.5 a5a51f1678 Bind to a re-exported public library directly, inlined stub or not
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]>
2026-09-23 12:09:28 -05:00
Chris AllenandClaude Opus 5.5 4930210555 Keep __gcc_except_tab after the other __TEXT sections in a -r output
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]>
2026-09-23 12:06:09 -05:00
Chris AllenandClaude Opus 5.5 4c6959bb76 Lay out a -r output's load commands as ld-prime does
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]>
2026-09-23 12:06:09 -05:00
Chris AllenandClaude Opus 5.5 719c9021ad Give the __objc_*list entries no symbol in a -r output
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]>
2026-09-23 12:06:09 -05:00
Chris AllenandClaude Opus 5.5 c1a7a7e903 Carry a whole-section object through -r as ld-prime does
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]>
2026-09-23 12:06:09 -05:00
Chris AllenandClaude Opus 5.5 317704eadf Write a -r output's __eh_frame as ld-prime does
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]>
2026-09-23 12:06:09 -05:00
Chris AllenandClaude Opus 5.5 c83051fafa Lay out __init_offsets by deployment target, as ld-prime does
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]>
2026-09-23 12:05:26 -05:00
Chris AllenandClaude Opus 5.5 6878d3d034 Default dylib versions to 0.0.0 and sign arm64 output only
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]>
2026-09-23 12:03:33 -05:00
Chris AllenandClaude Opus 5.5 757e561142 Order the umbrella commands and flag an anchors-only segment as ld-prime does
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]>
2026-09-23 12:03:28 -05:00
Chris AllenandClaude Opus 5.5 6eda447399 Lay sections out as ld-prime does
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]>
2026-09-23 12:02:10 -05:00
Chris AllenandClaude Opus 5.5 7e36ef7bf5 Name the stub helper's dyld word __dyld_private
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]>
2026-09-23 12:01:59 -05:00
Chris AllenandClaude Opus 5.5 97f4c07863 Preserve LTO symbols only for references from native code
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]>
2026-09-23 11:58:40 -05:00
Chris AllenandClaude Opus 5.5 3ea4d73c24 Convert method lists in x86-64 dylibs and merge absolute lists into __objc_data
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]>
2026-09-23 11:57:03 -05:00
Chris AllenandClaude Opus 5.5 7c6ffec979 Keep libSystem, need only the named dylibs, exempt libclang_rt from -all_load
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]>
2026-09-23 11:56:36 -05:00
Chris AllenandClaude Opus 5.5 5bcce0936b Write no fixup for Swift's __swift_FORCE_LOAD_$_ references
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]>
2026-09-23 11:55:02 -05:00
Chris AllenandClaude Opus 5.5 5038821005 Align x86-64 stubs by whether the lazy-binding helper exists
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]>
2026-09-23 11:54:53 -05:00
Chris AllenandClaude Opus 5.5 057b20ad44 Follow ld-prime's x86-64 Objective-C and stub layout
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]>
2026-09-23 11:52:49 -05:00
Chris AllenandClaude Opus 5.5 df5efafe5f Make every import from a weak-linked library a weak import
-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]>
2026-09-23 11:52:06 -05:00
Chris AllenandClaude Opus 5.5 973f7a10c6 Match ld-prime's Mach-O header flags
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]>
2026-09-23 11:51:15 -05:00
Chris AllenandClaude Opus 5.5 cb06dbf8d3 Strip the weak flag from a whole-section atom's first symbol
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]>
2026-09-23 11:50:06 -05:00
Chris AllenandClaude Opus 5.5 4cf64a175d Hash with the SHA-256 instructions on Apple silicon too
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]>
2026-09-23 11:49:09 -05:00
Rui Ueyama 6fc6e1916f Merge pull request #1673 from bitemyapp/up/g02-backend-reloc-fixes
Read CREL relocations of non-allocated sections on ARM32, PPC32, m68k and SH-4
2026-09-23 16:13:06 +09:00
Chris AllenandClaude Opus 5.5 13d2fa222c Tidy the utilities
Zip the glob NFA's three parallel arrays and fill a bracket range with
one slice fill, merge the two glob module headers left over from two C++
files, check SipHash's output width at compile time and split its input
into words with as_chunks, mark Timer must_use so a dropped one is a
compile-time warning, remove a Windows TODO from the C++ demangler that
never applied to this implementation, and fix a typo in a comment.

The merged glob header no longer lists qt_private_api_tag* as an
example of an Aho-Corasick pattern, since prefix patterns are matched
directly rather than by the Aho-Corasick matcher.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-22 23:29:26 -05:00
Chris AllenandClaude Opus 5.5 51e23d44d7 Promote symbols before classifying absolute relocations
Each output section promoted the imported symbols of its read-only
absolute relocations and classified its relocations in one pass, while
other sections ran in parallel. Whether a writable section's relocation
against the same symbol became a dynamic relocation therefore depended
on which section ran first, so the output changed from run to run. In a
non-PIE that references an imported variable and function from both
.rodata and .data, 37 of 100 links in one measurement emitted two extra
R_X86_64_64 dynamic relocations.

Collect all sections' relocations, promote, and only then classify, so
that the result is the same in every run: the word is resolved at link
time whenever a copy relocation or canonical PLT entry makes its address
known.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-22 22:16:38 -05:00
Chris AllenandClaude Opus 5.5 e874910ccb Simplify a few loops in the passes
The --undefined names were cloned only because get_symbol takes the
whole context mutably, so intern them through the symbol table instead.
The section-word reversal swaps whole words, the .tbss layout loop no
longer indexes past the last chunk, the has_lto_obj predicate replaces
an inline copy of itself, and the --exclude-libs ALL test is hoisted out
of the file loop. The dynamic symbol groups are extended in place, the
IRELATIVE count loses a temporary, and thunk addresses zip symbols with
their offsets.

In ICF, the digest no longer converts to little-endian on the way into
a hash whose key is random per run, the digest map is filled with
repeat_with, and HashMap is imported once.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-22 22:16:01 -05:00
Chris AllenandClaude Opus 5.5 7ec87c99ce Read decoded relocations for non-allocated sections on all targets
apply_reloc_nonalloc on ARM32, PPC32, m68k and SH-4 read the relocation
section of a non-allocated section as an array of ordinary records. A
CREL table of a non-allocated section is not decoded ahead of time, so
for an object with SHT_CREL relocations those bytes are compressed
records: linking a PPC32 or m68k object that Clang 19 built with -g and
-Wa,--crel panicked with a failed length assertion in
records_from_bytes. Use the relocation iterator, which decodes CREL
tables, as the other targets already do.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-22 22:15:45 -05:00
Chris AllenandClaude Opus 5.5 6d33903650 Order same-named output sections by their first member
A relocatable link keeps every COMDAT group's members in output
sections of their own, so an object with many groups of one name, such
as the .debug_macro groups GCC emits with -g3, produces output sections
whose sort keys tie. The tie fell back to the order in which the
parallel creation phase happened to make them, which changed from run
to run: 30 runs of the link in relocatable-compressed-debug-info.sh
produced 13 different outputs. Break the tie with the first member's
file priority and section index, which are in input order.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-22 22:12:47 -05:00
Chris AllenandClaude Opus 5.5 dd3aaef2ef Add PPC64 save/restore symbols after __start_ and __stop_ symbols
C++ mold adds the _savegpr0_* family after the __start_ and __stop_
symbols, and the internal object's symbol order is the order in the
output .symtab. Match it so that ppc64le outputs get the same .symtab,
and hence the same build ID, as C++ mold's.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-22 22:11:00 -05:00
Chris AllenandClaude Opus 5.5 a6c0d9de96 Emit PT_ARM_EXIDX and PT_RISCV_ATTRIBUTES after PT_GNU_RELRO
C++ mold defines these target-specific segments after PT_GNU_RELRO, but
the port defined them before PT_GNU_STACK. ARM and RISC-V outputs
therefore had their program headers in a different order than C++
mold's for the same input, and a different build ID. Define them where
C++ mold does.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-22 22:11:00 -05:00
Chris AllenandClaude Opus 5.5 a7828b5986 Tidy the chunk writers
The four places that write a target word through an IS_64 branch
(.got, .got.plt, .relr.dyn and an output section's absolute
relocations) now use the existing Word::<E>::new(value).write(slot).
The padding filler walks its buffer with chunks_exact_mut; thunk
symbols zip their two parallel vectors; relr_offsets concatenates its
shards once; the .ARM.exidx lookup uses find_map instead of position
plus a second match, and text_end borrows the section headers instead
of copying them; .gnu.hash looks each symbol's hash up once;
merged-section resolution takes the timers by destructuring instead of
cloning them; OutputSectionId::new folds its two checks into one; the
.sframe writer slices each FRE block by start and length; WORD_SIZE is
derived from the word type like IS_64; and ElfDyn derives what its
siblings derive.

The doc comment on ChunkHeader's local-symbol fields no longer starts
with a stray "For --gdb-index", and a few comment typos are fixed.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-22 22:07:38 -05:00
Chris AllenandClaude Opus 5.5 3f7da561ce Remove duplicated helpers in the input layer
shndx_at_in was identical to shndx_at, is_discarded_comdat re-implemented
is_discarded_comdat_sym, ObjectFile's Display duplicated display_file,
rels_mut looked its table up twice, find_null re-implemented a chunked
position search, the archive member name reader re-implemented cstr_at
and memchr, the file type probe indexed the header table it was
iterating, and the linker script reader spelled slice patterns as length
checks plus indexing.

The loop in is_dwarf32 now compares the end of the next unit header with
the section size instead of subtracting its start from the size, which
wrapped around when a unit's size field pointed past the end of a
malformed section.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-22 22:06:36 -05:00
Chris AllenandClaude Opus 5.5 50e0643cb1 Name the link entry point's types once
The command-line and result types of the per-target link functions were
spelled out in the twenty target crates, the executable and the driver.
Name them Cmdline and LinkResult in the driver and use the names
everywhere else.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-22 22:06:20 -05:00
Chris AllenandClaude Opus 5.5 9b50d69058 Check record alignment at compile time
The record views in elf.rs relied on a debug assertion, and therefore on
the FileRecord contract alone in release builds, for the alignment that
their pointer casts require. An inline const assertion is evaluated per
instantiation, so a record type that violates the contract no longer
compiles.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-22 22:06:04 -05:00
Chris AllenandClaude Opus 5.5 b4bed3505e Remove stale comments and duplicated module docs
Several comments compared the code to C++ mold's arena storage and
chunks, or named functions that do not exist in this implementation
(SharedFile<E>::mark_live_objects, Symbol<E>::get_addr and
open_file_impl); point them at the Rust functions or drop the
comparison. The ICF and thunks modules repeated their description in a
second module-level summary, and the TLS module docs had a stray title
line. drop_mappings did not say that it discards the relocation records
rewritten in place, and the madvise in write_build_id did not say why
dropping written pages of the output loses nothing. Also fix three
typos.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-22 22:04:32 -05:00
Chris AllenandClaude Opus 5.5 3bfc9b6d97 Keep pointer provenance in madvise_hugepage_interior
The page-aligned start of the interior range was turned back into a
pointer with an integer cast, which leaves it without the allocation's
provenance. Derive it from the caller's pointer with with_addr instead,
as the rest of the function already reads addresses with addr().

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-22 22:03:01 -05:00
Chris AllenandClaude Opus 5.5 1261269fd8 Use std's atomic helpers in Symbol
record_comdat_owner and set_visibility_bits hand-rolled fetch_min and
update. record_comdat_owner keeps its initial load so that a file that
does not lower the priority still leaves the cache line shared. The
packed visibility and flags bytes are no longer pub, since nothing
outside symbol.rs touches them and a direct store would corrupt the
state bits they share.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-22 22:02:42 -05:00
Chris AllenandClaude Opus 5.5 377abf91e0 Use the shared endian helpers in the backends
x86-64 and i386 defined their own little-endian write helpers with the
same bodies as write_ul16, write_ul32 and write_ul64 in util::endian,
and i386 read REL addends with inline from_le_bytes calls. Call the
shared functions directly, as the other fixed-endian targets do. The
ARM64 and RISC-V instruction accessors now delegate to them, like
LoongArch's already do.

Also drop an unused parameter of relax_tlsdesc_to_ie, a redundant mask
on SPARC relocation types (the record keeps the type in a byte of its
own), two TRAP comments that described C++ target structs instead of
the instruction, and the pub on two PPC64 ELFv1 functions that only
their module uses. SH-4 reports an unknown relocation with error! like
every other target, so that all of them are listed in one run.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-22 22:01:34 -05:00
Chris AllenandClaude Opus 5.5 48cae3a63a Make the concurrent map's key length an atomic
The key length was an UnsafeCell whose reads and write needed unsafe
blocks justified by the release/acquire pair of the key pointer. A
relaxed atomic expresses the same protocol, compiles to the same loads
and stores, and takes the probe comparisons out of unsafe code. Also
check the probe invariant at compile time and reject keys longer than
32 bits instead of truncating their lengths.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-22 22:00:47 -05:00
Chris AllenandClaude Opus 5.5 4348731de1 Tighten two helpers
driver is a public module, so `pub` made copy_chunks part of the
library's API although only the crate itself calls it; make it
pub(crate). from_hex now asserts its precondition in debug builds
instead of silently producing garbage for a non-hex byte.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-22 21:57:25 -05:00
Chris AllenandClaude Opus 5.5 8335147fe2 Drop dead option arms and simplify the uname call
Six flags in the ignored-for-compatibility chain were unreachable
because earlier arms already consume them: color-diagnostics,
eh-frame-hdr, fatal-warnings, enable-new-dtags, disable-new-dtags and
warn-once.

returns_etxtbsy zeroed a MaybeUninit<utsname> and then asserted it
initialized after calling uname. utsname consists of byte arrays, so a
zeroed one is already a valid value; start from that and drop the
assume_init step.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-22 21:57:24 -05:00
Chris AllenandClaude Opus 5.5 61fa2e17ca Give test scripts a closed stdin and return failure from run
The runner starts each script in its own process group, so a script
that read the terminal would stop there until the timeout. Give it
end-of-file instead.

run already returns ExitCode, so report setup errors through it
instead of exiting the process. Also drop the second sort of the
discovered scripts (make_jobs sorts the combined list), the unused
Clone on TestJob and the intermediate vector in the PATH search, and
give the unsafe kill a SAFETY comment like the other unsafe blocks.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-22 21:56:27 -05:00
Rui Ueyama 99d79c429b Don't allocate a GOT entry for GOT-base relocations
R_X86_64_GOTPC32, R_386_GOTPC, R_390_GOTPC and their variants compute
GOT + A - P, the distance to the start of the GOT section rather than
to an entry in it. They were grouped with the GOT-indirect relocations,
so every link that referenced the GOT base got an entry for
_GLOBAL_OFFSET_TABLE_ that nothing ever read, plus a RELATIVE
relocation to fill it in a PIE.
2026-09-21 16:00:33 +09:00
Rui Ueyama 7fe68bf109 Scan R_ARM_REL32 like other PC-relative relocations
R_ARM_REL32 was in the bucket of relocations that need no scanning,
so a reference to a symbol defined in a DSO was silently resolved as
if the symbol were at address zero. Route it through scan_pcrel like
PREL31 and MOVT_PREL, which gives imported data a copy relocation,
an imported function a canonical PLT entry, and a shared object an
error.
2026-09-21 15:57:25 +09:00
Rui Ueyama 659780e6c4 Scan GOT-relative relocations like PC-relative ones
R_X86_64_GOTOFF64 and its counterparts on i386, ARM32, s390x and SH4
compute S + A - GOT, which is a link-time constant only if the symbol
is in the output file. We didn't scan them at all, so a reference to a
symbol defined in a DSO was silently resolved as if the symbol were at
address zero. Route them through scan_pcrel, which already makes the
right decision: a copy relocation for imported data, a canonical PLT
entry for an imported function, and an error in a shared object.

Fixes https://github.com/rui314/mold/issues/1668
2026-09-21 15:57:25 +09:00
Rui Ueyama 254ccb339c Make is_absrel a Target method and skip those relocations everywhere 2026-09-21 15:20:19 +09:00
Rui Ueyama a5511c1a7b Replace pointer-and-assertion patterns with bounds-checked views
The port kept C++ idioms in which a raw pointer or an unchecked index
stood in for a slice, guarded by an assertion. Add SyncUnsafeCell, a
slice-of-cells view for parallel passes, and use it for the file
mapping, the symbol-table allocators, the symbol editor and decoded CREL
tables. Make the fields that parallel passes write atomic, which turns
allocate_aux, the dynsym pass, the gdb_index prefix scan and the COMDAT
signature slots into safe code. Write merged sections and the gdb_index
tables through split slices, and look up files and sections with plain
bounds checks.

This removes 58 unsafe blocks and 16 assertions.
2026-09-21 15:20:19 +09:00
Rui Ueyama 2d4a214d6e Merge pull request #1669 from nmoinvaz/perf/local-tests-first
Check a file's own symbol entry before looking the symbol up
2026-09-21 14:24:14 +09:00
Nathan Moin Vaziri c73791304f Check a file's own symbol entry before looking the symbol up
check_duplicate_symbols and check_symbol_types looked every reference
up in the symbol table, a cache miss each in a large link, before checks
on the file's own entry that skip it anyway. An undefined reference is
never a duplicate, and one without a type never mismatches.

Assisted-By: Claude <[email protected]>
2026-09-20 20:37:46 -07:00
Rui Ueyama 89a9b334e8 Merge pull request #1665 from nmoinvaz/perf/origin-tag-tests 2026-09-21 10:59:28 +09:00
Rui Ueyama c19c87988f Merge pull request #1664 from nmoinvaz/perf/parallel-got-dynrel-count 2026-09-21 10:59:06 +09:00
Rui Ueyama 15459ac587 Merge pull request #1666 from artemkulyk/aarch64-tlsle-ldst 2026-09-21 09:53:35 +09:00
Rui Ueyama 8afbeb85e3 Merge pull request #1663 from nmoinvaz/perf/cached-symbol-type
Check symbol types without loading the owner's symbol entry
2026-09-21 09:21:08 +09:00
Rui Ueyama c2e3a64db0 Merge pull request #1662 from nmoinvaz/perf/hoist-loop-invariant-reloc-reads
Read the loop-invariant relocation addresses once
2026-09-21 08:38:56 +09:00
artemkulyk a4b64d86b4 Fix AArch64 TLSLE LDST relocations
R_AARCH64_TLSLE_LDST{8,16,32,64,128}_TPREL_LO12_NC were unhandled, so
objects using them failed to link with "unknown relocation".  The immediate
is the local-exec TLS offset scaled by the access size.
2026-09-20 20:56:35 +02:00
Nathan Moin Vaziri 94adbf6e7f Test the origin's tag bits when taking a symbol's address
addr_with decoded the tagged origin word into its five-variant enum up
front and matched it twice, which left the enum spilled to the stack on
every relocation. It only needs to know whether the origin is a fragment
or an input section, and each is a mask and a compare.

Assisted-By: Claude <[email protected]>
2026-09-20 10:19:07 -07:00
Nathan Moin Vaziri 2777a8229e Check symbol types without loading the owner's symbol entry
The check read every referenced symbol's ELF entry from its owning file,
a second cache miss per reference, only to get the type the symbol
already caches.

Assisted-By: Claude <[email protected]>
2026-09-20 09:54:43 -07:00
Nathan Moin Vaziri 2157032638 Count the GOT's dynamic relocations in parallel
Sizing .rela.dyn walks every GOT symbol, a cache miss each, and the
section is sized three times on the way to the final layout. The walk
was the only serial one of that size left before the copy.

Assisted-By: Claude <[email protected]>
2026-09-20 09:50:47 -07:00
Nathan Moin Vaziri cfcd3137df Skip x86-64 absolute relocations the output section applies
scan_abs_relocations already collects every R_X86_64_64 in an allocated
section, and apply_abs_rels applies it. Resolving the symbol again here
only to fall through an empty match arm is wasted work.

Assisted-By: Claude <[email protected]>
2026-09-20 08:45:40 -07:00
Nathan Moin Vaziri 135b1e1a06 Compute the x86-64 GOT displacement only where it is used
Six of the fifty relocation kinds need G, but every relocation paid for
its aux-table lookup and bounds check. Move the computation into a
closure, as the other targets already do.

Assisted-By: Claude <[email protected]>
2026-09-20 08:44:28 -07:00
Nathan Moin Vaziri 244154313a Read the loop-invariant relocation addresses once
A section's address and the GOT base do not change while its relocations
are applied, but the section offset is atomic and the context carries
interior mutability, so the compiler must reload both after every store
into the output buffer. Read them before the loop instead.

Assisted-By: Claude <[email protected]>
2026-09-20 08:39:48 -07:00
Rui Ueyama b0d6f41094 Fix CI 2026-09-20 22:53:48 +09:00
Rui UeyamaandClaude Fable 5.1 026be81e51 Run cargo test in GitHub Actions
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]>
2026-09-20 21:58:39 +09:00
Rui UeyamaandClaude Fable 5.1 3476c49cd7 Name the harness's test target in the README examples
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]>
2026-09-20 21:56:22 +09:00
Rui UeyamaandClaude Fable 5.1 c7651fc424 Preserve path bytes through argument and file handling
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]>
2026-09-20 21:38:10 +09:00
Rui Ueyama c6a2cbc577 Fix CI 2026-09-20 18:46:58 +09:00
Rui UeyamaandClaude Fable 5.1 91c6b311a1 Name the default LTO library for the host
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]>
2026-09-20 18:08:37 +09:00
Rui UeyamaandClaude Fable 5.1 1572f94c80 Compute SHA-256 with the sha2 crate
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]>
2026-09-20 18:08:37 +09:00
Rui UeyamaandClaude Fable 5.1 e93ee3bfe8 Describe the layout as mirroring mold
The README pointed at "mold's Rust port"; mold is that port now.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
2026-09-20 18:08:37 +09:00
Rui UeyamaandClaude Fable 5.1 b6bd5b35e8 Refer to mold, not mold-rust, in comments
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]>
2026-09-20 18:08:37 +09:00
Rui UeyamaandClaude Fable 5.1 1ca9a0aa4a Stop using grep -q in test pipelines
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]>
2026-09-20 18:08:37 +09:00
Rui UeyamaandClaude Fable 5.1 65c3f1e825 Port mold's shell test runner
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]>
2026-09-20 18:08:37 +09:00
Rui Ueyama a9800541d0 Keep a compressed section's contents empty until it is uncompressed
InputSection stores its contents as a pointer and sh_size. For a
compressed section, sh_size is the Chdr's uncompressed size while the
pointer led to the compressed bytes, so reading contents() before
uncompress() made a slice far longer than its buffer. Only a debug_assert
guarded this, and a crafted SHF_COMPRESSED .eh_frame reached it through
the .eh_frame parser.

Store a null pointer for compressed and NOBITS sections instead, so that
a nonnull pointer always has sh_size bytes behind it, and pass the
compressed bytes to uncompress() from the file's section header. The gABI
forbids compressing allocated sections, so reject them with an error.
2026-09-20 17:13:43 +09:00
Rui Ueyama a31c187dbb Write output sections through split slices instead of raw pointers
Parallel writes into an output section went through OutputBuffer, a raw
pointer whose ranges were computed by the caller and only debug-asserted.
For ARM32 BE8 byte swapping those ranges came from mapping-symbol values
in the input file, so a corrupted symbol wrote outside of its section.

Replace OutputBuffer with for_each_member, which splits the buffer at
member offsets with rayon::iter::split and split_at_mut and hands each
member its own slice. Thunks get their slices by splitting the buffer
after the member loop, and the ARM32 swap indexes mapping-symbol ranges
within the member's slice. No unsafe code remains in either file.
2026-09-20 16:57:20 +09:00
Rui UeyamaandClaude Fable 5.1 21684a5551 Borrow display filenames
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]>
2026-09-20 15:55:59 +09:00
Rui UeyamaandClaude Fable 5.1 db2bb69f0c Traverse archive members lazily through one iterator
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]>
2026-09-20 15:53:46 +09:00
Rui UeyamaandClaude Fable 5.1 5b2e1f9480 Read small input files instead of mapping them
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]>
2026-09-20 15:52:37 +09:00
Rui Ueyama 20bbba9101 Apply absolute relocations through the member's own slice
Word-size absolute relocations were applied after the copy pass through
a raw pointer into the output section, with only a debug_assert on the
offset. A corrupted r_offset therefore wrote past the section in release
builds instead of failing.

Apply them in the per-member copy loop instead, indexing the member's
slice so that an out-of-range r_offset panics. abs_rels is built in
member order, so each member's relocations are one run of the vector;
AbsRel now records the member index and write_to finds the run with
partition_point. This removes write_word and its SAFETY assumption that
input relocations do not overlap.
2026-09-20 15:52:02 +09:00
Rui UeyamaandClaude Fable 5.1 c4d9a4e307 Split the output buffer with mold's split_ranges
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]>
2026-09-20 15:51:03 +09:00
Rui UeyamaandClaude Fable 5.1 c6f3998528 Declare chunk header lookup once for shared and mutable access
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]>
2026-09-20 15:49:17 +09:00
Rui UeyamaandClaude Fable 5.1 3160f79769 Collect thunk symbols with Rayon fold and reduce
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]>
2026-09-20 15:48:48 +09:00
Rui UeyamaandClaude Fable 5.1 13701fd00c Spell the utility helpers as mold does
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]>
2026-09-20 15:48:09 +09:00
Rui UeyamaandClaude Fable 5.1 e93556b44b Keep diagnostics state in mold's shape and demangle in util
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]>
2026-09-20 15:48:02 +09:00
Rui UeyamaandClaude Fable 5.1 02c664288c Remove a partial output from a signal handler
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]>
2026-09-20 15:47:04 +09:00
Rui UeyamaandClaude Fable 5.1 ac7ab5b17c Match symbol-list patterns with mold's glob matcher
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]>
2026-09-20 15:43:40 +09:00
Rui UeyamaandClaude Fable 5.1 d1e1f89845 Port mold's pass timers for -print_statistics
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]>
2026-09-20 15:39:43 +09:00
Rui UeyamaandClaude Fable 5.1 1c06d05fb1 Share the expanded command line through target dispatch
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]>
2026-09-20 15:33:53 +09:00
Rui UeyamaandClaude Fable 5.1 890ee6a889 Remove unused helpers
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]>
2026-09-20 15:32:46 +09:00
Rui UeyamaandClaude Fable 5.1 d4d4d79e25 Gate pointer-sized layout assertions on 64-bit hosts
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]>
2026-09-20 15:27:46 +09:00
Rui UeyamaandClaude Fable 5.1 d7d64f5c6b Merge match arms that clippy flagged as identical
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]>
2026-09-20 15:27:38 +09:00
Rui UeyamaandClaude Fable 5.1 0d09fd53bd Resolve remaining Clippy warnings
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]>
2026-09-20 15:27:30 +09:00
Rui UeyamaandClaude Fable 5.1 5ca4275fc9 Flatten output chunk modules
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]>
2026-09-20 15:21:11 +09:00
Rui UeyamaandClaude Fable 5.1 9e76ae48f7 Use pointer casts and raw borrows instead of as casts
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]>
2026-09-20 15:16:02 +09:00
Rui UeyamaandClaude Fable 5.1 8a142857b3 Use Self in impl blocks
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]>
2026-09-20 15:15:24 +09:00
Rui UeyamaandClaude Fable 5.1 a3853a067c Collapse nested ifs into let chains
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]>
2026-09-20 15:15:13 +09:00
Rui UeyamaandClaude Fable 5.1 2fcddfb519 Reformat for rustfmt's 2024 style edition
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]>
2026-09-20 15:13:51 +09:00
Rui UeyamaandClaude Fable 5.1 b7b16fd299 Move to Rust edition 2024
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]>
2026-09-20 15:13:40 +09:00
Rui UeyamaandClaude Fable 5.1 14c111a699 Flatten the test directory and run scripts directly
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]>
2026-09-20 15:12:04 +09:00
Rui UeyamaandClaude Fable 5.1 48b6653899 Flatten Mach-O format modules
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]>
2026-09-20 15:11:07 +09:00
Rui UeyamaandClaude Fable 5.1 fb27371417 Rename output_chunks to chunks
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]>
2026-09-20 15:10:50 +09:00
Rui UeyamaandClaude Fable 5.1 cd0eaa70d5 Rename the Arch trait and module to Target
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]>
2026-09-20 15:10:23 +09:00
Rui UeyamaandClaude Fable 5.1 00e0536273 Rename architecture crate directory to arch
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]>
2026-09-20 15:09:05 +09:00
Rui UeyamaandClaude Fable 5.1 d9d9bc0e19 Flatten CLI source and integration test directories
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]>
2026-09-20 15:08:28 +09:00
Rui UeyamaandClaude Fable 5.1 6dda2e0466 Consolidate shared Cargo workspace settings
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]>
2026-09-20 15:08:14 +09:00
Rui UeyamaandClaude Fable 5.1 93b76cf7b1 Drop unneeded Cargo settings
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]>
2026-09-20 15:07:56 +09:00
Rui UeyamaandClaude Fable 5.1 941f7de7bc Abort on panic in release builds
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]>
2026-09-20 15:07:56 +09:00
Rui Ueyama 7f26bc1097 Format 2026-09-20 12:12:28 +09:00
Rui Ueyama d567d10f15 Raise the default test timeout to 60 seconds
The 80k-section tests take about 2 seconds alone but 13 to 14 seconds
on every target when the suite runs 128 jobs at once, so any other load
on the machine pushed some of them past 30 seconds. The limit exists to
catch hangs, and 60 seconds still does that promptly.
2026-09-20 11:44:13 +09:00
Rui Ueyama 43a004eab6 Fold the Layout trait into Target
Layout carried only the file format: byte order, word size and the
record forms that differ between ELF32 and ELF64. No type implemented it
without also implementing Target, so the split earned its keep only in
RISC-V and LoongArch, where the record types depend on a const generic
and could not be chosen inside one Target impl.

Those two targets now take their record types from Class<IS_64>, a
helper private to the target module that maps the const parameter to
the ELF32 or ELF64 forms. ARM32 and SH4 lose their per-byte-order impls
and read section header words through u64::from like the other 32-bit
targets.
2026-09-20 11:44:08 +09:00
Rui Ueyama 4afe0ffd5b Rename the Arch trait and module to Target
What the trait enumerates are targets, not architectures: Ppc64V1 and
Ppc64V2 share an architecture and differ in ABI, and Arm64 and Arm64Be
differ only in byte order. The architecture-level notion already has its
own name, Family. The rest of the tree already said target, in
Arm64Target, TargetTraits, emulation_to_target and the comments, so the
trait was the one place the vocabulary broke. The workspace directory
arch/ keeps its name because target/ at the repository root is Cargo's
build directory.
2026-09-20 11:44:08 +09:00
Rui Ueyama 0e9fa3df08 Make Layout the only type parameter of ELF integers and records 2026-09-19 23:53:23 +09:00
Rui Ueyama 444cfcf971 Drop the Word item from the record traits 2026-09-19 23:53:23 +09:00
Rui Ueyama 0b3094f73a Spell a target's record types through the aliases everywhere 2026-09-19 23:53:23 +09:00
Rui Ueyama 2e6a67f188 Store r_info as one word in relocation records 2026-09-19 23:53:23 +09:00
Rui Ueyama 5331eafc08 Make ELF record accessors take and return host integers 2026-09-19 23:53:23 +09:00
Rui Ueyama 0ab442dbc6 Stop using grep -q in test pipelines
common.inc sets pipefail, and grep -q exits as soon as it finds a match.
If the producer still has output to write, it dies of SIGPIPE, the
pipeline fails, and a test that gates on the match silently skips or
fails. Plain grep reads its whole input, so the pipeline's status is
grep's own.
2026-09-19 23:53:23 +09:00
Rui Ueyama 60b97830ca Merge pull request #1660 from noritada/as_chunks
Use `<[T]>::as_chunks` instead of `<[T]>::chunks_exact`
2026-09-19 23:53:05 +09:00
Noritada Kobayashi d83916655c Use <[T]>::as_chunks instead of <[T]>::chunks_exact
This makes it clear, in terms of type, that each chunk is an array of
fixed length (`&[u8; 4]`), rather than a slice of variable length
(`&[u8]`).
2026-09-19 19:29:32 +09:00
Rui Ueyama 54b68f6e42 Merge no-op match arms that clippy flagged as identical
Clippy's match_same_arms lint reports 15 pairs. Most sit in relocation
tables that follow the relocation number order, where merging would
break the order, so only the two that are not in such tables change.
2026-09-19 14:07:08 +09:00
Rui Ueyama 36ec82df73 Drop pub(crate) on items in private modules
Apply clippy's redundant_pub_crate suggestion. An item in a private
module is crate-visible at most, so `pub(crate)` on it says nothing
that `pub` does not.
2026-09-19 14:07:08 +09:00
Rui Ueyama c5f69d9181 Use pointer casts and raw borrows instead of as casts
Apply clippy's ptr_as_ptr, borrow_as_ptr, ref_as_ptr and
ptr_cast_constness suggestions. `ptr.cast()` and `ptr::from_ref()`
cannot silently change the pointee type when a declaration elsewhere
changes, and `&raw mut x` passes a pointer to C without creating an
intermediate reference.
2026-09-19 13:42:51 +09:00
Rui Ueyama b56daa27b7 Merge pull request #1659 from adamnemecek/main
Use `Self` when possible.
2026-09-19 13:20:05 +09:00
adamnemecek 3bf748a8fd a couple more Self's 2026-09-18 19:57:25 -07:00
adamnemecek 3ca1cb6579 cargo fmt 2026-09-18 19:52:42 -07:00
adamnemecek 733952657a ran cargo clippy --allow-dirty --fix -- -Wclippy::use_self 2026-09-18 19:52:34 -07:00
Rui Ueyama 6fad4d5cf1 Fix CI 2026-09-19 10:49:28 +09:00
Rui Ueyama 7a117ee3cc Add rustfmt.toml and reformat 2026-09-19 10:48:37 +09:00
Rui UeyamaandClaude Fable 5.1 d4933923bb Align fixed-size literals to their size, without a modulus
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
2026-09-19 10:30:40 +09:00
Rui Ueyama 1c5cbc0aaf Merge pull request #3 from Xuanwo/fix-literal16-pageoff
Place 16-byte literals on a 16-aligned address
2026-09-19 10:18:19 +09:00
Rui Ueyama 2aeddd8659 Update .gitignore 2026-09-19 09:49:37 +09:00
Rui Ueyama 722d8f02bc Drop unneeded Cargo settings 2026-09-19 09:45:07 +09:00
XuanwoandCursor 88438e37be Place 16-byte literals on a 16-aligned address
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]>
2026-09-18 23:55:34 +08:00
Rui Ueyama 75a935d071 Simplify 2026-09-18 22:24:20 +09:00
Rui Ueyama 0fec5eec58 Report out-of-range non-allocated CREL relocations 2026-09-18 21:46:23 +09:00
Rui Ueyama f615c28339 Keep --package-metadata as bytes 2026-09-18 21:46:22 +09:00
Rui Ueyama e41ed90846 Drop COMDAT groups without members from -r output 2026-09-18 21:46:17 +09:00
Rui Ueyama ec2c9fcbc2 Apply RISC-V non-allocated relocations from CREL tables 2026-09-18 21:46:15 +09:00
Rui Ueyama 1b17bdf0d2 Write back the addends of all relocations that -r passes through 2026-09-18 21:46:12 +09:00
Rui Ueyama 6cd991a3e4 Bind PPC64 ELFv1 .opd references to the first symbol of a descriptor
rewrite_opd looks up the symbol of a function descriptor by its offset
with a binary search. Aliases share a descriptor, so the search could
land on any of them, and a section-relative reference to a static
function then went through one of its global or weak aliases: it became
preemptible and resolved to whichever definition won. Use the lower
bound instead; the stable sort keeps the local symbol first.
2026-09-18 21:46:12 +09:00
Rui Ueyama 8dfbccd8a6 Keep local symbols of non-allocated mergeable sections in .symtab 2026-09-18 21:46:01 +09:00
Rui Ueyama ec3a8d247b Fix --wrap for symbols whose names start with __real_ 2026-09-18 21:45:54 +09:00
Rui Ueyama ebbda8f4bc Collapse nested ifs into let chains 2026-09-18 16:58:23 +09:00
Rui Ueyama 61c0854ddc Reformat for rustfmt's 2024 style edition 2026-09-18 16:58:21 +09:00
Rui Ueyama 1efb465bfa Move to Rust edition 2024 2026-09-18 16:58:18 +09:00
Rui Ueyama fa30ac90cb Add README files for src/arch and src/chunks 2026-09-18 15:04:37 +09:00
Rui Ueyama 9b9d9d7ef9 Adopt rustfmt's Max width heuristics 2026-09-18 14:19:38 +09:00
Rui Ueyama 8ceae6875f Fix CI 2026-09-18 13:45:24 +09:00
Rui Ueyama e61c7dbcb7 Update README 2026-09-18 13:11:25 +09:00
Rui Ueyama d64a0ce248 Restore GitHub Actions after Rust migration 2026-09-18 12:25:09 +09:00
Rui Ueyama a7e9aac9d3 Remove reports 2026-09-18 12:25:09 +09:00
Rui Ueyama af97c80062 Merge Rust implementation and its history 2026-09-18 10:07:47 +09:00
Rui UeyamaandClaude Fable 5.1 d659f5930f Vendor Cargo dependencies for the distribution build
`dist.sh arm` failed in the networked fetch step: cargo could not read
its git database for the mimalloc dependency ("Value too large for
defined data type"). ext4 hands 64-bit directory offsets to the QEMU
process, the armhf glibc reports EOVERFLOW when they don't fit a 32-bit
off_t, and the libgit2 built into the 32-bit cargo uses that interface.
Only the git dependency is affected; everything else goes through Rust's
std, which uses the 64-bit calls.

Keep cargo's cache on a tmpfs inside the networked container and have it
`cargo vendor --locked` into target/dist-vendor-$arch together with the
printed config. The offline build reads the vendored sources through
`--config` and no longer needs a cargo home at all.

Tests: ./dist.sh x86_64

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_0141irCwtPmPMZa9s7pCN3Cz
2026-09-17 22:26:49 +09:00
Rui UeyamaandClaude Fable 5.1 011e194896 Build on 32-bit hosts
Two struct-size assertions hold only with 8-byte pointers: SymbolAux ends
in a Vec and PendingComdatSignature starts with a fat pointer, so they
are 48 and 16 bytes on a 32-bit host and rustc rejected the crate for
armv7. Gate them on target_pointer_width = "64"; the other five size
assertions have no pointer-sized fields and pass on both widths.

CI now runs cargo check for armv7-unknown-linux-gnueabihf over the whole
workspace so this cannot regress unnoticed.

Tests: cargo check --release --target armv7-unknown-linux-gnueabihf --workspace --all-targets
Tests: cargo test --release (pass=5681 skip=654 fail=0)

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_0141irCwtPmPMZa9s7pCN3Cz
2026-09-17 22:26:49 +09:00
Rui UeyamaandClaude Fable 5.1 8fe0ffb586 Pin apt sources to snapshot.debian.org for every Debian image
The bullseye targets (aarch64, arm, ppc64le, s390x) left apt on the live
deb.debian.org mirrors. Those mirrors dropped the architectures Debian LTS
does not support when bullseye entered LTS, so `dist.sh ppc64le` and
`dist.sh s390x` fail in `apt-get install` with "Unable to locate package
build-essential". The pinned snapshot the images ship commented out in
sources.list still has them, and using it also gives these builds the
reproducibility the header comment already claims.

Make the snapshot switch the default apt_setup; riscv64 (deb822 sources)
and loongarch64 (live sid) keep their own.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_0141irCwtPmPMZa9s7pCN3Cz
2026-09-17 17:37:49 +09:00
Rui Ueyama 8794986da3 Fix sed portability issue in test 2026-09-16 12:56:07 +09:00
Rui Ueyama 44bfa2a9b2 Preserve DSO symbol versions during archive extraction
Retain the original versioned reference when following undefined symbols
in shared libraries. This prevents an unversioned archive definition from
causing unnecessary extraction while preserving symbol interposition.
Skip the retained undefined references in the secondary symbol type check
and port both upstream regression tests unchanged.

Validation: full workspace tests passed with 34 unit tests and 5,684
integration tests across 14 target configurations; 651 skipped, 0 failed.
Four additional targets lacked a compiler or QEMU. The archive regression
fails before the fix, and both new tests pass on all available targets.

Upstream-Commit: 294e22a86a
2026-09-16 11:58:29 +09:00
Rui Ueyama 294e22a86a Preserve DSO symbol versions during archive extraction
Retain the original versioned reference when following undefined
symbols in shared li6braries. This prevents an unversioned archive
definition from causing unnecessary extraction while preserving symbol
interposition.

Fixes https://github.com/rui314/mold/issues/1657
2026-09-16 11:45:53 +09:00
Rui Ueyama 60f9b36dcf Generate large LTO section tables without compiling thousands of functions
Append 70,000 empty sections to GCC-generated LTO assembly for a tiny
program. Preserve the extended section numbering checks for slim and
fat LTO objects and verify that the fat object actually invokes LTO.

Validation: this test passes on all 14 available target configurations.
The observed LoongArch run dropped from 19 seconds to 4.4 seconds; the
14-target run completed in 5.7 seconds.
2026-09-15 21:34:37 +09:00
Rui Ueyama b24a27a9f2 Check merged debug strings in one awk scan
Replace 1,000 grep invocations with one awk process while checking all
500 DWARF32 and 500 DWARF64 names. This avoids timeouts spent repeatedly
spawning grep during parallel test runs.

Validation: the updated test passes on all 14 available target
configurations. The check accepts a complete dump and rejects missing
names and empty input.
2026-09-15 20:39:29 +09:00
Rui Ueyama b56649e972 Omit .gnu_debuglink from separate debug files
Remove the NOBITS debug-link placeholder and remap section indices so
symbols and section headers still refer to the correct sections. Port
the upstream regression test unchanged.

Validation: separate-debug-file tests across 14 target configurations:
29 passed, 13 skipped, 0 failed. The regression fails before the fix and
passes afterward.

Upstream-Commit: f4edcbd37e
2026-09-15 19:52:45 +09:00
Rui Ueyama f4edcbd37e Omit .gnu_debuglink from separate debug files
Remove the malformed NOBITS debug link and adjust section indices so
symbols and section headers still refer to the correct sections.

Add a regression test for following the debug link and preserving
symbols in later sections.

Fixes https://github.com/rui314/mold/issues/1656
2026-09-15 16:52:04 +09:00
Rui UeyamaandClaude Fable 5.1 1f6b4b4c32 Emit .eh_frame relocations in output order for -r
The relocatable output lays out all leader CIEs before the FDE blocks
of every file, but EhFrameRelocSection::copy_buf emitted the
relocations per file: a file's CIE relocations followed by its FDE
relocations. When a file after the first has a CIE with a relocation,
which is any object with a personality routine, that relocation was
written after the previous files' FDE relocations, so .rela.eh_frame
was not sorted by offset.

Consumers pair CIEs and FDEs with a contiguous run of relocations in a
single scan over .eh_frame, and parse_ehframe is one of them. On the
unsorted section the personality relocation was attached to an earlier
file's FDE with a negative in-record offset, so the final link wrote the
value into the preceding FDE and left the CIE's personality pointer
zero. libgcc treats an encoded zero as "no personality", so functions
under that CIE lost their cleanups and catch handlers: an exception
thrown inside their try block escaped uncaught. Found by dev-util/perf,
whose build partially links a Rust workload object after C objects.

Emit the CIE relocations of every file first and only then the FDE
relocations, which matches the layout, and add a test that links a C
object before a C++ object with `-r`, checks that the relocations are
sorted, and catches an exception through the result.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_0141irCwtPmPMZa9s7pCN3Cz
2026-09-15 16:52:04 +09:00
Rui Ueyama dce5c48666 Dead-strip unused thread-local variables
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.
2026-09-15 15:12:41 +09:00
Rui UeyamaandClaude Fable 5.1 0e41bb98e4 Emit .eh_frame relocations in output order for -r
The relocatable output lays out all leader CIEs before the FDE blocks
of every file, but the relocations for them were emitted per file: a
file's CIE relocations followed by its FDE relocations. When a file
after the first has a CIE with a relocation, which is any object with
a personality routine, that relocation was written after the previous
files' FDE relocations, so .rela.eh_frame was not sorted by offset.

Consumers pair CIEs and FDEs with a contiguous run of relocations in a
single scan over .eh_frame, mold included. On the unsorted section the
personality relocation was attached to an earlier file's FDE with a
negative in-record offset: we panicked on the slice index, and C++ mold
silently wrote the value into the preceding FDE and left the personality
pointer zero, which libgcc treats as "no personality", dropping that
function's cleanups and catch handlers. Found by dev-util/perf in the
Gentoo campaign; a Rust workload object partially linked after C objects
triggered it.

Emit the CIE relocations of every file first and only then the FDE
relocations, which matches the layout. The C++ implementation received
the same change.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_0141irCwtPmPMZa9s7pCN3Cz
2026-09-15 11:13:36 +09:00
Rui Ueyama c52c765f98 Document simplification commits and correctness validation 2026-09-15 01:27:29 +09:00
Rui Ueyama 3ccb8e2291 Preserve lazy addend reads when emitting relocations 2026-09-15 01:05:16 +09:00
Rui Ueyama 8a56434bae Limit implementation module visibility and remove stale explanations 2026-09-15 00:57:51 +09:00
Rui Ueyama 4658ec473a Use native zlib for both compression and decompression 2026-09-15 00:56:16 +09:00
Rui Ueyama b2effe0931 Use bounded memchr searches for NUL-terminated strings 2026-09-15 00:55:29 +09:00
Rui Ueyama e918027933 Use bounded static chunks for GDB background work 2026-09-15 00:54:40 +09:00
Rui Ueyama ebc789e41f Use existing map entry IDs for interned GDB names 2026-09-15 00:54:07 +09:00
Rui Ueyama 46bbf3df03 Gather owned sparse output-section contributions per file 2026-09-15 00:52:47 +09:00
Rui Ueyama 19162bea6c Use the standard indexed parallel iterator for live files 2026-09-15 00:51:53 +09:00
Rui Ueyama c77f241e9b Deduplicate CIEs with file and record indices 2026-09-15 00:50:58 +09:00
Rui Ueyama bb257d5a37 Unify test process errors and result finalization 2026-09-15 00:49:51 +09:00
Rui Ueyama 67ba746692 Centralize GNU option parsing and declarative option forwarding 2026-09-15 00:47:39 +09:00
Rui Ueyama 284c571636 Share output symbol and addend mapping for emitted relocations 2026-09-15 00:44:20 +09:00
Rui Ueyama 1c639473ab Share nonallocated relocation resolution across architectures 2026-09-15 00:42:28 +09:00
Rui Ueyama 8e0967d72b Stream dependency reports through the shared buffered writer 2026-09-15 00:40:08 +09:00
Rui Ueyama 9d9f0e96e7 Compute final statistics without global atomic counters 2026-09-15 00:39:27 +09:00
Rui Ueyama 65479de34d Keep output files with their mapping state 2026-09-15 00:38:42 +09:00
Rui Ueyama 9bca3eacef Traverse archive members lazily through one iterator 2026-09-15 00:36:38 +09:00
Rui Ueyama 15b984a27c Assign input file IDs after sorting owned reader results 2026-09-15 00:34:45 +09:00
Rui Ueyama d6039a2a58 Encapsulate cooperative background jobs 2026-09-15 00:33:48 +09:00
Rui Ueyama 4980d26ad8 Share persistent worker bins and caches 2026-09-15 00:32:54 +09:00
Rui Ueyama d677902cc0 Declare chunk header lookup once for shared and mutable access 2026-09-15 00:31:37 +09:00
Rui Ueyama 95fef65b28 Represent header-only output sections directly as chunk headers 2026-09-15 00:30:01 +09:00
Rui Ueyama 2712c5e183 Share 32-bit and 64-bit ELF record accessors 2026-09-15 00:28:42 +09:00
Rui Ueyama 7d6d588ee3 Derive page size and glob emptiness from existing state 2026-09-15 00:27:40 +09:00
Rui Ueyama de4910ff57 Keep parser-only settings out of Args 2026-09-15 00:27:40 +09:00
Rui Ueyama 2308083712 Remove unused symbol and endian helpers 2026-09-15 00:26:50 +09:00
Rui Ueyama f7e0ff86a1 Remove unused machine detection callback 2026-09-15 00:26:50 +09:00
Rui Ueyama 1e8dae17c2 Remove redundant file priority registry and DSO keep mask 2026-09-15 00:26:50 +09:00
Rui Ueyama aa4a728710 Remove unused target metadata registry 2026-09-15 00:26:12 +09:00
Rui Ueyama 47a35e8b38 Consolidate shared Cargo workspace settings 2026-09-14 23:21:26 +09:00
Rui Ueyama e2e85b3fc7 Document why architecture targets use separate crates 2026-09-14 23:01:08 +09:00
Rui Ueyama 3d67399506 Flatten ELF source modules 2026-09-14 22:58:05 +09:00
Rui Ueyama 946c71146d Rename architecture crate directory to arch 2026-09-14 22:54:34 +09:00
Rui Ueyama 972347aae9 Flatten the test directory and run scripts directly
Move shell cases and the runner directly under tests/, and remove the
standalone symbol output chunk test files and their references.

Make shell scripts executable and invoke them directly, honoring their
shebangs and execute permissions.
2026-09-14 22:54:34 +09:00
Rui Ueyama da0de56368 Flatten CLI source and integration test directories 2026-09-14 22:54:34 +09:00
Rui Ueyama 5e5db87568 Flatten target crate source directories 2026-09-14 22:54:34 +09:00
Rui Ueyama 3be3c7faef Borrow the separate debug output path 2026-09-14 22:54:34 +09:00
Rui Ueyama ef0e3553b4 Keep Context formatting beside its type definition 2026-09-14 22:54:34 +09:00
Rui Ueyama f58bf44acf Remove the unused SDA base symbol handle 2026-09-14 22:54:34 +09:00
Rui Ueyama 846d42aa73 Keep compressed sections with their destination indices 2026-09-14 22:54:34 +09:00
Rui Ueyama 66e7950766 Build complete symbol table writing jobs 2026-09-14 22:54:34 +09:00
Rui Ueyama d3c8f63e19 Use fixed arrays for output task buffers 2026-09-14 22:54:34 +09:00
Rui Ueyama fbe442097a Accumulate SFrame totals while gathering live entries 2026-09-14 22:54:34 +09:00
Rui Ueyama 2569831fbf Preserve borrowed filenames in background merging 2026-09-14 22:54:34 +09:00
Rui Ueyama ae5095177b Claim relocatable undefined symbols without a candidate vector 2026-09-14 22:54:34 +09:00
Rui Ueyama 433eca009e Iterate files directly in COMDAT and symbol version passes 2026-09-14 22:54:34 +09:00
Rui Ueyama bcba36dc9a Buffer dependency reports and reuse their symbol set 2026-09-14 22:54:34 +09:00
Rui Ueyama 92e2afcc28 Stream GC and ICF reports through buffered output 2026-09-14 22:54:34 +09:00
Rui Ueyama c3cea186f9 Build ICF report groups only for folded sections 2026-09-14 22:54:34 +09:00
Rui Ueyama 89596a87d6 Reuse ICF edge counts for prefix offsets 2026-09-14 22:54:34 +09:00
Rui Ueyama 6131f9b77b Collect only sections with unsorted relocations 2026-09-14 22:54:34 +09:00
Rui Ueyama c3cbd297fc Share thin archive member construction 2026-09-14 22:54:34 +09:00
Rui Ueyama 0380abbb7b Enumerate archive members lazily during target detection 2026-09-14 22:54:34 +09:00
Rui Ueyama b5aee2485e Find FDE parent CIEs by binary search 2026-09-14 22:54:34 +09:00
Rui Ueyama 6c504e434d Sort FDE records directly with stable cached keys 2026-09-14 22:54:34 +09:00
Rui Ueyama 87badfdb83 Construct relocation section names from bytes 2026-09-14 22:54:34 +09:00
Rui Ueyama 8fe5b00ea3 Infer timer nesting from completed intervals 2026-09-14 22:54:34 +09:00
Rui Ueyama b9d8f49037 Match the Windows LTO stub to the shared reader interface 2026-09-14 22:54:34 +09:00
Rui Ueyama db51683955 wip 2026-09-14 22:54:34 +09:00
Rui Ueyama fbb314400c Resolve remaining Clippy warnings without changing lock semantics 2026-09-14 22:54:34 +09:00
Rui Ueyama c0ef81a144 Refresh duplicated and outdated module documentation 2026-09-14 22:54:34 +09:00
Rui Ueyama 8c29736bd8 Keep Compressor formatting beside its type definition 2026-09-14 22:54:34 +09:00
Rui Ueyama 73c5095f1e Remove the unused path_dirname helper 2026-09-14 22:54:34 +09:00
Rui Ueyama 900d689cb3 Remove unused output section bookkeeping fields 2026-09-14 22:54:34 +09:00
Rui Ueyama fa71e1b222 Print timer children in their recorded order 2026-09-14 22:54:34 +09:00
Rui Ueyama ed2801b547 Format GDB input filenames only for diagnostics 2026-09-14 22:54:34 +09:00
Rui Ueyama 747a7508ca Skip empty inputs when scheduling GDB index work 2026-09-14 22:54:34 +09:00
Rui Ueyama 322056731f Reuse the ARM exception index buffer for final encoding 2026-09-14 22:54:34 +09:00
Rui Ueyama 48db48cda2 Format the thunk symbol suffix once per thunk 2026-09-14 22:54:34 +09:00
Rui Ueyama 385dadf43d Share target validation for object files and shared libraries 2026-09-14 22:54:34 +09:00
Rui Ueyama 2bfbce1933 Reuse chroot path handling for linker script sysroots 2026-09-14 22:54:34 +09:00
Rui Ueyama f08abb1b3c Build library candidate names once per search 2026-09-14 22:54:34 +09:00
Rui Ueyama f7edd50ff1 Borrow original arguments when restarting for LTO 2026-09-14 22:54:34 +09:00
Rui Ueyama ec231699c7 Move entry and initializer names into the symbol table 2026-09-14 22:54:34 +09:00
Rui Ueyama a50a793cda Narrow dynamic symbol insertion to its required tables 2026-09-14 22:54:34 +09:00
Rui Ueyama 557f3689c2 Borrow exported symbols while writing the GNU hash table 2026-09-14 22:54:34 +09:00
Rui Ueyama a78277b2f4 Reuse stable partitioning for DWARF section order 2026-09-14 22:54:34 +09:00
Rui Ueyama 5c5cba2179 Sort initializer sections with cached keys 2026-09-14 22:54:34 +09:00
Rui Ueyama 9f316eb875 Stream map reports through buffered output 2026-09-14 22:54:34 +09:00
Rui Ueyama 52b713b996 Retain generated merged section names only on insertion 2026-09-14 22:54:34 +09:00
Rui Ueyama 5053a1926e Retain only compressed section contents needed by GDB 2026-09-14 22:54:34 +09:00
Rui Ueyama ec685cb37a Consume output section builders after parallel collection 2026-09-14 22:54:34 +09:00
Rui Ueyama a026122121 Share file record layout guarantees across ELF traits 2026-09-14 22:54:34 +09:00
Rui Ueyama 1dede4350e Tie parallel output buffers to the lifetime of their storage 2026-09-14 22:54:34 +09:00
Rui Ueyama aed83a2109 Remove unused record parsing and misleading file helpers 2026-09-14 22:54:34 +09:00
Rui Ueyama d6bf14c5b3 Borrow version definition names from mapped linker scripts 2026-09-14 22:54:34 +09:00
Rui Ueyama 653406ff4d Assign final GDB name offsets in the existing layout pass 2026-09-14 22:54:34 +09:00
Rui Ueyama f50cfe847a Drop GDB input metadata after each file is parsed 2026-09-14 22:54:34 +09:00
Rui Ueyama 04f026cbcc Avoid copying stable chunk IDs, symbol IDs, and repro paths 2026-09-14 22:54:34 +09:00
Rui Ueyama da8e459060 Move reader state into archive results and deferred LTO jobs 2026-09-14 22:54:34 +09:00
Rui Ueyama abedadff59 Borrow unmodified response file tokens from mapped input 2026-09-14 22:54:34 +09:00
Rui Ueyama 96e6a38d08 Share expanded command line arguments through target dispatch 2026-09-14 22:54:34 +09:00
Rui Ueyama 40ad3e5700 Match C++ output file reuse detection with uname 2026-09-14 22:54:34 +09:00
Rui Ueyama 750523e694 Wrap CREL addend accumulation at signed integer boundaries 2026-09-14 22:54:34 +09:00
Rui Ueyama 86498df474 Assign task buffers by consuming their ordered iterator 2026-09-14 22:54:34 +09:00
Rui Ueyama 135b13f3cc Move claimed LTO COMDAT keys into their final storage 2026-09-14 22:54:34 +09:00
Rui Ueyama d52673605f Collect removed sections only when GC reporting is enabled 2026-09-14 22:54:34 +09:00
Rui Ueyama ffca665b28 Consume glob construction state into an immutable matcher 2026-09-14 22:54:34 +09:00
Rui Ueyama be74299a61 Borrow start and stop names that need no normalization 2026-09-14 22:54:34 +09:00
Rui Ueyama 74908a7035 Use memmem for literal byte substring searches 2026-09-14 22:54:34 +09:00
Rui Ueyama 7d1dee4a3a Format timer labels only when recording is enabled 2026-09-14 22:54:34 +09:00
Rui Ueyama b2775275f8 Format undefined reference messages without temporary strings 2026-09-14 22:54:34 +09:00
Rui Ueyama 4eb13f32c4 Borrow known ELF symbol and relocation type names 2026-09-14 22:54:34 +09:00
Rui Ueyama 96ea73e773 Borrow test jobs and the work counter in scoped threads 2026-09-14 22:54:34 +09:00
Rui Ueyama df5c9e146d Store the selected target triple in the test runner mode 2026-09-14 22:54:34 +09:00
Rui Ueyama 3d715f6571 Name the ARM instruction and data mapping kinds 2026-09-14 22:54:34 +09:00
Rui Ueyama a21598f0ef Remove unused bookkeeping fields 2026-09-14 22:54:34 +09:00
Rui Ueyama 4f04b640a8 Copy dynamic relocation offsets only when adjusting for RELR 2026-09-14 22:54:34 +09:00
Rui Ueyama c9cf514677 Count and write dynamic entries with a shared visitor 2026-09-14 22:54:34 +09:00
Rui Ueyama 2f7fd93117 Write GOT entries directly through a shared visitor 2026-09-14 22:54:34 +09:00
Rui Ueyama 0052475b7d Iterate over compression input chunks directly 2026-09-14 22:54:34 +09:00
Rui Ueyama a58c342b76 Flatten parallel results without per-group staging vectors 2026-09-14 22:54:34 +09:00
Rui Ueyama 77bce45d83 Borrow RISC-V extension names and serialize them into one string 2026-09-14 22:54:34 +09:00
Rui Ueyama 2cc4d6a81c Derive the debuglink filename from its option 2026-09-14 22:54:34 +09:00
Rui Ueyama 54f549c31e Read section and version indices directly from mapped bytes 2026-09-14 22:54:34 +09:00
Rui Ueyama 643bb2aefb Borrow shared object sonames from mapped storage 2026-09-14 22:54:33 +09:00
Rui Ueyama 3a62c12861 Iterate over dynamic entries without collecting intermediate vectors 2026-09-14 22:54:33 +09:00
Rui Ueyama c8e2be01ca Borrow archive names from their mapped files 2026-09-14 22:54:33 +09:00
Rui Ueyama a1d0c8a6a3 Borrow display filenames when the mapped path permits it 2026-09-14 22:54:33 +09:00
Rui Ueyama e21540ed26 Copy only unresolved state for background section merging 2026-09-14 22:54:33 +09:00
Rui Ueyama 0d3bc19530 Borrow DWARF section IDs and format diagnostic names lazily 2026-09-14 22:54:33 +09:00
Rui Ueyama 0faea2291f Rewrite and sort relocation tables in place 2026-09-14 22:54:33 +09:00
Rui Ueyama 9226b64d6d Borrow read-only section member and symbol lists 2026-09-14 22:54:33 +09:00
Rui Ueyama 0f9aea233b Walk unchanged chunk lists without cloning their IDs 2026-09-14 22:54:33 +09:00
Rui Ueyama 5430746f22 Borrow synthetic symbol option metadata 2026-09-14 22:54:33 +09:00
Rui Ueyama 055332c946 Keep internal ELF symbols in their owning object 2026-09-14 22:54:33 +09:00
Rui Ueyama 7340b1c35e Borrow forced symbol names during GC and LTO 2026-09-14 22:54:33 +09:00
Rui Ueyama a029e10c62 Consume version script and dynamic list option queues 2026-09-14 22:54:33 +09:00
Rui Ueyama c7632feaa4 Borrow retained symbol names from the mapped file 2026-09-14 22:54:33 +09:00
Rui Ueyama 22b16f16cd Move response token buffers into command-line arguments 2026-09-14 22:54:33 +09:00
Rui Ueyama b4867dd3ff Borrow command-line parser scratch arguments 2026-09-14 22:54:33 +09:00
Rui Ueyama 86ff43bf2d Reject -run on macOS as in C++ 2026-09-14 22:54:33 +09:00
Rui Ueyama 56b8bb797e Watch existing Git version inputs without watching the entire repository 2026-09-14 22:54:33 +09:00
Rui Ueyama c068d03df4 Use cc for Cargo-aware C compiler and archiver configuration 2026-09-14 22:54:33 +09:00
Rui Ueyama b8ae5a8c7d Locate wrapper output under custom Cargo profiles 2026-09-14 22:54:33 +09:00
Rui Ueyama d43a8b286a Match C++ detection of existing nondirectories for -R 2026-09-14 22:54:33 +09:00
Rui Ueyama 941a8d8d7c Initialize output buffers with the requested filler byte 2026-09-14 22:54:33 +09:00
Rui Ueyama 41face3c76 Demangle C++ symbol names in diagnostics 2026-09-14 22:07:16 +09:00
Rui Ueyama e9e7903f61 Implement -reexported_symbols_list 2026-09-14 22:04:33 +09:00
Rui Ueyama 73f7db67f8 Implement -no_exported_symbols 2026-09-14 22:01:27 +09:00
Rui Ueyama 7a8f61e04d Implement -reexport_framework 2026-09-14 21:57:23 +09:00
Rui Ueyama 2b7c1a5e82 Implement -needed_library 2026-09-14 21:55:56 +09:00
Rui Ueyama 9cc101d0c0 Write Xcode SDK import reports
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.
2026-09-14 21:52:45 +09:00
Rui Ueyama 26025fa7de Implement -fatal_warnings
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.
2026-09-14 21:50:10 +09:00
Rui Ueyama 4708e65cfc Warn about dylibs requiring a newer deployment target
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.
2026-09-14 21:49:03 +09:00
Rui Ueyama 219ca46c80 Reject binary dylibs built for incompatible platforms
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.
2026-09-14 21:47:46 +09:00
Rui Ueyama 41af70ed9d Warn when object files require a newer deployment target
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.
2026-09-14 21:37:04 +09:00
Rui Ueyama df1bc18120 Reject live object files built for incompatible platforms
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.
2026-09-14 21:35:58 +09:00
Rui Ueyama 0395c30370 Match C integer syntax for numeric options 2026-09-13 21:47:20 +09:00
Rui Ueyama 8bbf7cf05c Apply chroot to thin archive members in every reader 2026-09-13 21:42:32 +09:00
Rui Ueyama da5c77278e Apply chroot when reading retained symbol names 2026-09-13 21:41:51 +09:00
Rui Ueyama b2ae03fdb8 Apply chroot to absolute output paths 2026-09-13 21:40:42 +09:00
Rui Ueyama b19ea1a396 Share immutable command-line arguments with background merging 2026-09-13 20:32:04 +09:00
Rui Ueyama d0f01784ff Borrow section names in cached sort keys 2026-09-13 20:30:50 +09:00
Rui Ueyama a1fedf5821 Apply dynamic-list matches directly to symbols in parallel 2026-09-13 20:30:09 +09:00
Rui Ueyama ace37f38f7 Borrow version and dynamic pattern metadata 2026-09-13 20:28:12 +09:00
Rui Ueyama 7fecd46e67 Keep parser tokens local and compare labels without allocating 2026-09-13 20:26:38 +09:00
Rui Ueyama c73f59645d Represent command-line target families with the existing enum 2026-09-13 20:25:21 +09:00
Rui Ueyama 9b051c1d43 Borrow canonical target names from static storage 2026-09-13 20:24:17 +09:00
Rui Ueyama dc127e5280 Accept vertical tabs as linker script whitespace 2026-09-13 20:22:41 +09:00
Rui Ueyama c9041967d1 Restore the default transparent huge page policy at startup 2026-09-13 20:21:19 +09:00
Rui Ueyama 99f51f03eb Bound legacy Rust demangler length arithmetic 2026-09-13 20:20:43 +09:00
Rui Ueyama c5a8e52925 Report output mapping failures instead of buffering zeroed data 2026-09-13 20:20:43 +09:00
Rui Ueyama df5a97ede2 Start with the first enabled linker target 2026-09-13 20:15:17 +09:00
Rui Ueyama dcf75d7d8b Represent object origins with a single enum 2026-09-13 20:15:17 +09:00
Rui Ueyama 6d2d640802 Cast LTO callbacks directly to C pointers 2026-09-13 20:15:17 +09:00
Rui Ueyama 6b502f1581 Format report lines directly into their output strings 2026-09-13 20:15:17 +09:00
Rui Ueyama a0fe942d2d Borrow option and dynamic strings directly from the context 2026-09-13 20:15:17 +09:00
Rui Ueyama a58e6f14ff Read environment flags without requiring UTF-8 2026-09-13 20:15:17 +09:00
Rui Ueyama 5e936482cb Preserve argument bytes in the embedded command line 2026-09-13 20:15:17 +09:00
Rui Ueyama 7ec6910413 Preserve bytes when forwarding LTO plugin options 2026-09-13 20:15:17 +09:00
Rui Ueyama 3cc86fe2c5 Keep archive paths and exclusion names in their original encoding 2026-09-13 20:15:17 +09:00
Rui Ueyama d74d0d0ef4 Preserve bytes in dynamic library names 2026-09-13 19:36:44 +09:00
Rui Ueyama 86dd63ead2 Distribute GDB index work through disjoint mutable chunks 2026-09-13 17:55:39 +09:00
Rui Ueyama 48ab9a44f3 Use zlib to calculate and combine Adler-32 checksums 2026-09-13 17:54:10 +09:00
Rui Ueyama 4fa0487aa3 Use memmem for glob literal substring searches 2026-09-13 17:53:17 +09:00
Rui Ueyama e09ce32f73 Use standard owners for the job lock and notification pipe 2026-09-13 17:52:55 +09:00
Rui Ueyama 2294f1d7f7 Represent report destinations with an enum 2026-09-13 17:51:10 +09:00
Rui Ueyama 160c059352 Store debug compression levels in their algorithm variants 2026-09-13 17:48:32 +09:00
Rui Ueyama 93d79e105f Borrow SONAMEs when sorting version requirements 2026-09-13 17:48:28 +09:00
Rui Ueyama d1e99e7a86 Intern argument symbols without cloning their option lists 2026-09-13 17:47:12 +09:00
Rui Ueyama 3d06f6484e Consume mutable slice prefixes with split_off_mut 2026-09-13 17:47:11 +09:00
Rui Ueyama 6f8aa2f99d Derive Clone for glob matcher components 2026-09-13 17:46:08 +09:00
Rui Ueyama b78feeaf7f Use a boxed slice for the serialized GDB index tables 2026-09-13 17:45:31 +09:00
Rui Ueyama cc01683a5c Keep output cleanup safe to call from signal handlers 2026-09-13 17:45:03 +09:00
Rui Ueyama 1a39c9318d Give Glob a default that matches an empty pattern set 2026-09-13 17:44:00 +09:00
Rui Ueyama 42d21ec936 Preserve bytes in symbol and section name options 2026-09-13 17:43:14 +09:00
Rui Ueyama 7449dc3873 Remove the redundant Context target marker
Context already uses E through its input files and output sections, so it
does not need a PhantomData field.

Validation: workspace check across all targets.
2026-09-13 15:41:51 +09:00
Rui Ueyama 7d6e9e8a26 Keep deferred files out of ReaderJob
Reserve ReaderJob for command-line inputs. Store deferred scripts and LTO
files as tuples with required mapped-file references, and remove unused
archive metadata from the command-line job. Move reader contexts into the
script queue while preserving command-line sorting.

Validation: workspace check; 28 script, archive and LTO tests passed
(two 32-bit prerequisite skips); clang, Chrome and TensorFlow debug
outputs match the baseline.
2026-09-13 15:41:51 +09:00
Rui Ueyama e82b5d9419 Use Once to register statistics counters
Replace the manual registration flag with std::sync::Once. Keep counter
updates atomic and skip registration when statistics are disabled.

Validation: workspace check; clang, Chrome and TensorFlow debug links
with matching counter values under --stats.
2026-09-13 15:41:51 +09:00
Rui Ueyama 283712d80d Make the LoongArch target marker a unit struct
Remove PhantomData<()> from LoongArchTarget. Its const parameter needs no
marker field, and the target remains a zero-sized type.

Validation: workspace check, including both LoongArch targets.
2026-09-13 15:24:28 +09:00
Rui Ueyama 81b0eb991d Store the shuffle seed in its enum variant
Replace the separate shuffle mode and seed fields with ShuffleSections.
Keep the last explicit seed while parsing, so a later bare shuffle option
reuses it even after --reverse-sections. Extend the existing seed test to
cover that option sequence.

Validation: workspace check; shuffle and reverse integration tests;
the new option sequence also passed before the implementation change.
2026-09-13 15:24:28 +09:00
Rui Ueyama a6a78bce25 Store section order data in enum variants
Replace SectionOrderKind and its separate fields with a SectionOrder
enum. Keep names or numbers in their variants and retain the original
token only for address diagnostics.

Validation: workspace check and existing section-order integration tests.
2026-09-13 15:10:15 +09:00
Rui Ueyama a221b33e4c Store glob token data in enum variants
Allocate character tables only for bracket expressions. Literal tokens
carry their bytes, and star and question tokens carry no data. Match the
variants directly when parsing patterns and building the NFA.

Validation: workspace check; four glob unit tests; 34 glob and version
script integration tests; clang, Chrome and TensorFlow debug benchmarks.
2026-09-13 15:08:16 +09:00
Rui Ueyama 03a77ed441 Store build ID data in enum variants
Replace BuildIdKind and the separate value/hash_size fields with one
BuildId enum. Each parsed option sets a complete variant, and note
emission reads its payload.

Validation: workspace check; existing build-id test; 10 checks covering
absent IDs, fixed byte values and option overrides.
2026-09-13 14:47:02 +09:00
Rui Ueyama be273267d6 Use one enum for relocation iteration
Remove the redundant struct and constructors. Keep the iterator opaque
and retain always-inline dispatch for ordinary and deferred CREL records.

Validation: workspace check; 15 relocation and GDB-index tests; three
large debug links with byte-for-byte identical output.
2026-09-13 14:45:00 +09:00
Rui Ueyama 7ffd95453f Use a u32 sentinel for ARM32 exception-index metadata
Replace Arm32InputSectionExtra with u32, using u32::MAX for an absent
.ARM.exidx section. Add InputSectionExtra::empty so ARM32 starts at the
sentinel instead of u32's default zero.
2026-09-13 14:17:24 +09:00
Rui Ueyama 714f673cbf Use a boxed slice for relaxation metadata
Implement InputSectionExtra directly for Box<[RelocDelta]> and use it on
RISC-V and LoongArch. Remove the wrapper that only forwarded access to
its boxed slice.
2026-09-13 14:15:32 +09:00
Rui Ueyama ba7760e427 Use the unit type for empty input-section metadata
Implement InputSectionExtra for () and use it on targets without extra
section state. Remove NoInputSectionExtra and its redundant size check.
2026-09-13 14:13:11 +09:00
Rui Ueyama c718a0def7 Use fmt::from_fn for lazy diagnostic formatting
Replace the section display wrapper and both file-name display wrappers
with formatting closures. Share the file-name formatter between input
parsing and merged-section diagnostics.
2026-09-13 14:11:38 +09:00
Rui Ueyama 2cb26dab38 Fill ICF edges through disjoint mutable slices
Use Rayon's recursive splitter and split_at_mut to give each task a
separate slice of the preallocated edge array. Preserve edge order and
the per-vertex count checks while removing EdgeBuffer and its unsafe
Sync implementation.
2026-09-13 13:59:01 +09:00
Rui Ueyama 5a87ab3a50 Collect thunk symbols with Rayon fold and reduce
Use owned vectors for parallel thunk symbol collection, removing
ThunkSymbolBins and its unsafe Sync implementation. Atomic symbol marking
still deduplicates entries, and the final sort preserves output order.
2026-09-13 13:39:08 +09:00
Rui Ueyama 5ec85a91bf Use standard mutexes for reader result buffers
Collect files and scripts in per-worker vectors, locking only to append
completed results. Consume the bins after the scoped jobs join and restore
command line order as before.
2026-09-13 13:24:53 +09:00
Rui Ueyama 1c097506eb Use standard mutexes for COMDAT work buffers
Replace ComdatWorkBins with per-worker mutexes, locking once per input file.
Remove the custom UnsafeCell wrapper while preserving buffer reuse.
2026-09-13 13:24:53 +09:00
Rui Ueyama b057cc82be Use standard mutexes for output section caches
Keep one cache per Rayon worker and lock it once per input file. Remove the
custom UnsafeCell wrapper and its unsafe Sync implementation while
preserving cache reuse across jobs.
2026-09-13 13:08:41 +09:00
Rui Ueyama 67f9388285 Use u32 sentinels for optional symbol indices
Use u32::MAX for absent entries and store zero-based indices directly.
Initialize all optional slots explicitly and keep SymbolAux at 64 bytes.
2026-09-13 13:08:41 +09:00
Rui Ueyama 7f640167a2 Use a raw slice pointer for mapped file bytes 2026-09-13 12:51:54 +09:00
Rui Ueyama b7e53ea5e6 Use standard ranges for output file slices 2026-09-13 12:49:58 +09:00
Rui Ueyama e3a89f222d Reject overflowing decimal defsym values
Report a parse error instead of silently defining address zero when a
decimal --defsym value exceeds u64::MAX, as for hexadecimal values.

Test the largest valid decimal and hexadecimal values, zero, and values
beyond the limit. The new regression fails with the original Rust linker;
all seven defsym integration tests pass with this fix.
2026-09-13 12:43:30 +09:00
Rui Ueyama 60ba943a6f Sanitize start and stop symbol names byte by byte
Keep generated names as bytes and replace each non-ASCII-alphanumeric byte
with an underscore, matching C++ mold for valid and invalid UTF-8 input.

Add a regression that links references to ordinary start/stop symbols.
It passes on all 14 available target configurations and with C++ mold,
and fails with the original Rust linker.
2026-09-13 12:43:30 +09:00
Rui Ueyama 833be2a105 Preserve bytes in aliases and retained symbol names
Keep linker-script assignments, --defsym definitions and targets, retained
names, and forced undefined symbols as byte vectors through lookup and GC.
Accept raw command-line symbol names and trim retain-file entries like C++ mold.

Add a regression with distinct invalid UTF-8 and replacement-character names,
raw alias names, archive extraction, and GC roots. It passes with the Rust and
C++ linkers and fails with the original Rust linker. Existing defsym and
retain-symbols tests also pass.
2026-09-13 12:43:30 +09:00
Rui Ueyama 85a3c6f135 Use a portable operating system random source
Replace the unconditional /dev/urandom read with getrandom so ICF, UUID build
IDs, and unseeded section shuffling also work on Windows.
2026-09-13 12:43:26 +09:00
Rui Ueyama 3dfe91ce8b Document the FrozenMap entry ID invariant
Explain that linker lookups use published entry IDs from the owning map and
never probe nonexistent entries.
2026-09-13 11:00:35 +09:00
Rui Ueyama d756118ab3 Use atomic storage for COMDAT ownership
Store the temporary winning file priority in an actual AtomicU32 instead of
casting an immutable u32 field to an atomic reference. Keep ordinary
symbol-index access behind getters and an exclusive setter.

Add a concurrent ownership regression verified under Miri with both Stacked
Borrows and Tree Borrows.
2026-09-13 11:00:35 +09:00
Rui Ueyama a6d4f75da8 Use boxed ownership and live indices in FileList
Store files in Vec<Box<T>> and live entries as u32 pool indices. Resolve
access through the pool so indexed access and live iteration do not rely
on cached pointers across Box borrows.

Use disjoint pool slices for mutable and parallel iteration, preserving
stable file indices while letting Box and Vec manage ownership.
2026-09-13 11:00:04 +09:00
Rui Ueyama 301382e5a2 Merge pull request #1653 from gruenich/feature/update-github-actions 2026-09-12 16:37:00 +09:00
Christoph Grüninger 4e56b6d371 [ci] Update GitHub Actions podman-login to v2
Fix deprecation warning for Node.js 20.
Further, update actions/checkout to v7.
2026-09-12 08:32:23 +02:00
Rui Ueyama 6968a402a1 Abort on panic in release builds
Use the abort panic strategy for release builds to reduce generated
code. A panic now terminates the process without unwinding the stack.

An isolated five-run comparison across three workloads reduced .text
size by 12.7% on the local x86-64 machine and 13.6% on Fedora AArch64.
The measured link-time differences were small relative to run-to-run
variation.

Validated release builds for all 20 targets and successful links for
all three workloads on both machines.
2026-09-11 20:10:53 +09:00
Rui Ueyama af8c689722 Bump mold version to 2.42.1
Update all workspace package versions and the lockfile together.

Upstream-Commit: 9b376bc6a9
2026-09-11 17:18:54 +09:00
Rui Ueyama 9b376bc6a9 Bump mold version to 2.42.1 2026-09-11 15:56:48 +09:00
Rui Ueyama 7a7d03d186 Skip timer recording when profiling is disabled
Avoid clock reads, getrusage calls and shared record locks during ordinary
links. Explicit --perf keeps the existing timing output. Cover nested timers,
background handles and repeated stops in a unit test.
2026-09-11 14:08:17 +09:00
Rui Ueyama f359d51458 Preallocate only the appended output range
Match C++ output growth by passing the old length and appended size to
fallocate. Revisiting the already-written prefix flushes dirty extents on btrfs
and adds about a second to Chromium debug links on Fedora. The GDB-index suite
passes across available targets, and the change is neutral on ext4.
2026-09-11 14:08:17 +09:00
Rui Ueyama a25435ec43 Accept PPC64 function descriptors in path regression tests
Inspect ELF symbol type and definition with readelf instead of requiring nm to
label foo as text. PPC64 ELFv1 function symbols name descriptors in .opd and
correctly appear as data to nm. This lets the existing path and reproduction
tests run across the full target suite.
2026-09-11 14:08:17 +09:00
Rui Ueyama f6265afa79 Don't let an archive member shadow a later object file's definition
Initialize reachability when creating object, LTO, and shared inputs so
mandatory objects outrank unextracted archive members during initial resolution.
Process every --undefined-glob match, including multiple matches in reachable
files, so GC retains each matching definition. Port both upstream regressions
verbatim.

Upstream-Commit: af7d056853
2026-09-11 14:08:17 +09:00
Rui Ueyama 3fd572410d Hint uncommon symbol address cases
Mark dead fragments, copy relocations, and dead input sections as cold paths,
matching the upstream branch hints while retaining ICF leader resolution and
discarded-section diagnostics.

Upstream-Commit: 5bdd5e6284
2026-09-11 14:08:17 +09:00
Rui Ueyama c39ecd6e07 Compute imported-symbol weakness while marking imports
Collect strong undefined references during the existing import/export traversal
and apply binding changes together with the import flags. Remove the later full
object-symbol scan, preserving weak binding when every undefined reference is
weak.

Upstream-Commit: a4bcbb45ae
2026-09-11 14:08:17 +09:00
Rui Ueyama db7753b997 Cache merged output sections per worker
Remember up to 32 merged-section keys and IDs in each conversion worker,
avoiding the shared lock on cache hits. Copy immutable keys rather than
retaining pointers into the growable section vector, and discard the cache when
conversion finishes.

Upstream-Commit: 4a83b5d856
2026-09-11 14:08:17 +09:00
Rui Ueyama 6c27ac899c Gather shared-library symbol names in parallel
Record shared-library names and default-version aliases directly into worker
bins during parsing. Reserve both symbol vectors first so their recorded
slots stay stable until the existing parallel gather fills them. FileList
retains the backing files through SONAME deduplication. This removes the
temporary name arrays and the later registration pass.

Upstream-Commit: 20eb664a7e
2026-09-11 14:08:17 +09:00
Rui Ueyama 725db8a81a Avoid redundant atomic OR operations
Load flag bytes before setting bits, skipping the read-modify-write when all
requested bits are already present. Apply the shared helper to symbol and
input-section setters; keep fetch_or for operations that need the previous
value.

Upstream-Commit: c72d19dcca
2026-09-11 13:14:25 +09:00
Rui Ueyama af7d056853 Don't let an archive member shadow a later object file's definition
Initialize reachability when creating object, LTO, and shared inputs so
mandatory objects outrank unextracted archive members during the first
symbol resolution pass.

Process all --undefined-glob matches regardless of file reachability so
--gc-sections retains every matching definition. Cover archive ordering,
directly linked objects, and multiple matches in one object.
2026-09-10 14:45:33 +09:00
Rui Ueyama 5bdd5e6284 Hint uncommon symbol address cases 2026-09-10 14:45:31 +09:00
Rui Ueyama a4bcbb45ae Compute imported-symbol weakness while marking imports
The removed pass took a few milliseconds; the end-to-end change is
within noise.
2026-09-10 14:45:24 +09:00
Rui Ueyama 4a83b5d856 Cache merged output sections per worker
MergedSection::get_instance took a shared lock for every mergeable
input section, though nearly all of them resolve to the same few
output sections. Each worker now remembers up to 32 sections it has
already resolved, keyed on the same name, flags, type and entry size,
and checks them before taking the lock. The cache lives only for the
conversion pass, during which those fields are immutable.

Geometric-mean link time over the 21-program suite fell 0.87% at 32
threads on a Threadripper 7980X and 3.88% at 20 threads on an M1
Ultra, where create_merged_sections on clang-release went from 24 ms
to 10 ms.
2026-09-10 14:45:03 +09:00
Rui Ueyama 20eb664a7e Gather shared-library symbol names in parallel
Shared libraries inserted their symbol names into the symbol map one
at a time on the parsing thread, leaving a serial tail behind large
DSOs. Their unversioned names now go through the same per-thread bins
as object-file symbols, so gather_symbols creates the canonical
Symbols in parallel across hash shards. The symbol vector is reserved
up front, so the recorded slots stay valid until the gather fills them.

Geometric-mean link time over the 21-program suite fell 1.41% at 32
threads on a Threadripper 7980X, almost all of it from blender-release
(0.177 s to 0.142 s), which links against many shared libraries.
2026-09-10 14:44:31 +09:00
Rui Ueyama c72d19dcca Avoid redundant atomic OR operations
Relocation scanning requests GOT, PLT and TLS entries for the same
symbols repeatedly, and each request took exclusive ownership of the
symbol's cache line even when the bits were already set. Atomic's
operator|= now loads first and skips the read-modify-write when no
bit would change. fetch_or stays unconditional for callers that need
it.

Measured before the check moved from a symbol-specific helper into
the wrapper: geometric-mean link time over the 21-program suite fell
1.95% at 32 threads on a Threadripper 7980X and 2.71% on an M1 Ultra.
2026-09-10 14:44:16 +09:00
Rui Ueyama 1967c81832 Preserve Unix path bytes through argument and file handling
Use OsString for arguments and PathBuf for filesystem names so valid Unix paths
survive parsing and reach the filesystem unchanged. Preserve their bytes in
loader metadata, LTO callbacks, reproduction archives and dependency rules as
well.

Add a regression using compiler-generated objects with non-UTF-8 bytes and
spaces in their names. Cover direct arguments, nested response files, library
search, linker scripts, thin archives and reproduction replay.
2026-09-09 14:16:13 +09:00
Rui Ueyama 05ee232da0 Resolve symbol output chunks through the owning context
Store compact chunk IDs in symbol origins instead of pointers with erased
lifetimes and target types. Resolve each ID through an append-only context
registry, preserving the 48-byte symbol layout and removing the raw pointer
dereference.

Add runtime tests for context moves, chunk reordering and 32-bit layouts, plus
compile-fail tests for dangling and incorrectly typed references. The unit
tests, API tests, doctests and 15 related native linker tests pass.
2026-09-09 13:48:39 +09:00
Rui Ueyama 71401cb632 Preserve shared library SONAMEs as byte strings
Keep SONAME bytes intact during library deduplication, dependency checks and
dynamic string and version-table output. Distinct non-UTF-8 names must retain
distinct library identities.

Add two versioned libraries whose names previously collapsed under lossy UTF-8
decoding. Verify both names in .dynstr and run the linked program. All 43 native
SONAME, dependency and version-script tests pass.
2026-09-09 13:43:05 +09:00
Rui Ueyama 6f151f5889 Preserve symlink input names in reproduction archives
Make archive member paths absolute without resolving symlinks, matching the
names retained in response.txt. Reopen each input to copy its original contents
as before.

Add a regression that extracts and replays archives for a relative symlink and
an absolute path through a symlinked directory. Both native reproduction tests
pass.
2026-09-09 13:36:21 +09:00
Rui Ueyama cf3ad924aa Check nested linker script targets during library search
Use the reader context to determine the target of a nested script before
accepting it. This lets an incompatible script beside its parent be skipped in
favor of a compatible script in a later library directory.

Add an x86-64 regression with competing i386 and x86-64 nested scripts. It fails
before the fix and passes in C++ mold; all 15 native linker-script tests pass.
2026-09-09 13:34:20 +09:00
Rui Ueyama 434a0d7499 Read Zstd-compressed DWARF headers with streaming decompression
Stop decompression when the requested output buffer is full, allowing the
twelve-byte DWARF classification probe to read a prefix of a larger frame.
Preserve the premature-end diagnostic for short output.

Add a test with verified Zstd-compressed DWARF32 and DWARF64 objects and check
their output order. The new test fails before the fix and passes in C++ mold.
All seven native Zstd and debug-sorting tests pass.
2026-09-09 13:33:27 +09:00
Rui Ueyama 0380bea1dc Parse attached visibility labels in embedded version scripts
Recognize global:foo and local:* in the shared version-command parser so VERSION
blocks behave like standalone version scripts. Keep colons intact in input
filenames.

Add a regression covering local and global visibility and colon-containing input
names. Discover the new test/ directory alongside the existing shell suite. All
32 native version-script tests pass.
2026-09-09 13:31:47 +09:00
Rui Ueyama 9c7f14afdf Document upstream synchronization and release performance
Record how all 27 upstream commits are represented in Rust, including
existing equivalents and C++-only changes. Describe the Rust-specific
optimizations and the completed build, integration, unit-test and static
checks.

Document the 21-workload release benchmark, build settings and noise
rejection criteria. Include raw samples supporting the measured 6.1%
improvement over the original Rust build and the remaining 2.9%
geometric-mean gap to C++.
2026-09-09 10:38:33 +09:00
Rui Ueyama 3898598581 Inline common symbol address calculation
Inline addr_with and the small auxiliary-index accessors used during
relocation and symbol-table writes. Move rare discarded-section and
.eh_frame diagnostics into a cold, non-inlined helper while retaining
ICF leader handling in the common address path.

Read the auxiliary record once when checking for PLT entries. These
changes reduce call and lookup overhead without changing computed
addresses or diagnostic behavior.
2026-09-09 10:38:29 +09:00
Rui Ueyama 9196e3821d Inline literal-pattern automaton transitions
Force AhoCorasick::walk into its caller because it runs once per input
byte while matching symbol names. Keeping the state index and current
priority in the caller's registers removes per-byte call overhead. The
transition logic and highest-priority match rules stay unchanged.
2026-09-09 10:38:23 +09:00
Rui Ueyama 9494e857ac Avoid redundant reachability writes and empty GC allocations
Use mark_reachable for shared-library edges so repeatedly visiting an
already reachable DSO does not keep writing its reachability flag. This
avoids unnecessary cache-line traffic during parallel traversal.

Allocate child-work buffers only when a batch discovers new sections,
and transfer full buffers with mem::take. Exported symbols can already
make most sections roots, leaving many batches with no children; those
batches no longer need a heap allocation.
2026-09-09 10:38:18 +09:00
Rui Ueyama 9ae2154d65 Hash complete ICF digest words directly
Feed ICF digest words directly to SipHash when its byte buffer is empty,
avoiding temporary byte arrays and buffering work during propagation.
Keep the byte-update fallback for partial buffers, preserve the native
digest byte order and wrapping length counter, and inline the hot update
and finalization paths.

Compare word updates with byte updates across partial prefixes, scalar
tails and length wraparound to verify that the digest is unchanged.
2026-09-09 10:38:14 +09:00
Rui Ueyama 5ce808338e Pack optional symbol auxiliary indices into four bytes
Replace the eight Option<u32> fields in SymbolAux with four-byte indices
using u32::MAX for absence. This removes separate discriminants and
reduces each auxiliary record from 96 to 64 bytes. Use u32::MAX - 1 for
the provisional dynamic-symbol index so it remains distinct from the
missing-entry sentinel.

Update table insertion and lookup helpers, assert the record size, and
test absent indices, index zero, the provisional value and reuse.
2026-09-09 10:38:08 +09:00
Rui Ueyama e36bf62474 Fix -Wrestrict warning
Upstream changes temporary-output-name construction to avoid a GCC
-Wrestrict warning from C++ string concatenation. Rust already builds
these names through formatting and has no matching expression to fix.
This empty commit records that no Rust change is needed.

Upstream-Commit: dad2695f3e
2026-09-09 10:37:58 +09:00
Rui Ueyama b20d780768 Upgrade Debian 11 to Debian 12
Debian 11 LTS has ended, and its apt-get started failing on CI.

Upstream-Commit: fcd9dea78f
2026-09-09 10:37:54 +09:00
Rui Ueyama 47513055fb Size the CREL section table once
Allocate decoded_crel for all section indices on the first CREL section.
This avoids repeatedly growing and copying the table during section
parsing. Files without CREL allocate no table.

Upstream-Commit: ff0cb501b0
2026-09-09 10:37:51 +09:00
Rui Ueyama d4a2b4b63c Skip redundant COMDAT state updates for newly parsed files
Parsing already omits losing COMDAT members and marks retained sections
alive, so skip redundant state updates for newly parsed files. Keep the
member walk when reusing parsed files, when LTO can change ownership, or
with --gdb-index.

Upstream-Commit: 91252d8aa5
2026-09-09 10:37:47 +09:00
Rui Ueyama e8a68b1bbb Use larger thread-local arena blocks
Upstream increases per-thread arena blocks from 64 KiB to 1 MiB to
reduce global reservations, retaining the local-allocation cutoff at one
quarter of a block. Rust uses vectors and mimalloc rather than that
arena, so there is no corresponding block-size setting to change. Record
the C++ change with an empty commit.

Upstream-Commit: 4a11b7849d
2026-09-09 10:37:43 +09:00
Rui Ueyama 46efea10eb Scan global symbol names once during registration
Populate name-length caches during symbol registration. Scan global
names up to '@' or NUL, then scan the version suffix only when present,
retaining the raw length before version normalization or --wrap. This
removes a separate cache-population pass for both regular and LTO
objects.

Upstream-Commit: 53715c07ee
2026-09-09 10:37:39 +09:00
Rui Ueyama 5dfefa867a Inline small dynamic table insertion methods
Mark the .dynsym, .plt and .plt.got add_symbol functions inline so the
compiler can expand their calls in the serial table-allocation loop.
This exposes the small insertion helpers to optimization while
preserving table insertion order and index assignment.

Upstream-Commit: 41d2f376c3
2026-09-09 10:37:35 +09:00
Rui Ueyama 0800e0ed0d Reuse cached version-suffix checks for COMDAT signatures
Reuse the version-suffix flag set during global-symbol registration
instead of scanning COMDAT signature names for '@' again. Handle a bare
trailing '@' separately because it does not set the flag.

Upstream-Commit: 3a58944ff9
2026-09-09 10:37:32 +09:00
Rui Ueyama 28ddca3727 Check dynamic-symbol version conflicts in parallel
Check dynamic-symbol version conflicts in parallel, using existing
symbol-map locking and synchronized diagnostics. Keep the error
checkpoint after all checks finish.

Upstream-Commit: ec7c803207
2026-09-09 10:37:28 +09:00
Rui Ueyama 81054189b7 Allocate fragment symbols while reattaching sections
Create fragment records as section-symbol relocations are reattached,
collecting only the records each file needs in relocation order. Reserve
exactly their final slots in the central symbol vector, then construct
symbols and append their IDs to each file in parallel. This removes the
preliminary relocation-counting pass and unused dummy symbols. Retain
the cheap CREL-header capacity bound to avoid moving existing symbols.

Upstream-Commit: f9bdf4b4de
2026-09-09 10:37:24 +09:00
Rui Ueyama 7d98a49a2f Populate independent symbol-table ranges concurrently
Rust already runs the synthesized-symbol, object-file and shared-file
symbol-table writers concurrently. Their symbol, string and
extended-section-index ranges are disjoint, so independent writers need
no shared append point. This empty commit records that the upstream
parallelization is already present.

Upstream-Commit: e9de4def18
2026-09-09 10:37:18 +09:00
Rui Ueyama fc3271050f Reuse dynamic string metadata in both output writers
Keep each dynamic symbol's name and string offset after sizing .dynstr,
reusing cached GNU hash sort keys when available. Both output writers
can then use independent parallel loops without repeating prefix scans
or name lookups. The cache costs 24 bytes per dynamic symbol.

Upstream-Commit: 18d173697c
2026-09-09 10:37:14 +09:00
Rui Ueyama 5963506d8d Cache exact symbol name lengths without enlarging Symbol
Rust already caches exact u32 symbol name lengths while keeping Symbol
at 48 bytes. Its name accessors use that cache without rescanning long
names, so there is no corresponding layout or lookup change to apply.
This empty commit records the upstream equivalence.

Upstream-Commit: fa1e8edc39
2026-09-09 10:37:09 +09:00
Rui Ueyama 01b59b2155 Use a flat lookup table for symbol-map shards
Rust already uses flat hashbrown tables for symbol-map shards. Compare
cached hashes before strings and fold higher hash bits into bucket
placement so fixed shard-selection bits do not concentrate keys in the
same buckets. Preserve entropy in the high-bit lookup tags and keep
symbol storage stable while reducing lookup overhead.

Upstream-Commit: 841880212f
2026-09-09 10:37:05 +09:00
Rui Ueyama 2316c952ad Merge and lay out non-allocated strings in the background
Resolve non-allocated merged sections and assign their offsets in a
background Rayon task while symbol processing, GC and relocation
scanning continue. Give the task owned metadata and immutable input
bytes, create .comment before starting it, and publish the result before
section sorting and sizing. Keep allocated-fragment layout after GC, and
reassign offsets when debug sections are reordered.

Upstream-Commit: f585fee6ca
2026-09-09 10:36:56 +09:00
Rui Ueyama f42386cf28 Assign defined-symbol version indices in parallel
Fill .gnu.version slots in parallel after allocating the array. Each
dynamic symbol owns a distinct slot, so these writes are independent.

Upstream-Commit: 95137b235e
2026-09-09 10:36:50 +09:00
Rui Ueyama abc06691d2 Collect GC roots in per-thread buffers
Rust already collects newly marked GC roots in local Rayon worker
vectors and combines them before graph traversal. This avoids contention
on a shared concurrent vector, matching the upstream change. No source
change is needed; this empty commit records that equivalence.

Upstream-Commit: 95dbca6708
2026-09-09 10:36:47 +09:00
Rui Ueyama 6d3e8f3ff4 Stop version matching once the result is determined
Give consecutive version-script patterns with the same version index one
priority. Stop matching when no higher-priority result is possible, and
check simple substring patterns before glob patterns.

Upstream-Commit: f8cb3daf00
2026-09-09 10:36:43 +09:00
Rui Ueyama 875e3541c4 Hash mergeable fragments while splitting sections
Compute fragment hashes and cardinality estimates during the string or
record scan, avoiding a second traversal of the input contents and
fragment offsets.

Upstream-Commit: 0f582b0a23
2026-09-09 10:36:37 +09:00
Rui Ueyama ea8ee584b5 Speed up consecutive debug relocations
Debug relocations often visit consecutive merged strings. Cache the last
symbol and mergeable-section metadata in each relocation stream, so
input sections do not share mutable lookup state. Try the next fragment
before binary search and prefetch upcoming entries.

Upstream-Commit: 2c402f707f
2026-09-09 10:36:33 +09:00
Rui Ueyama e81d70eebd Hash GNU symbol names four bytes at a time
Process four unsigned bytes per iteration and factor the DJB recurrence
into pairs to shorten its dependency chain. Hash the remaining bytes
with the scalar loop.

Upstream-Commit: 046a644906
2026-09-09 10:36:25 +09:00
Rui Ueyama 4037f207fe Partition dynamic symbols in parallel
Add a stable partition helper that evaluates predicates and scatters
elements into disjoint output ranges in parallel. Use it for the
local/global and import/export partitions in sort_dynsyms, preserving
the relative order within both groups. Evaluate predicates sequentially
for small ranges.

Upstream-Commit: 8566358143
2026-09-09 10:36:20 +09:00
Rui Ueyama d09c44da9a Parallelize PLT symbol string sizing
Sum PLT symbol string sizes in parallel so large PLTs do not make
symbol-table sizing wait for a single thread.

Upstream-Commit: 477db5f372
2026-09-09 10:36:14 +09:00
Rui Ueyama f7a381610c Prefetch symbols during dynamic table allocation
Prefetch symbol records ahead of their auxiliary records to hide the two
dependent pointer loads in the serial table-allocation loop.

Upstream-Commit: 9887339f72
2026-09-09 10:36:10 +09:00
Rui Ueyama a3d83c5327 Allocate dynamic symbol auxiliary data in parallel
Keep dynamic symbols grouped by their owning input file so auxiliary
records can be initialized and indexed in parallel without globally
sorting symbol IDs. File ownership makes writes independent. Preserve
first-use order within each file, suppress repeated IDs, and retain
existing auxiliary data before the serial table-allocation loop.

Upstream-Commit: 0706198355
2026-09-09 10:36:06 +09:00
Rui Ueyama 65279c9624 Cache dynamic symbol sort keys in a contiguous array
Cache GNU hash bucket indices and name views before sorting dynamic
symbols. This avoids repeated pointer chasing, name-length lookups and
bucket calculations in the comparison function.

Upstream-Commit: cbcf79c0ef
2026-09-09 10:35:55 +09:00
Rui Ueyama 613422a00e Parallelize dynamic symbol and string table generation
Compute string offsets with parallel scans and write dynamic symbols and
strings in parallel. This removes serial work on large dynamic symbol
tables while preserving output order.

Upstream-Commit: 6060657622
2026-09-09 10:35:07 +09:00
Rui Ueyama dad2695f3e Fix -Wrestrict warning 2026-09-08 23:16:43 +09:00
Rui Ueyama fcd9dea78f Upgrade Debian 11 to Debian 12
Debian 11 LTS has ended, and its apt-get started failing on CI.
2026-09-08 22:52:43 +09:00
Rui Ueyama ff0cb501b0 Size the CREL section table once
Allocate decoded_crel for all section indices on the first CREL section.
This avoids repeatedly growing and copying the table during section
parsing. Files without CREL allocate no table.
2026-09-08 22:11:12 +09:00
Rui Ueyama 91252d8aa5 Skip redundant COMDAT state updates for newly parsed files
Parsing already omits losing COMDAT members and marks retained sections
alive, so skip redundant state updates for newly parsed files. Keep the
member walk when reusing parsed files, when LTO can change ownership, or
with --gdb-index.
2026-09-08 22:11:12 +09:00
Rui Ueyama 4a11b7849d Use larger thread-local arena blocks
Increase the per-thread arena block from 64 KiB to 1 MiB to reduce
global reservations. Keep the local-allocation cutoff at one quarter of
a block.
2026-09-08 22:11:12 +09:00
Rui Ueyama 53715c07ee Scan global symbol names once during registration
Populate name-length caches during symbol registration. Scan global
names up to '@' or NUL, then scan the version suffix only when present,
retaining the raw length before version normalization or --wrap. This
removes a separate cache-population pass for both regular and LTO
objects.
2026-09-08 22:11:12 +09:00
Rui Ueyama 41d2f376c3 Inline small dynamic table insertion methods
Move the .dynsym, .plt and .plt.got add_symbol definitions into their
classes so the compiler can inline calls in the serial table-allocation
loop.
2026-09-08 22:11:12 +09:00
Rui Ueyama 3a58944ff9 Reuse cached version-suffix checks for COMDAT signatures
Reuse the version-suffix flag set during global-symbol registration
instead of scanning COMDAT signature names for '@' again. Handle a bare
trailing '@' separately because it does not set the flag.
2026-09-08 22:11:12 +09:00
Rui Ueyama ec7c803207 Check dynamic-symbol version conflicts in parallel
Check dynamic-symbol version conflicts in parallel, using existing
symbol-map locking and synchronized diagnostics. Keep the error
checkpoint after all checks finish.
2026-09-08 22:11:12 +09:00
Rui Ueyama f9bdf4b4de Allocate fragment symbols while reattaching sections
Create fragment symbols in the arena as section-symbol relocations are
reattached. Collect pointers locally and reserve the file's symbol
vector once before appending them in relocation order, eliminating the
preliminary counting pass.
2026-09-08 22:11:12 +09:00
Rui Ueyama e9de4def18 Populate independent symbol-table ranges concurrently
Run synthesized-symbol, object-file and shared-file symbol-table writers
concurrently. They write disjoint symbol, string and
extended-section-index ranges; initialize section symbols before
starting these writers.
2026-09-08 22:11:12 +09:00
Rui Ueyama 18d173697c Reuse dynamic string metadata in both output writers
Keep each dynamic symbol's name and string offset after sizing .dynstr,
reusing cached GNU hash sort keys when available. Both output writers
can then use independent parallel loops without repeating prefix scans
or name lookups. The cache costs 24 bytes per dynamic symbol.
2026-09-08 22:11:12 +09:00
Rui Ueyama fa1e8edc39 Cache exact symbol name lengths without enlarging Symbol
Store a signed 32-bit name length in Symbol's existing padding to avoid
rescanning long names. Saturate at INT32_MAX and scan the suffix only
for saturated lengths.
2026-09-08 22:11:12 +09:00
Rui Ueyama 841880212f Use a flat lookup table for symbol-map shards
Replace per-key unordered_map nodes with linear-probing slots containing
cached hashes and pointers to arena entries. This reduces lookup
overhead while keeping symbol addresses stable.
2026-09-08 22:11:12 +09:00
Rui Ueyama f585fee6ca Merge and lay out non-allocated strings in the background
Resolve non-allocated merged sections and assign their offsets in a
low-priority TBB task while symbol processing, GC and relocation
scanning continue. Create .comment before starting the task and join
before section sorting and sizing consume the results. Keep
allocated-fragment layout after GC, and reassign offsets if debug
sections are reordered.
2026-09-08 22:11:12 +09:00
Rui Ueyama 95137b235e Assign defined-symbol version indices in parallel
Fill .gnu.version slots in parallel after allocating the array. Each
dynamic symbol owns a distinct slot, so these writes are independent.
2026-09-08 22:11:12 +09:00
Rui Ueyama 95dbca6708 Collect GC roots in per-thread buffers
Collect newly marked root sections in per-thread vectors, then flatten
them before graph traversal. This avoids contention on the shared
concurrent vector.
2026-09-08 22:11:12 +09:00
Rui Ueyama f8cb3daf00 Stop version matching once the result is determined
Give consecutive version-script patterns with the same version index one
priority. Stop matching when no higher-priority result is possible, and
check simple substring patterns before glob patterns.
2026-09-08 22:11:12 +09:00
Rui Ueyama 0f582b0a23 Hash mergeable fragments while splitting sections
Compute fragment hashes and cardinality estimates during the string or
record scan, avoiding a second traversal of the input contents and
fragment offsets.
2026-09-08 22:11:12 +09:00
Rui Ueyama 2c402f707f Speed up consecutive debug relocations
Debug relocations often visit consecutive merged strings. Cache the last
symbol and mergeable section per thread, keyed by input section and
symbol index. Try the next fragment before binary search and prefetch
upcoming entries.
2026-09-08 22:11:12 +09:00
Rui Ueyama 046a644906 Hash GNU symbol names four bytes at a time
Process four unsigned bytes per iteration and factor the DJB recurrence
into pairs to shorten its dependency chain. Hash the remaining bytes
with the scalar loop.
2026-09-08 22:11:12 +09:00
Rui Ueyama 8566358143 Partition dynamic symbols in parallel
Add a stable partition helper that evaluates predicates and scatters
elements in parallel. Use it for the local/global and import/export
partitions in sort_dynsyms, keeping the standard algorithm for small
ranges.
2026-09-08 22:11:12 +09:00
Rui Ueyama 477db5f372 Parallelize PLT symbol string sizing
Sum PLT symbol string sizes in parallel so large PLTs do not make
symbol-table sizing wait for a single thread.
2026-09-08 22:11:12 +09:00
Rui Ueyama 9887339f72 Prefetch symbols during dynamic table allocation
Prefetch symbol records ahead of their auxiliary records to hide the two
dependent pointer loads in the serial table-allocation loop.
2026-09-08 22:11:12 +09:00
Rui Ueyama 0706198355 Allocate dynamic symbol auxiliary data in parallel
Initialize SymbolAux in the per-file collection pass, where symbol
ownership makes the writes independent. This reduces work in the serial
allocation loop.
2026-09-08 22:11:12 +09:00
Rui Ueyama 6b259c2bd6 Make failed negative assertions fail the shell test harness 2026-09-08 11:16:24 +09:00
Rui Ueyama 245aea7e8a Preserve byte edges in Unicode export tries 2026-09-08 11:13:33 +09:00
Rui Ueyama 78710dc9e5 Respect signed bounds in chained import addends 2026-09-08 11:12:11 +09:00
Rui Ueyama 2bdd7be771 Prune dylibs after creating synthetic references 2026-09-08 11:11:04 +09:00
Rui Ueyama cee9b1875d Check undefined references after dead stripping 2026-09-08 11:09:51 +09:00
Rui Ueyama 740a9acda2 Retain explicitly requested symbols during dead stripping 2026-09-08 11:06:30 +09:00
Rui Ueyama 19332ebc20 Keep absolute symbol values independent of the image slide 2026-09-08 11:04:01 +09:00
Rui Ueyama 969f873f54 Filter TBD symbols and reexports by target architecture and platform 2026-09-08 11:01:46 +09:00
Rui Ueyama a22e149a47 Compile only live archive members during LTO 2026-09-08 10:54:45 +09:00
Rui Ueyama e74f6fa3c4 Report relocatable link failures before notifying the parent 2026-09-08 10:53:33 +09:00
Rui Ueyama d09eebd79d Fix symbol resolution rank comparisons 2026-09-08 10:52:41 +09:00
Rui Ueyama cbcf79c0ef Cache dynamic symbol sort keys in a contiguous array
Cache GNU hash bucket indices and name views before sorting dynamic
symbols. This avoids repeated pointer chasing, name-length lookups and
bucket calculations in the comparison function.
2026-09-07 19:02:32 +09:00
Rui Ueyama 6060657622 Parallelize dynamic symbol and string table generation
Compute string offsets with parallel scans and write dynamic symbols and
strings in parallel. This removes serial work on large dynamic symbol
tables while preserving output order.
2026-09-07 19:02:32 +09:00
Rui Ueyama b11bcd8e6d Move deferred LTO archive names into object files
Deferred LTO jobs already own archive names, but claiming each member
allocated a second copy. Move the name into the object and use the object's
archive name when setting as_needed.

Validation: all 27 native LTO tests pass. Workspace Clippy passes with
warnings as errors. All 545 upstream test files match C++ mold exactly.
2026-09-07 11:55:10 +09:00
Rui Ueyama bd95fc90ae Keep the shell test suite identical to C++ mold
Remove five Rust-only shell tests so the test cases and common support file
match the C++ suite exactly. The Cargo runner still provides test discovery and
execution.

Validation: compare every tracked test file and Git file mode against C++ mold
at 92eb17a07c60a88b3a1a8c9cef014ac84eaf0cf9; no missing, extra or modified test
cases.
2026-09-07 11:34:39 +09:00
Rui Ueyama 8d59a33536 Claim IR files for LTO in the command line order
Port C++ mold 92eb17a07c. Record IR files from
direct inputs, regular and thin archives, and linker scripts, then claim them
serially after sorting by their command line positions.

Remove the redundant claim mutex and once-only initialization guard. Require
mutable context access when claiming an IR file, and retain the simple
loaded-state check used by the serial plugin lifecycle.

Copy the upstream regression test unchanged. It reproduces an undefined
__cxx_global_var_init before the fix. All 27 native LTO tests pass afterward.
2026-09-07 11:34:39 +09:00
Rui Ueyama ee1a4c6cd5 Make linker-synthesized symbols local again unless exported
Port C++ mold 450832db6b. Apply the
internal-object binding rule after handling input-local symbols and relocatable
output, preserving global binding for exported synthetic symbols.

Copy the upstream symtab-binding test unchanged. It fails before the fix and
passes afterward. Native symbol-table, synthesized-symbol, start/stop, export
and relocatable-output checks: 24 passed, 1 skipped.
2026-09-07 11:30:30 +09:00
Rui Ueyama a01c78f771 Add the upstream data-limit regression test
Port the test from C++ mold d3cf4a23e0 unchanged.
The Rust symbol and section arenas were already replaced with vectors, so there
is no large writable arena reservation to change.

Validation: the native rlimit-data test passes with the current allocator.
2026-09-07 11:29:02 +09:00
Rui Ueyama b8876bc125 Skip an empty .sframe section
Port C++ mold 32626eba5d. Ignore empty SFrame
input before reading its header, as required for startup objects assembled by
GNU assembler 2.45.

Copy the upstream regression test unchanged. It failed with a corrupted-section
diagnostic before the fix and passes afterward. Native SFrame tests: 1 passed, 2
skipped because the assembler lacks SFrame v3 support.
2026-09-07 11:27:54 +09:00
Rui UeyamaandClaude Fable 5.1 92eb17a07c Claim IR files for LTO in the command line order
The parallel file reader handed IR files to the LTO plugin in
whatever order it found them. LLVM's LTO assumes that the module
holding the prevailing copy of a COMDAT group arrives before the
modules holding the other copies, as it does with a serial linker;
otherwise the losing copies' internal symbols, such as
__cxx_global_var_init.N, become undefined references in the LTO
result. The reader now only records IR files and claims them after
all input files have been found, sorted by command line position.
Claims were already serialized, so no parallelism is lost, and the
serializing mutex and once_flag in lto-unix.cc are no longer needed.

Fixes https://github.com/rui314/mold/issues/1649

Co-Authored-By: Claude Fable 5.1 <[email protected]>
2026-09-07 09:35:25 +09:00
Rui UeyamaandClaude Fable 5.1 450832db6b Make linker-synthesized symbols local again unless exported
35f4a334 changed is_local() to derive a symbol's binding in .symtab from
its visibility. Linker-synthesized symbols never have their visibility
merged from the internal object's ELF symbols, so they went from
STB_LOCAL to STB_GLOBAL by accident. That adds _DYNAMIC, __bss_start,
_end and the like to the set of global symbols, which confuses ABI
checkers that read .symtab.

Restore the old rule for the internal object's symbols: they are local
unless we export them. The exported ones, such as __global_pointer$ on
RISC-V or __start_*/__stop_* under -z start-stop-visibility=protected,
stay global as before.

Reported by Yao Zi in https://github.com/rui314/mold/pull/1650

Fixes https://github.com/rui314/mold/issues/1651

Co-Authored-By: Claude Fable 5.1 <[email protected]>
2026-09-06 14:55:13 +09:00
Rui Ueyama 68f9f33f3a Inline executable path lookups 2026-09-05 23:10:38 +09:00
Rui Ueyama 6bbfbcfc64 Use Origin directly instead of the OriginState alias 2026-09-05 22:46:48 +09:00
Rui Ueyama 66224d9ec2 Use memchr directly for symbol version separators 2026-09-05 22:46:28 +09:00
Rui Ueyama bc051b2e02 Use endian helpers directly in architecture backends 2026-09-05 22:43:59 +09:00
Rui Ueyama 620669c7a0 Use the standard library to find the executable path 2026-09-05 22:36:57 +09:00
Rui Ueyama c2fe4663d2 Simplify endian imports and remove unused SipHash aliases 2026-09-05 20:26:59 +09:00
Rui Ueyama fffec1424a Move endian utilities into a shared module
Keep Endian, byte order markers, and integer wrappers together in
util::endian. Add fixed-endian read/write helpers and use them from
Endian and architecture code.

Use one macro to provide new, get, and set consistently for all five
standard integer wrappers.
2026-09-05 20:15:39 +09:00
Rui Ueyama d3540dcab2 Simplify nested expressions and field access chains 2026-09-05 19:22:31 +09:00
Rui Ueyama 7cc7c4c99c Pass section headers to program header helpers
Make create_phdr's define and append helpers accept &ElfShdr directly.
Reuse section headers at their call sites and use short osec bindings
for optional sections.

Validation:
- cargo build --locked --workspace
- cargo test --locked --workspace: 5,487 integration tests passed,
  637 skipped, no failures; all 14 library tests passed. Four cross
  targets lacked a compiler or QEMU.
2026-09-05 18:06:12 +09:00
Rui Ueyama a6debf1ad3 Refactor 2026-09-05 17:53:48 +09:00
Rui Ueyama c8f06bb1a5 Rename output_chunks to chunks
Rename the module directory and update its declaration, imports, and
call sites to use crate::chunks.

Validation:
- cargo check --locked --workspace
- cargo test --locked --workspace: 5,487 integration tests passed,
  637 skipped, no failures; all library tests passed. Four cross targets
  were unavailable because their compiler or QEMU was missing.
2026-09-05 16:21:19 +09:00
Rui Ueyama 2eef5735f0 Flatten output chunk modules
Give each section a sibling module containing its type and functions,
and update callers to use the direct paths. Remove the misc and version
grouping modules and redundant nested wrappers.

Validation:
- cargo check --locked --workspace
- cargo test --locked --workspace: 5,488 integration tests passed,
  636 skipped, no failures; all library tests passed. Four cross targets
  were unavailable because their compiler or QEMU was missing.
2026-09-05 16:17:50 +09:00
Rui Ueyama 6fcdeb9088 Move map lookup and traversal to FrozenMap
ConcurrentMap exposed lookup and traversal while insertion was still
possible. Move those entry points to FrozenMap and keep the construction
API limited to allocation, insertion, prefetch, and freezing.

Freeze the GDB name map before collecting sorted entry references.
Extend the existing map test to verify lookup by the original entry IDs
after freezing, including the associated keys and values.
2026-09-05 15:38:47 +09:00
Rui Ueyama 01d51ca47c Define FrozenMap as a tuple struct
Replace FrozenMap's single named field with a private tuple field and
update freeze(), Default, and the forwarding methods to use it.
2026-09-05 15:24:11 +09:00
Rui Ueyama f9dd7ab7c6 Prefetch concurrent map buckets on more architectures
ConcurrentMap::prefetch only issued a cache hint on x86-64. Move the hint
into a shared utility and use stable inline assembly for x86, ARM32/64,
RISC-V32/64, PowerPC32/64, s390x, and LoongArch64.

Assume SSE on both x86 variants. Encode RISC-V PREFETCH.R as its ORI hint
so it remains a no-op without Zicbop. Keep unsupported architectures as
no-ops and preserve the map's empty-table guard.
2026-09-05 15:16:02 +09:00
Rui UeyamaandClaude Opus 4.8 d8f8560370 Fold objc classrefs into the GOT in a fixed order
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
2026-09-05 14:35:48 +09:00
Rui Ueyama 19416424bb Centralize transparent huge page advice
Move the Linux and Android MADV_HUGEPAGE calls into shared utilities,
with no-op implementations on other targets. Reuse them for output
mappings, concurrent-map storage, and symbol allocations, keeping the
symbol advice limited to complete pages within the allocation.

Extract platform-specific allocation and deallocation helpers for the
concurrent map, and guard page-alignment rounding against overflow.
2026-09-05 14:31:57 +09:00
Rui UeyamaandClaude Fable 5.1 e18cfde4c8 Name a symbol's owner file(), a FileId, as mold-rust does
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
2026-09-05 14:03:18 +09:00
Rui UeyamaandClaude Fable 5.1 69f304e90e Own synthesized sections and symbols by an internal object, as mold-rust does
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
2026-09-05 14:00:41 +09:00
Rui UeyamaandClaude Fable 5.1 9ef53a0e0c Rename Symbol::isec to input_section, mold-rust's accessor name
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
2026-09-05 13:51:50 +09:00
Rui UeyamaandClaude Fable 5.1 15202bda4a Move the dyld info stream builders into output_chunks/dyld_info.rs
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
2026-09-05 13:46:35 +09:00
Rui UeyamaandClaude Fable 5.1 7677acd7fe Move the chained-fixups builders into output_chunks/chained_fixups.rs
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
2026-09-05 13:46:00 +09:00
Rui UeyamaandClaude Fable 5.1 6875763e5e Move the function-starts and data-in-code builders into misc.rs
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
2026-09-05 13:34:17 +09:00
Rui UeyamaandClaude Fable 5.1 bdaa4705f1 Move the symbol table writer into output_chunks/symtab.rs
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
2026-09-05 13:33:46 +09:00
Rui UeyamaandClaude Fable 5.1 803259480e Move the export trie encoder into output_chunks/export_trie.rs
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
2026-09-05 13:33:25 +09:00
Rui UeyamaandClaude Fable 5.1 8b2952144e Move the __unwind_info encoder into output_chunks/unwind_info.rs
Pure code motion: encode_unwind_info joins UnwindInfoSection, 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
2026-09-05 13:33:07 +09:00
Rui UeyamaandClaude Fable 5.1 d5d57ea952 Move the __eh_frame writer into output_chunks/eh_frame.rs
Pure code motion: copy_eh_frame becomes EhFrameSection's copy_buf,
next to its struct, as mold-rust's eh_frame.rs is laid out.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
2026-09-05 13:32:51 +09:00
Rui UeyamaandClaude Fable 5.1 a4336ccf44 Move the code signature writer into output_chunks/misc.rs
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
2026-09-05 13:31:57 +09:00
Rui UeyamaandClaude Fable 5.1 ed5a30a558 Address output chunks by ChunkId, one struct per kind, as mold-rust does
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
2026-09-05 13:29:51 +09:00
Rui UeyamaandClaude Fable 5.1 c86b6c75f4 Check duplicate symbols in a pass of its own, as mold-rust does
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
2026-09-05 13:03:13 +09:00
Rui UeyamaandClaude Fable 5.1 e947d232a0 Rename ObjectFile.syms to symbols, mold-rust's field name
The per-file list of symbol ids; only the name diverged.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
2026-09-05 13:00:07 +09:00
Rui UeyamaandClaude Fable 5.1 5a3635bf2e Rename InputSection.output_offset to offset, mold-rust's field name
Same field, same role; only the name diverged.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
2026-09-05 12:58:15 +09:00
Rui UeyamaandClaude Fable 5.1 cd12ef85da Rename InputSection.osec to output_section, mold-rust's field name
Same field, same role; only the name diverged.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
2026-09-05 12:57:35 +09:00
Rui UeyamaandClaude Fable 5.1 9d36b7d113 Rename InputSection.data_ptr to contents, mold-rust's field name
Same field, same role; only the name diverged.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
2026-09-05 12:57:31 +09:00
Rui UeyamaandClaude Fable 5.1 2d78ede892 Rename InputSection.obj to file, mold-rust's field name
Same field, same role; only the name diverged.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
2026-09-05 12:57:27 +09:00
Rui UeyamaandClaude Fable 5.1 38a86f1171 Name the symbol table symbols and the output symtab symtab, as mold-rust does
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
2026-09-05 12:57:22 +09:00
Rui UeyamaandClaude Fable 5.1 0cb6d11160 Rename check_undefined_symbols to report_undef_errors, mold-rust's name
The pass is the same one; the name diverged for no reason.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
2026-09-05 12:56:24 +09:00
Rui UeyamaandClaude Fable 5.1 7850ba2959 Rename run_lto to do_lto, mold-rust's name
The pass is the same one; the name diverged for no reason.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
2026-09-05 12:56:23 +09:00
Rui UeyamaandClaude Fable 5.1 d3cf4a23e0 Commit the arena's address space on demand
The 8 GiB arena was mapped read-write, so on hosts with overcommit disabled
(vm.overcommit_memory=2) Linux ignored MAP_NORESERVE and charged the whole
reservation against the commit limit, and fork() charged it again for the
child. Even a hello-world link then failed with "fork: Cannot allocate
memory". Reserve the range with PROT_NONE instead and make it writable in
64 MiB chunks as the bump pointer advances, as the Windows code already does
with MEM_COMMIT. Only the chunks in use are charged, by mmap and by fork()
alike, and transparent huge pages still back them.

Under RLIMIT_DATA, which counts writable private mappings the same way, a
trivial link now needs 0.5 GiB instead of more than 8 GiB.

Fixes https://github.com/rui314/mold/issues/1644

Co-Authored-By: Claude Fable 5.1 <[email protected]>
2026-09-05 12:33:48 +09:00
Rui UeyamaandClaude Fable 5.1 35a4e42dbf Check the symbol name length, and test a 65536-byte name
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
2026-09-05 12:21:07 +09:00
Rui UeyamaandClaude Fable 5.1 cfdbd4e3dc Record thunk addresses without a temporary list
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
2026-09-05 11:20:27 +09:00
Rui UeyamaandClaude Fable 5.1 da828112de Preserve file-open errors
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
2026-09-05 11:20:27 +09:00
Rui UeyamaandClaude Fable 5.1 f3e11d9947 Make diagnostics process-wide
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
2026-09-05 11:16:25 +09:00
Rui Ueyama 35e5d81cf1 Store input section IDs as separate file and index fields
InputSectionId stored an already packed integer while FragmentRef kept
its component indices. Store a private ObjId and zero-based u32 index in
InputSectionId, with direct field access in file() and index(). Confine
the packed representation to raw() and from_raw().

Keep the eight-byte size and the existing Origin bit layout. Use the
reserved u32::MAX index for NONE and apply the index-plus-one encoding
at the packing boundary so its payload remains zero. Extend the existing
round-trip test to cover zero indices, maximum supported indices, and
the placeholder value.
2026-09-05 09:42:38 +09:00
Rui Ueyama c4b3b1ff7c Move fragment reference encoding into FragmentRef
Origin was assembling and splitting FragmentRef's two indices itself,
while InputSectionId already supplied raw and from_raw methods. Add the
same encoding interface to FragmentRef and use it from Origin::new and
Origin::get.

Keep FragmentRef's typed fields and preserve the existing packed layout.
Origin still checks the 30-bit merged-section limit before shifting the
payload to make room for its tag.
2026-09-05 09:35:12 +09:00
Rui Ueyama f564f2e4b8 Reuse known file IDs when resolving input sections
ObjectFile section access and output-section construction already know
the owning file ID, but building a section ID reloads it from each input
section. Construct IDs from the known owner and the decoded dense index,
and pass the owner once into regular_ids_mut.

Move paired ID/reference lookup to ObjectFile so it resolves the ELF
section index once. Reuse the validated dense-index accessors for shared
and mutable section access, avoiding redundant owner loads and checks.
2026-09-04 22:42:47 +09:00
Rui Ueyama 05f4bb3c84 Skip bounds checks when resolving validated section IDs
InputSectionIds are assigned when a section is inserted into a file's
dense vector, and their file indices refer to the stable file pool.
Resolving these IDs currently repeats bounds checks for both indices on
hot section-access paths.

Add internal unchecked accessors for the pool and dense section vector,
and use them in Context::input_section. Document their insertion-based
preconditions and retain debug assertions on both bounds. Keep the
unsafe operations within these accessors and the ID resolution method.
2026-09-04 22:36:14 +09:00
Rui Ueyama 93b13c89eb Limit early fragment estimates to reachable CREL inputs
The early fragment estimate scans ordinary relocation tables before
archive extraction, including members that may never be selected. That
adds work to large links such as ClickHouse just to predict capacity.

Restrict the early estimate to cheap CREL header counts from direct
inputs and already reachable archive members. Leave ordinary relocation
counting to the existing preparation of selected files after COMDAT
selection. This retains the early reservation needed by CREL-heavy links
while avoiding an extra scan of ordinary relocation records.
2026-09-04 22:11:29 +09:00
Rui Ueyama 103c68bec5 Reserve fragment-symbol capacity before populating globals
Rewriting mergeable-section relocations can append millions of dummy
symbols after global symbols have been constructed. Reserving only local
symbols allows this later append to grow the vector and copy the already
populated table, which is particularly costly for CREL-heavy links.

Estimate fragment demand before gathering globals, using relocation
counts from CREL headers and inspecting ordinary relocations that refer
to mergeable section symbols. Include that estimate in the initial
reservation and compute it across files in parallel. Reapply huge-page
advice only when vector capacity actually changes.
2026-09-04 21:12:22 +09:00
Rui Ueyama 4a0485c774 Restore support for symbol names longer than 65535 bytes
The ClickHouse benchmark contains C++ symbol names longer than the u16
limit, causing valid input to panic in Symbol::new. Restore u32 storage
for the cached name length and retain the checked conversion into that
representation.

Change the boundary test to construct a 65536-byte name successfully and
verify that Symbol::name returns all of its bytes. This removes the
restriction introduced by narrowing the length field.
2026-09-04 20:58:33 +09:00
Rui Ueyama 182785e0c4 Update comments to describe vector and file-pool ownership
Replace obsolete arena terminology with the actual storage and lifetime
rules: symbols and section members live in vectors, input files belong
to a stable pool, and parallel construction reserves disjoint ranges
before exposing writable slots.

Remove stale ArenaPtr distance restrictions and describe background
debug-info work in terms of tasks and concurrency. These are comment
updates; allocation, ownership, and linker behavior are unchanged.
2026-09-04 19:00:56 +09:00
Rui Ueyama d7ee65d5d6 Retain huge pages and reduce vector growth
Reserve an upper bound for local symbols when gathering globals so that
appending locals does not immediately move a populated symbol vector.
Request transparent huge pages for whole pages inside that allocation,
leaving partial boundary pages that may contain allocator metadata
alone.

Let per-file section vectors grow with the inputs actually inserted;
reserving for every ELF header wastes space on relocation and metadata
headers that never become InputSections. Validate the packed file-index
limit when inserting files into the pool, and use debug assertions for
the redundant checks when encoding and decoding validated section IDs.
2026-09-04 18:58:19 +09:00
Rui Ueyama 6dd1c44ad9 Decode section table entries once per lookup
Section lookups were constructing an InputSectionId and decoding it
again even when the caller only needed a reference. Factor the ELF
section table decoding into input_index and use the resulting dense
index directly for shared, mutable, and paired ID/reference access.

Keep regular entries and entries with merge metadata on the same lookup
path. Extend the Origin round-trip test to cover the full supported
32-bit section-index range.
2026-09-04 18:26:26 +09:00
Rui Ueyama 046a28ce3d Access relaxation metadata through a mutable field reference
Have InputSectionExtra expose r_deltas_mut, returning a mutable
reference to the owned delta array. InputSection::set_r_deltas can then
perform the assignment itself while retaining its nonempty-array
assertion.

RISC-V and LoongArch return their embedded field. Targets without
relaxation metadata retain the unreachable guard for attempts to mutate
it, keeping the target-specific implementation limited to field access.
2026-09-04 18:23:06 +09:00
Rui Ueyama 34be96e62e Represent mergeable sections as fragment metadata
Rename MergeableSection to MergeInfo and update the section-table flags
and accessors to describe its role as metadata for an ordinary input
section. Update merging, symbol attachment, and debug-info callers to
use the new names.

Preserve the existing ownership model: eligible SHF_MERGE inputs remain
in their dense slots and are marked dead while their contents are
represented by fragments. Store merge metadata only for those inputs,
without adding a merge-info field to every InputSection.
2026-09-04 18:22:04 +09:00
Rui Ueyama b18be913d5 Replace the section arena with per-file vectors
Logical section IDs and directly owned target state allow input sections
to move when their storage grows. Store InputSection values in each
SectionList's dense vector and resolve IDs by indexing that vector. Keep
merged inputs in their slots so existing references remain valid.

Remove SectionArena, its thread-local allocation blocks, and the manual
section destruction code. Vector ownership now supplies allocation and
destruction, and the shared virtual-memory reservation module can be
deleted because neither symbols nor input sections use it anymore.
2026-09-04 18:19:08 +09:00
Rui Ueyama f477f0e277 Embed target-specific state in input sections
The self-relative pointer to InputSectionExtras prevents input sections
from moving and requires a separate arena allocation for target state.
Make InputSection generic over Arch and embed an associated
InputSectionExtra value directly in each section.

ARM32 stores its exception-index section link, and RISC-V and LoongArch
own their relaxation-delta arrays. Other targets use a zero-sized extra
type. Propagate the target type through section owners and callers, and
remove the self-relative pointer encoding and extras allocator.
2026-09-04 18:14:56 +09:00
Rui Ueyama 1b1c50a24f Decouple input section IDs from arena addresses
Arena-relative section IDs tie callers to the location of an allocation.
Represent InputSectionId as an object-file ID and a dense index within
that file, packed into 64 bits. Reserve two bits for Origin's tag and
keep zero available as the placeholder value.

Add a dense per-file slot table that still resolves to the existing
arena during this transition. Update symbol origins, output-section
members, relaxation, and layout lookups to resolve the logical ID
through its owner. This allows section storage to change independently
of the IDs used throughout the linker.
2026-09-04 18:04:09 +09:00
Rui Ueyama cc69390144 Replace the symbol arena with a vector
Symbols are already referenced through SymbolId, so their addresses only
need to remain stable while a construction or mutation phase borrows
them. Replace the 8 GiB SymbolArena reservation with Vec<Symbol> and let
the vector own and release the initialized symbols.

Keep the existing parallel construction scheme: reserve capacity before
publishing writable slots, assign disjoint ranges to workers, and set
the initialized length after they finish. Remove the custom virtual
memory allocation, slice access, and destruction code for symbols.
2026-09-04 17:58:31 +09:00
Rui UeyamaandClaude Opus 4.8 b91e7baa16 Bind a dylib's weak exports by weak lookup, like ld64
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
2026-09-04 15:54:53 +09:00
Rui UeyamaandClaude Fable 5.1 6052c825ef Write __unwind_info with a common-encodings table and full pages, like ld64
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
2026-09-04 15:28:19 +09:00
Rui UeyamaandClaude Fable 5.1 545786e840 Give each selector one __objc_selrefs slot, shared by stubs and lists
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
2026-09-04 15:28:19 +09:00
Rui UeyamaandClaude Fable 5.1 f49d26e055 Mark GOT slots of local definitions INDIRECT_SYMBOL_LOCAL
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
2026-09-04 15:28:19 +09:00
Rui UeyamaandClaude Fable 5.1 5861925e23 List the selector stubs in the symbol table, like ld64
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
2026-09-04 15:28:19 +09:00
Rui UeyamaandClaude Fable 5.1 b5ade11e17 Auto-hide weak definitions in dylibs and bundles too
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
2026-09-04 15:28:19 +09:00
Rui UeyamaandClaude Fable 5.1 a1cf9bce53 Bind references to exported weak definitions by weak lookup, like ld64
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
2026-09-04 15:28:19 +09:00
Rui UeyamaandClaude Fable 5.1 c01bfcc8f0 Rewrite a merged class's ro record in its original section
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
2026-09-04 15:28:19 +09:00
Rui UeyamaandClaude Fable 5.1 6bfbd16b0d Coalesce selector references and CFString constants in final links too
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
2026-09-04 15:28:19 +09:00
Rui UeyamaandClaude Fable 5.1 ab2ef8d9c8 Keep a Swift class's metadata initializer when rewriting its class_ro
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
2026-09-04 15:28:19 +09:00
Rui UeyamaandClaude Fable 5.1 4427dac374 Fix the bind-special ordinals: main executable is -1, self is 0
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
2026-09-04 15:28:19 +09:00
Rui UeyamaandClaude Fable 5.1 2b210eee78 Prefer the most aligned copy of a weak definition, like ld64
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
2026-09-04 15:28:19 +09:00
Rui UeyamaandClaude Fable 5.1 cf6533ccec Never coalesce a weak definition's subsection that holds other symbols
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
2026-09-04 15:28:19 +09:00
Rui UeyamaandClaude Fable 5.1 4c1938cc5c Coalesce weak copies that differ only by trailing zero padding
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
2026-09-04 14:10:26 +09:00
Rui UeyamaandClaude Fable 5.1 f896b55a1b Lay cold functions out last, like ld64
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
2026-09-04 14:06:29 +09:00
Rui UeyamaandClaude Fable 5.1 a227f726dd Carry every input CIE and FDE through -r, in ld64's form
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
2026-09-04 14:06:27 +09:00
Rui UeyamaandClaude Fable 5.1 10e057952a Keep DWARF-mode unwind records through -r, named by symbol as ld64 does
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
2026-09-04 13:53:55 +09:00
Rui UeyamaandClaude Fable 5.1 2a808de21b Normalize a -r output's section flags like ld64
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
2026-09-04 13:53:55 +09:00
Rui UeyamaandClaude Fable 5.1 04dedaccfc Coalesce Objective-C references in -r output, like ld64
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
2026-09-04 13:53:54 +09:00
Rui UeyamaandClaude Fable 5.1 29002dffb4 Drop the __LLVM segment from -r output too
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
2026-09-04 13:41:02 +09:00
Rui UeyamaandClaude Fable 5.1 921aa4cb6d Name and order -r local symbols as ld64 does
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
2026-09-04 13:41:02 +09:00
Rui UeyamaandClaude Fable 5.1 0d43d6d32e Order the sections of a -r output as ld64 does
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
2026-09-04 13:27:16 +09:00
Rui UeyamaandClaude Fable 5.1 73014c2513 Reject a malformed __eh_frame instead of panicking
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
2026-09-04 13:15:18 +09:00
Rui UeyamaandClaude Fable 5.1 336d439bb1 Keep N_PEXT on private externs demoted by -r, like ld64
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
2026-09-04 13:15:18 +09:00
Rui UeyamaandClaude Fable 5.1 20983d5be1 Keep N_COLD_FUNC through -r, like ld64
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
2026-09-04 13:04:46 +09:00
Rui UeyamaandClaude Fable 5.1 436da8048f Name merged ObjC lists like ld64
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
2026-09-04 12:58:44 +09:00
Rui UeyamaandClaude Fable 5.1 ccd1616644 Coalesce weak definitions in -r output, like ld64
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
2026-09-04 12:53:24 +09:00
Rui UeyamaandClaude Fable 5.1 ee3a4fb8a0 Merge categories of classes whose ro record shares a subsection
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
2026-09-04 12:48:25 +09:00
Rui UeyamaandClaude Fable 5.1 ef12fdf0e5 Lazy binding for classic dyld info, laid out as ld64 does
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
2026-09-04 12:48:25 +09:00
Rui UeyamaandClaude Fable 5.1 fcf0dc4c9c Rename the synthesized-record types to DataBlob/DataField
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
2026-09-04 12:48:03 +09:00
Rui UeyamaandClaude Fable 5.1 5f16e4aaf0 Merge categories of Swift classes; validate before retiring anything
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
2026-09-04 12:39:08 +09:00
Rui UeyamaandClaude Fable 5.1 c8c8c8f1d7 Symbol-table fidelity: auto-hide through -r, list-section locals
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
2026-09-04 12:27:46 +09:00
Rui UeyamaandClaude Fable 5.1 9c4902971d Drop unreferenced local labels from -r output, like ld64
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
2026-09-04 12:21:55 +09:00
Rui UeyamaandClaude Fable 5.1 a0225e56ab Merge Objective-C categories into their classes, like ld64
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
2026-09-04 12:15:37 +09:00
Rui UeyamaandClaude Fable 5.1 16f3143f8d Rewrite Objective-C method lists in the relative form, like ld64
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
2026-09-04 12:01:52 +09:00
Rui UeyamaandClaude Fable 5.1 abdd885e06 Weak dylib loads, weak-reference mismatches and MH_BINDS_TO_WEAK as ld64
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
2026-09-04 11:47:41 +09:00
Rui UeyamaandClaude Fable 5.1 a9c7c7da55 Bind through re-exports to public dylibs, loaded implicitly
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
2026-09-04 11:42:26 +09:00
Rui UeyamaandClaude Fable 5.1 e2fde99867 Act on auto-link options as a sorted set; drop unused auto-linked dylibs
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
2026-09-04 11:35:30 +09:00
Rui UeyamaandClaude Fable 5.1 322825333f Order load commands as ld64 does
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
2026-09-04 11:35:30 +09:00
Rui UeyamaandClaude Fable 5.1 f290cc4796 Fold __objc_classrefs into __got from macOS 15 on, like ld-prime
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
2026-09-04 11:26:56 +09:00
Rui UeyamaandClaude Fable 5.1 b72c09a80e Match ld-prime's default fixup format
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
2026-09-04 11:16:36 +09:00
Rui UeyamaandClaude Fable 5.1 4539d72f6f Place, order and flag output sections as ld64 does
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
2026-09-04 11:14:55 +09:00
Rui UeyamaandClaude Fable 5.1 b597bac992 Keep each atom's offset modulo its section alignment, like ld64
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
2026-09-04 10:55:02 +09:00
Rui UeyamaandClaude Fable 5.1 bafbd7f868 Ignore auto-link options that name nothing, silently
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
2026-09-04 06:55:11 +09:00
Rui UeyamaandClaude Fable 5.1 6d43d23117 Read a dylib's exports from its export trie
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
2026-09-04 06:38:49 +09:00
Rui UeyamaandClaude Fable 5.1 fdc4ebedd8 Keep n_desc flags on symbols re-emitted by -r
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
2026-09-04 06:30:09 +09:00
Rui UeyamaandClaude Fable 5.1 b4e449e240 Load Swift archive members under -ObjC like ld64
-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
2026-09-04 06:30:09 +09:00
Rui UeyamaandClaude Fable 5.1 a9b5416bab Emit the merged __objc_imageinfo in -r output
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
2026-09-04 06:13:44 +09:00
Rui UeyamaandClaude Fable 5.1 194e009786 Add -keep_private_externs; demote private externs in -r by default
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
2026-09-04 05:55:57 +09:00
Rui UeyamaandClaude Fable 5.1 b821d9a02b Write debug-note stabs in -r output instead of merging DWARF
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
2026-09-04 05:54:01 +09:00
Rui UeyamaandClaude Fable 5.1 d1cfb1c061 Do not intern the entry symbol in -r links
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
2026-09-04 05:37:01 +09:00
Rui UeyamaandClaude Fable 5.1 167b87dd52 Add -final_output
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
2026-09-04 05:37:01 +09:00
Rui UeyamaandClaude Fable 5.1 a6441fedaf Follow re-exports into universal binaries and into dylibs already loaded
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
2026-09-04 05:37:01 +09:00
Rui UeyamaandClaude Fable 5.1 06c2e419de Add -bundle_loader
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
2026-09-04 05:24:54 +09:00
Rui UeyamaandClaude Fable 5.1 e12e9df39d Read TBD version 5 (JSON) stubs
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
2026-09-04 05:19:14 +09:00
Rui UeyamaandClaude Fable 5.1 13ae0d8d60 Accept relocations one past a section's end; skip empty sections in copy
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
2026-09-04 05:17:43 +09:00
Rui UeyamaandClaude Fable 5.1 3d27ac14df Resolve @loader_path from a dylib's real path, symlinks resolved
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
2026-09-04 05:08:14 +09:00
Rui UeyamaandClaude Fable 5.1 ad7330409d Make an -alias of an imported symbol an indirect re-export
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
2026-09-04 05:08:14 +09:00
Rui UeyamaandClaude Fable 5.1 b3764733cf Support an entry point defined in a dylib
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
2026-09-04 05:08:14 +09:00
Rui UeyamaandClaude Fable 5.1 eaef924229 Accept a numeric platform in -platform_version
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
2026-09-04 04:58:03 +09:00
Rui UeyamaandClaude Fable 5.1 252282f3f1 Do not rebase the slot of a 64-bit SUBTRACTOR pair; remap FDEs at layout
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
2026-09-04 04:58:03 +09:00
Rui UeyamaandClaude Fable 5.1 e3ed38b00a Merge the objc stubs' selector refs into the input __objc_selrefs
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
2026-09-04 04:51:35 +09:00
Rui UeyamaandClaude Fable 5.1 01cfbb33ce Keep SUBTRACTOR/UNSIGNED pairs in order when sorting relocations
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
2026-09-04 04:23:27 +09:00
Rui UeyamaandClaude Fable 5.1 1716e758b5 Carry auto-link options through -r instead of acting on them
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
2026-09-04 04:23:27 +09:00
Rui UeyamaandClaude Fable 5.1 36eec6f880 Accept the probes and options Xcode's build system sends
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
2026-09-04 04:23:27 +09:00
Rui UeyamaandClaude Fable 5.1 010a63e57c Add install.sh creating the ld64.mold symlink
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
2026-09-04 00:02:37 +09:00
Rui UeyamaandClaude Fable 5.1 c4dd05d8c1 Name the binary mold and exit 0 for a bare -v
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
2026-09-04 00:01:36 +09:00
Rui Ueyama e70f3ca23e Limit symbol name lengths to 16 bits
Use u16 for Symbol::name_len, reducing the width of the cached length
field while keeping the existing thin pointer to the name bytes. This
representation imposes a maximum symbol name length of 65535 bytes.

Check the conversion in Symbol::new so that longer names produce an
explicit diagnostic. Add a regression test for a 65536-byte name to
exercise that boundary.
2026-09-03 21:45:16 +09:00
Rui Ueyama d3e688cd83 Use arena IDs for symbol input sections 2026-09-03 21:37:37 +09:00
Rui Ueyama e9f327a938 Store input-section origins by index 2026-09-03 18:47:26 +09:00
Rui Ueyama 417d17cbea Simplify origin value types 2026-09-03 16:22:39 +09:00
Rui Ueyama 4f20b0e648 Unify symbol origin encoding 2026-09-03 16:16:27 +09:00
Rui Ueyama 3d0fa5d93c Refactor symbol origin decoding 2026-09-03 16:09:29 +09:00
Rui Ueyama ee48017b66 Trim unnecessary inline annotations
Let the compiler decide whether to inline private and generic helpers that do
not correspond to explicitly inline C++ code. Keep forced inlining on the hot
ELF and relocation paths identified by the C++ implementation and perf
profiles.

Firefox-debug performance remained neutral in both single-threaded and
32-thread runs. The full test suite and release clippy checks pass.
2026-09-03 15:57:45 +09:00
Rui UeyamaandClaude Fable 5.1 001ab16553 Write the output from background threads as each range becomes final
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
2026-09-03 12:41:41 +09:00
Rui UeyamaandClaude Fable 5.1 207f231c41 Derive LC_UUID from the code signature's page hashes
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
2026-09-03 12:33:23 +09:00
Rui UeyamaandClaude Fable 5.1 4338c3d6c8 Keep the pessimistic thunk entries: the trim pass costs 5% for 0.07%
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
2026-09-03 11:42:19 +09:00
Rui UeyamaandClaude Fable 5.1 d2d92b3b64 Point symbols at their merged literal's surviving copy
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
2026-09-03 11:31:41 +09:00
Rui UeyamaandClaude Fable 5.1 ea24f6e901 Mark dead-strip liveness in place on an atomic section flag
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
2026-09-03 11:29:45 +09:00
Rui UeyamaandClaude Fable 5.1 2f876fc539 Port mold-rust's thunk model: symbol-owned entries, trimmed once addresses are final
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
2026-09-03 11:24:20 +09:00
Rui UeyamaandClaude Fable 5.1 a52e24ab3d Make Symbol's flag word atomic with a mark bit; give SymAux thunk_addrs
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
2026-09-03 11:16:14 +09:00
Rui UeyamaandClaude Fable 5.1 d34649e7d1 Compact InputSection from 64 to 56 bytes (shndx instead of a header pointer)
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
2026-09-03 11:11:40 +09:00
Rui UeyamaandClaude Fable 5.1 ac110785f0 Walk only the local or external half of an object's symbol table
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
2026-09-03 11:07:43 +09:00
Rui UeyamaandClaude Fable 5.1 cafb1be2ec Compact Cie and Fde (64 -> 48, 88 -> 56 bytes)
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
2026-09-03 10:52:21 +09:00
Rui UeyamaandClaude Fable 5.1 5cc83eff7c Store symbol and subsection ids as u32
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
2026-09-03 10:49:14 +09:00
Rui UeyamaandClaude Fable 5.1 87f83b0d31 Pack Symbol to 40 bytes and make the slot side table sparse
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
2026-09-03 10:41:06 +09:00
Rui UeyamaandClaude Fable 5.1 0d8af191ea Derive a subsection's address instead of caching it (InputSection 72 -> 64)
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
2026-09-03 10:35:13 +09:00
Rui UeyamaandClaude Fable 5.1 df8ec7df04 Pack a relocation's target into one word (Reloc 32 -> 24 bytes)
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
2026-09-03 10:30:33 +09:00
Rui UeyamaandClaude Fable 5.1 cff22708a8 Use each object's nlist array straight from the mapped file
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
2026-09-03 10:28:12 +09:00
Rui UeyamaandClaude Fable 5.1 e10f2115d0 Key string-table shards by symbol id so the output is reproducible
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
2026-09-03 10:09:09 +09:00
Rui UeyamaandClaude Fable 5.1 d9893a93a8 Flatten the export trie in parallel
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
2026-09-03 10:05:59 +09:00
Rui UeyamaandClaude Fable 5.1 519b9af855 Compact UnwindRecord from 80 to 32 bytes
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
2026-09-03 09:41:51 +09:00
Rui UeyamaandClaude Opus 4.8 4e051eb6f2 Compact InputSection from 80 to 72 bytes
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
2026-09-03 09:05:54 +09:00
Rui Ueyama 8bd1468e66 Store InputSection contents as a bare pointer (88 -> 80 bytes)
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.
2026-09-03 00:34:27 +09:00
Rui Ueyama f5f6f8f049 Narrow InputSection.output_offset to u32 (96 -> 88 bytes)
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.
2026-09-03 00:25:50 +09:00
Rui Ueyama e30950020c Compact InputSection from 112 to 96 bytes
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.
2026-09-03 00:19:00 +09:00
Rui Ueyama b46ad35b98 Pack Symbol's eight booleans into one byte (56 -> 48 bytes)
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.
2026-09-03 00:08:28 +09:00
Rui Ueyama f05a6fc56d Scan each thunk batch's relocations in parallel
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.
2026-09-03 00:02:17 +09:00
Rui Ueyama b076efb8af Parallelize symbol construction and id scatter in the sharded gather
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.
2026-09-02 23:54:21 +09:00
Rui Ueyama 4347f987c1 Parallelize the extern-symbol batch and local-symbol counting in load
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.
2026-09-02 23:54:21 +09:00
Rui Ueyama c88493a8ab Skip the per-subsection name hash in output-section assignment
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.
2026-09-02 23:54:21 +09:00
Rui Ueyama febb3b5e7f Encode the export trie without a pointer hash map and emit in parallel
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.
2026-09-02 23:54:21 +09:00
Rui Ueyama c590bc9351 Sort output symbols by name with an unstable parallel sort
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.
2026-09-02 23:18:22 +09:00
Rui Ueyama e3e64867ed Parallelize weak-def coalescing and auto-hiding
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.
2026-09-02 23:13:51 +09:00
Rui Ueyama a0b77d3e78 Compact Reloc from 40 to 32 bytes (u32 target payloads and thunk_off)
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.
2026-09-02 22:56:59 +09:00
Rui UeyamaandClaude Fable 5 1275469fe7 Store symbol origin object/dylib indices as u32
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
2026-09-02 22:41:15 +09:00
Rui UeyamaandClaude Fable 5 2c37160a86 Store a symbol's defining subsection as a u32 index
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
2026-09-02 22:34:04 +09:00
Rui UeyamaandClaude Fable 5 25dba85262 Move symbol slot indices to an aux side table
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
2026-09-02 22:25:25 +09:00
Rui UeyamaandClaude Fable 5 058b391fe4 Pack Symbol's synthetic-slot indices as u32 sentinels
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
2026-09-02 22:12:21 +09:00
Rui UeyamaandClaude Fable 5 07772763f5 Reference the section header from subsections, don't embed it
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
2026-09-02 22:02:33 +09:00
Rui UeyamaandClaude Fable 5 1b4055b00f Store relocations per object, not a Vec per subsection
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
2026-09-02 21:50:47 +09:00
Rui Ueyama 1ce76ea568 Clean up translated comments
Merge duplicated C++ and Rust comment blocks into single explanations that
describe the Rust implementation while retaining the original rationale.
Remove stale C++ type references and restore the missing rustdoc summary for
sorted concurrent-map entries.
2026-09-02 21:39:33 +09:00
Rui Ueyama 3396be62b4 Remove unused linker APIs
Remove helpers, accessors, and constants that are unreachable from the mold
command and its tests. This includes wrapper methods whose only callers were
other dead wrappers, unused ELF integer operations, and unused concurrent-map
APIs.

Keep the remaining internal len methods crate-visible so Clippy does not treat
them as incomplete public collection APIs.
2026-09-02 21:29:43 +09:00
Rui UeyamaandClaude Fable 5 e5ad5c7aea Skip identical-code folding for objects with debug info
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
2026-09-02 20:55:41 +09:00
Rui Ueyama 7a340c6469 Store origin tags in the origin word
Keep Origin eight bytes and use its low two bits to distinguish input
sections, output chunks, merged-section fragments, and symbols. Remove the
separate origin-kind bits from Symbol’s visibility byte.

This follows C++ mold’s tagged-pointer representation without the auxiliary
tables and per-variant codecs required by the discarded 32-bit experiment.
2026-09-02 20:47:56 +09:00
Rui UeyamaandClaude Fable 5 a0d65078e0 Write planned stab entries into prefix-summed ranges
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
2026-09-02 20:47:02 +09:00
Rui UeyamaandClaude Fable 5 d4b6be2653 Write the string table straight into the output
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
2026-09-02 20:27:06 +09:00
Rui Ueyama f6f2e909a6 Revert "Pack symbol origins into 32 bits"
The experiment reduced Symbol from 48 to 40 bytes, but it required a
manual codec for every ChunkId variant, a separate namespace for allocated
merged sections, and context-dependent origin decoding. It added 434 net
lines.

It also made the previous Rust linker about 2% slower on Firefox debug and
10% slower on TensorFlow debug, even though it remained faster than C++ mold.
Prefer the simpler 64-bit origin until the memory saving justifies those
costs.
2026-09-02 20:07:33 +09:00
Rui Ueyama 68f8cbc3d6 Pack symbol origins into 32 bits
Use unary low-bit tags so fragment origins have a 31-bit payload. Pack
allocated merged-section IDs and fragment bucket IDs into that payload,
and use compact encodings for input sections, chunks, and symbols.

Also compact the remaining symbol metadata so that Symbol shrinks from 48
to 40 bytes.
2026-09-02 20:07:10 +09:00
Rui Ueyama 94e2104d70 Remove temporary thunk address vectors
Split the output-section and symbol-table borrows so
gather_thunk_addresses can record addresses while traversing thunks. This
matches the C++ loop and avoids a temporary allocation for each section.
2026-09-02 17:44:14 +09:00
Rui Ueyama 4322dad7c2 Inline stderr terminal checks
Call IsTerminal directly at the two sites that select automatic diagnostic
coloring, and remove the one-line helper.
2026-09-02 17:32:30 +09:00
Rui Ueyama de1123d600 Make the archive member parser an iterator
Replace the callback-based internal parser with an Iterator built using
std::iter::from_fn. Keep the eager Vec results and member creation order
unchanged.
2026-09-02 17:25:02 +09:00
Rui Ueyama a37ae7e290 Preserve file-open errors
Return the original io::Error from the shared file-opening path instead of
discarding it and reading errno later. Keep optional file lookup semantics by
mapping NotFound to None, and remove errno_string.
2026-09-02 17:12:51 +09:00
Rui Ueyama 20d42d3a31 Update C++-specific comments for Rust
Rewrite comments copied verbatim from C++ so they describe the Rust
implementation and terminology. Remove notes that have no Rust counterpart,
and update source-file references to their Rust names.

Keep C++ comparisons when they document an intentional implementation
difference or a remaining performance gap.
2026-09-02 16:38:15 +09:00
Rui UeyamaandClaude Fable 5 72a9021baf Count ICF classes with mold-rust's DigestMap
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
2026-09-02 15:51:01 +09:00
Rui UeyamaandClaude Fable 5 373834c5b6 Dedup the string table by pointer, not by content
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
2026-09-02 15:38:20 +09:00
Rui UeyamaandClaude Fable 5 9cb01c768e Make ICF a faithful port of mold-rust's
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
2026-09-02 15:28:44 +09:00
Rui UeyamaandClaude Fable 5 037cfb9b44 Gate range-extension thunks on the segment's executable total
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
2026-09-02 15:17:43 +09:00
Rui UeyamaandClaude Fable 5 5af9cab92b Keep each subsection's unwind records as a range
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
2026-09-02 15:17:43 +09:00
Rui UeyamaandClaude Fable 5 427406c65e Plan stab entries per object in parallel
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
2026-09-02 14:59:17 +09:00
Rui UeyamaandClaude Fable 5 bb5398172b Sort the defined globals once for the symtab and the trie
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
2026-09-02 14:59:17 +09:00
Rui UeyamaandClaude Fable 5 238d70cd7e Mark live sections with work-stealing tasks, not rounds
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
2026-09-02 14:43:42 +09:00
Rui UeyamaandClaude Fable 5 32626eba5d Skip an empty .sframe section
GNU assembler 2.45 emits a zero-size .sframe section for an input file
that needs no unwind info, such as glibc's Scrt1.o on Ubuntu 25.10.
parse_sframe() read a header past the end of such a section and reported
it as corrupted.

Fixes https://github.com/rui314/mold/issues/1646

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-09-02 14:36:22 +09:00
Rui UeyamaandClaude Fable 5 3bb56a5d22 Move staged objects into the arenas in parallel
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
2026-09-02 14:16:18 +09:00
Rui UeyamaandClaude Fable 5 2a48c5e998 Claim local symbols per object in parallel
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
2026-09-02 14:14:17 +09:00
Rui UeyamaandClaude Fable 5 ad7ad42759 Emit the output symbol table in parallel blocks
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
2026-09-02 14:11:37 +09:00
Rui UeyamaandClaude Fable 5 6f325a1cba Split cstring literals with memchr
The per-string NUL scan in literal splitting used a byte-at-a-time
iterator; memchr vectorizes it, the same as the strtab name reads.

Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
2026-09-02 14:08:13 +09:00
Rui UeyamaandClaude Fable 5 27ad36a6f3 Distribute relocations to subsections with one merge walk
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
2026-09-02 14:08:13 +09:00
Rui UeyamaandClaude Fable 5 8a35f74f9b Build the LINKEDIT tables as one parallel task group
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
2026-09-02 13:56:36 +09:00
Rui UeyamaandClaude Fable 5 501f2cbef8 Populate the string table at precomputed offsets in parallel
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
2026-09-02 13:56:36 +09:00
Rui UeyamaandClaude Fable 5 feaaed0ec4 Propagate ICF digests three times per class count
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
2026-09-02 13:56:36 +09:00
Rui UeyamaandClaude Fable 5 fdc8839486 Build the export trie by parallel divide and conquer
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
2026-09-02 13:37:40 +09:00
Rui UeyamaandClaude Fable 5 5518410457 Replace the output-symtab index map with a flat array
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
2026-09-02 13:37:40 +09:00
Rui UeyamaandClaude Fable 5 b2b1a91b61 Sort symbol names by a cached eight-byte prefix key
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
2026-09-02 13:37:40 +09:00
Rui UeyamaandClaude Fable 5 64efedd281 Assign subsections their final addresses in the layout scan
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
2026-09-02 13:27:55 +09:00
Rui UeyamaandClaude Fable 5 87b5928596 Plan local symbols per object; flatten the trie fixpoint
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
2026-09-02 13:27:55 +09:00
Rui UeyamaandClaude Fable 5 6c5cf73d5f Build the export trie from sorted names, in parallel
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
2026-09-02 13:27:55 +09:00
Rui UeyamaandClaude Fable 5 46c133999c Claim auto-linked dylibs lightly; stop ICF refinement at fixpoint
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
2026-09-02 13:27:55 +09:00
Rui UeyamaandClaude Fable 5 6b200d4672 Classify the symbol table in one parallel pass
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
2026-09-02 13:27:55 +09:00
Rui UeyamaandClaude Fable 5 42f830f1fc Build __unwind_info's second-level pages in parallel
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
2026-09-02 13:27:55 +09:00
Rui UeyamaandClaude Fable 5 4c5545e1d3 Use the mold fork of mimalloc
Same pinned git dependency as mold-rust's cli crate, so the two
linkers run on the identical allocator build.

Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
2026-09-02 13:27:55 +09:00
Rui UeyamaandClaude Fable 5 b7fb2518a7 Use mimalloc, as mold does everywhere
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
2026-09-02 13:27:55 +09:00
Rui UeyamaandClaude Fable 5 a5b00341ac Fork a child to hide exit latency
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
2026-09-02 13:27:55 +09:00
Rui UeyamaandClaude Fable 5 1cf68b16da Build the output string table in parallel
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
2026-09-02 13:27:55 +09:00
Rui UeyamaandClaude Fable 5 1f5df5c5aa Parse .tbd stubs in one pass
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
2026-09-02 13:27:55 +09:00
Rui UeyamaandClaude Fable 5 00be3dd589 Parse stub libraries in parallel, borrowing their strings
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
2026-09-02 13:27:55 +09:00
Rui Ueyama 2e731001d9 Make diagnostics process-wide
Mold performs one link per process, so keep diagnostic settings, error state,
and output serialization in a process-wide singleton. This removes
diagnostic plumbing from contexts and helper APIs while preserving the mutex
that keeps messages from worker threads from interleaving.
2026-09-02 12:27:24 +09:00
Rui UeyamaandClaude Fable 5 a7c3255ba1 Snapshot symbol addresses when layout reaches __LINKEDIT
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
2026-09-02 12:04:29 +09:00
Rui UeyamaandClaude Fable 5 f66a44b321 Validate string tables once, not per name
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
2026-09-02 12:01:29 +09:00
Rui UeyamaandClaude Fable 5 22d053d82c Group sections by raw names and write the output in parallel
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
2026-09-02 11:55:59 +09:00
Rui UeyamaandClaude Fable 5 9da117f38b Integrate staged objects in parallel
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
2026-09-02 11:51:46 +09:00
Rui UeyamaandClaude Fable 5 b5d58d75da Encode __unwind_info once, patch personalities at copy
__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
2026-09-02 11:48:38 +09:00
Rui UeyamaandClaude Fable 5 7dd685ca14 Parallelize resolution, fixup collection and symtab scans
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
2026-09-02 11:38:18 +09:00
Rui UeyamaandClaude Fable 5 dd218b9194 Refine ICF over digests, not content
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
2026-09-02 11:38:18 +09:00
Rui UeyamaandClaude Fable 5 9c2da67f2d Trace sub-phases under MOLD_TIMING
-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
2026-09-02 11:27:57 +09:00
Rui UeyamaandClaude Fable 5 12ceaac4c4 Copy and relocate subsections in parallel
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
2026-09-02 11:26:38 +09:00
Rui UeyamaandClaude Fable 5 33b39f5552 Hash the UUID in parallel
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
2026-09-02 11:26:36 +09:00
Rui UeyamaandClaude Fable 5 651a392914 Encode the export trie once
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
2026-09-02 11:26:07 +09:00
Rui UeyamaandClaude Fable 5 25de6ce140 Mark live sections in parallel
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
2026-09-02 11:26:05 +09:00
Rui UeyamaandClaude Fable 5 e1b550629b Deduplicate the string table with prehashed keys
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
2026-09-02 11:25:38 +09:00
Rui UeyamaandClaude Fable 5 3943a958b5 Prehash symbol names with xxh3, mold's key discipline
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
2026-09-02 11:25:15 +09:00
Rui UeyamaandClaude Fable 5 e732289120 Map input files instead of reading them
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
2026-09-02 11:24:48 +09:00
Rui UeyamaandClaude Fable 5 3923c68304 Adopt mold-rust's pass and function names
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
2026-09-02 11:24:25 +09:00
Rui UeyamaandClaude Fable 5 1f60a66171 Discard losing copies of coalesced weak definitions
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
2026-09-02 10:43:23 +09:00
Rui UeyamaandClaude Fable 5 34fa8170eb Skip thunk entries for forward targets within reach
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
2026-09-02 10:43:23 +09:00
Rui UeyamaandClaude Fable 5 719d963c24 Support -no_warn_duplicate_libraries
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
2026-09-02 10:43:23 +09:00
Rui UeyamaandClaude Fable 5 aa757efc3f Compress the rebase opcode stream
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
2026-09-02 10:30:57 +09:00
Rui UeyamaandClaude Fable 5 0f9ae3307c Deduplicate the string table
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
2026-09-02 10:26:53 +09:00
Rui UeyamaandClaude Fable 5 24f4a685a1 Carry DWARF frame info through -r
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
2026-09-02 08:49:27 +09:00
Rui UeyamaandClaude Fable 5 30c65ca2e4 Port mold's sharded symbol interning
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
2026-09-02 02:44:56 +09:00
Rui UeyamaandClaude Fable 5 a153f7456d Document why the output is not written through mmap
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
2026-09-02 02:38:07 +09:00
Rui UeyamaandClaude Fable 5 7a89667804 Support -print_statistics
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
2026-09-02 02:33:01 +09:00
Rui UeyamaandClaude Fable 5 51cc15626e Apply linker optimization hints
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
2026-09-02 02:31:15 +09:00
Rui UeyamaandClaude Fable 5 8118036c65 Honor arch and object qualifiers in -order_file
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
2026-09-02 02:24:30 +09:00
Rui UeyamaandClaude Fable 5 f172bd24b4 Lay out output sections in parallel
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
2026-09-02 02:23:30 +09:00
Rui UeyamaandClaude Fable 5 b5a9d94602 Move archive reading into archive_file.rs
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
2026-09-02 02:20:41 +09:00
Rui UeyamaandClaude Fable 5 1e165ad192 Move dead stripping into dead_strip.rs
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
2026-09-02 02:20:08 +09:00
Rui UeyamaandClaude Fable 5 73e04518bb Move range-extension thunks into thunks.rs
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
2026-09-02 02:19:13 +09:00
Rui UeyamaandClaude Fable 5 7dcffd8f53 Report dead-stripped symbols in the map file
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
2026-09-02 02:16:01 +09:00
Rui UeyamaandClaude Fable 5 63320a0989 Support -non_global_symbols_strip_list and -keep_list
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
2026-09-02 02:15:31 +09:00
Rui UeyamaandClaude Fable 5 7e1b043191 Support -t
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
2026-09-02 02:13:03 +09:00
Rui UeyamaandClaude Fable 5 cb1e7dec6e Relax TLV loads of local thread-locals
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
2026-09-02 02:13:03 +09:00
Rui UeyamaandClaude Fable 5 4ea28585cb Relax GOT loads of local symbols
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
2026-09-02 02:13:03 +09:00
Rui UeyamaandClaude Fable 5 608a6174f9 Support -allowable_client and enforce subframework linking
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
2026-09-02 02:13:03 +09:00
Rui UeyamaandClaude Fable 5 c805c9a4ae Support -sectalign
-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
2026-09-02 02:13:03 +09:00
Rui UeyamaandClaude Fable 5 e49dd32e33 Support -alias and -alias_list
-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
2026-09-02 02:13:03 +09:00
Rui UeyamaandClaude Fable 5 b34501a1d0 Support -why_live
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
2026-09-02 02:13:03 +09:00
Rui UeyamaandClaude Fable 5 48fb84963b Support -why_load
"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
2026-09-02 02:13:02 +09:00
Rui UeyamaandClaude Fable 5 a295b02901 Carry DWARF through -r
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
2026-09-02 02:12:46 +09:00
Rui UeyamaandClaude Fable 5 896edd590d Port sold's static-initializer exception test
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
2026-09-02 02:12:46 +09:00
Rui UeyamaandClaude Fable 5 0de2a32e99 Interpret $ld$ linker directives in .tbd stubs
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
2026-09-02 02:12:46 +09:00
Rui UeyamaandClaude Fable 5 c97a360887 Support --print-dependencies
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
2026-09-02 02:12:46 +09:00
Rui UeyamaandClaude Fable 5 e3bb687078 Support -object_path_lto
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
2026-09-02 02:12:27 +09:00
Rui UeyamaandClaude Fable 5 f6d967e610 Warn on app-extension-unsafe dylibs
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
2026-09-02 02:12:27 +09:00
Rui UeyamaandClaude Fable 5 7f7d50f461 Resolve @rpath, @loader_path and @executable_path in reexports
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
2026-09-02 02:12:27 +09:00
Rui UeyamaandClaude Fable 5 e8477e88b4 Support section$start/section$end boundary symbols
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
2026-09-02 02:12:27 +09:00
Rui UeyamaandClaude Fable 5 018a315368 Support -order_file
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
2026-09-02 02:12:27 +09:00
Rui UeyamaandClaude Fable 5 2e665fa0a8 Support -export_dynamic and auto-hide executables' weak definitions
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
2026-09-02 02:12:27 +09:00
Rui UeyamaandClaude Fable 5 e588ba8b94 Imply -init_offsets under chained fixups
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
2026-09-02 02:12:27 +09:00
Rui UeyamaandClaude Fable 5 f54bd0a33f Diagnose unaligned chained fixups with their source
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
2026-09-02 02:12:27 +09:00
Rui UeyamaandClaude Fable 5 7d8b3744a9 Port sold's bind-addend tests
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
2026-09-02 02:12:27 +09:00
Rui UeyamaandClaude Fable 5 7760d8132e Port sold's literal-deduplication test
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
2026-09-02 02:12:27 +09:00
Rui UeyamaandClaude Fable 5 fa0b0408d4 Stamp the linker in LC_BUILD_VERSION
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
2026-09-02 02:12:27 +09:00
Rui UeyamaandClaude Fable 5 126c684249 Support LC_DATA_IN_CODE
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
2026-09-02 02:12:27 +09:00
Rui UeyamaandClaude Fable 5 2d06ea4d45 Match ld64's map file format exactly
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
2026-09-02 02:12:27 +09:00
Rui UeyamaandClaude Fable 5 9034152c7c Name the guilty files in symbol resolution errors
"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
2026-09-02 02:12:27 +09:00
Rui UeyamaandClaude Fable 5 4c8e262941 Reject mixed thread-local and regular references
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
2026-09-02 01:46:37 +09:00
Rui UeyamaandClaude Fable 5 7da35e8473 Reject -pagezero_size for anything but an executable
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
2026-09-02 01:46:37 +09:00
Rui UeyamaandClaude Fable 5 7dd69d2706 Support -mark_dead_strippable_dylib
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
2026-09-02 01:46:37 +09:00
Rui UeyamaandClaude Fable 5 1e9a5548dc Support -oso_prefix
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
2026-09-02 01:46:37 +09:00
Rui UeyamaandClaude Fable 5 d1ec531160 Support -add_empty_section
-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
2026-09-02 01:46:37 +09:00
Rui UeyamaandClaude Fable 5 3d6944e21e Support -umbrella
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
2026-09-02 01:46:37 +09:00
Rui UeyamaandClaude Fable 5 4c8fd18d8c Accept -reproducible
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
2026-09-02 01:46:37 +09:00
Rui UeyamaandClaude Fable 5 0fe24be5ed Support -macos_version_min
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
2026-09-02 01:46:37 +09:00
Rui UeyamaandClaude Fable 5 18d52a54ad Support -search_dylibs_first
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
2026-09-02 01:46:37 +09:00
Rui UeyamaandClaude Fable 5 069afe4655 Port fifty tests from sold that already pass
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
2026-09-02 01:46:37 +09:00
Rui UeyamaandClaude Fable 5 1efabadc2c Carry unwind info through -r
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
2026-09-02 01:46:37 +09:00
Rui UeyamaandClaude Fable 5 239d9a26e8 Support -r
-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
2026-09-02 01:46:37 +09:00
Rui UeyamaandClaude Fable 5 fa489fdee6 Add README
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
2026-09-02 01:46:37 +09:00
Rui UeyamaandClaude Fable 5 8e3d6d19d7 Support -flat_namespace
-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
2026-09-02 01:46:37 +09:00
Rui UeyamaandClaude Fable 5 8f542a8ed4 Support -Z
-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
2026-09-02 01:46:37 +09:00
Rui UeyamaandClaude Fable 5 2e8c1726cf Support -needed-l and -needed_framework
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
2026-09-02 01:46:37 +09:00
Rui UeyamaandClaude Fable 5 2d6cc8a5c0 Support -hidden-l
-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
2026-09-02 01:46:37 +09:00
Rui UeyamaandClaude Fable 5 d141e60aa6 Support -U and -undefined warning/suppress
-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
2026-09-02 01:46:37 +09:00
Rui UeyamaandClaude Fable 5 4c24efc5ee Support -dependency_info
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
2026-09-02 01:46:36 +09:00
Rui UeyamaandClaude Fable 5 abf55bc513 Support thread-locals imported from dylibs
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
2026-09-02 01:46:36 +09:00
Rui UeyamaandClaude Fable 5 8955f1533a Support -init_offsets
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
2026-09-02 01:45:14 +09:00
Rui UeyamaandClaude Fable 5 f81cd7ae6f Support re-exported dylibs
-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
2026-09-02 01:45:14 +09:00
Rui UeyamaandClaude Fable 5 07f7270d5a Support -no_uuid
-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
2026-09-02 01:45:14 +09:00
Rui UeyamaandClaude Fable 5 6eb3729dfb Fold identical code
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
2026-09-02 01:45:14 +09:00
Rui UeyamaandClaude Fable 5 844e9ff1ff Parse input files in parallel
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
2026-09-02 01:45:14 +09:00
Rui UeyamaandClaude Fable 5 7745f98dd1 Adopt mold's symbol resolution model
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
2026-09-02 01:45:14 +09:00
Rui UeyamaandClaude Fable 5 f167c38c14 Create range-extension thunks for out-of-reach arm64 branches
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
2026-09-02 01:45:14 +09:00
Rui UeyamaandClaude Fable 5 b47dcc6d28 Support -add_ast_path
-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
2026-09-02 01:45:14 +09:00
Rui UeyamaandClaude Fable 5 4c7253d8c0 Support -bind_at_load and -application_extension
-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
2026-09-02 01:45:14 +09:00
Rui UeyamaandClaude Fable 5 c67d844ff1 Support -dead_strip_dylibs
-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
2026-09-02 01:45:14 +09:00
Rui UeyamaandClaude Fable 5 4c13947413 Support -undefined dynamic_lookup
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
2026-09-02 01:45:14 +09:00
Rui UeyamaandClaude Fable 5 2456c163c3 Add -pagezero_size, -stack_size, -sectcreate, -x, -w, -help, @response
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
2026-09-02 01:45:14 +09:00
Rui UeyamaandClaude Fable 5 3dd38fe8c4 Accept -search_paths_first, -headerpad_max_install_names and -O
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
2026-09-02 01:45:14 +09:00
Rui UeyamaandClaude Fable 5 ca2b5b407c Support LTO
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
2026-09-02 01:45:14 +09:00
Rui UeyamaandClaude Fable 5 cafaef09f6 Support auto-linking (LC_LINKER_OPTION); Swift programs now link
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
2026-09-02 01:45:14 +09:00
Rui UeyamaandClaude Fable 5 90eff69493 Emit chained fixups
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
2026-09-02 01:45:14 +09:00
Rui UeyamaandClaude Fable 5 86422904ba Parallelize output writing and code-signature hashing
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
2026-09-02 01:45:14 +09:00
Rui UeyamaandClaude Fable 5 2d897a6e29 Support -map
-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
2026-09-02 01:45:14 +09:00
Rui UeyamaandClaude Fable 5 5be7e0a92e Index archive members by defined symbol
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
2026-09-02 01:45:14 +09:00
Rui UeyamaandClaude Fable 5 5788ae3e00 Emit debug stabs so debuggers can find DWARF
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
2026-09-02 01:45:14 +09:00
Rui UeyamaandClaude Fable 5 f1ee228c48 Support -v, and add tests for duplicate-symbol errors and -e
ld64's -v prints version information to stderr and continues linking;
clang forwards its own -v to the linker, so it must not be an error.

Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
2026-09-02 01:45:14 +09:00
Rui UeyamaandClaude Fable 5 57ec9cab40 Sniff the target architecture from input files
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
2026-09-02 01:45:14 +09:00
Rui UeyamaandClaude Fable 5 80b6b7ebac Support -current_version and -compatibility_version
-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
2026-09-02 01:45:14 +09:00
Rui UeyamaandClaude Fable 5 98a34024ba Support -exported_symbols_list and -unexported_symbols_list
-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
2026-09-02 01:45:14 +09:00
Rui UeyamaandClaude Fable 5 2774beada4 Support -u
-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
2026-09-02 01:45:14 +09:00
Rui UeyamaandClaude Fable 5 d68517fced Merge __objc_imageinfo sections
__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
2026-09-02 01:45:14 +09:00
Rui UeyamaandClaude Fable 5 d70e567ca1 Support private external symbols
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
2026-09-02 01:45:14 +09:00
Rui UeyamaandClaude Fable 5 3284cd4bd0 Support -filelist
-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
2026-09-02 01:45:14 +09:00
Rui UeyamaandClaude Fable 5 5434d6c026 Support weak dylibs
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
2026-09-02 01:45:14 +09:00
Rui UeyamaandClaude Fable 5 e08c099b11 Compute LC_UUID from the output contents
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
2026-09-02 01:45:14 +09:00
Rui UeyamaandClaude Fable 5 4afe06665b Deduplicate literals
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
2026-09-02 01:45:14 +09:00
Rui UeyamaandClaude Fable 5 65fc73b369 Emit LC_FUNCTION_STARTS
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
2026-09-02 01:45:14 +09:00
Rui UeyamaandClaude Fable 5 46041f5dd0 Handle weak definitions and weak imports in the output
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
2026-09-02 01:45:14 +09:00
Rui UeyamaandClaude Fable 5 d3a10aa026 Support -all_load, -force_load and -ObjC
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
2026-09-02 01:45:14 +09:00
Rui UeyamaandClaude Fable 5 13eaa804d4 Add the x86_64 target
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
2026-09-02 01:45:14 +09:00
Rui UeyamaandClaude Fable 5 a6b135eeed Support -bundle output
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
2026-09-02 01:45:14 +09:00
Rui UeyamaandClaude Fable 5 06b322cc38 Support -rpath
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
2026-09-02 01:45:14 +09:00
Rui UeyamaandClaude Fable 5 629d0fb9f7 Implement -dead_strip
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
2026-09-02 01:45:14 +09:00
Rui UeyamaandClaude Fable 5 63e85e67c5 Split sections into subsections
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
2026-09-02 01:45:14 +09:00
Rui UeyamaandClaude Fable 5 09a83d9a8d Synthesize __eh_frame for DWARF-only unwind info
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
2026-09-02 01:45:14 +09:00
Rui UeyamaandClaude Fable 5 9a17fc596e Deduplicate dylibs by install name
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
2026-09-02 01:45:14 +09:00
Rui UeyamaandClaude Fable 5 4fbe3bfa29 Accept -dead_strip as a no-op
rustc always passes -dead_strip. Keeping unreferenced subsections is
correct, just larger; real dead stripping comes later.

Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Avqib5YEt3uKNfSmwEcNcJ
2026-09-02 01:45:14 +09:00
Rui UeyamaandClaude Fable 5 33d04f97bd Synthesize _objc_msgSend$selector stubs
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
2026-09-02 01:45:14 +09:00
Rui UeyamaandClaude Fable 5 f326d4c6a4 Follow external .tbd reexports
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
2026-09-02 01:45:14 +09:00
Rui UeyamaandClaude Fable 5 0eee798ccd Support -framework and -F
-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
2026-09-02 01:45:14 +09:00
Rui UeyamaandClaude Fable 5 cb8b1af406 Support creating dylibs
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
2026-09-02 01:45:14 +09:00
Rui UeyamaandClaude Fable 5 369336ee85 Emit an export trie
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
2026-09-02 01:45:14 +09:00
Rui UeyamaandClaude Fable 5 a29e8a2dd5 Support thread-local variables
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
2026-09-02 01:45:14 +09:00
Rui UeyamaandClaude Fable 5 c158bd6f93 Generate __unwind_info from compact unwind records
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
2026-09-02 01:45:14 +09:00
Rui UeyamaandClaude Fable 5 191c6708f7 Support archives and multi-object links
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
2026-09-02 01:45:14 +09:00
Rui UeyamaandClaude Fable 5 aa28a49e77 Convert common symbols to zero-fill definitions
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
2026-09-02 01:45:14 +09:00
Rui UeyamaandClaude Fable 5 0cf241d53f Emit rebase and bind information for data pointers
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
2026-09-02 01:45:14 +09:00
Rui UeyamaandClaude Fable 5 62505d2683 Support calling functions in dylibs
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
2026-09-02 00:13:04 +09:00
Rui UeyamaandClaude Fable 5 3441c6f2a1 Create a minimal Mach-O linker that produces a working executable
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
Rui Ueyama c433f96074 Move SipHash into utility module
Restore the configurable C++ implementation with const-generic round
counts and output width. Keep ICF-specific digest conversion in icf.rs.
2026-09-01 20:59:50 +09:00
Rui Ueyama 36ad403f7c Add Linux distribution script
Port mold dist.sh to build Rust release binaries in old distro
containers. Download a pinned official Rust toolchain as gzip, fetch
Cargo.lock dependencies before disabling the network, and normalize
package timestamps.

Allow install-mold.sh to consume an external Cargo target directory.
2026-09-01 20:39:34 +09:00
Rui UeyamaandClaude Fable 5 b8ba529e57 Fix path_clean dropping leading .. components
path_clean("../../a/b") returned "a/b": the ParentDir branch popped a
trailing ".." from the output instead of appending another one, so any
path with two or more leading ".." components collapsed into a wrong
path. C++ mold uses std::filesystem::path::lexically_normal, which
keeps leading ".." components intact.

Library search paths are cleaned with this function, so mold failed
with "library not found" whenever a Makefile referenced an in-tree
library as -L../../<dir>, which is a very common pattern. Found by the
Gentoo all-package campaign within its first hour: sys-fs/lvm2,
sys-fs/xfsprogs, sci-astronomy/montage, dev-libs/OpenNI2,
dev-erlang/fast_yaml, and app-backup/spideroak-bin all failed with
mold-rust and built with mold-c++.

Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_0118ERbwNepcG1Z9gh5cm6Ut
2026-09-01 18:49:49 +09:00
Rui UeyamaandClaude Fable 5 3881cd2a69 Remove manpage update workflow
Benchmark record, 2026-09-01 (unrelated to this change; this tree is
what was measured, built as 6becb4da before this message was amended):

C++ reference: mold at e322a3cc, i.e. the translation base fbaaa75f
plus 15 fixes that do not touch hot paths.

Protocol: ~/bench/tab4-bench.sh as-is — run 1 warms the page cache,
median of runs 2-6 reported; all runs of one linker complete before
the other linker starts; sync + dirty-writeback wait between runs;
--no-fork; default thread count (min(ncores, 32) = 32 on this
128-core machine); frequency boost off, performance governor,
THP madvise, idle machine.

Workload                    C++ (s)  Rust (s)  Rust vs C++
blender-debug                  0.94      0.82       -12.8%
blender-release                0.20      0.17       -15.0%
chrome-debug-arm64             1.88      1.97        +4.8%
chrome-debug-x86_64            1.75      1.80        +2.9%
chrome-release-arm64           0.77      0.79        +2.6%
chrome-release-x86_64          0.66      0.69        +4.5%
clang-debug                    1.49      1.28       -14.1%
clang-release                  0.11      0.10        -9.1%
clickhouse-debug               1.14      1.09        -4.4%
clickhouse-release             0.43      0.43        +0.0%
firefox-debug-arm64            1.07      0.98        -8.4%
firefox-debug                  0.87      0.87        +0.0%
firefox-release                0.21      0.20        -4.8%
godot-debug                    0.51      0.47        -7.8%
godot-release                  0.08      0.08        +0.0%
libmergedlo.so-debug           0.46      0.47        +2.2%
libmergedlo.so-release         0.20      0.18       -10.0%
pytorch-debug                  0.93      0.84        -9.7%
pytorch-release                0.15      0.14        -6.7%
tensorflow-debug               3.16      2.89        -8.5%
tensorflow-release             0.69      0.63        -8.7%

Geomean -5.1% (Rust faster); 13 faster, 3 even, 5 slower. No workload
regressed against the import-time table in the initial commit; the
open item remains chrome's +2-5%.

Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_0118ERbwNepcG1Z9gh5cm6Ut
2026-09-01 18:03:03 +09:00
Rui Ueyama 6ce8c8fcda Keep imported mold tests unmodified 2026-09-01 15:25:22 +09:00
Rui Ueyama 958bce3d88 Update generated manual for arm64 rewrite-endbr 2026-09-01 15:22:05 +09:00
Rui Ueyama 4959c02bd6 [LTO] Document linker version string lifetime 2026-09-01 15:21:13 +09:00
Rui Ueyama da5cc1049c Fix thin archives with a __.SYMDEF member 2026-09-01 15:19:46 +09:00
Rui Ueyama 5768f27eef Report malformed hexadecimal --defsym values 2026-09-01 15:17:54 +09:00
Rui Ueyama 17214ae348 Add mergeable weak-definition regression test 2026-09-01 15:16:14 +09:00
Rui Ueyama a98878f988 Add PT_NOTE section-order regression test 2026-09-01 15:14:35 +09:00
Rui Ueyama 414976992d Add mixed DWARF merged-string regression test 2026-09-01 15:13:06 +09:00
Rui Ueyama d4963fbc38 Add nonalloc relocation range regression test 2026-09-01 15:11:41 +09:00
Rui Ueyama 5075908706 Recognize GCC LTO objects with 65280 or more sections 2026-09-01 15:10:24 +09:00
Rui Ueyama ee7446a9d7 Support -z rewrite-endbr on arm64 2026-09-01 15:07:49 +09:00
Rui Ueyama 046ca4fa79 Remove unused function 2026-09-01 14:46:04 +09:00
Rui Ueyama 19dce43b67 Remove unused linker code
Drop the unused x86-64 relocation slice, obsolete FDE boundary variable and
unreferenced page-alignment helper.
2026-09-01 14:10:18 +09:00
Rui Ueyama 3dc5e88f6d Use an enumerated RISC-V relocation scan
Keep the allocation-free forward search while iterating over relocation
records directly, as conventional Rust and clippy expect.
2026-09-01 14:10:18 +09:00
Rui Ueyama 2cf15f20f0 Parallelize section shrinking updates
Apply relocation deltas, adjust symbols and recompute executable output
section layouts in parallel, matching the three file and chunk parallel
loops in C++ mold.
2026-09-01 14:10:18 +09:00
Rui Ueyama 19d8d87797 Parallelize ARM BE8 code-byte swapping
Process object files in parallel as C++ mold does. Use the existing
disjoint-range output-buffer abstraction so each worker can reverse its
input sections in place.
2026-09-01 14:10:18 +09:00
Rui Ueyama f55084e9dc Parallelize PPC64v1 OPD rewriting
Process object files in parallel as C++ mold does. Serialize symbol access
with the existing byte-sized symbol lock and rewrite each file's relocation
records in place.
2026-09-01 14:10:17 +09:00
Rui Ueyama 273693951d Avoid allocating RISC-V relocation iterators
Use the same direct backward or forward linear search as C++ mold when
finding the HI20 relocation paired with a LO12 relocation.
2026-09-01 14:10:17 +09:00
Rui Ueyama a2b72c33f8 Match mold's zlib sync-flush workaround
Use stock zlib's low-level API to reproduce the C++ Z_BLOCK, deflatePending,
optional deflatePrime and Z_SYNC_FLUSH sequence. This avoids the old
libbacktrace boundary bug and preserves C++ mold's compressed bytes.
2026-09-01 14:10:17 +09:00
Rui Ueyama d29ee0c4c5 Restrict disk-full diagnostics to the output mapping
Install the Unix handlers with SA_SIGINFO and track the mapped output range
as it is created, extended or moved. Report a possible full disk only when
SIGBUS or SIGSEGV identifies an address in that range.
2026-09-01 14:10:17 +09:00
Rui Ueyama 344773166c Implement mold statistics counters
Port the C++ Counter facility and collect the same parser, section, symbol,
ICF, relocation, COMDAT, thunk and merged-section statistics. Keep counter
updates dormant unless --stats is requested.
2026-09-01 14:10:17 +09:00
Rui Ueyama 4bffbbd530 Include the git commit hash in the mold version 2026-09-01 14:10:17 +09:00
Rui Ueyama b8d9d12619 Apply diagnostic options while parsing arguments 2026-09-01 14:10:17 +09:00
Rui Ueyama 8ab361da56 Implement the MOLD_JOBS process gate 2026-09-01 14:10:17 +09:00
Rui Ueyama e8ab792f46 Track all mapped files for repro and dependencies 2026-09-01 14:10:17 +09:00
Rui Ueyama 879e9bed13 Preserve RISC-V attributes in relocatable output 2026-09-01 14:10:17 +09:00
Rui Ueyama d0aecfd12a Drop symbols belonging to all dead input sections 2026-09-01 14:10:17 +09:00
Rui Ueyama 5dcfcbedb2 Assign thunk names in deterministic output order 2026-09-01 14:10:17 +09:00
Rui Ueyama 43964392e1 Match C++ mold file priorities for LTO output 2026-09-01 14:10:17 +09:00
Rui Ueyama 5051b051be Fix s390x GOTENT allocation during relocation scan 2026-09-01 14:10:17 +09:00
Rui UeyamaandClaude Fable 5 adb411d475 [LTO] Don't return a dangling pointer as the linker version string
get_api_version handed the plugin the buffer of a stack-local
std::string, which dies when the function returns. Make it static.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-09-01 13:49:46 +09:00
Rui UeyamaandClaude Fable 5 878599b795 Fix an infinite loop on a thin archive with a __.SYMDEF member
The thin archive reader skipped a __.SYMDEF symbol table without
advancing the read position, so it kept parsing the same member header
forever. Skip past the member contents like the other symbol table
branch does.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-09-01 13:49:46 +09:00
Rui UeyamaandClaude Fable 5 4ebc916bc9 Add missing relocation names to rel_to_string
R_RISCV_TLSDESC, R_RISCV_GOT32_PCREL, R_SPARC_SIZE32 and R_SPARC_JMP_IREL
were printed as "unknown (n)" in diagnostics.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-09-01 13:49:46 +09:00
Rui UeyamaandClaude Fable 5 a129618d11 Report an error for a malformed hexadecimal --defsym value
parse_defsym_value returned a default-constructed variant on a bad hex
string such as 0xz, which holds a null Symbol pointer. The null pointer
was then dereferenced when marking GC roots, crashing the linker instead
of printing a diagnostic.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-09-01 13:49:46 +09:00
Rui UeyamaandClaude Fable 5 a9af11f74a Don't attach section pieces to symbols resolved to other files
reattach_section_pieces rewrote every symbol whose local definition is in
a mergeable section, including global symbols that lost resolution to a
definition in another file. The loser then redirected the symbol to its
own section piece. Since the pass runs on all files in parallel, it was
also a data race on the shared Symbol.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-09-01 13:49:46 +09:00
Rui UeyamaandClaude Fable 5 d55b408ff0 [s390x] Fix byte order of 12-bit displacement fields
R_390_12, R_390_PC12DBL, R_390_PLT12DBL, R_390_GOT12 and R_390_GOTPLT12
OR'd their 12-bit value into the instruction through a little-endian
16-bit accessor. s390x is big-endian, so the value landed in the wrong
bytes, corrupting both the displacement and the base register field.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-09-01 13:49:46 +09:00
Rui UeyamaandClaude Fable 5 f3b74cbb6a Fix PT_NOTE creation to consult the right chunk vector
create_phdrs walks a filtered, address-sorted copy of ctx.chunks, but the
loop that merges adjacent note sections into one PT_NOTE examined
ctx.chunks with an index into that copy. The two vectors differ e.g. when
the ELF and program headers are not memory-mapped, in which case every
note section got its own PT_NOTE segment.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-09-01 13:49:46 +09:00
Rui UeyamaandClaude Fable 5 1f56c36413 Fix string overlap in huge merged sections with mixed DWARF32/64
When a merged section is laid out in two regions, 32-bit-addressable
fragments followed by the rest, the loop that rebases per-shard offsets to
section-global ones started at shard 1. That is fine for the 32-bit region,
whose shard 0 needs no adjustment, but it left shard 0 of the 64-bit region
unadjusted, so its fragments overlapped the beginning of the section. Start
at shard 0; the 32-bit fragments of that shard just get zero added.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-09-01 13:49:46 +09:00
Rui UeyamaandClaude Fable 5 e2a28d61eb [RISC-V] Fix R_RISCV_ADD8 and R_RISCV_SUB8 in allocated sections
The alloc relocation path incremented the local pointer loc instead of the
byte it points to, so the relocated byte was left untouched. The result was
wrong whenever an 8-bit label difference spanned code whose size changed
during relaxation. The non-alloc path already did this correctly.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-09-01 13:49:46 +09:00
Rui UeyamaandClaude Fable 5 19e5360530 Fix swapped arguments to the relocation range checker
The non-alloc relocation paths of i386, ARM64, s390x, SPARC64 and PPC64V1
passed the relocation value and the relocation index to check_range in the
wrong order. The intended range check was thus never performed, and if the
relocation index happened to fall outside the value range, the error path
indexed the relocation table with the relocation value, reading out of
bounds.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-09-01 13:26:59 +09:00
Rui Ueyama fed11f6eca Fix FreeBSD -run and serialize sanitizer tests
Use the same KERN_PROC_PATHNAME sysctl as C++ mold when locating the
current executable on FreeBSD, where /proc may not be mounted. Run one
shell test at a time under sanitizers so large cases do not overlap,
while leaving two CPUs available to the linker under TSan.
2026-09-01 12:43:09 +09:00
Rui Ueyama 13757779c5 Copy the man-page update workflow 2026-09-01 12:25:51 +09:00
Rui Ueyama 063a8fda2a Limit CI builds to required mold targets
Use Cargo per-target features in native and platform jobs so they do not
rebuild all 20 linker instantiations. Tests that require an i386 linker
already skip when that target is disabled. Keep test-all-targets using
defaults for complete QEMU coverage.
2026-09-01 12:13:35 +09:00
Rui UeyamaandClaude Fable 5 f68cb6fb18 Recognize GCC LTO objects with 65280 or more sections
If an object file has SHN_LORESERVE (0xff00) or more sections, e_shnum
is zero and the actual number of sections is stored in the first section
header's sh_size field. is_gcc_lto_obj() used the raw e_shnum, so it
scanned no sections and misclassified such LTO objects as regular
object files.

Fixes https://github.com/rui314/mold/issues/1642

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-09-01 12:12:10 +09:00
Rui Ueyama 6e5fa07222 wip 2026-09-01 11:56:49 +09:00
Rui Ueyama a53b8db45b Fix sanitizer and FreeBSD CI failures
Give ThreadSanitizer extra time for the many-output-sections test.

Disable incremental artifacts and debug info in the disk-constrained FreeBSD VM.

Also suppress the non-x86 unused prefetch parameter warning.
2026-09-01 11:06:43 +09:00
Rui Ueyama 33261b57c8 Install Git in MSYS CI 2026-09-01 10:34:53 +09:00
Rui Ueyama 890313dd9c Use Git CLI for Cargo in MSYS CI
MSYS Cargo's bundled libgit2 rejects GitHub's certificate. Use the Git CLI,
which reads the certificate setup provided by MSYS2.
2026-09-01 10:29:15 +09:00
Rui Ueyama dceb9bbb4b Port macOS, Windows, MSYS2 and FreeBSD CI jobs
Add the four platform jobs from C++ mold and make the Rust implementation
compile on each native platform.

Use the corresponding Windows virtual-memory, output-file, subprocess, LTO
and process-timer facilities. Keep the Unix paths for macOS and FreeBSD while
applying platform-specific system calls only where C++ mold does.

Tested with cargo build --release, cargo test and cargo clippy --release
--all-targets. The MinGW executable also links against dlfcn-win32.
2026-09-01 10:25:19 +09:00
Rui Ueyama 6d9d6fe801 Discard successful test work directories
The all-target CI job exhausted its runner disk because it retained the
output of every completed shell test. Remove each work directory after a
passing or skipped test while preserving logs and all failed-test files for
diagnosis.
2026-09-01 00:06:21 +09:00
Rui Ueyama 305fddf397 Reduce all-target CI artifacts
The multi-architecture build generated incremental and debug artifacts on
an ephemeral runner, leaving too little room for the test outputs and
causing the one-hour timeout. Disable both for this job.

In Ubuntu 24.04 Podman, the clean build used 693 MB and the completed test
tree used 7.4 GB. All targets passed with pass=6782 skip=895 fail=0.
2026-08-31 23:24:56 +09:00
Rui Ueyama bffdaa5b1f Fix big-endian ARM synthesized literals
ARM BE8 instructions are little-endian, but data embedded in PLT and
thunk entries uses target byte order. Match C++ mold's ul32 instruction
templates and U32<E> literal stores.

CI-equivalent Ubuntu 24.04 Podman:

  all targets: pass=6782 skip=895 fail=0
  armeb: pass=368 skip=53 fail=0
2026-08-31 23:15:01 +09:00
Rui Ueyama b6603f2d3c Keep CI commands on one line
Write each Cargo invocation on one line instead of using shell continuation
characters.
2026-08-31 21:45:03 +09:00
Rui Ueyama 9192ec01bc Limit CI to test-bearing targets
All workspace crates are built before testing, but the target crates and CLI
binary contain no unit tests. Select the library and integration test targets
so Cargo does not exhaust the runner disk linking empty harnesses.
2026-08-31 21:42:47 +09:00
Rui Ueyama c472dc5b34 Fix output preallocation on musl
musl uses an unsigned type for statfs.f_type while libc exposes
TMPFS_MAGIC as a signed value. Convert the constant to the field's type so
the comparison builds with both musl and glibc.

Match C++ mold by attempting fallocate when fstatfs fails.
2026-08-31 21:18:54 +09:00
Rui Ueyama c1ce9ebb75 Configure PATH in distro containers
The job-level environment did not populate the container configuration, so
the Rust toolchain action still lost the system directories when it extended
GITHUB_PATH. Set PATH through container.env instead.
2026-08-31 21:09:08 +09:00
Rui Ueyama c6393dfd84 Fix PATH setup for Tumbleweed CI
The Tumbleweed image does not declare PATH in its container
configuration. GitHub's implicit default disappears when rust-toolchain
adds Cargo's bin directory through GITHUB_PATH. Define the normal system
path explicitly for the distro matrix so Bash remains available.
2026-08-31 21:04:22 +09:00
Rui Ueyama 678d194a68 Let the dependency installer bootstrap Bash
Run install-test-deps.sh with /bin/sh so minimal container images can
invoke it. Expand the Debian cross-toolchain package list without Bash
brace expansion, install Bash on Tumbleweed, and remove the Alpine
bootstrap step from CI.
2026-08-31 20:51:43 +09:00
Rui Ueyama df81f7363c Sync Gentoo repository before installing dependencies
Retain the repository synchronization performed by the original mold
script before asking Portage to install curl.
2026-08-31 20:46:32 +09:00
Rui Ueyama 8857b8796d Install Bash before Alpine tests
The Alpine base image does not include Bash, so install it before
running install-test-deps.sh. Keep the dependency script Bash-based so it
can use brace expansion.
2026-08-31 20:38:33 +09:00
Rui Ueyama d77d5e36d9 Add GitHub Actions test coverage
Run the complete multi-architecture suite and native tests across the
same Linux distribution matrix as C++ mold. Install the extra toolchains
used by the upstream tests and fail CI if any expected target is
unavailable.

Build and test ASan and TSan configurations with nightly Rust. Use the
system allocator for sanitizer builds and instrument the two C adapters as
well. Limit TSan to two CPUs to avoid false reports from Rayon while still
exercising parallel execution.

Make install-test-deps.sh portable across the distribution containers.

Tested with cargo test: pass=5360 skip=636 fail=0. ASan passed 477
native tests with 9 skips, and TSan passed 454 native tests with 32 skips.
2026-08-31 20:32:33 +09:00
Rui Ueyama 2a2453f93d Rewrite emitted relocations in place
Match C++ mold's relaxation behavior by updating input relocation records
at the point where instructions are rewritten. Store decoded CREL records
in typed mutable side tables so emitted relocation sections observe the
same updates, and remove the relocation type reconstruction hooks.
2026-08-31 18:57:06 +09:00
Rui Ueyama 8c837eef01 wip 2026-08-31 18:26:48 +09:00
Rui Ueyama 21787c1417 Remove synthetic ELF layouts
Use I386, X86_64, M68k, and Sparc64 as representative layouts when
classifying GCC LTO objects, matching C++ mold. Use real target layouts for
the compile-time record assertions and remove Elf32Le, Elf64Le, Elf32Be, and
Elf64Be.

Also remove the obsolete Record trait. Direct file records use
FileRecord, and Record had no implementations or callers.

Checked with cargo check --workspace. The test suite was not rerun as
requested.
2026-08-31 17:49:23 +09:00
Rui Ueyama 63452ee050 Simplify ELF layout metadata
Rename ProgramHeaderRecord and CompressionHeaderRecord to the shorter
PhdrRecord and ChdrRecord names. Expand the four plain layout definitions
directly and remove the plain_layout macro.

Infer the ELF class from the layout word size and whether relocations
carry addends from RelRecord. This removes the duplicated IS_64 and IS_RELA
declarations from every target.

Tests: cargo test (pass=5360 skip=636 fail=0)
Lint: cargo clippy --release --all-targets
2026-08-31 17:41:22 +09:00
Rui Ueyama bfff76057f Map ELF tables directly onto file records
Keep input section headers and symbols in their endian-aware on-file
representation instead of decoding them into target-independent mirrors.
Carry that representation through output chunks and expose fields through
record traits, so reads and writes remain target-endian.

Remove SymbolEntry, SectionHeader, SymTable, ShdrTable, and RecordLayout
now that no converted record mirrors remain. Move the SFrame constants to
elf/consts.rs.

On the 21-workload, 32-thread benchmark, the geometric-mean time changed
by -1.1% versus 801fc. Excluding tensorflow-debug's unusually large
improvement, it was effectively flat at -0.1%.

Tests: cargo test (pass=5360 skip=636 fail=0)
Lint: cargo clippy --release --all-targets
2026-08-31 17:25:36 +09:00
Rui Ueyama 80c1b9c258 Use endian-aware ELF records
Represent uniform ELF records with public target-endian fields and select
class-dependent symbol, program-header, and compression-header layouts
through Layout. Convert differing layouts as complete records instead of
adding field-by-field forwarding accessors.

Remove the raw st_info API and the old offset-based record readers.

Tests: cargo test
Tests: cargo clippy --release --all-targets
2026-08-31 15:05:16 +09:00
Rui Ueyama 801fc08a37 wip 2026-08-31 14:44:09 +09:00
Rui Ueyama cd04a2f32d Use target-specific relocation records
Define byte-backed integer and relocation types that mirror the C++ ElfRel
specializations, including the SPARC64 type-data field.

Select each relocation record through Layout and view input and output
tables as ordinary typed slices. This removes the host-native conversion
wrappers and the synthetic r_info representation.

Preserve target byte order independently of the host.

Tests: cargo test
Tests: cargo clippy --release --all-targets
2026-08-31 14:14:12 +09:00
Rui Ueyama dc15979a21 Generate relocation names from constants
Define relocation constants and diagnostic-name functions for each
architecture from a single macro invocation. Keep each macro input to the
relocation name and value, and use stringify! to produce the diagnostic
spelling.

Remove the separate relnames module so relocation names and values can no
longer drift apart.

Validation passes with cargo build --release and cargo clippy --release
--all-targets. Cargo test reports 5360 passed, 636 skipped, and no failures.
2026-08-31 12:52:52 +09:00
Rui Ueyama c5f5720a7c Make relocation traversal iterable
Replace the callback-only relocation traversal API with streaming Rust
iterators. Make CrelReader and RelocationIter implement Iterator while
preserving deferred CREL decoding without allocation.

Use ordinary for loops in the architecture backends and gdb-index reader.
Keep a specialized fold implementation so Iterator::for_each can select the
ordinary or CREL representation once.

The full test suite passes with 5360 cases passed, 636 skipped, and no
failures. Clippy is clean.

Five-run firefox-debug medians improved from 0.798 s to 0.765 s at 32
threads and from 11.135 s to 11.002 s with one thread. The C++ reference
at fbaaa75f measured 0.792 s and 11.999 s, respectively.
2026-08-31 12:41:09 +09:00
Rui Ueyama a91d19d5e5 Use the global Rayon thread pool
Configure Rayon's process-global pool after target detection and process
forking. Register the main thread as worker zero so --threads=N limits the
linker to N active threads.

Run the linker directly instead of wrapping its entire body in
ThreadPool::install. This follows mold's command-oriented execution model
and its process-wide TBB parallelism limit.
2026-08-31 11:48:12 +09:00
Rui Ueyama a1b835bd5e Align Rust source names with mold
Rename Rust modules to match their C++ source counterparts where
practical. Use archive_file, cmdline, error, output_chunks,
shrink_sections, and perf while keeping mapped_file, output_file, lto,
and subprocess unchanged.

Remove filename-only comments added during the port. They were not copied
documentation and became redundant once the filenames were aligned.

Update all module paths and target entry points for the renamed modules.
2026-08-31 11:35:52 +09:00
Rui Ueyama defabc0672 Reduce the shell-test timeout
Limit each shell test to 30 seconds instead of 300 so a hung compiler or
QEMU process fails promptly.
2026-08-31 11:07:01 +09:00
Rui Ueyama 25433d61ed Add test dependency installer
Carry over mold's distribution-specific dependency installer as
install-test-deps.sh. Drop CMake packages that the Rust build no longer uses.

Keep test setup out of the user build instructions. Explain the stable Rust
and native C compiler requirements directly in README.
2026-08-31 10:59:36 +09:00
Rui Ueyama 39b103e44e Update user documentation and add installer
Copy the current README, manual, logos, and license from mold. Adapt the
build and installation instructions for the Rust implementation.

Add install-mold.sh to install the binary, preload library, manual, license,
and linker symlinks under a configurable PREFIX with DESTDIR support.
2026-08-31 10:41:29 +09:00
Rui Ueyama 91164c701b Rename csrc to c
Move the two remaining C source files from csrc/ to c/ and update the
build script and LTO documentation to use the new paths. The files are
otherwise byte-identical.

Tests: cargo build --release
Tests: cargo test (pass=5360 skip=636 fail=0)
Tests: cargo clippy --release --all-targets
Tests: cargo fmt --all -- --check
2026-08-31 10:05:17 +09:00
Rui Ueyama fbecc040bb Use the pinned mimalloc Rust crate
Replace the local allocator wrapper and vendored mimalloc sources with the
rui314/mimalloc_rust repository pinned at:

cc58b72775b8b5bc89ee37752af61fcb4f7c2909

The dependency contains mimalloc 3.5.0 and its overlapping upstream files
are byte-identical to the deleted copy. Update the workspace, allocator
import, lockfile, and README.

Tests: cargo build --release
Tests: cargo test (pass=5360 skip=636 fail=0)
Tests: cargo clippy --release --all-targets
Tests: cargo fmt --all -- --check
2026-08-31 09:52:51 +09:00
Rui Ueyama 197c1e8cb9 Run the shell suite through cargo test
Register a custom integration-test target for mold-cli and move the Rust
runner into the mold-tests workspace crate. Keep all 537 shell-test files
byte-for-byte unchanged under tests/cases, run every available target and
QEMU job by default, and stage Cargo-built test artifacts in the active
profile directory.

Remove run-tests.sh and make every workspace crate a default member so
plain cargo test is the complete test entry point.

Use explicit wrapping arithmetic for unsigned C++ operations exposed by
the debug-profile test suite.

Tested with:
  cargo test
  cargo build --release
  cargo clippy --release --all-targets
  cargo fmt --all -- --check

Full shell-suite result: 5,996 scheduled, pass=5360, skip=636, fail=0.
2026-08-31 09:24:02 +09:00
Rui Ueyama 4fbfe4cba7 Ignore local benchmark and backup files
Keep the externally managed bench directory and editor backup files out of
the mold-rust working tree.
2026-08-31 08:02:47 +09:00
Rui Ueyama e0cb69af5d Preserve mold's comments in the Rust port
Treat every C++ implementation comment at mold commit
fbaaa75f9f as a first-class porting
asset. Restore all 2,179 non-namespace comment occurrences at their
Rust counterparts, including file-level explanations, ABI notes,
examples, TODOs, original typos, inline instruction annotations, and
repeated comments. Add provenance where one Rust implementation
consolidates multiple C++ implementations. Closing namespace labels
are the sole comments without a Rust construct.

Verify that all 537 upstream test files remain byte-for-byte identical
to the pinned C++ tree.

The audit also exposed a few implementation-fidelity gaps: reopen
input mappings before writing a repro archive, reset discarded-COMDAT
symbol indices after owner selection, mirror C++ GOT
dynamic-relocation accounting branches, and reserve version-section
output storage as C++ does. These are faithful translations, not new
algorithms.

Validated with cargo build --release, ./run-tests.sh --all (pass=5360
skip=636 fail=0), cargo clippy --release --all-targets, cargo fmt
--check, and git diff --check.
2026-08-31 08:01:19 +09:00
Rui Ueyama 2fee580c65 Use stable map entries for GDB index construction
Mirror C++ ConcurrentMap sorted-entry handling: reserve each shard vector, use unstable sorting, retain direct stable entry references, and test occupancy through the atomic key pointer alone.

Store direct stable NameEntry references in indexed-name records so the table writer does not translate a bucket ID for every public-name occurrence.

Reference C++ mold: fbaaa75f9f

Validation:
- cargo build --release
- ./run-tests.sh --all: pass=5360 skip=636 fail=0
- cargo clippy --release --all-targets

32-thread benchmark, median of five, with sync before and sync plus sleep 1 after every linker invocation:
- chrome-debug-arm64: C++ 1.736 s, Rust 1.834 s (+5.65%)
- chrome-debug-arm64 --no-gdb-index: C++ 1.576 s, Rust 1.592 s (+1.02%)
- chrome-release-arm64: C++ 0.715 s, Rust 0.738 s (+3.22%)

The remaining Chrome debug difference is isolated to TBB low-priority arena behavior; Rayon has no corresponding task-priority feature.
2026-08-30 23:49:29 +09:00
Rui Ueyama d879a504e7 Keep input files in stable owning pools
Mirror mold obj_pool/dso_pool ownership plus live pointer vectors so removing unreachable files does not move or renumber the underlying objects.

Reference C++ mold: fbaaa75f9f

Benchmark: 32 threads, median of five measured runs after one C++ warmup. C++ and Rust runs were interleaved; sync was run before, and sync plus sleep 1 after, every linker invocation.

workload                     C++ (s)  Rust (s)  Rust vs C++
blender-debug                  0.842     0.767       -8.91%
blender-release                0.190     0.165      -13.16%
chrome-debug-arm64             1.709     1.788       +4.62%
chrome-debug-x86_64            1.594     1.614       +1.25%
chrome-release-arm64           0.714     0.745       +4.34%
chrome-release-x86_64          0.608     0.625       +2.80%
clang-debug                    1.301     1.142      -12.22%
clang-release                  0.098     0.096       -2.04%
clickhouse-debug               0.928     0.901       -2.91%
clickhouse-release             0.372     0.391       +5.11%
firefox-debug                  0.768     0.773       +0.65%
firefox-debug-arm64            0.912     0.926       +1.54%
firefox-release                0.205     0.200       -2.44%
godot-debug                    0.444     0.412       -7.21%
godot-release                  0.066     0.073      +10.61%
libmergedlo.so-debug           0.424     0.427       +0.71%
libmergedlo.so-release         0.184     0.173       -5.98%
pytorch-debug                  0.779     0.735       -5.65%
pytorch-release                0.144     0.133       -7.64%
tensorflow-debug               3.080     2.784       -9.61%
tensorflow-release             0.664     0.602       -9.34%
2026-08-30 23:23:50 +09:00
Rui Ueyama f41bfcd5c7 Import current mold Rust port
C++ reference: mold fbaaa75f9f

Benchmark protocol: 32 threads; one C++ warmup followed by five
interleaved measured runs per linker; median reported. Every linker
invocation was preceded by sync and followed by sync; sleep 1.

Workload                    C++ (s)  Rust (s)  Rust vs C++
blender-debug                 0.821     0.770       -6.21%
blender-release               0.187     0.165      -11.76%
chrome-debug-arm64            1.714     1.815       +5.89%
chrome-debug-x86_64           1.602     1.643       +2.56%
chrome-release-arm64          0.713     0.751       +5.33%
chrome-release-x86_64         0.608     0.636       +4.61%
clang-debug                   1.303     1.139      -12.59%
clang-release                 0.097     0.097       +0.00%
clickhouse-debug              0.917     0.906       -1.20%
clickhouse-release            0.379     0.386       +1.85%
firefox-debug-arm64           0.918     0.935       +1.85%
firefox-debug                 0.787     0.802       +1.91%
firefox-release               0.200     0.206       +3.00%
godot-debug                   0.445     0.418       -6.07%
godot-release                 0.066     0.075      +13.64%
libmergedlo.so-debug          0.424     0.439       +3.54%
libmergedlo.so-release        0.184     0.171       -7.07%
pytorch-debug                 0.794     0.736       -7.30%
pytorch-release               0.142     0.136       -4.23%
tensorflow-debug              3.043     2.808       -7.72%
tensorflow-release            0.657     0.610       -7.15%

Validation: cargo build --release; ./run-tests.sh --all (pass 5360,
skip 636, fail 0); cargo clippy --release --all-targets.
2026-08-30 22:46:00 +09:00
Rui Ueyama 504249d994 Merge pull request #1641 from jvoisin/bti
Support -z rewrite-endbr on arm64
2026-08-29 15:27:13 +09:00
Rui Ueyama ecf335a4e1 Update README 2026-08-29 14:40:31 +09:00
Rui UeyamaandClaude Fable 5 fbaaa75f9f Verify SHA-256 hashes of source archives in dist.sh
Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-27 17:08:32 +09:00
Rui UeyamaandClaude Fable 5 91cab12f9a Fix CI
mold demangles with the platform's __cxa_demangle, and FreeBSD's libcxxrt
spells `operator delete[]` as `operator delete []`, so the quoted name in
the test never matched there. Test literal matching of brackets with
`S::operator[](int)` instead, which every demangler spells the same way.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-27 12:27:34 +09:00
jvoisin 3d4dc7291e Support -z rewrite-endbr on arm64
So far -z rewrite-endbr was implemented only for x86-64, where it
rewrites unnecessary ENDBR64 landing pads (Intel CET) with NOPs for
functions whose addresses are not taken. arm64 has an equivalent
control-flow-integrity feature, BTI, whose landing pad is `bti c`.

This commit generalizes rewrite_endbr into a template shared by both
targets: the landing pad and NOP byte patterns are selected per-arch
(ENDBR64/NOP on x86-64, `bti c`/`nop` on arm64). The whole-program
analysis that preserves landing pads for address-taken functions is
unchanged.

Unlike x86-64, arm64 needs range extension thunks, and a thunk reaches
its target with an indirect branch (`br x16`). A function that is only
called directly can therefore still be entered indirectly once its call
is routed through a thunk, so we must keep its landing pad. We preserve
the landing pad of every thunked symbol; the thunk symbol set has
already been narrowed to the ones that actually need a thunk by
remove_redundant_thunks().

Note that on arm64 with recent glibc, `main`'s landing pad is
legitimately removed: the CRT passes a local __wrap_main stub (whose
landing pad is kept) to __libc_start_main and reaches main through a
direct branch, which does not require a landing pad.

Also note that code built with -mbranch-protection=standard (BTI +
pointer authentication) starts functions with `paciasp`, which doubles
as a landing pad, so there is no separate `bti c` to rewrite and this
option has no effect on such code.

This was locally tested on an Apple M2 machine running Asahi Linux.

Fixes #1552.
2026-08-26 17:52:23 +02:00
Rui UeyamaandClaude Fable 5 e633272dc9 Range-check R_PPC64_TOC16_DS on PPC64 ELFv2
The relocation has a 16-bit displacement from the TOC pointer, but the
ELFv2 backend applied it without a range check and silently truncated
displacements to .toc entries beyond 32 KiB, producing a load from the
wrong address. The ELFv1 backend already checked it; do the same here.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-26 19:18:56 +09:00
Rui UeyamaandClaude Fable 5 5dce8bb4fc Report .eh_frame relocations that do not fit in 32 bits
FDEs refer to their functions with 32-bit PC-relative relocations, and
.eh_frame_hdr stores 32-bit offsets, but mold applied them without any
range check. A function more than 2 GiB away from .eh_frame, which
happens with a huge text section or with -Ttext or --section-start
placing code far from the default layout, silently got a wrapped-around
FDE that the unwinder cannot use, while GNU ld and lld reject such
links. Those relocations are now range-checked like all others, and so
are the .eh_frame_hdr entries.

The aarch64 long thunk test placed code with unwind tables 4 GiB away
from .eh_frame and relied on the bogus FDEs going unnoticed. It now
compiles the far functions without unwind tables and keeps the far
section fixed while swapping the functions between the sections, so
calls in both directions are still covered.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-26 19:18:56 +09:00
Rui UeyamaandClaude Fable 5 28513b5882 Match quoted names in version scripts and dynamic lists literally
GNU ld takes a quoted name in a version script or a dynamic list as a
literal string, while an unquoted one is a glob pattern. mold dropped
the quotes and treated everything as a pattern, so `"operator
delete[](void*)"` in an `extern "C++"` block never matched anything
because `[]` parsed as an empty character class. Glob metacharacters in
a quoted name are now escaped before the name is handed to the matcher.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-26 19:17:50 +09:00
Rui UeyamaandClaude Fable 5 df35e2ba4a Accept local: and global: without a following space in version scripts
The tokenizer keeps colons within a token for the C++ scope operator,
so `local:*;` lexed as a single token that the parser then took for a
global symbol pattern named `local:*`, and nothing was localized. The
label is now split off such a token.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-26 19:17:50 +09:00
Rui UeyamaandClaude Fable 5 72a956d56e Do not interwork ARM32 branches to symbols that are not functions
mold took the instruction set of a branch target from the LSB of the
symbol value alone, but only STT_FUNC symbols encode the instruction
set that way. A Thumb BL to a plain label in hand-written assembly,
which is NOTYPE and has an even address, was rewritten to BLX and
crashed with SIGILL, and a Thumb B.W to it went through a mode-switching
thunk. Like GNU ld, we now switch the instruction set only for function
symbols and for PLT entries, which are always ARM code.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-26 19:17:50 +09:00
Rui UeyamaandClaude Fable 5 32e7a1a111 Synthesize the PPC64 _savefpr, _restfpr, _savevr and _restvr routines
With -Os, GCC calls linker-synthesized routines not only to save and
restore general-purpose registers but also floating-point and vector
registers. mold only provided the former, so such links failed with
undefined symbol errors. The new routines use the same layout and
encodings as GNU ld; the section that holds them is renamed to
.save_restore_regs accordingly.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-26 19:17:50 +09:00
Rui UeyamaandClaude Fable 5 64d7331cef Pass common symbols through to a relocatable output
With -r, common symbols are not converted to .bss, but to_output_esym()
had no case for them and wrote them out as absolute symbols whose value
was their alignment, so linking the -r output again resolved them to a
bogus fixed address. They are now emitted as SHN_COMMON symbols with the
alignment in st_value, as in the input files.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-26 19:17:50 +09:00
Rui UeyamaandClaude Fable 5 1e5d6ba386 Fix the alignment inferred from R_RISCV_ALIGN with a power-of-two addend
The addend of R_RISCV_ALIGN is the number of NOP bytes the assembler
emitted, which is the requested alignment minus the minimum instruction
size, so the alignment is the smallest power of two greater than the
addend rather than the addend rounded up to a power of two. `.balign 4`
in RVC code (addend 2) and `.balign 8` in non-RVC code (addend 4) were
therefore treated as 2- and 4-byte alignments; in a static link that
left glibc's __sigsetjmp at a 2 mod 4 address.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-26 19:17:50 +09:00
Rui Ueyama 04a455a416 Bump mimalloc version to 3.5.0
Fixes https://github.com/rui314/mold/issues/1639
2026-08-26 10:12:28 +09:00
Rui UeyamaandClaude Fable 5 d00482d2dd Allow far calls to unresolved weak functions
An unresolved weak function resolves to address 0. In a huge executable,
a call site can be more than 2 GiB away from it, so a call relocation
such as R_X86_64_PLT32 cannot encode the displacement and mold reported
an out-of-range error. Such a call is never executed because the program
has to null-check the function first, so we now skip the range check for
call relocations against such symbols and write the truncated value.
This covers x86-64, RISC-V, s390x, SPARC64 and LoongArch; ARM already
rewrites such calls to NOPs, and PowerPC calls go through thunks. Data
references keep their range checks because their results feed the null
check.

This fixes linking Godot's sanitizer build, whose .text ends 70 MB past
the 2 GiB mark.

Fixes https://github.com/rui314/mold/issues/1640

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-26 09:12:20 +09:00
Rui UeyamaandClaude Fable 5 7279781fbd Define __global_pointer$ inside the image if there's no .sdata
Without .sdata, we defined RISC-V's __global_pointer$ as absolute
address 0. Since crt1.o initializes gp with a PC-relative
`lla gp, __global_pointer$`, such an executable could not be linked
more than 2 GiB away from address 0, for example with -Ttext-segment.
We now define the symbol at the beginning of the image, like GNU ld
which defines it relative to .data or .bss in that case. The exact
value doesn't matter because we don't relax gp-relative accesses.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-26 09:09:51 +09:00
Rui Ueyama f716fb62de Fix test for some targets
Fixes https://github.com/rui314/mold/issues/1638
2026-08-23 21:24:11 +09:00
Rui UeyamaandClaude Fable 5 d9c9b906ad Count section piece symbols while parsing sections
reattach_section_pieces() traversed every relocation of every alloc
section once just to count the references to mergeable sections, so
that it could allocate their symbols at once, and then traversed them
again to create the symbols. The count needs only ELF data, so it is
now taken while the sections are parsed, where a CREL relocation table
has just been decoded and is still in cache.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-23 21:09:05 +09:00
Rui UeyamaandClaude Fable 5 364413c41b Scan response file tokens with a lookup table
The tokenizer tested each byte against six whitespace characters and
three quoting characters in turn. A table makes the scan a single
load and compare per byte, and halves the time to tokenize Chromium's
1.9 MiB response file.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-23 21:09:05 +09:00
Rui UeyamaandClaude Fable 5 8d9cdfef3d Cut per-symbol overhead in register_global_symbols()
The loop looked up every undefined symbol in the --wrap set even when
no --wrap option was given, hashing 5.1 million long C++ names on a
Chromium link, and fetched the thread's ShardedMap bin through TBB's
thread-local lookup once per symbol rather than once per file.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-23 21:08:48 +09:00
Rui UeyamaandClaude Fable 5 21042fa988 Flatten output section members in parallel
create_output_sections() copied each output section's members from its
per-file vectors into the final array on one thread and then freed the
vectors one by one, which took about 10 ms on a Chromium link. Copy
and free per file in parallel instead, computing the alignment in the
same pass.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-23 21:08:48 +09:00
Rui UeyamaandClaude Fable 5 00f3077dae Skip defined symbols without locking in claim_unresolved_symbols()
The pass took each symbol's lock once per undefined reference, 5.1
million times on a Chromium link, although nearly every reference is
to a defined symbol, which the pass leaves alone. Popular symbols are
referenced from thousands of files, so their locks bounced between
cores. Find the references to symbols without a definition first,
which needs no lock because the pass does not change defined symbols,
and take the locks only for those.

Chromium release on a Threadripper 7980X at 32 threads: the pass goes
from 21 ms to 2 ms. Output files are bit-identical.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-23 21:08:48 +09:00
Rui UeyamaandClaude Fable 5 97f9e8e416 Resolve only the affected symbols when a hidden symbol hits a DSO
When a symbol with hidden visibility resolved to a DSO, symbol
resolution started over from scratch for all symbols with that DSO
definition excluded, and Chromium has three such symbols. Resolution is
independent per symbol, so only those symbols are resolved again, and
if one of them moves to an archive member that is not reachable yet,
liveness is propagated from that member alone. The final resolution
pass after section parsing recomputes everything else as before.

Chromium release on a Threadripper 7980X at 32 threads: resolve_symbols
goes from 143 ms to 115 ms. Output files are bit-identical.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-23 21:08:48 +09:00
Rui UeyamaandClaude Fable 5 8579808892 Sort input files in parallel
Sorting the 44,683 files of a Chromium link into command line order
took 7 ms on one thread at the end of the input reading phase.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-23 21:08:48 +09:00
Rui UeyamaandClaude Fable 5 68fceadb7d Collect absolute relocations without per-member vectors
scan_abs_relocations() gathered each member's absolute relocations into
a vector of its own and then flattened the vectors on one thread. For
.data.rel.ro of a Chromium link that was 147,000 vectors holding 1.1
million entries, and the flattening and freeing took about 10 ms of
serial time. Count the entries per member first and write them to
their final positions in parallel instead.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-23 21:08:48 +09:00
Rui UeyamaandClaude Fable 5 29a43f104d Open thin archive members from their own jobs
The job that read a thin archive opened all of its members before
feeding them to the reader loop, so a large archive's members were
opened one after another on one thread. Chromium's libblink_core.a
has 2,424. The archive job now only lists the member paths, and each
member's job opens its file, in parallel with the others.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-23 21:08:48 +09:00
Rui UeyamaandClaude Fable 5 38ec6e4ad3 Register mergeable sections without a lock
Each MergeableSection added itself to its MergedSection's member list
under a mutex from the parallel conversion loop. An output has only a
few merged sections, so on a Chromium link, 32 threads took the same
mutex 150,000 times and spent most of the pass in futex calls. Collect
the members from the files after the loop instead.

Chromium release on a Threadripper 7980X at 32 threads:
create_merged_sections goes from 55 ms to 34 ms and its system time
from 0.74 s to 0.07 s. Output files are bit-identical.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-23 21:08:48 +09:00
Rui UeyamaandClaude Fable 5 4af8d3f8e9 Read small input files instead of mmap'ing them
mmap(2) takes the process's address space lock, so with tens of
thousands of input files, the calls serialize at a few microseconds
each no matter how many threads make them. Linking Chromium maps 44,683
files, and that serialization alone bounds the input reading phase at
about 220 ms, more than a third of the link. Files up to 32 KiB, which
are 64% of Chromium's inputs by count but 16% by size, are now read
into malloc'ed buffers instead; read(2) takes no such lock, and larger
files are still mmap'ed so that their pages are shared with the page
cache rather than copied.

Chromium release on a Threadripper 7980X at 32 threads: the input
reading phase goes from 228 ms to 108 ms and the whole link from 0.69 s
to 0.56 s, with peak RSS unchanged. Output files are bit-identical.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-23 21:08:48 +09:00
Rui UeyamaandClaude Fable 5 e344452b1e Speed up response file tokenization
The tokenizer pushed every byte through an ostringstream and copied
each token twice. Tokens without quotes or backslashes, which is nearly
all of them, are now substrings of the mapped file, and whitespace is
tested inline instead of through isspace().

Expanding Chromium's 1.9 MiB response file of 19,000 arguments took
33 ms of single-threaded startup on a Threadripper 7980X; it now takes
5 ms. Output files are bit-identical.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-23 21:08:48 +09:00
Rui UeyamaandClaude Fable 5 e3ae2eb7ff Process absolute relocations in parallel
scan_abs_relocations() classified an output section's absolute
relocations and copy_buf() applied them in serial loops, although
write_dynrels() already processes them in parallel shards. A PIE
Chromium has 1.1 million of them in .data.rel.ro alone, so each loop
ran for about 30 ms on one thread while the others waited.

Chromium release on a Threadripper 7980X at 32 threads: scan_relocations
goes from 84 ms to 50 ms, copy from 89 ms to 75 ms, and the whole link
from 0.90 s to 0.86 s. Output files are bit-identical.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-23 17:06:02 +09:00
Rui UeyamaandClaude Fable 5 6f0f855ce6 Test the visited bit before setting it atomically in GC
Most relocations refer to a section that another thread has already
visited, yet visit() always did an atomic fetch_or, so popular sections
bounced between cores on every reference. Check the bit with a plain
load first, as Atomic::test_and_set() does; it is never cleared during
marking, so a stale positive answer is still correct.

Chromium release on a Threadripper 7980X at 32 threads: the mark phase
goes from 65 ms to 25 ms, the whole link from 0.96 s to 0.92 s. Output
files are bit-identical.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-23 16:54:49 +09:00
Rui UeyamaandClaude Fable 5 c2822be61a Skip the DWARF 5 type unit test if GDB chokes on the input
GDB 10 aborts on GCC's DWARF 5 type units even when the executable
contains no .gdb_index section, which broke CI on Debian 11. Probe GDB
with an index-less executable first and skip the test if it cannot
print a type from it.

Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01CKdZWtcaghcU7m5zoZtMfN
2026-08-21 20:46:39 +09:00
Rui Ueyama 0d3fe4ed34 Simplify DWARF unit header parsing 2026-08-21 16:35:44 +09:00
Rui Ueyama 6b22256da3 Fix DWARF 5 type unit gdb index compatibility
Emit version 9 indexes when type units are present so GDB 12 falls
back instead of interpreting DWARF 5 type-unit offsets as
.debug_types. Keep version 7 for indexes without type units.

Allow the regression test to run with older readelf versions and skip
it under TSAN, where relocatable links hit known oneTBB races.
2026-08-21 15:32:25 +09:00
Rui UeyamaandClaude Fable 5 0ddd5b0420 Support DWARF 5 type units in gdb index
With -gdwarf-5 -fdebug-types-section, GCC moves each type into its own
type unit and emits it as a one-section COMDAT .debug_info contribution,
so an object file contains several .debug_info sections. We assumed one
.debug_info section per file containing nothing but compilation units:
type units were missing from the index, and a pubnames offset could be
interpreted relative to the wrong contribution.

Read every live .debug_info contribution instead, classifying units by
their header's unit_type. Type units are collected into the .gdb_index
type-unit table along with their signatures, and each pubnames set is
matched to its contribution through the relocation on the set header's
debug_info_offset field. COMDAT group selection has already discarded
duplicate type units by the time we read them, so exactly one copy of
each type unit is indexed.

Fixes https://github.com/rui314/mold/issues/1634

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-21 13:42:04 +09:00
Rui Ueyama 02a4923ba2 Rename a variable 2026-08-21 10:56:25 +09:00
Rui UeyamaandClaude Fable 5 8349c41980 Fix spurious duplicate symbol error with mixed LTO and non-LTO archives
If an IR file claims a COMDAT group, the LTO result provides the group's
contents. However, post-LTO COMDAT group selection started from a clean
slate, so an archive member extracted only after LTO could win a claimed
group, resurrecting a copy that collides with the LTO result.

Remember the claim on the signature symbol, and let only LTO-generated
files win such a group after LTO. LLVM keeps claimed groups in its
output; GCC emits their contents without a group, so no file wins.

Fixes https://github.com/rui314/mold/issues/1637

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-21 10:56:25 +09:00
Rui Ueyama abd0a19cfb Rename a class 2026-08-20 23:43:53 +09:00
Rui Ueyama e3e5fba05f Fix a -Wmaybe-uninitialized warning 2026-08-20 22:57:04 +09:00
Rui Ueyama cb33534e38 Simplify 2026-08-20 22:49:01 +09:00
Rui UeyamaandClaude Fable 5 c4c090bce7 Store relaxation deltas in the arena
shrink_section() fills r_deltas once, and the later passes only read it,
so it does not need to be a growable vector afterwards. Build the deltas
in a local vector and copy them into the arena. This shrinks InputSection
by eight bytes on RISC-V and LoongArch and makes the class trivially
destructible on every target, so InputSection objects can live in the
arena without an owner that runs their destructors.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-20 22:49:01 +09:00
Rui Ueyama 6e1d9b68a3 Pack InputSection state to save eight bytes
Several InputSection booleans were stored separately even though one
atomic byte is sufficient. Pack liveness, GC visitation, address
significance, decompression, and ICF-removal state into flags, and add
relaxed fetch_or/fetch_and operations for updating them.

Also remove fde_end by marking the final FDE in each section range in
existing FdeRecord padding. This reduces InputSection<X86_64> from 56 to
48 bytes without increasing FdeRecord or Symbol.
2026-08-19 16:02:44 +09:00
Rui Ueyama 7187b766f7 Reuse InputSection storage across ICF phases
The output offset, dense ICF index, and arena-relative ICF leader are
needed at disjoint points in the link. Keeping separate members wastes
space and requires a separate eligibility flag.

Store them in a union, using icf_idx == -1 to represent ineligibility.
Process the gathered ICF sections directly and retain icf_leader only for
eliminated sections after ICF, allowing leaders to restore their offsets.
2026-08-19 16:02:44 +09:00
Rui Ueyama ebb41cbd65 Reduce ICF class-counting overhead
Counting distinct ICF classes costs about as much as a digest
propagation round, but it was done after every round. Since refinement
only splits classes, convergence does not need to be checked that often.

Run three propagation rounds between class counts and move digest-buffer
swapping into propagate(). This reduces counting overhead while preserving
the final partition.
2026-08-19 16:02:44 +09:00
Rui Ueyama 30765e5020 Remove redundant command line option 2026-08-18 20:25:01 +09:00
Rui Ueyama 6a1b0f7924 Merge pull request #1636 from WaterWhisperer/fix-1632
Fix .srodata output name and LTO option spelling
2026-08-18 20:20:11 +09:00
Rui Ueyama dd3cdb3c9d Report the COMDAT owner for ODR violations
Name the object containing the prevailing COMDAT group so users can
identify the conflicting definitions.

https://github.com/rui314/mold/issues/1635
2026-08-18 17:14:50 +09:00
WaterWhisperer 5147fc580c Fix .srodata output name and LTO option spelling 2026-08-18 16:09:19 +08:00
Rui Ueyama e04dded86e Fix a crash on ODR violations
When an ODR violation leaves a relocation referring to a discarded COMDAT
section, ObjectFile::scan_relocations() reports an error, but
scan_relocations() continues with the absolute-relocation pass. That pass
assumes symbols are resolved and may dereference a null Symbol::file,
causing mold to crash instead of exiting with the diagnostic.

Add a checkpoint between the two relocation passes so mold exits before
inconsistent symbol state reaches the absolute-relocation scan.

Fixes https://github.com/rui314/mold/issues/1635
2026-08-18 15:22:46 +09:00
Rui UeyamaandClaude Fable 5 a718dbbcd5 Fix crash when linking CREL object files with -r
create_comdat_group_sections assumed that a relocation section in a
comdat group is always SHT_RELA or SHT_REL. If an input file uses the
CREL format, the relocation sections are SHT_CREL instead, so we took
the branch for ordinary member sections and dereferenced a null
pointer, as mold does not create an InputSection for a relocation
section. mold -r had been crashing on such files since CREL input
support was added in mold 2.38.0.

Recognize SHT_CREL the same way as SHT_RELA/SHT_REL so that the
member's relocation section is propagated to the output group.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-15 09:27:02 +09:00
Rui UeyamaandClaude Fable 5 a7504210dd Store input and output sections in the arena
Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-15 09:27:02 +09:00
Rui Ueyama d8f4506ab5 Store InputSection file as a pointer 2026-08-14 22:15:34 +09:00
Rui Ueyama 5ef9acad1f Cache arena allocations in per-thread blocks 2026-08-14 22:15:34 +09:00
Rui Ueyama 3d16156250 Store section fragment references as 32-bit offsets 2026-08-14 18:19:42 +09:00
Rui Ueyama c340272e63 Fix CI 2026-08-13 13:01:48 +09:00
Rui Ueyama 635956d3b7 Emit correct symbol indices for local symbols with --emit-relocs
Commit 35f4a334 changed Symbol::is_local() to check visibility and
version-script localization, but get_output_sym_idx() also relies on it
to decide whether a symbol's slot offset is relative to the local or the
global partition of .symtab. An input-local symbol has default
visibility and no version, so is_local() returned false for it, and a
relocation emitted by --emit-relocs against a named local symbol
referred to an arbitrary global symbol.

This went unnoticed on most targets because GAS usually redirects
relocations against local symbols to section symbols, which take a
different code path. On LoongArch and RISC-V, the assembler keeps them
as-is because linker relaxation may change section-relative offsets, so
relocations against symbols like .LC0 were visibly wrong.

Fixes https://github.com/rui314/mold/issues/1631
2026-08-13 12:33:34 +09:00
Rui Ueyama 5f71cbf988 Write tombstones to .eh_frame_hdr for zero-length FDEs
Compilers may emit a meaningless FDE covering a zero-length address
range, e.g. for a function fragment that hot/cold splitting has
entirely optimized away. Such an FDE begins at the same address as the
FDE of the next real function, giving the binary search table in
.eh_frame_hdr duplicate keys. If a search lands on the empty FDE
instead of the real one, the unwinder fails to find an existing
exception handler and aborts the process.

We now write a tombstone value to .eh_frame_hdr for such an FDE. The
tombstone compares greater than any real address, so a binary search
never returns it. The FDE itself is harmless because an empty address
range doesn't match any program counter.
2026-08-13 12:14:21 +09:00
Rui Ueyama 6157cc99f1 Pack AArch64 range extension thunks
AArch64 range extension thunks are small and unlikely to benefit
measurably from aligning every entry to a 16-byte boundary. Padding
every entry with brk instructions wastes meaningful space when an
output contains many thunks.

Keep each thunk group 8-byte aligned, but pack its entries tightly so
the ARM64 short and long forms occupy 12 and 24 bytes instead of 16
and 32 bytes.
2026-08-12 20:02:27 +09:00
Rui Ueyama 441dc1d1fd Update mold.1 (automated commit) 2026-08-11 04:42:53 +00:00
Rui Ueyama d12e0e4e9c Bump mold version to 2.42.0 2026-08-11 11:34:45 +09:00
Rui Ueyama f76ea9442a Speed up range extension thunk tests 2026-08-11 11:34:45 +09:00
Rui Ueyama b9d57111ed Fix a RISC-V crash issue 2026-08-11 10:39:12 +09:00
Rui Ueyama de35271267 Upgrade bundled mimalloc to 3.4.5 2026-08-11 10:39:12 +09:00
Rui Ueyama cf0e0f7996 Fix relocations against undefined weak TLS symbols
A weak TLS variable is usually guarded by a separate weak non-TLS
symbol indicating whether the TLS definition is available. A typical
use looks like this:

  extern char foo_used __attribute__((weak));
  extern __thread int foo __attribute__((weak));

  if (&foo_used)
    foo = 1;

If foo remains undefined, its access is unreachable and there is no
meaningful value to apply to its TLS relocations. Leave those
relocations unapplied.

This prevents AArch64 from rejecting an otherwise unreachable TLS
access when an R_AARCH64_TLSLE_ADD_TPREL_HI12 relocation is out of range.

https://github.com/rui314/mold/issues/1626
2026-08-11 10:39:12 +09:00
Rui Ueyama a94df206b6 Fix SH4 PLT delay slots
SH4 raises a slot-illegal exception when a PC-relative load is placed
in a branch delay slot. Our PLT entries did exactly that:

  jmp     @r0
  mov.l   2f, r1

QEMU now checks for such illegal delay-slot loads as of
https://github.com/qemu/qemu/commit/b754cb2dcde26a7bc8a9d17bb6900a0ac0dd38e2,
matching real hardware and the SH-4 ISA. Older QEMU versions accepted
this sequence because they did not enforce the restriction, so upgrading
QEMU caused dynamically linked mold output to trap when entering a PLT
slot.

Move the relocation-index load before the jump and put a nop in the
delay slot. Grow each PLT entry from 16 to 20 bytes to make room while
keeping the two literal values 4-byte aligned.
2026-08-11 10:39:12 +09:00
Rui Ueyama 1ce5b39f7e Install loongarch64 build dependencies from the main Debian archive 2026-08-11 10:14:46 +09:00
Rui Ueyama 80af3a8138 Use the official distribution site instead of unreliable mirrors 2026-08-10 21:32:11 +09:00
Rui Ueyama 799b26f8e2 Embed git hash in distribution binaries 2026-08-09 22:07:41 +09:00
Rui Ueyama a9dc01b477 Consolidate Gentoo test caches 2026-08-07 10:59:58 +09:00
Rui Ueyama ac73431954 Support -Ttext-segment 2026-08-07 10:47:29 +09:00
Rui UeyamaandClaude Fable 5 3eca43961d Fix a SIGPIPE race in two tests
`exit` in an awk script that follows readelf in a pipeline closes the
pipe as soon as the first match is found. If readelf has not finished
writing its output by then, it is killed by SIGPIPE, which pipefail
turns into a test failure. The race is timing-dependent and was
observed roughly once per thousand runs on a loaded machine.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-07 00:07:57 +09:00
Rui Ueyama 4b56395f5e Construct RELR once per output chunk 2026-08-06 22:28:26 +09:00
Rui Ueyama cf0b26045f Use ArenaPtr to refer to symbol aux records 2026-08-06 20:49:13 +09:00
Rui UeyamaandClaude Fable 5 283238426e Overhaul the Gentoo test harness
This rewrites lib/gentoo-test.sh to make a full-distro run practical
and its results easier to trust:

- Build each package with mold first, and run the GNU ld control build
  only when the mold build fails. Results are classified into success/,
  failure/ (builds with GNU ld but not with mold, i.e. a mold bug) and
  broken/ (doesn't build with either linker, so not our fault).

- Run test suites only for the package under test by scoping
  FEATURES=test through package.env. Applying it globally drags in the
  test dependencies of the entire dependency graph and makes a large
  part of the tree unresolvable.

- Let emerge apply the configuration changes it would otherwise just
  suggest (--autounmask-continue). An ephemeral container has no
  hand-maintained package.use, so without this, thousands of packages
  are unbuildable out of the box.

- Bring the container image to a consistent ~amd64 state with a world
  update at image build time. A stable stage3 base with testing
  packages on top otherwise accumulates unsolvable dependency
  conflicts, such as perl disagreeing with its installed virtuals.

- Share work between containers through host bind mounts: source
  tarballs are downloaded once into a shared DISTDIR, and binary
  package pools let each dependency be built once instead of once per
  container that needs it. The mold and GNU ld runs use separate pools
  so that a control build can never install mold-linked binaries.

- Serialize the container image build with flock. Parallel invocations
  used to race to build the same image, one per job slot.

- Work around a deadlock in portage's binary package extraction with
  lib/gentoo-gpkg-workaround.py: tar_stream_reader.close() waits for
  the external decompressor to exit without draining its stdout, which
  hangs when the undrained residue exceeds the pipe buffer.

- Refuse to run if dist/mold doesn't embed a git commit hash, since
  test results are keyed by it.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-06 19:41:05 +09:00
Rui Ueyama 8aa9429728 Fix CI 2026-08-02 16:30:01 +09:00
Rui Ueyama cdbc8db1cb Fix -Wdeprecated-enum-enum-conversion 2026-08-02 13:08:12 +09:00
Rui UeyamaandClaude Fable 5 a7b79b2a52 Prefetch hash table buckets when resolving mergeable sections
MergeableSection::resolve_contents inserts every section fragment into
the parent's ConcurrentMap. The table is typically much larger than
the cache, so each insertion stalls on a cache miss for its first
probe. Since split_contents has already computed all fragment hashes,
prefetch the bucket eight insertions ahead.

On firefox-debug at 32 threads, this cuts the CPU time of
compute_section_sizes, which performs the insertions, by about 15%
(2.7 to 2.3 CPU-seconds) and the pass's wall time from 0.11 s to
0.10 s; the end-to-end change is within noise. All 5809 tests pass.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-02 12:27:25 +09:00
Rui UeyamaandClaude Fable 5 ad038099a0 Increase ConcurrentMap shard count from 16 to 64
get_sorted_entries splits the bucket array into NUM_SHARDS pieces, and
MergedSection::compute_section_size assigns fragment offsets with one
task per shard, so the shard count caps that pass's parallelism at 16
threads even when more are available. Raise it to 64.

Probing is confined to a shard, and a probe that visits MAX_RETRY
occupied slots aborts, so MIN_NBUCKETS grows with the shard count to
keep at least MAX_RETRY buckets per shard in the smallest maps.

Since fragment offsets are assigned shard by shard, the layout of
merged sections changes slightly (inter-shard alignment padding).
Outputs remain bit-reproducible for a given linker version, which is
the guarantee mold makes.

On firefox-debug at 32 threads, compute_section_sizes goes from
0.13 s to 0.11 s; the end-to-end change is within noise. All 5809
tests pass.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-02 12:22:59 +09:00
Rui UeyamaandClaude Fable 5 c30224584a Recompute RELR layout data in parallel
RelDynSection::update_shdr runs several times per link because the
size of .relr.dyn feeds back into section layout: set_osec_offsets
calls it until the size converges, and compute_section_headers and
the final pre-copy refresh add more calls. Each call walked all
chunks serially and ended with a serial sort of every RELR offset.
On a Firefox debug link (~360k GOT slots, ~1M relative relocations),
this accounted for most of a ~0.2 s single-threaded stretch between
layout and output copy that is plainly visible in a CPU utilization
trace.

Three changes:

- GotSection::get_num_dynrels no longer materializes the full GOT
  entry array just to count relocations. Computing each entry's
  value involves a symbol address lookup, which dominated the cost.
  The counting logic mirrors get_got_entries' relocation choices.
  This is most of the win: the GOT was the longest pole on every
  call even with the collection parallelized.

- Collect per-chunk dynamic relocation counts and RELR offsets in
  parallel; chunks are independent of one another.

- Process chunks in address order when concatenating their RELR
  offsets. Each chunk emits its offsets in ascending order and
  chunks occupy disjoint address ranges, so the concatenation is
  sorted by construction and the global sort is gone.

The output is byte-identical on firefox-debug against the unpatched
linker at the same commit. All 5809 tests pass. An interleaved A/B
on firefox-debug shows the 32-thread link going from 1.18 s to
about 1.05 s (medians of five).

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-02 11:22:06 +09:00
Rui Ueyama 5b84f386ee Simplify the hidden base-version symbol fix 2026-08-01 22:03:01 +09:00
Rui Ueyama 9b101aa9c7 Merge pull request #1629 from fmaroszek/fix-hidden-base-version-symbol
Skip hidden base-version symbols in DSOs
2026-08-01 21:40:22 +09:00
Rui Ueyama 3ff882b02c Collect RELR offsets directly from output chunks
RELR needs only the addresses to which relative relocations apply. Even after
the parent commit, mold creates complete REL/RELA records, partitions them in
RelDynSection, and then discards every field except r_offset.

Have each output chunk return a compact vector of its RELR offsets. Aggregate
and sort those vectors in RelDynSection, then encode them directly. Each chunk
also records how many of its dynamic relocations moved to RELR, so it can write
the remaining records straight to its final output slice.

Android+RELR keeps a temporary array for the remaining records because the
size of their APS2 encoding affects layout.

Seven interleaved 32-thread runs with --no-fork gave these median wall times:

                      Parent       This commit
  Firefox release    0.472518 s    0.396796 s    (-16.03%)
  Chromium debug     2.081396 s    2.044275 s    ( -1.78%)

Compared with 64b92151, the accumulated improvements are 17.79% for Firefox
release and 1.68% for Chromium debug.

An earlier seven-run A/B test found no consistent cost from returning each
vector instead of appending through an output parameter:

  Firefox release:  append 0.396 s, return 0.390 s
  Chromium debug:   append 2.005 s, return 2.028 s

The differences are less than 2% and point in opposite directions.

All 471 native x86-64 tests pass, with nine expected skips. A release build for
all supported targets also passes.
2026-08-01 17:17:03 +09:00
Rui Ueyama 19ae69dd61 Write dynamic relocations into exact output slices
RelDynSection currently asks each producer for a vector and appends it to a
second vector. OutputSection also builds one vector per shard and flattens
them. Large links therefore allocate, copy, and destroy relocation records
multiple times.

Have each producer report its exact relocation count on Chunk. RelDynSection
allocates one destination array and gives each producer a pointer to its exact
slice. OutputSection records per-shard prefix counts so shards can fill
disjoint slices in parallel.

Recompute counts on every layout pass because synthetic GOT relocation counts
can change before layout converges.

Seven interleaved 32-thread runs against 64b92151, with --no-fork, gave these
median wall times:

  Firefox release:  0.482679 s -> 0.472518 s  (-2.11%)
  Chromium debug:   2.079169 s -> 2.081396 s  (+0.11%)

All 471 native x86-64 tests pass, with nine expected skips. Release builds for
x86-64 and PPC64V1 also pass.
2026-08-01 17:17:03 +09:00
Rui UeyamaandClaude Fable 5 64b92151a0 Skip an unnecessary layout pass for non-Android links
After fix_synthetic_symbols, we re-ran set_osec_offsets
unconditionally. That pass is needed only under
--pack-dyn-relocs=android, whose packed relocation format encodes
addends in variable-length SLEB128, so fixing synthetic-symbol
values can change the encoded size of .rela.dyn. No other mode
emits anything whose size depends on addends; in particular,
RELR's size depends only on relocation locations, which are fixed
by this point. Make the pass conditional on the Android mode.

On the Firefox release benchmark with 32 threads and --no-fork,
this shortens the link from 0.44 s to 0.41 s. Outputs are
byte-for-byte identical before and after for both a RELR workload
(Firefox) and a RELA workload (Chromium release).

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-31 21:45:43 +09:00
Rui Ueyama 5e33441a0d Iterate over prevailing symbols directly 2026-07-31 12:19:39 +09:00
Rui Ueyama dd06a8969b Match large glob sets with an NFA
The scalar glob matcher is faster for ordinary version scripts, but its
work grows linearly with the number of patterns. Compile sets of 64 or more
complex patterns into one bit-parallel NFA. Each state occupies one bit, so
a machine word advances 64 states at once. Keep the existing scalar and
Aho-Corasick paths unchanged for smaller and simpler pattern sets.

On 200,000 synthetic 60-byte symbol names, the standalone matcher changes
as follows:

  128 patterns: 1.00 s -> 0.197 s (5.1x faster)
  256 patterns: 1.98 s -> 0.380 s (5.2x faster)

A normal TensorFlow debug link remains on the scalar path. A sanity check
shows apply_version_script changing from 0.177 s to 0.175 s.
2026-07-31 11:59:53 +09:00
Rui Ueyama 0a59e34909 Speed up glob matching
The general glob matcher represents all patterns as one NFA and updates
several bit vectors for every byte of every symbol. Its cost therefore
depends on the total pattern length even when a version script contains only
a few complex patterns.

Compile each complex pattern into literal, star, question, and bracket
tokens. Match without recursion, remember the most recent star, and use
string_view::find for literal runs following a star. This replaces MultiGlob
and removes the Bitvector class.

Also store non-root Aho-Corasick edges as sibling lists. Most trie nodes have
one child, so this reduces a node from 1,040 to 24 bytes on a 64-bit host. A
dense root table keeps the common transition fast. Construct failure links
breadth-first, as required by the algorithm.

With symbol-table traversal held constant, the median of five 32-thread
TensorFlow debug links changes as follows:

  apply_version_script wall time: 0.503 s -> 0.179 s (-64%)
  apply_version_script CPU time:  14.137 s -> 5.467 s (-61%)
  total wall time:                 3.626 s -> 3.285 s (-9%)
2026-07-31 10:49:32 +09:00
Rui Ueyama 34a2edb995 Iterate over symbols when applying version scripts
Version script wildcard matching currently visits each object file and scans
its global symbol slots. A symbol is processed only when that file provided
the prevailing definition, so many entries are skipped. More importantly, an
entire file is one task, which leaves workers idle when large object files
reach the end of the pass.

Iterate over the symbol table instead. Its 64 shards visit every symbol once
and distribute the work more evenly. Rename ShardedMap::for_each to
parallel_for_each to make the scheduling behavior explicit.

With the same glob matcher and 32 threads, the median of five TensorFlow
debug links changes as follows:

  apply_version_script wall time: 0.699 s -> 0.503 s (-28%)
  apply_version_script CPU time:  13.456 s -> 14.137 s (+5%)
  total wall time:                 3.800 s -> 3.626 s (-5%)
2026-07-31 10:49:32 +09:00
Franciszek MaroszekandClaude Opus 5 5898fbcb6e Skip hidden base-version symbols in DSOs
A defined dynamic symbol at VER_NDX_GLOBAL with the VERSYM_HIDDEN bit set
is a legacy compat alias. GNU as emits one for `.symver foo, foo@VER`
without the `remove` modifier, so that binaries linked before the symbol
was versioned keep resolving. It must not satisfy a new unversioned
reference to `foo`; that has to bind to the default version `foo@@VER`.

SharedFile::parse cleared VERSYM_HIDDEN before classifying the symbol, so
such an alias took the "unversioned symbol" path and claimed the plain
name. With both it and the `foo@@VER` definition claiming that name,
resolve_symbols keeps whichever comes first in .dynsym, which is the
alias. No verneed entry is then emitted, and glibc refuses to hand out a
non-base version for an unversioned lookup, so the alias wins at runtime
too and the caller silently gets the old implementation.

lksctp-tools' libsctp.so.1 exports sctp_connectx this way. Linking with
mold selects the pre-SCTP_SOCKOPT_CONNECTX3 ABI, which reports success
but never fills in the caller's association ID.

Skip such symbols while parsing. They cannot be referred to by any name,
as there is no version string to form `foo@VER` from. ld.bfd reports an
undefined reference when one is the only definition, which is what
skipping produces.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-07-30 14:23:20 +02:00
Rui Ueyama 7324c2a006 Fix 32 bit host build
Fixes https://github.com/rui314/mold/issues/1628
2026-07-30 10:16:50 +09:00
Rui Ueyama 48cea99498 Fix clang compiler warning 2026-07-28 21:11:29 +09:00
Rui Ueyama 755a39ed53 Fix CI 2026-07-28 21:02:49 +09:00
Rui Ueyama bf19884539 Fix CI 2026-07-28 18:48:14 +09:00
Rui Ueyama 17abd5cdea Skip repeated thunk range checks 2026-07-28 18:03:28 +09:00
Rui Ueyama dfb03f26f8 Shrink thunks concurrently
Each provisional thunk owns independent symbol and offset vectors. Filter
unneeded entries and recompute their offsets concurrently instead of
shrinking the thunks one at a time.

On the Chromium Arm64 debug link, seven interleaved A/B runs reduce the
remove_redundant_thunks median from 0.080 to 0.073 seconds and median
wall time from 1.924 to 1.897 seconds. The output is byte-identical.
2026-07-28 16:00:37 +09:00
Rui Ueyama 474d4ce4df Sort thunk symbols concurrently
Symbol order does not affect thunk placement, so defer deterministic sorting
until all thunks for an output section have been constructed. Sort independent
thunk vectors concurrently instead of sorting each one in the serial batch
loop.

On the Chromium Arm64 debug link, nine interleaved A/B runs reduce median wall
time from 1.98 to 1.97 seconds. The compute_section_sizes median falls from
0.116 to 0.095 seconds. The output is byte-identical.
2026-07-28 16:00:31 +09:00
Rui Ueyama 4cce716e72 Reduce serial work when creating thunks
Initialize input-section offsets in parallel. Section offsets and end offsets
are monotonic, so use lower_bound to find A and C instead of walking over
millions of sections a second and third time.

On the Chromium Arm64 debug link, five interleaved A/B runs reduce median wall
time from 2.01 to 1.95 seconds. create_range_extension_thunks falls from about
0.17 to 0.11 seconds. The output is byte-identical.
2026-07-28 16:00:31 +09:00
Rui Ueyama e4303913ee Avoid contention when creating thunks
Collect newly discovered thunk targets in per-worker vectors and concatenate
them after each batch. The existing atomic flag still removes duplicates, and
the final sort preserves deterministic output without serializing every vector
insertion through one mutex.

On the Chromium Arm64 debug link, five interleaved A/B runs reduce median wall
time from 2.19 to 2.03 seconds. The compute_section_sizes phase falls from
0.341 to 0.181 seconds, and its system CPU time falls from 5.146 to 0.620
seconds. The output is byte-identical.
2026-07-28 16:00:31 +09:00
Rui Ueyama efbba44cdc Compact mergeable section fragments
Allocate MergedSection objects in the linker arena and store each fragment's
parent section as a 32-bit arena index. This shrinks SectionFragment from 24
to 16 bytes and its ConcurrentMap bucket from 64 to 32 bytes.

Chromium's x86-64 debug link contains about 2.17 million merge-map buckets.
Across five interleaved 32-thread runs, median wall time changes from 1.91 to
1.90 seconds and median peak RSS falls from 24,870,408 to 24,808,228 KiB, a
reduction of 62,180 KiB.
2026-07-28 16:00:31 +09:00
Rui Ueyama 6a9ecbad23 Build GDB index tables during layout
Build the symbol table and constant pool in a low-priority task after
debug-info offsets are fixed. The final output path then reads relocated
address ranges and copies the prepared tables.

The stages have different dependencies and useful parallelism. Reading
input name data needs only input sections and can use all available
workers. Building the table needs final .debug_info offsets but stops
scaling near 12 workers, so run it in a separate bounded arena. Reuse one
task group for both stages.

Keep the standard serial open-addressed hash construction. This preserves
deterministic output without the prototype atomic claim table and repeated
parallel probe rounds.

Against an unrestricted table arena on the same tree, the bounded arena
reduced the 10-run median from 1.935 s to 1.905 s and user CPU from
43.510 s to 42.865 s.

Chromium debug link, 32 threads, 10 interleaved run medians:

  wall:       1.975 s -> 1.900 s (-3.8%)
  user:      43.125 s -> 42.600 s (-1.2%)
  system:     9.740 s ->  9.770 s (+0.3%)
  peak RSS: 24,817,662 -> 24,889,408 KiB (+70 MiB)
2026-07-28 16:00:31 +09:00
Rui Ueyama 046101eda3 Store input section contents as a pointer
Use the ELF section header for the stored input size and keep only the
data pointer in InputSection. Decompressed sections use sh_size because
their replacement buffer has the uncompressed size.

This reduces InputSection<X86_64> from 88 to 80 bytes. Relaxed RISC-V
and LoongArch sections continue to use the original section-header size
when copying their input bytes.

Chromium debug link, 32 threads, 10 interleaved run medians:

  wall:       1.960 s -> 1.950 s (-0.5%)
  user:      42.565 s -> 42.560 s
  system:     9.775 s ->  9.615 s (-1.6%)
  peak RSS: 24,834,922 -> 24,816,480 KiB (-18 MiB)
2026-07-28 16:00:31 +09:00
Rui Ueyama 594b03674d Combine COMDAT parsing and selection
Parse each reachable object and record its COMDAT candidates in one
parallel traversal. Reuse the signature name while parsing instead of
recovering it from the selected Symbol for the GCC debug-macro check.

Chromium debug link, 32 threads, 10 interleaved run medians:

  wall:    1.965 s -> 1.960 s (-0.3%)
  user:   43.160 s -> 42.615 s (-1.3%)
  system:  9.820 s ->  9.845 s (+0.3%)
2026-07-28 16:00:30 +09:00
Rui Ueyama 5f7764124c Use 32-bit references for linker metadata
Reserve a sparse address-space arena for symbols and input files. Objects in
that arena can refer to each other with four-byte self-relative pointers,
which halves the size of symbol-reference arrays and reduces Symbol from 48
to 40 bytes. On 32-bit hosts, reserve 256 MiB instead of 8 GiB; this places a
practical limit on linker metadata without affecting 32-bit targets linked on
a 64-bit host.

Keep the standard per-shard hash tables, but allocate their Symbol values in
stable arena blocks. Store global names beside those values, derive local
names from their owner files, and represent each COMDAT group in eight bytes.
Context still destroys file objects normally so their separately allocated
data is released before the arena is unmapped.

On the Chromium x86-64 debug benchmark with 32 threads, median wall time over
eight runs falls from 1.990 to 1.970 seconds. Median user time changes from
42.880 to 43.015 seconds, while system time falls from 9.965 to 9.890 seconds.
Median peak RSS falls from 25,625,100 to 24,841,684 KiB, a reduction of
783,416 KiB.

The generated output was byte-identical.
2026-07-28 16:00:30 +09:00
Rui Ueyama fc7cbd5dad Compact ELF section tables
Chromium has 41.96 million ELF section-table slots but only 5.86 million
regular input sections. Store a 32-bit value per slot instead of an
InputSection pointer. Zero denotes an absent section, the high bit denotes a
mergeable section, and the remaining bits index stable per-file pools.

InputSectionTable owns both pools and presents the same indexed and range
interfaces used by linker passes. Moving a section to the mergeable pool is
now explicit instead of relying on a constructor that clears a pointer passed
by reference.

Chromium debug link, 32 threads, eight interleaved run medians:

  wall:      2.005 s -> 2.000 s (-0.2%)
  user:     43.595 s -> 43.375 s (-0.5%)
  system:   10.160 s -> 9.865 s (-2.9%)
  peak RSS: 26,145,360 -> 25,598,862 KiB (-534 MiB)

All section contents matched apart from .comment and the fast build-ID note.
2026-07-28 16:00:30 +09:00
Rui Ueyama 1861e3c308 Radix-sort large GDB type lists
GDB index type vectors contain fixed-width 32-bit records. Sort lists with at
least 256 records using four byte-wise radix passes, while retaining comparison
sorting for smaller lists. Reuse worker-local scratch storage so the linear
path does not allocate for every name.

Chromium debug link, 32 threads, eight interleaved run medians:

  wall:     1.965 s -> 1.955 s (-0.5%)
  user:    44.720 s -> 43.890 s (-1.9%)
  system:   9.940 s -> 10.185 s (+2.5%)

The complete 4.86 GiB output was byte-identical.
2026-07-28 16:00:30 +09:00
Rui Ueyama 1f77c59113 Share discarded local symbols
Chromium debug has 18.39 million local symbols, and 9.71 million belong to
discarded COMDAT sections. After COMDAT elimination, allocate Symbol objects
only for retained local symbols and map every discarded local symbol to one
global zero-valued placeholder. The ELF gABI requires local symbols in a
discarded COMDAT member to be discarded and forbids references to them from
outside the group, so the placeholder does not preserve per-symbol metadata.

Chromium debug link, 32 threads, six interleaved run medians:

  wall:     2.095 s -> 2.060 s (-1.7%)
  user:    48.095 s -> 46.735 s (-2.8%)
  system:  10.110 s -> 10.280 s (+1.7%)
  peak RSS: 26,946,944 -> 26,241,790 KiB (-0.67 GiB)

Chromium sections matched apart from version and fast build-ID metadata.
2026-07-28 16:00:30 +09:00
Rui Ueyama fb80d619ae Stream debug CREL relocations
Chromium debug contains 93.8 million relocations in nonallocated sections.
Its 230 MiB CREL input expands to about 2.1 GiB of ElfRel records. Decode
and apply those relocations in one pass instead of retaining the expanded
arrays. Keep exact arrays for allocated sections, whose relocations are
consumed later.

for_each_reloc calls a target-specific callback for every relocation. GCC
15 does not inline these large callbacks on its own, leaving a function call
for each ordinary REL or RELA relocation. Mark the callbacks ALWAYS_INLINE;
this has no effect on CREL's representation but avoids that regression.

Chromium debug link, 32 threads, six-run medians:

  wall:     2.215 s -> 2.115 s (-4.5%)
  user:    50.145 s -> 48.415 s (-3.5%)
  system:  11.065 s -> 10.215 s (-7.7%)
  peak RSS: 27.96 GiB -> 25.64 GiB (-2.32 GiB)

On an Asahi machine, adding ALWAYS_INLINE to the streaming implementation
changes the native clang-debug-arm64 link at 16 threads as follows:

  wall:  2.36 s -> 1.85 s (-21.6%)
  user:  35.3 s -> 27.1 s (-23.2%)

Chromium output matched apart from the fast build-ID payload. The two Asahi
outputs were byte-identical.
2026-07-28 16:00:30 +09:00
Rui Ueyama f554cfef24 Read GDB index inputs in the background
Compilation-unit headers and GNU pubnames do not depend on relocated
debug contents. Read them from per-file input sections and build the name
map in a low-priority TBB arena while foreground passes continue. Address
ranges and final serialization still wait until debug relocations are applied.

Chromium debug link, 32 threads, six-run medians:

  wall:     2.255 s -> 2.175 s (-3.5%)
  user:    49.670 s -> 49.485 s (-0.4%)
  system:  11.460 s -> 11.260 s (-1.7%)
  peak RSS: 27.95 GiB -> 27.98 GiB (+36 MiB)

The generated .gdb_index was byte-identical.
2026-07-28 16:00:30 +09:00
Rui Ueyama d9137264f6 Reuse symbols for COMDAT groups
Most regular COMDAT signatures already name a global symbol. Reuse that
Symbol as the group identity instead of interning 14.8 million signatures in
a second map. Local, section, and versioned signatures use the same symbol
table through a name-based fallback.

Symbol resolution has been cleared when COMDAT owners are selected, so
sym_idx is temporarily available. Record the lowest file priority there, mark
each group whose file has that priority as the owner, and restore sym_idx with
one symbol-map traversal before normal resolution resumes. This keeps
ownership on the signature itself and needs no dense group IDs or auxiliary
side arrays.

On the Chromium x86-64 debug benchmark with 32 threads, median wall time over
eight runs falls from 2.310 to 2.250 seconds. Median user time falls from
49.520 to 47.390 seconds, while system time changes from 11.720 to 11.935
seconds. Median peak RSS falls from 29,485,754 to 29,103,198 KiB, a reduction
of 382,556 KiB.

The generated output was byte-identical.
2026-07-28 16:00:30 +09:00
Rui Ueyama 7887246f23 Cache input symbol name lengths
ELF symbols already contain their string-table offsets. Cache only a one-byte
NameLen instead of retaining a 16-byte string_view for each input symbol.
Longer lengths use logarithmic lower bounds, so lookups scan only the uncached
suffix. Chromium has 37.8 million input symbols, making the per-symbol
reduction substantial.

On the Chromium x86-64 debug benchmark with 32 threads, median wall time over
eight runs changes from 2.320 to 2.315 seconds. Median user and system time
fall from 49.800 and 11.625 seconds to 49.645 and 11.580 seconds. Median peak
RSS falls from 30,132,552 to 29,503,344 KiB, a reduction of 629,208 KiB.

The generated output was byte-identical.
2026-07-28 16:00:30 +09:00
Rui Ueyama a222bda697 Store symbol name lengths compactly
Store short name lengths exactly in one byte and use the remaining values as
logarithmic lower bounds. Looking up a long name then scans only its suffix.
Copy a long version-prefix name into the string pool when its original storage
is not NUL-terminated. This reduces Symbol<X86_64> from 56 to 48 bytes.

On the Chromium x86-64 debug benchmark with 32 threads, median wall time over
eight runs falls from 2.355 to 2.340 seconds. Median user time falls from
50.475 to 49.875 seconds, while system time is unchanged at about 11.85
seconds. Median peak RSS falls from 30,330,582 to 30,146,730 KiB, a reduction
of 183,852 KiB.

The generated output was byte-identical.
2026-07-28 16:00:30 +09:00
Rui Ueyama 63d88b5e46 Store input section names compactly
An input section's object and section header already identify its name in
the section-header string table. Store only the 16-bit length in InputSection
and recover the pointer from that existing metadata. This reduces
InputSection<X86_64> from 104 to 88 bytes.

On the Chromium x86-64 debug benchmark with 32 threads, median wall time over
eight runs changes from 2.365 to 2.360 seconds. Median user and system time
fall from 50.940 and 12.005 seconds to 50.495 and 11.700 seconds. Median peak
RSS falls from 30,470,228 to 30,335,754 KiB, a reduction of 134,474 KiB.

The generated output was byte-identical.
2026-07-28 16:00:30 +09:00
Rui Ueyama eb615efc16 Store decoded CREL in exact arrays
A CREL header gives the relocation count before decoding starts. Add
ExactArray, which owns an uninitialized array of a fixed size and provides
indexed access, and fill it directly. Existing relocation consumers receive a
span over the array.

This avoids vector capacity slack and initialization that the decoder
immediately overwrites.

On the Chromium x86-64 debug benchmark with 32 threads, median wall time over
eight runs falls from 2.400 to 2.370 seconds. Median user and system time fall
from 51.655 and 12.115 seconds to 50.855 and 11.965 seconds. Median peak RSS
falls from 31,006,116 to 30,470,208 KiB, a reduction of 535,908 KiB.

The generated output was byte-identical.
2026-07-28 16:00:30 +09:00
Rui Ueyama 3af5464546 Speed up GDB index construction
Chromium contributes 35.8 million GDB name records. Keep each temporary
record at 16 bytes, replace its parsed form with its indexed form after
interning, and deduplicate names within each compilation unit with a small
linear hash table instead of sorting them.

Compute each string hash and length in one scan, assign constant-pool ranges
with a parallel prefix sum, and record type-vector slots while interning. Keep
the slot-claiming protocol shared by both ConcurrentMap insertion interfaces.

On the Chromium x86-64 debug benchmark with 32 threads, median wall time
over eight runs falls from 2.550 to 2.390 seconds. Median user and system time
fall from 54.735 and 12.390 seconds to 51.275 and 12.095 seconds. Median peak
RSS falls from 32,086,034 to 30,999,878 KiB, a reduction of 1,086,156 KiB.

The generated output was byte-identical.
2026-07-28 16:00:30 +09:00
Rui Ueyama 50f68848d5 Use worker-local HyperLogLog sketches
The cardinality estimators used shared atomic registers even though their
callers already partition work across threads. Give each worker a local sketch
and merge the registers after the parallel scan. Apply the same scheme to GDB
index names and mergeable-section fragments.

Chromium debug link, 32 threads, five interleaved run medians:

  wall: 2.60 s -> 2.57 s (-1.2%)
  user: 56.22 s -> 55.95 s (-0.5%)

The generated .gdb_index was byte-identical.
2026-07-28 16:00:30 +09:00
Rui Ueyama e1f1ba00e0 Avoid repeated section name scans
Section construction repeatedly recovered names from .shstrtab even when the
caller had just looked up the same name. Pass known names into constructors and
defer other lookups until a name is actually needed.

Chromium debug link, 32 threads, five interleaved run medians:

  wall: 2.61 s -> 2.60 s (-0.4%)
  user: 57.38 s -> 56.22 s (-2.0%)

The generated output was byte-identical.
2026-07-28 16:00:30 +09:00
Rui Ueyama 7813baa40f Avoid building discarded COMDAT sections
Duplicate COMDAT groups account for millions of sections in large debug
links. Read the symbol table and group membership first, choose the winning
groups after archive extraction, and only then construct input sections and
local symbols for retained members.

Let ShardedMap report whether gather created each symbol so new symbols can
be initialized in the same pass. An empty section table means that a file has
not reached section construction, avoiding a separate parsing-state flag.

Chromium debug link, 32 threads, five interleaved run medians:

  wall:     2.77 s -> 2.61 s (-5.8%)
  peak RSS: 33.2 GiB -> 30.9 GiB (-2.3 GiB)

The generated output was byte-identical.
2026-07-28 16:00:30 +09:00
Rui Ueyama ac7334246c Simplify 2026-07-28 16:00:30 +09:00
Rui Ueyama 9cb33a0e7d Merge canonical symbol flags
Canonical PLTs and copy relocations are alternative ways to give an
imported symbol an address in the output. Which one is needed follows
from the symbol type, so recording separate scan flags duplicates
information.

Use one CANONICAL action and NEEDS_CANONICAL flag, then choose the table
to populate when dynamic symbol entries are assigned.
2026-07-28 16:00:22 +09:00
Rui UeyamaandClaude Fable 5 5157cef1fc [PPC64] Reject files with an unsupported combination of ABI and endianness
We choose between the two PPC64 ABIs by endianness because ELFv1 is
normally big-endian and ELFv2 little-endian. However, that
correspondence is a convention rather than a rule. In particular, musl
uses ELFv2 on big-endian too, so linking for example Alpine ppc64
objects silently produced an ELFv1-style executable with a synthesized
.opd whose startup code crashes immediately.

We support only the usual combinations, so make get_elf_type() treat the
others as unrecognizable, just like an ELF file for an unsupported
machine. Also give unrecognizable ELF files a proper error message; we
used to print "incompatible file type: ppc64 is expected but got " with
a trailing empty string.

Fixes https://github.com/rui314/mold/issues/1498

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-26 14:43:12 +09:00
Rui UeyamaandClaude Fable 5 35f4a334a0 Do not demote non-exported globals to local symbols in .symtab
We computed the binding of a symbol in the static symbol table from its
dynamic import/export status, so in an ordinary executable, every global
symbol was emitted as STB_LOCAL. That misleads all symbol table
consumers; nm prints "t main" instead of "T main", and gdb had to work
around it for a feature that depends on symbol bindings.

Only symbols hidden by visibility or localized by a version script
should be demoted. GNU ld keeps hidden symbols STB_GLOBAL with hidden
visibility while lld converts them to STB_LOCAL; we follow lld, which
also matches our previous output for such symbols.

One wrinkle: claim_unresolved_symbols() assigned version index 0
(VER_NDX_LOCAL) to imported symbols so that their .gnu.version entries
would be 0, which would make the new is_local() misclassify them as
version-script-localized. Leave ver_idx unassigned for imported symbols
and have the .gnu.version writer emit 0 for unversioned undefined
symbols directly, so that VER_NDX_LOCAL on a symbol means only
"localized by a version script".

Fixes https://github.com/rui314/mold/issues/906

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-26 14:43:12 +09:00
Rui UeyamaandClaude Fable 5 fba0a2185d Report duplicate definitions of a versioned symbol
A default-versioned symbol `foo@@VER` can be referred to not only as
`foo` but also as `foo@VER`, so defining both `foo@@VER` and `foo@VER`
is a duplicate definition of the versioned name. We didn't diagnose it
and silently emitted a dynamic symbol table containing two definitions
of `foo@VER`, leaving it up to the dynamic loader which one a versioned
reference binds to. GNU ld and lld reject this; now we do too.

We check for duplicates by scanning the dynamic symbol table once it's
populated, because the problem manifests if and only if both symbols
are exported, and the exported set is much smaller than the set of all
symbols. GNU ld additionally reports the error for non-exported
symbols; lld doesn't, and we behave like lld.

Fixes https://github.com/rui314/mold/issues/828

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-26 14:43:12 +09:00
Rui UeyamaandClaude Fable 5 1fb2901b19 Report an error for a defined dynamic symbol with version index 0
We ignored defined dynamic symbols whose .gnu.version entry is 0
(VER_NDX_LOCAL) on the assumption that such symbols are bound locally.
That assumption is wrong; a symbol that's actually local to a DSO
doesn't appear in .dynsym at all, so such an entry is ill-formed. GNU
ld briefly emitted version index 0 for unversioned defined symbols
(https://sourceware.org/bugzilla/show_bug.cgi?id=33577), and linking
against a DSO created by such ld made us report confusing "undefined
symbol" errors for symbols that plainly exist in the DSO.

lld rejects such files as corrupted. Follow suit and report an error
pointing at the offending file and symbol instead of silently ignoring
it.

Fixes https://github.com/rui314/mold/issues/1534

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-26 13:46:57 +09:00
Rui UeyamaandClaude Fable 5 2b605279fe Report an error for a shared object in a static link
We silently accepted a shared object file given on the command line even
in the static mode and simply ignored it, producing an executable whose
references to the shared object's symbols remain unresolved. Such a
binary crashes at runtime. GNU ld and lld reject this at link time, and
so should we.

Fixes https://github.com/rui314/mold/issues/1616

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-26 13:46:57 +09:00
Rui UeyamaandClaude Fable 5 00a27307f8 Use git rev-parse to obtain the current commit hash
We used to read files under .git directly to avoid a build-time dependency
on git, but the on-disk repository format is not stable. The hash lookup
broke for repositories using the new reftable format, and it never worked
in git worktrees, where .git is a file rather than a directory.

Just ask git instead. Building from a git checkout now requires git, which
is not much to ask; building from a source tarball still works and simply
omits the hash, as before.

Fixes https://github.com/rui314/mold/issues/1621

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-26 11:36:14 +09:00
Rui UeyamaandClaude Fable 5 920a516125 Fix false ODR violation with mixed LTO and non-LTO archives
After LTO, we reset the reachability of archive members and redo symbol
resolution to re-derive which members are actually needed, because the
LTO-generated code may no longer use some of the members that were
extracted to satisfy references from IR files. However, the results of
the pre-LTO COMDAT group selection survived into the second resolution:
ComdatGroup::owner kept the first winner's priority, and the losers'
member sections remained dead.

Therefore, if a group's winner was an archive member that is not
re-extracted after LTO, no remaining file could provide the group's
sections, and we reported a false "refers to a discarded COMDAT section"
error for a perfectly valid program.

Reset the COMDAT group state in do_lto() so that the post-LTO symbol
resolution selects groups from a clean slate among the files that
actually remain in the link.

Fixes https://github.com/rui314/mold/issues/1565

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-26 10:37:15 +09:00
Rui Ueyama 4b00446c59 Support DTPREL relocations in non-allocated sections
Fixes https://github.com/rui314/mold/issues/1626
2026-07-26 00:10:27 +09:00
Rui UeyamaandClaude Fable 5 1d306dc0ac Intern symbols without a concurrent hash table
Like the previous commit did for comdat groups, symbol interning no
longer goes through a single concurrent hash table. Object file
parsing only records each global symbol name along with which slot of
the file's symbol vector it belongs to; gather_symbols() then
deduplicates the names into Symbol objects and fills in the files'
symbol pointers, processing each hash shard on a single thread with
no synchronization. Besides avoiding concurrent hash table
insertions, this hashes each name only once and no longer constructs
a temporary Symbol per lookup, which the concurrent map's insert
interface required. Linker-synthesized symbols, shared libraries and
linker scripts keep interning through a locked slow path in
get_symbol().

Linking a Chromium debug build on a Threadripper 7980X saves ~12
CPU-seconds (83 -> 71) and about 0.25 seconds of wall time at 32
threads. Output files are bit-identical.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-20 15:29:27 +09:00
Rui UeyamaandClaude Fable 5 383ff33215 Deduplicate comdat groups without a concurrent hash table
We used to intern comdat group signatures into a single concurrent
hash table as object files are parsed. The concurrent map is costly
under 32-way insertion, and every file redundantly hashed the
signature strings, which are long mangled names for C++ programs.

Signatures are now handled by a new ShardedMap class: parsing only
appends them to thread-local buffers binned by hash, and once all
files have been read, each bin is deduplicated by a single thread
with no synchronization, filling in the files' ComdatGroupRefs. A
signature is hashed once, when it's recorded. Deduplication runs in
resolve_symbols() right before comdat elimination rather than at the
end of read_input_files() so that files parsed later, such as ELF
files produced by the LTO plugin, are covered when we redo symbol
resolution. LTO comdats otherwise take a mutex-protected slow path.

Linking a Chromium debug build on a Threadripper 7980X is about 4%
faster (3.22s -> 3.06s at 32 threads), saving ~4 CPU-seconds. Output
files are bit-identical.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-20 15:07:11 +09:00
Rui UeyamaandClaude Fable 5 4ea8cb996f Read input files in parallel
We used to read input files one at a time in the command line order,
with a single loop opening and mmap'ing each file and walking archive
files. That loop, not parsing, is the bottleneck of the read phase;
for a Chromium debug build with ~45,000 input files, it takes ~1.3
seconds, about 40% of the total link time.

We now open and read files with a parallel for-each loop with the
feeder pattern; an archive file feeds one job per member. The command
line parser records the reader state (--as-needed etc.) and a
hierarchical position for each input file argument, and files are
sorted back into the command line order afterwards to assign
priorities. Linker scripts, which can mutate the linker context, are
parsed after the parallel loop, one at a time in the command line
order. LTO claims are serialized because the plugin interface has
shared state.

Linking Chromium debug on a Threadripper 7980X is ~2% faster at 32
threads and ~14% faster at 64 threads, where the serial loop
previously made additional threads a net loss. Output files are
bit-identical to before.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-20 11:37:47 +09:00
Rui UeyamaandClaude Fable 5 2d09378cfd Remove the use of mremap from LockingOutputFile
Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-19 19:10:37 +09:00
Rui UeyamaandClaude Fable 5 a8fbf52ef1 Map the output file with room to grow instead of using mremap
mremap, which we used to grow the output file mapping for
.gdb_index, is Linux-specific, so commit 9b7c5267 broke the build on
the BSDs and macOS. Instead of growing a mapping that was created
with the exact file size, we now map the output file with twice as
much address space as its size upfront. The mapping beyond the end
of the file may not be touched, but growing the file with ftruncate
makes the tail accessible, so extend() no longer needs any mmap
call, and the output buffer never moves. Reserving address space
does not consume any physical memory; pages materialize only when
they are first touched.

This also fixes the madvise(MADV_HUGEPAGE) call in extend(), which
had always failed with EINVAL because its address was not
page-aligned. That went unnoticed because an mremap'ed mapping
inherits the huge-page flag from the original mapping.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-19 18:41:18 +09:00
Rui Ueyama 848135170a Update mold.1 (automated commit) 2026-07-19 05:50:51 +00:00
Rui UeyamaandClaude Fable 5 1c11d94c02 Allocate InputSections from a per-file pool
A large link creates tens of millions of InputSection objects, one
heap allocation each, and the profiler attributed 5% of all cycles
to their construction. Allocate them from a per-file std::deque
instead and refer to them through plain pointers. Section death is
represented by a null entry as before; the object itself stays in
the pool until the file is destroyed.

This removes the InputSection constructor from the profile and
cuts the allocation count by 4x, but the end-to-end link time is
unchanged within noise on a Chromium debug build; the constructor
cost was evidently not on the critical path. The change still pays
its way by making `sections` a plain pointer vector, which drops
the unique_ptr indirection and the .get() noise throughout.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-19 12:07:52 +09:00
Rui UeyamaandClaude Fable 5 45dea59990 Discard temporary local symbols by default
Compilers emit numerous temporary local symbols such as .L.str.42
for unnamed program elements. They are compiler-internal: nothing
references them through the symbol table, and DWARF refers to their
data by section offset. Nevertheless we kept them in the output by
default, following GNU ld. A Chromium debug build contains 2.26
million of them, and they added ~180 MiB of .symtab and .strtab to
the 4.9 GiB output.

Discard them by default, as lld does. The new --discard-none option
restores the previous behavior. -r and --emit-relocs continue to
keep all local symbols, since emitted relocations may refer to
them, and RISC-V and LoongArch no longer need their special case,
as the default now covers them.

Link time is unchanged within noise; the benefit is a ~4% smaller
output for large debug builds and a symbol table that no longer
resolves addresses to compiler-internal labels.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-18 23:32:56 +09:00
Rui UeyamaandClaude Fable 5 9b7c526704 Use mremap to extend the output file mapping
Previously, data appended to the output file, such as .gdb_index,
was staged in a heap buffer or a separate memory mapping. We can
instead just grow the existing mapping with mremap(MREMAP_MAYMOVE).
Consumers of the output buffer derive pointers from ctx.buf, so the
mapping is free to move, and since filesize now includes appended
data, the build ID and the .gnu_debuglink CRC naturally cover the
entire file.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-17 22:05:41 +09:00
Rui UeyamaandClaude Fable 5 a30c483ffa Parallelize section marking for __start_/__stop_ references
When a live section refers to a __start_<name> or __stop_<name>
symbol, every section named <name> must be kept alive. The garbage
collector marked those sections one by one in the thread that
found the reference. A single reference can fan out to an enormous
number of sections: ClickHouse is built with XRay instrumentation,
which emits one xray_instr_map section per function, and the XRay
runtime references __start_xray_instr_map, so one thread marked
and visited a couple hundred thousand sections while the rest of
the machine waited for the mark phase to finish.

This was a regression introduced by 384527fd: before it,
C-identifier-named sections were unconditional GC roots and
entered the root set through a parallel loop. That commit
correctly made their retention conditional on a reference to
their marker symbols but expanded the fanout serially. Keep the
precise semantics and spread the fanout over threads with a
nested parallel loop, storing the sections of each name as a
vector instead of entries of a multimap.

This shortens ClickHouse's debug link from 1.98s to 1.45s at the
default 32 threads; its gc pass drops from 0.56s to 0.09s.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-17 18:14:48 +09:00
Rui UeyamaandClaude Fable 5 d444584f2b Allocate ConcurrentMap tables with huge pages
ConcurrentMap allocated its hash table with MAP_POPULATE, which
zeroes every page of the mapping inside the mmap system call, on
one thread. The string-merge map for a Clang debug build spans
several gigabytes, so every other thread stalled for ~170ms at the
barrier between splitting and inserting strings while one thread
pre-faulted the table.

Map the table lazily and advise MADV_HUGEPAGE instead. Populating
the table on demand from concurrent inserts spreads the fault cost
over all threads, huge pages make each fault populate 2MiB rather
than a base page, and they also cut the TLB miss rate of the
random probes into the table. Explicit parallel pre-faulting was
measured to add nothing on top of this. This shortens a Clang
debug link from 2.13s to 1.85s and a Chromium debug link from
3.38s to 3.27s at the default 32 threads.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-17 13:44:55 +09:00
Rui UeyamaandClaude Fable 5 c8d44d73ce Write .gdb_index directly to the mmap'ed output file
.gdb_index was constructed in a malloc'ed buffer that close()
appended to the output with a single buffered write, copying the
entire section (534 MiB for a Chromium debug build) through the
page cache on one thread, into disk blocks that ext4 had to
allocate inside the write path.

Add OutputFile::extend, which returns a buffer for data appended
past the mapped image. A memory-mapped output file grows with
ftruncate, preallocates the new disk blocks, and memory-maps the
appended range, so write_gdb_index constructs the section directly
in the output file, in parallel. The range gets its own mapping
because growing the existing one with mremap could move it,
invalidating pointers into it held by the caller. Output files
that cannot be mapped (special files such as stdout, the separate
debug file, and Windows) keep buffering appended data on the heap,
now private to each implementation; the shared buf2 field is gone.

The fallocate calls are now guarded by a configure-time
HAVE_FALLOCATE check rather than #ifdef __linux__.

This shortens a Chromium debug link from 3.31s to 3.18s and makes
close_file effectively free.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-17 11:50:46 +09:00
Rui UeyamaandClaude Fable 5 97867c5354 Parallelize dynamic relocation collection
RelDynSection::update_shdr collected dynamic relocations by walking
all chunks serially, and a single output section such as
.data.rel.ro can account for most of an output's dynamic
relocations (~1.5 million for a Chromium debug build, mostly
R_RELATIVE for vtables). The walk computes each relocation's offset
and addend with Symbol::get_addr, which made it a ~0.1s
single-threaded stretch between parallel passes.

Process an output section's absolute relocations in fixed-size
parallel shards and flatten the per-shard results, so the resulting
section contents are identical to the serial version. This shortens
a Chromium debug link from 3.58s to 3.49s at the default 32
threads.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-17 10:57:56 +09:00
Rui UeyamaandClaude Fable 5 e2190e9a60 Avoid string allocations in command line option matching
Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-17 09:08:22 +09:00
Rui UeyamaandClaude Fable 5 0a5bd29b56 Speed up command line option processing
parse_nonpositional_args tested each command line argument against
every option name, and each test allocated a few heap strings in
add_dashes. A command line for a large program consists almost
entirely of input file paths (a Chromium debug link passes ~19,000
paths but fewer than 100 options), so we were spending several
hundred milliseconds of serial time on failed option matches before
opening the first input file.

An argument that does not start with '-' cannot match any option,
so such arguments now go straight to the output list. This shortens
a Chromium debug link from 3.98s to 3.59s on a 64-core machine
because option processing delayed the start of the parallel parsing
phase.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-17 08:41:55 +09:00
Rui UeyamaandClaude Fable 5 be3d214c47 Fix SH4 dynamic relocations losing their addends
SH-4 object files store relocation addends in the relocated places
rather than in r_addend, and mold assumed that dynamic relocations
work the same way, emitting them with zero r_addend and the addend
written to the relocated place. The glibc dynamic linker, however,
follows the usual RELA convention: it reads the addend of a symbolic
dynamic relocation only from r_addend, and it reads the relocated
place only for R_SH_RELATIVE relocations whose r_addend is zero. Any
symbolic dynamic relocation with a non-zero addend thus lost its
addend at load time.

A typical victim is the vtable pointer of a typeinfo object in a
position-independent executable. It must point into libstdc++'s
vtable (e.g. _ZTVN10__cxxabiv117__class_type_infoE + 8), so its
dynamic relocation carries a non-zero addend. With the addend
dropped, virtual calls on such a typeinfo dispatch to wrong vtable
slots, which crashes the C++ exception machinery as soon as a
personality routine tries to match a catch clause. The icf-lsda test
caught this on sh4aeb because its CI toolchain creates PIEs by
default; the little-endian sh4 toolchain defaults to
position-dependent executables, in which the vtable is copy-relocated
and no dynamic addend is involved, so the same test passed there.

Remove the ElfRel specializations that zeroed addends for symbolic
relocations; the generic RELA record types have the identical layout
and keep addends. Relocation records emitted for -r or --emit-relocs
keep following the object-file convention, since their consumers
expect addends in the relocated places.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-15 18:10:26 +09:00
Rui UeyamaandClaude Fable 5 4ef13c9609 Speed up gdb-index construction
Creating a .gdb_index was slower than it should have been for three
reasons. read_pubnames_cu looked up compunits with a linear scan,
which made it quadratic in the number of compunits; the compunit
vector is sorted by offset, so a binary search suffices. gdb_hash
was computed for every name occurrence even though the same name
typically appears in many compunits; we now compute it only when a
name is inserted into the map for the first time. Finally, nametypes
were sorted with a stable sort, but their ordering is total, so a
plain sort produces the same result without the extra buffer.

On a Chromium debug build, this makes the write_gdb_index pass 2x
faster with --threads=1 (10.2s to 5.2s) and 28% faster at the
default 32 threads (0.69s to 0.50s). The output is bit-identical.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-15 18:10:26 +09:00
Rui UeyamaandClaude Fable 5 3699cc68e2 Speed up version script symbol matching
Symbols are matched against version script patterns with an
Aho-Corasick automaton (for single-substring patterns) or an
NFA-based glob matcher, both of which scan the entire symbol name on
every query. But almost all patterns in real version scripts are a
match-all ("local: *;"), an exact name, or a name with one trailing
or leading "*", all of which can be matched with a plain string
comparison.

Handle such patterns directly and reserve the automatons for the
ones that actually need scanning, such as "*foo*" or "a*b".

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-15 18:10:26 +09:00
Rui Ueyama bfa7117757 Merge pull request #1624 from git-staus/main
Install build dependencies with `sudo`
2026-07-15 10:50:37 +09:00
git-staus 9bf01c1f1d Install build dependencies with sudo
The `README` gives users commands to copy, but `install-build-deps.sh`
is not ran as root, even though the CMake install step is indicated to
run with `sudo`. FreeBSD seems not to come with `sudo` by default, so
modifying the install script might be unwise. Simply place it in front
of the script invocation itself in the `README`.
2026-07-15 02:10:05 +02:00
Rui UeyamaandClaude Fable 5 5ba31dc3ea Clean up ICF
Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-14 22:34:18 +09:00
Rui UeyamaandClaude Fable 5 86b42639cc Remove the affinity partitioner from the ICF propagation loop
The affinity partitioner replayed each round's chunk-to-thread
placement so that a thread would re-read the digests it wrote in the
previous round from its own cache. That no longer pays off: after the
recent ICF rework, the propagation loop streams over the digest and
edge arrays and is bound by memory bandwidth rather than cache reuse.

Linking Chromium on a Threadripper 7980X (64 cores, 8 CCDs), the
propagate timer is unchanged at 16 and 128 threads and loses only
about 2 ms (4%) at 32 threads, which is invisible in the total link
time. In exchange, the default auto partitioner retains full work
stealing, which balances better against the skewed per-section edge
counts, and the partitioner no longer needs to be threaded through
two function signatures.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-14 21:59:50 +09:00
Rui UeyamaandClaude Fable 5 a06b6234c9 Fix ICF folding functions with different exception tables
compute_digest hashes an eligible relocation target without identity,
relying on a graph edge to keep different targets distinguishable
through the propagation rounds. But gather_edges only created edges
for a section's own relocations, not for the relocations in its FDEs,
so the reference from an FDE to its LSDA never became an edge. As a
result, two functions with identical machine code whose exception
tables catch different types were considered identical, and folding
them changes which exceptions the survivor catches. The bug is as old
as mold's ICF and also affects --icf=safe, since such functions are
usually not address-taken.

Create edges for FDE relocations, mirroring exactly what
compute_digest hashes. The included test, two byte-identical
functions catching different exception types, aborts with an uncaught
exception without this fix.

On 32-bit ARM the equivalent bug still exists through a different
mechanism: exception tables are .ARM.extab sections tied to functions
via .ARM.exidx rather than FDE references, which ICF does not yet
track. The test skips ARM for now.

A Chromium debug build is unaffected (same rounds, same number of
folded sections) since Chromium builds with -fno-exceptions.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-14 20:51:11 +09:00
Rui UeyamaandClaude Fable 5 6b5df4d6cf Use sentinel-terminated index arrays in ICF
gather_sections and gather_edges each kept a per-element count array
next to a starting-index array and combined the two at the end.
Counting directly into the index array shifted by one and running the
prefix sum in place removes the count arrays, and the extra entry at
the end means edge_indices[i + 1] is valid for every vertex, so the
propagation loop no longer needs to special-case the last vertex to
find where its edge list ends. The empty-input early return in
gather_edges falls out as well.

No behavior change.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-14 20:51:11 +09:00
Rui UeyamaandClaude Fable 5 3e62b432a5 Compute ICF digests from the previous round's digests
A propagation round used to compute a vertex's next digest by hashing
its initial digest together with its neighbors' current digests,
which required keeping three digest arrays: the initial one and two
that alternate between current and next.

Hash the vertex's current digest instead of its initial one. This is
the textbook formulation of iterated partition refinement, and it
distinguishes exactly the same vertex pairs in every round: a current
digest determines the initial digest injectively (up to hash
collisions, which the whole algorithm already assumes away), so the
per-round partitions, the number of rounds and the final equivalence
classes are all unchanged.

The initial digest array disappears, saving 16 bytes per vertex, and
the current/next arrays are now two plainly named vectors that swap
after each round instead of an indexed triple with a flip-flop slot
index.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-14 20:51:11 +09:00
Rui UeyamaandClaude Fable 5 f478001cc0 Remove per-vertex convergence tracking from ICF
The propagation rounds used to record which vertices' digests had
stopped changing in a `converged` bitmap and skip rehashing them in
later rounds. That was worthwhile when rounds were expensive, but by
now a round is a cheap, uniform data-parallel pass, and the
bookkeeping costs as much as it saves: reading and comparing two
digests per vertex per round just to skip a short hash computation.

Rehash every vertex on every round instead. The convergence check was
the last user of Digest's equality operator, so that is gone too.

For a Chromium release build, the whole ICF pass got slightly faster
(0.166s to 0.152s wall, 4.7s to 4.3s of CPU time).

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-14 20:51:11 +09:00
Rui UeyamaandClaude Fable 5 7172b136c9 Remove ICF leaf premerging
Leaf sections, those with no outgoing references, used to be merged
upfront so that they don't have to participate in the propagation
rounds as graph vertices. That was worth the code when the rounds and
the convergence check were expensive, but both have since become
cheap, and the propagation machinery handles leaves naturally: a
leaf's digest never changes, so it is marked as converged after the
second round and costs almost nothing from then on.

This deletes merge_leaf_nodes, is_leaf and the icf_leaf flag, and
moves eligibility classification to gather_sections. The leader
branch in hash_symbol is gone too because no section has a leader
anymore by the time digests are computed.

Also tidy up the neighborhood: the counting pass of gather_edges
skipped symbols that point to a section fragment before asking for
their input section, but a symbol's origin is a tagged pointer that
is either a fragment or an input section, so get_input_section()
already returns nullptr for fragment symbols. Dropping the redundant
check makes the counting pass and the filling pass identical in
shape.

For a Chromium release build, the ICF pass takes the same time as
before; the propagation rounds absorb roughly what merge_leaf_nodes
used to cost. The graph gets about 30% more vertices, which costs
some transient memory.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-14 20:51:11 +09:00
Rui UeyamaandClaude Fable 5 c6f436690b Represent ICF digests as a pair of 64-bit words
A digest used to be std::array<uint8_t, 16>, so DigestMap had to
memcpy the bytes into 64-bit words before deriving the slot index and
tag from them. Defining Digest as a struct of two u64 members gives
the words names and makes the copies disappear.

icf.cc is compiled once per target, so the struct is defined in every
one of those translation units. The definitions are all identical, so
this is fine under the one-definition rule, just as the old type
alias was.

No behavior change.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-14 20:51:03 +09:00
Rui UeyamaandClaude Fable 5 923df2f0d6 Merge ICF leaf sections by digest
Leaf sections used to be merged with a dedicated hash map keyed by
raw section contents, with its own hasher and equality functor. That
equality was subtly weaker than the one the propagation rounds use:
it compared FDE bodies but ignored which CIE each FDE points to, so
two identical functions whose CIEs recorded different personality
routines could be merged incorrectly. It also ignored section flags.

Instead, compute the same digest for leaves that compute_digest
produces for graph sections and merge leaves through DigestMap, the
same structure the propagation rounds use. One definition of section
equality now covers both paths, which fixes the personality bug, and
LeafHasher, LeafEq and the last digest-keyed use of tbb's hash map in
this file disappear.

The trade-off is that leaf merging now relies on 128-bit digest
uniqueness instead of comparing contents exactly, but that is the
same assumption the rest of ICF is already built on.

For a Chromium debug build linked with --icf=all, merge_leaf_nodes
went from 0.07s to 0.10s because a leaf's contents are now hashed by
SipHash twice, once to insert and once to look up. 152 of 3.6M folded
sections are no longer merged; those are the sections the weaker
equality was conflating.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-14 20:49:55 +09:00
Rui UeyamaandClaude Fable 5 b922536782 Fold ICF leader election into digest counting
To group sections at the end of ICF, we used to build a concurrent
hash map from digest to the lowest-priority section holding it, which
duplicated most of what the digest-counting hash set already does.
That map was also slow enough to construct and destruct that we
deferred freeing it until exit.

This patch generalizes the counting set into DigestMap by adding a
section pointer to each slot. The counting rounds now elect the
lowest-priority section for each digest as a side effect, so by the
time the propagation loop converges, the final round has already
elected the leader of every equivalence class, and the grouping step
reduces to a single lookup pass. The digest-keyed tbb map and its
std::hash specialization disappear, and so does ctx.on_exit, whose
only purpose was to defer freeing that map.

For a Chromium debug build linked with --icf=all, the grouping pass
went from 0.23s to 0.01s and the propagation loop got 0.11s slower
for maintaining the leaders, making the whole ICF pass about 10%
faster (0.89s to 0.79s). The ICF fold decisions are unchanged.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-14 16:39:26 +09:00
Rui UeyamaandClaude Fable 5 22a8d49788 Count distinct ICF digests on every propagation round
To detect convergence of the ICF propagation rounds, we count the
number of distinct digests and stop when it ceases to increase. We
previously gated the count on a cheaper necessary-looking condition:
as long as the number of changed digests per round was still moving,
we assumed the graph could not have converged and skipped counting.

That assumption is wrong. A changing digest does not imply a changing
partition, because sections in the same equivalence class change
their digests in lockstep. For example, in a chain of sections that
are all distinct from the start, the partition is final after the
first round, but the changed-digest count keeps moving until the
propagation depth reaches the chain length, so the gate would delay
termination by the depth of the chain.

Since counting became cheap, we can simply count on every round and
stop as soon as the count repeats, which is the earliest round at
which convergence is provable. propagate() no longer needs to count
changed digests and report them to the caller.

For a Chromium debug build linked with --icf=all, this reduces the
number of propagation rounds from 40 to 28. The 12 saved rounds are
ones in which most sections had already converged, so the wall clock
time is unchanged. The output file is bit-identical.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-14 15:34:46 +09:00
Rui Ueyama 1ef1ea49ed Simplify 2026-07-14 15:29:47 +09:00
Rui Ueyama 0a04fb22ad Upgrade bundled mimalloc to v3.3.2 2026-07-13 19:59:18 +09:00
Rui UeyamaandClaude Fable 5 5f2d627597 Count distinct ICF digests with a hash set instead of sorting
To detect convergence of the ICF propagation rounds, we count the
number of distinct digests and stop when it ceases to increase. We
previously obtained the count by copying all digests and running a
parallel sort, which was expensive enough that we ran it only once
per 10 propagation rounds, overshooting the convergence point by up
to 9 rounds plus a full confirmation batch.

This patch replaces the sort with a concurrent open-addressing hash
set. Since the number of distinct digests cannot exceed the number of
digests, the table is allocated once at full size and never grows.
Slots are stamped with the round number, so the table needs no
clearing between rounds; and a slot match effectively compares an
entire 128-bit digest, so the count is exact under the same
hash-collision assumption the algorithm is already built on. Counting
is now cheap enough to run on every round that passes the existing
changed-digest gate, so the loop exits at the earliest round at which
convergence is provable, and the batching heuristic is gone.

For a Chromium debug build linked with --icf=all, the propagation
loop got 2.2x faster (0.73s to 0.34s), the number of propagation
rounds dropped from 59 to 40, and the whole ICF pass went from 1.29s
to 0.90s. The output file is unchanged.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-13 16:04:33 +09:00
Rui Ueyama f485f44d06 Update the MADV_HUGEPAGE comment
madvice to enable transparent hugepages works only on ext4 on recent
Linux kernels.
2026-07-11 14:14:23 +09:00
Rui Ueyama a708f03e9a Fix Windows builder 2026-07-10 18:54:55 +09:00
Rui UeyamaandClaude Fable 5 b126f83271 Ask for transparent huge pages for large mimalloc allocations
This changes the bundled mimalloc.

mimalloc calls madvise(MADV_HUGEPAGE) on suitable memory regions only
when the caller also allows large OS pages, i.e. hugetlbfs pages.
That condition makes sense for hugetlbfs, whose pages are physically
committed at map time and thus shouldn't back lazily-committed
segments, but transparent huge pages have no such cost because their
pages materialize on fault. Since both were gated on the same flag,
huge mimalloc objects, which are allocated through a code path that
never sets the flag, were never eligible for THP under the madvise
THP mode that Linux distributions ship by default. mold's largest
data structures, such as the symbol and relocation arrays, are
exactly such objects; several gigabytes of them were backed by 4 KiB
pages.

This patch makes the THP advise depend only on the allow_thp option
(enabled by default except on Android) and leaves the hugetlbfs gate
as is. mimalloc v3 effectively behaves this way already; only the v2
line has this problem.

Linking a Chromium debug build gets about 2% faster in the default
fork mode (3.37s to 3.29s) and 15% faster with --no-fork (4.05s to
3.43s) on our machine, as the heap becomes almost fully backed by
huge pages, which also makes the process exit much cheaper.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-10 07:28:34 +09:00
Rui UeyamaandClaude Fable 5 0086e97e8d Drop page table entries in parallel before exit
The kernel reclaims an exiting process's address space in a single
thread, which takes an appreciable amount of time for a process that
maps tens of gigabytes of input files. We now drop the page table
entries for input file mappings with madvise(MADV_DONTNEED) on all
threads before exiting. File contents stay in the page cache, so warm
rebuilds are unaffected.

This makes linking a Chromium debug build with --no-fork about 11%
faster (4.55s to 4.05s) on our machine. Without --no-fork, the
teardown happens in the detached child process, which now releases
its memory sooner.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-10 07:10:15 +09:00
Rui UeyamaandClaude Fable 5 e57c733e74 Re-enable transparent huge pages if a parent process disabled them
The PR_SET_THP_DISABLE prctl flag is inherited across fork and exec,
so a linker spawned from a process that has disabled transparent huge
pages for itself also runs without them, no matter what the system
THP setting says. mold allocates on the order of 10 GiB of heap for a
large link, and backing it with 4 KiB pages instead of 2 MiB pages
makes linking nearly twice as slow (6.2s vs 3.4s for a Chromium debug
build on our machine), so clear the inherited flag at startup.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-10 07:09:28 +09:00
Rui Ueyama 1adc707d49 Add unique_comdats statistic counter 2026-07-08 16:52:09 +09:00
Rui Ueyama 2ad8219352 Fix dist.sh 2026-07-08 16:37:03 +09:00
Rui Ueyama c0b29dc05e Upgrade bundled blake3 to 1.8.5 2026-07-03 10:18:38 +09:00
Rui Ueyama 2b340d400b Upgrade bundled zlib to 1.3.2 2026-07-03 10:15:29 +09:00
Rui Ueyama 7f75a1adde Upgrade bundled OneTBB to 2023.0.0 2026-07-03 10:13:15 +09:00
Rui Ueyama 930bb45339 Upgrade bundled mimalloc to 2.3.2 2026-07-03 10:11:21 +09:00
Rui Ueyama 36e80ccea5 Fix CI 2026-07-03 09:06:24 +09:00
Rui Ueyama 0ddff68d52 Merge pull request #1618 from agrieve/as-needed-2
Remove DSOs used only by GC'ed sections
2026-07-03 09:00:52 +09:00
Andrew Grieve 3d546cd068 Remove DSOs used only by GC'ed sections
lld does this by default. See: https://crbug.com/529102889

If a DSO is only referenced by sections that are garbage-collected, the
DSO's DT_NEEDED entry will be omitted.
2026-07-02 15:59:06 -04:00
Rui Ueyama f5fd48874a Revert recent changes that upgraded distros for ./dist.sh
These changes broke cross builds for ppc64le and s390x. I couldn't fix
it easily. Every fix attempt also takes a long time because of slow
qemu-user emulation.

I'll revert CMakeLists.txt and dist.sh to the original state. I don't
think we need to upgrade distros used by dist.sh as long as the binaries
produced using these images work.
2026-07-01 14:38:21 +09:00
Rui Ueyama 6b7e8d86e2 Run test scripts directly instead of via bash -x
This ensures that all test files have x bit.
2026-07-01 11:40:45 +09:00
Rui Ueyama aa50e9f88d Simplify
Now our tar version is recent enough to use the `--sort` option.
We still need to run gzip explicitly in the pipeline because we want
to pass -n (--no-name) to not save the current time as the file
timestamp in a gzip header.
2026-06-30 17:59:09 +09:00
Rui Ueyama 26ccd7aecc Simplify 2026-06-30 17:18:35 +09:00
Rui Ueyama 6b1914d062 Simplify 2026-06-30 17:08:09 +09:00
Rui Ueyama 3bb16732ae Merge pull request #1612 from a5ehren/mold_container
build: update dist.sh by refreshing all distro images
2026-06-30 16:43:19 +09:00
Rui Ueyama 81ccee4446 Merge pull request #1615 from marxin/test-permissions-2 2026-06-30 11:35:09 +09:00
Martin Liska dc5c6c5fa4 chore(tests): add missing +x for a test 2026-06-29 23:04:55 +02:00
Ehren Bendler f07bf7352f build: update dist.sh by refreshing all distro images
* update x86-64 to Debian 10. This has glibc 2.28 which matches the oldest actually used distro these days (rhel 8) and gcc-8.5 is new enough to bootstrap clang and libc++, which saves the whole gcc build step
* set the riscv64 target to use debian Trixie, which was the first stable release for that arch
* bullseye (arm,ppac,s390), trixie (riscv), and the loongarch sid snapshot all have clang-19 available
* make sure all images install the libc++-19-dev package to get libc++ static version
2026-06-29 13:21:20 -04:00
Ehren Bendler 2ec5e72691 build: add convienence flags for selecting the C++ stdlib on Linux
* MOLD_USE_LIBCXX now configures the build to look for and use llvm's libc++. This is already the default on macOS and BSD
* Added MOLD_STATIC_* flags for several libs to give more config beyond the overall MOLD_MOSTLY_STATIC
2026-06-29 13:15:49 -04:00
Rui Ueyama f4a62c7503 Fix CI 2026-06-16 17:40:10 +09:00
Rui Ueyama 075c63861d Simplify 2026-06-16 16:46:04 +09:00
Rui Ueyama 94c259a89a Merge pull request #1609 from sunyuechi/fix-empty-default-symver
Don't let "local: *" hide a foo@@ default-versioned symbol
2026-06-16 16:14:16 +09:00
Rui Ueyama 8b79fbbf02 Attempt to fix buildbot 2026-06-16 15:08:44 +09:00
Rui UeyamaandClaude Opus 4.8 ad4c489fff Rewrite relaxed relocation types for --emit-relocs
When mold relaxes an instruction, the relocation attached to it no
longer matches the rewritten code. With --emit-relocs that mismatch is
written to the output and confuses consumers such as BOLT. This change
addresses it by rewriting each emitted relocation to the type that
matches the relaxed instruction.

RISC-V and LoongArch already did this in a separate rewrite_reloc_types()
pass that could drift from apply_reloc_alloc; fold it into the latter and
delete the pass. ARM64, x86-64, i386 and s390x did not rewrite types
and now do, e.g. x86-64 GOTPCRELX => PC32, i386 GOT32X => GOTOFF, s390x
GOTENT => PC32DBL, ARM64 GOT loads => ADR_PREL_PG_HI21 and TLSDESC =>
IE/LE. Markers that GNU ld also keeps, such as x86-64 TLS and sparc64
GOTDATA_OP, are left unchanged.

Fixes https://github.com/rui314/mold/issues/1610

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-16 14:12:08 +09:00
Sun Yuechi 71cbd79f8c Don't let "local: *" hide a foo@@ default-versioned symbol
A symbol exported with an empty default version (foo@@) was treated as a
plain unversioned symbol, so a version script's "local: *" wildcard
localized it and dropped it from the dynamic symbol table; GNU ld keeps
it exported. Route foo@@ through parse_symbol_version() and assign it
VER_NDX_GLOBAL so it overrides the wildcard.

Signed-off-by: Sun Yuechi <[email protected]>
2026-06-15 19:49:36 +08:00
Rui UeyamaandClaude Fable 5 85c115e1be Sign-extend in-place addends of i386 relocations
Co-Authored-By: Claude Fable 5 <[email protected]>
2026-06-11 11:31:37 +09:00
Rui UeyamaandClaude Opus 4.8 d0aa95abe9 Support the SFrame v3 stack-unwinding format
SFrame is a compact alternative to .eh_frame used for asynchronous stack
unwinding by profilers and debuggers. As of binutils 2.46, gas emits
.sframe (Version 3) by default with -Wa,--gsframe, so object files now
routinely carry it. Like .eh_frame, .sframe can't be treated as opaque
bytes: an output section is a single header followed by a PC-sorted index
of Function Descriptor Entries (FDEs) and a blob of Frame Row Entries
(FREs), so the linker has to parse the inputs and rebuild it.

This adds SFrame Version 3 support. We parse each input .sframe into its
FDEs, drop the entries for garbage-collected or ICF-folded functions,
concatenate the survivors, sort the index by function address and rewrite
the header. The FRE data carries no relocations and is position
independent, so it is copied verbatim; only each FDE's func_start is
relocated, which we resolve to a PC-relative offset (the output always
sets SFRAME_F_FDE_FUNC_START_PCREL). A PT_GNU_SFRAME segment is emitted so
the runtime can find the section.

For a relocatable output we can't resolve addresses yet, so the merged
index is left unsorted and each func_start is emitted as a relocation in
.rela.sframe for the final link to resolve, matching GNU ld -r.

We support only Version 3. A .sframe with the wrong magic is reported as
an error, whereas a section written in an older version is ignored.

Fixes https://github.com/rui314/mold/issues/1511

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-09 20:16:15 +09:00
Rui Ueyama eb273d1c1c Refactor 2026-06-07 19:58:57 +09:00
Rui Ueyama 3e3687e64e Merge pull request #1577 from bamo/fix/no-interp-on-shared
Don't emit PT_INTERP for -shared outputs
2026-06-07 19:37:50 +09:00
Rui Ueyama 8f598a0f96 Add a missing dependency 2026-06-07 14:25:14 +09:00
Rui Ueyama 6a2f747bf9 Fix -Wformat-truncation 2026-06-07 14:25:03 +09:00
Rui Ueyama 9c24d197c1 Merge pull request #1597 from minhbq-99/fix-align
Fix PT_LOAD alignment issue
2026-06-07 12:55:57 +09:00
Rui UeyamaandClaude Opus 4.8 5beb02714c Increase thunk batch size and tie max_thunk_size to it
Large C++ debug libraries on ARM64 (e.g. deal.II's libdeal_II.g.so) can
have a single batch reference enough distinct PLT-routed DSO imports that
the first-pass thunk group exceeds the old fixed 1 MiB max_thunk_size,
tripping the assertion in create_range_extension_thunks().

Replace the branch_distance/5 formula with explicit per-target batch
sizes (32/8/16 MiB for ARM64/ARM32/PPC) and derive max_thunk_size from
the batch as batch_size/2 (16/4/8 MiB). The reservation in d_thunk_end()
already accounts for max_thunk_size, so a larger cap is safe by
construction, and batch_size/2 is about the largest reservation ARM32's
16 MiB branch range can accommodate without throttling the batch.

Add a regression test that calls 50,000 imported functions from a single
function, which on ARM64 produces a first-pass thunk group well over the
old 1 MiB limit.

Fixes https://github.com/rui314/mold/issues/1538

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-07 00:20:34 +09:00
Rui UeyamaandClaude Opus 4.8 4afb95ac5b Support SPARC64's large PLT format
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-07 00:20:30 +09:00
Rui UeyamaandClaude Opus 4.8 2d45a8eab7 Make the HWASAN test work with older Clang
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-06 19:49:37 +09:00
Rui UeyamaandClaude Opus 4.8 a0c46e95d3 Support R_AARCH64_MOVW_PREL_G* relocations
Fixes https://github.com/rui314/mold/issues/1592

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-06 18:50:54 +09:00
Rui UeyamaandClaude Opus 4.8 2a90660aa0 Fix invalid artifact name for distros containing / or :
actions/upload-artifact rejects names with a forward slash or colon, so
the test-results upload failed for the opensuse/tumbleweed matrix entry
(and would have failed for gentoo/stage3, debian:11, and the ubuntu:*
entries on a test failure). Sanitize matrix.distro by mapping / and :
to - before using it as the artifact name.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-06 18:13:22 +09:00
Rui UeyamaandClaude Opus 4.8 f147d78a56 Make --emit-relocs reflect relaxed instructions on RISC-V and LoongArch
After relaxation, the relocations attached to the relaxed instructions no
longer match the new, shorter instructions. With --emit-relocs we copied the
input relocations verbatim, emitting stale types at stale offsets.

Rewrite each relocation's offset and type in RelocSection::copy_buf to match
the relaxed code, recomputing the outcome the same way apply_reloc_alloc
recomputes its instruction rewrites. -r output is not relaxed, so its
relocations are passed through unchanged.

Fixes https://github.com/rui314/mold/issues/1584

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-06 17:31:57 +09:00
Rui Ueyama aa52f90d69 Simplify 2026-06-06 13:15:32 +09:00
Rui UeyamaandClaude Opus 4.8 7a66f71796 Set sh_entsize and sh_addralign correctly for Android packed relocs
The Android packed relocation section (SHT_ANDROID_RELA/REL) holds a
variable-length SLEB128-encoded delta stream, not a table of fixed-size
entries, so sh_entsize should be 0 and the byte stream needs no word
alignment. We were inheriting sizeof(ElfRel) and sizeof(Word) from the
plain .rela.dyn path. These fields aren't consulted at runtime (bionic
locates the blob via DT_ANDROID_RELA/RELASZ), so this only corrects the
metadata and lets the section pack without spurious padding.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-06 12:52:17 +09:00
Rui Ueyama 2708ba6201 Fix LTO COMDAT owner selection race 2026-06-06 12:52:17 +09:00
Rui Ueyama 400dfad97c Fix Android packed reloc size oscillation
Fixes https://github.com/rui314/mold/issues/1595
2026-06-06 12:52:17 +09:00
Rui Ueyama 6841e77230 Merge pull request #1568 from gruenich/feature/update-github-actions
[ci] Update GitHub Actions to their latest major releases
2026-06-05 15:48:38 +09:00
Rui Ueyama e9c3d1629f Merge pull request #1599 from agrieve/icf-plt
icf: Do not fold relocations pointing to preemptible symbols
2026-06-04 21:33:52 +09:00
Rui Ueyama b91f3363e1 Merge pull request #1600 from agrieve/icf-alignment
icf: Merge alignments of folded sections
2026-06-04 16:17:22 +09:00
Andrew Grieve 899655b2c1 icf: Merge alignments of folded sections
When folding identical sections, ICF was discarding the alignment of the
folded sections and only keeping the alignment of the chosen leader. If
a section with stricter alignment (e.g. 4-byte) was folded into a
section with looser alignment (e.g. 1-byte), the folded function could
be placed at an odd address.

On x86/x86_64, member function pointers use the lowest bit of the
address for virtual/non-virtual distinction. If a non-virtual member
function is folded and placed at an odd address, it is incorrectly
interpreted as virtual, leading to crashes. This fix ensures that we
update the leader's alignment to be the maximum alignment of all folded
sections. We do this in a global pass over all sections (both leaf and
non-leaf merged sections) before the sweep phase.

Fixes #1596
2026-06-03 15:13:31 -04:00
Andrew Grieve 17e705efaf icf: Do not fold relocations pointing to preemptible symbols
If a relocation points to a preemptible (imported) symbol, it should not
be considered identical to a relocation pointing to a local symbol, even
if their targets currently have identical bodies. Preemptible symbols
can be overridden at runtime, so we must preserve their unique identity.

Fixes #1598
2026-06-03 12:45:16 -04:00
Bui Quang Minh ad4a683ff6 Fix PT_LOAD alignment issue
With a PT_LOAD segment, we need to make sure p_offset % p_align ==
p_vaddr % p_align. In set_file_offsets, we try to assign contiguous file
offsets when sections are contiguous in memory. But when the alignment
of section changes and it's bigger than page size, we cannot do that.
The offset might need to be adjusted to follow the alignment rule.

A test binary with
__attribute__((section(".sum"))) __attribute__((aligned(8192))) void sum() {}

Section Headers:

[16] .plt.got          PROGBITS        00000000000015c0 0005c0 000008 00  AX  0   0 16
[17] .sum              PROGBITS        0000000000002000 001000 00000b 00  AX  0   0 8192

.sum section is assigned contiguous offset to its previous section. As a
result, in create_phdr, .sum is mapped to the same PT_LOAD segment as
previous sections.

Program Headers:

LOAD           0x000588 0x0000000000001588 0x0000000000001588 0x000b80 0x000b80 R E 0x2000

p_offset = 0x588
p_vaddr = 0x1588
p_align = 0x2000

So p_offset % p_align != p_vaddr % p_align, it violates the alignment
rule. As a result, if this scenario happens to a shared library, it
cannot be dlopen() on a system with glibc < 2.35 which is before this
commit:
https://github.com/bminor/glibc/commit/163f625cf9becbb82dfec63a29e566324129c0cd.

Fix it by not assigning contiguous offset when section's alignment is
different and it's bigger than page size.
2026-06-02 22:08:51 +07:00
Christoph Grüninger d557daa268 [ci] Update GitHub Actions to their latest major releases
Fixes most deprecation warnings that Node20.js will stop working
in June.
2026-05-30 22:02:11 +02:00
Rui Ueyama 4f08de1147 Simplify 2026-05-20 20:53:55 +09:00
Rui Ueyama bdb00bc558 Simplify 2026-05-20 20:27:15 +09:00
Bennett Amodio abcbd451b4 perf: cache symbol names to avoid per-call strlen
Several parse-time sites converted `symbol_strtab.data() + st_name`
implicitly to string_view, running strlen on each call. Precompute
a string_view vector parallel to elf_syms and read from it.
2026-05-20 19:40:13 +09:00
Bennett Amodio f1b4be87f6 perf: cache InputSection name to avoid per-call strlen
InputSection::name() implicitly converts a `const char *` to
string_view, which calls strlen on the shstrtab entry at every call.
Precompute the name as a string_view once in the constructor.

Linking clang Debug is ~1.3% faster; mlir-opt Debug is ~2.6% faster.
2026-05-20 19:40:09 +09:00
Rui Ueyama 441f231dbf Revert "perf: cache symbol names to avoid per-call strlen"
This reverts commit 5f272c4184 as it
broke tests.
2026-05-20 10:22:36 +09:00
Rui Ueyama 1f44c6c6df Revert "perf: cache InputSection name to avoid per-call strlen"
This reverts commit c32946dfab as it
broke tests.
2026-05-20 10:22:25 +09:00
Rui Ueyama 154e99b2ff Merge pull request #1588 from bamo/perf/cache-symbol-names
perf: cache symbol names to avoid per-call strlen
2026-05-20 10:03:35 +09:00
Rui Ueyama 048b7b2dfd Merge pull request #1587 from bamo/perf/cache-input-section-name
perf: cache InputSection name to avoid per-call strlen
2026-05-20 09:56:50 +09:00
Rui Ueyama 8b5eeb84fa Merge pull request #1589 from bamo/perf/concurrentmap-prefault
perf: pre-fault ConcurrentMap pages with MAP_POPULATE
2026-05-20 08:54:55 +09:00
Rui Ueyama 3c018ca56f Merge pull request #1586 from andreas-schwab/main
Fix ambiguous readelf option in gdb-index-dwarf5 test
2026-05-20 08:07:14 +09:00
Bennett Amodio 4a9e30defd perf: pre-fault ConcurrentMap pages with MAP_POPULATE
ConcurrentMap::insert reads ent.key before writing, so on an empty
slot the read first installs a PTE pointing at the system zero page,
then the CAS triggers wp_page_copy and a cross-CPU TLB shootdown.
Under heavy parallel insertion this shows up as ~3% of link time in
smp_call_function_many_cond plus a much larger inclusive cost in
asm_exc_page_fault.

Pass MAP_POPULATE to pre-fault the whole map in the mmap() thread.
Reads no longer install zero-page PTEs and writes no longer trigger
CoW. Linking clang-23 Debug is ~5% faster, llvm-tblgen ~7%,
clang-tblgen ~9%; max RSS rises by 0.1-0.6%. Falls back cleanly on
platforms that don't define MAP_POPULATE.
2026-05-19 14:09:59 -07:00
Bennett Amodio 5f272c4184 perf: cache symbol names to avoid per-call strlen
Several parse-time sites converted `symbol_strtab.data() + st_name`
implicitly to string_view, running strlen on each call. Precompute
a string_view vector parallel to elf_syms and read from it.
2026-05-19 09:58:36 -07:00
Bennett Amodio c32946dfab perf: cache InputSection name to avoid per-call strlen
InputSection::name() implicitly converts a `const char *` to
string_view, which calls strlen on the shstrtab entry at every call.
Precompute the name as a string_view once in the constructor.

Linking clang Debug is ~1.3% faster; mlir-opt Debug is ~2.6% faster.
2026-05-19 08:34:17 -07:00
Andreas Schwab e77e9d52b8 Fix ambiguous readelf option in gdb-index-dwarf5 test
The binutils readelf command now accepts a --debug-dir=<DIR> option which
makes --debug=gdb_index ambiguous.
2026-05-19 17:06:06 +02:00
Rui Ueyama a2e670e2a4 Merge pull request #1581 from marxin/test-permissions
chore(tests): add missing +x for 2 tests
2026-05-18 10:20:07 +09:00
Rui UeyamaandClaude Opus 4.7 019fb43e8c Simplify test
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-05-18 10:14:50 +09:00
Rui UeyamaandClaude Opus 4.7 61933bfd79 Fix relative response file paths failing after -C on emulation redo
Commit 6d6a128b moved the -C chdir(2) into parse_nonpositional_args(),
which is invoked by both the speculative X86_64 mold_main() and the
re-run via redo_main(). When -C is in effect, the second invocation
re-entered expand_response_files() from the post-chdir directory, so
@rsp with a relative path could no longer be opened.

Save the cwd before parsing and restore it before redo_main() so the
re-run resolves relative paths from the original directory. Also
switch the chdir call itself to std::filesystem::current_path().

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-05-18 10:13:42 +09:00
Rui Ueyama b4bb6a856d Merge pull request #1579 from bamo/fix/gdb-index-rnglistx
--gdb-index: use DW_FORM_rnglistx index instead of iterating all offset entries
2026-05-18 10:12:02 +09:00
Rui Ueyama b7bbf9c15c Merge pull request #1580 from bamo/fix/msys-windows-include
Include <windows.h> directly in mold.h
2026-05-18 10:11:29 +09:00
Martin Liska a3affe8789 chore(tests): add missing +x for 2 tests 2026-05-13 22:05:58 +02:00
Bennett Amodio 87b2889254 Include <windows.h> directly where Windows APIs are used
src/mold.h (HANDLE in MappedFile) and lib/perf.cc (FILETIME /
GetProcessTimes / GetCurrentProcess) both use Windows APIs inside
_WIN32 blocks but neither included <windows.h>. The MSYS2 UCRT64 build
worked because <tbb/enumerable_thread_specific.h> (pulled in via
lib/lib.h) used to #include <windows.h> for its TLS code.

oneTBB 2023.0.0 replaced that bulk include with forward declarations
of just the TLS functions ("declare prototypes of just the needed TLS
functions, instead of including whole windows.h"). When MSYS2 picked
up 2023.0.0, build-msys started failing with "'HANDLE' does not name
a type" at src/mold.h:224 (and the same shape of error in lib/perf.cc
after the mold.h fix landed).

Include <windows.h> explicitly in both translation units so the build
doesn't rely on transitive paths through third-party headers.

Last passing run: https://github.com/rui314/mold/actions/runs/25585325507/job/75112599227
First failing run: https://github.com/rui314/mold/actions/runs/25767054228/job/75681837767
2026-05-13 10:24:06 -07:00
Bennett Amodio 70ca8ffa53 --gdb-index: use DW_FORM_rnglistx index instead of iterating all offset entries
A compile unit's DW_AT_ranges attribute in DW_FORM_rnglistx form holds an
index into the offset table at DW_AT_rnglists_base; the corresponding
entry locates the range list for that DIE. mold was ignoring the index
and iterating every offset, picking up range lists belonging to child
DIEs (subprograms, lexical_blocks, inlined_subroutines) that use
DW_FORM_rnglistx as well.

The resulting .gdb_index address table contained child-DIE ranges nested
inside the CU's range. GDB 17 detects the overlap, prints "warning:
.gdb_index address table has a range (...) that overlaps with an earlier
range, ignoring .gdb_index", and discards the index. Older GDBs accept
the bloated table without complaint -- the duplicate entries all point
to the same CU, so lookups still resolve correctly -- which is why this
went undetected for years.

The bug only triggers when a CU contains a non-contiguous lexical_block,
which clang emits for scopes with EH landing pads from throwing RAII
destructors. Replace the loop with offsets[ranges.value], matching the
DW_FORM_sec_offset branch and GDB's own read_rnglist_index.

The new test invokes clang++ directly since DW_FORM_rnglistx is a
clang-specific behavior; gcc through 13 uses DW_FORM_sec_offset, which
mold has always handled correctly. The test skips cleanly if clang++
isn't installed or doesn't emit rnglistx for the source.

Reported by @MikeWang000000.

Fixes https://github.com/rui314/mold/issues/1578
2026-05-12 15:53:46 -07:00
Bennett Amodio 10b37a81fb Don't emit PT_INTERP for -shared outputs
When --dynamic-linker is passed alongside -shared, mold currently emits
a .interp section and a PT_INTERP program header. Both lld and BFD ld
silently drop --dynamic-linker for -shared outputs because shared
libraries are loaded by the dynamic linker rather than bootstrapped by
the kernel, so PT_INTERP serves no purpose there.

Gate .interp creation on (!shared || pie) so we keep it for regular
executables, -pie executables, and -shared -pie runnable shared
libraries (the libc.so.6-style case), and drop it only for the pure
shared-library case. The PT_INTERP program header at
output-chunks.cc:219 is conditional on ctx.interp being non-null, so
the segment vanishes automatically with no second edit.

Fixes https://github.com/rui314/mold/issues/1572
2026-05-08 16:28:51 -07:00
Rui Ueyama 45970e661d Update mold.1 (automated commit) 2026-05-08 08:06:27 +00:00
Rui Ueyama 155a8106c9 Merge pull request #1576 from bamo/feat/cli-no-warnings
Support -w / --no-warnings to suppress warnings
2026-05-08 17:05:35 +09:00
Bennett Amodio 51b9683ce7 Support -w / --no-warnings to suppress warnings
The Warn() machinery has consulted ctx.arg.suppress_warnings since
6d2dbd42 ("Preparation to support Apple ld64's -w option", March
2023), but no cmdline flag ever set it. Wire -w (the GNU/BFD
spelling) and --no-warnings (the lld long form) to set the field.

Because Warn::Warn early-returns when the flag is true, before the
ctx.arg.fatal_warnings branch sets has_error, -w also cancels
--fatal-warnings — matching lld's documented behavior.

The "warnings only" semantics match GCC, clang, and lld: real errors
still print and still fail the link. BFD ld's -w is broader and
suppresses error messages too, which would hide the cause of link
failures.

Fixes https://github.com/rui314/mold/issues/1493
2026-05-07 15:44:49 -07:00
Rui UeyamaandClaude Opus 4.7 e2d0c1a93b Accept ! as a bracket-expression negation marker in glob patterns
Linker scripts produced for lld (e.g., the version script Chrome's
build emits) use the POSIX/shell convention `[!...]` to negate a
bracket character class. mold previously accepted only `[^...]`, so
those scripts failed to match. Accept both, treating `!` and `^`
identically at the start of a bracket expression.

Fixes https://github.com/rui314/mold/issues/1575

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-05-07 12:18:41 +09:00
Rui UeyamaandClaude Opus 4.7 6b0ecea236 Don't drop DSO undef references when versym is VER_NDX_LOCAL
Commit 6b8293bf changed the early `ver` computation in
SharedFile::parse so that undef symbols use the raw versym instead
of being forced to VER_NDX_GLOBAL. Some BFD ld versions (notably
binutils 2.35 on Debian 11) emit VER_NDX_LOCAL (0) as the versym
for unversioned undefined dynamic symbols, so the subsequent
`if (ver == VER_NDX_LOCAL) continue;` check silently dropped those
undef references. With the DSO's reference invisible to mold, the
matching object-file definition was never marked as exported, and
at runtime the dynamic loader failed with "undefined symbol".

Restore the old behavior: defined symbols with VER_NDX_LOCAL are
still skipped (they aren't really exposed for dynamic linking),
but undef references are always processed.

Also fix the verdef/verneed merge in the same path. vd_ndx and
vna_other share a single versym index space (a producing linker
assigns them from one counter), so a single vector covers both.
read_verdef and read_verneed are folded into one read_version_strings
that populates that shared-index vector directly, replacing the
buggy `append` concatenation.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-05-06 23:32:27 +09:00
Rui UeyamaandClaude Opus 4.7 6b8293bf8c Use .gnu.version_r when registering undefined DSO symbols
For an undefined symbol in a shared library, mold treated the versym
as VER_NDX_GLOBAL and registered the symbol under its bare name. That
meant an undef foo@VER_1.0 in libbar.so was looked up as just foo, so
a non-default foo@VER_1.0 in another DSO did not satisfy it and
--no-allow-shlib-undefined wrongly reported "undefined symbol: foo".

Parse .gnu.version_r and, when an undef has a versym indexing into it,
register the symbol as name@VERSION so the existing versioned-symbol
resolution path picks up matching definitions.

Fixes https://github.com/rui314/mold/issues/1571

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-05-06 18:29:32 +09:00
Rui UeyamaandClaude Opus 4.7 82b5ed48bf Fix flaky compress-debug-sections-{zlib,zstd}-level tests on Alpine
`readelf | grep -q` plus `set -o pipefail` is fragile: when grep -q
matches it stops reading immediately, readelf gets SIGPIPE on its next
write, and pipefail propagates the resulting 141. Alpine (musl) hits
this where glibc happens to escape it. Drop -q so grep drains stdin.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-05-06 15:57:23 +09:00
Rui UeyamaandClaude Opus 4.7 4d313a269c Rename gc-sections.cc visit() to avoid std::visit ADL clash on gcc 10
The new StartStopMap parameter contains std::string_view, so the
unqualified call to visit() inside mark() and the recursive call inside
visit() itself were resolved to std::visit by ADL on gcc 10 (Debian 11),
breaking the build.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-05-06 14:40:33 +09:00
Rui UeyamaandClaude Opus 4.7 384527fd29 Don't unconditionally retain C-identifier-named sections in --gc-sections
A section whose name is a valid C identifier was treated as a GC root,
keeping it (and everything reachable from it) alive even when nothing
referenced its synthesized __start_/__stop_ marker symbols. That could
pull in unrelated code with undefined references and cause spurious
"undefined symbol" errors that lld and ld.bfd don't produce.

Build a multimap of c-identifier-named sections up front and, during
marking, when a relocation targets __start_<name> or __stop_<name>,
mark every section named <name> alive.

Fixes https://github.com/rui314/mold/issues/1571

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-05-05 17:50:33 +09:00
Rui Ueyama d947b1a6d3 Update mold.1 (automated commit) 2026-05-04 14:00:41 +00:00
Rui UeyamaandClaude Opus 4.7 9e32fce011 Fix CI
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-05-04 22:53:57 +09:00
Rui UeyamaandClaude Opus 4.7 b7102d26ca Add hidden --use-android-relr-tags option
Pre-Android-12 (API 30 and earlier) bionic loaders predate the official
ELF gABI assignment of DT_RELR/DT_RELRSZ/DT_RELRENT and use legacy
OS-specific tag values DT_ANDROID_RELR (0x6fffe000), DT_ANDROID_RELRSZ
(0x6fffe001), DT_ANDROID_RELRENT (0x6fffe003), and section type
SHT_ANDROID_RELR (0x6fffff00). With --use-android-relr-tags, mold emits
those instead of the standard ones so binaries linked with
--pack-dyn-relocs=relr can be loaded on those older devices. The flag
is undocumented (not listed in --help) since it's only relevant to
shipping to legacy Android targets.

https://github.com/rui314/mold/issues/1544

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-05-04 22:53:56 +09:00
Rui UeyamaandClaude Opus 4.7 59957fce3b Add --pack-dyn-relocs=android support
Implements the Android Packed Relocations Format (APS2) used by Android
binaries to compress dynamic relocations. Two new modes are accepted:
`--pack-dyn-relocs=android` encodes all dynamic relocations as APS2 in
.rela.dyn (SHT_ANDROID_RELA) and emits DT_ANDROID_RELA(SZ) instead of
DT_RELA(SZ); `--pack-dyn-relocs=android+relr` is the same but with
R_RELATIVE relocations diverted to .relr.dyn via the existing RELR path.

The encoder groups consecutive relocations sharing offset_delta and
r_info to amortize SLEB128 overhead. The encoded bytes are decoded by
Android's bionic loader and by llvm-readelf, which the new test uses to
verify correctness.

main.cc gains a final set_osec_offsets() call after fix_synthetic_symbols.
Symbol values assigned there change addends for relocations that
reference synthetic symbols, which can shift the SLEB128-encoded section
size; re-running the layout convergence keeps sh_size consistent with
the final encoding.

Fixes https://github.com/rui314/mold/issues/1544

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-05-04 22:53:56 +09:00
Rui UeyamaandClaude Opus 4.7 c36c5a64fc Centralize dynamic relocation generation in RelDynSection
Previously each chunk wrote its dynamic relocations into a magic offset
in .rel.dyn (tracked via per-chunk reldyn_offset), with a separate
construct_relr pass building per-chunk relr vectors. Now each chunk
returns its dynrels via a virtual collect_dynrels(), and RelDynSection
aggregates them into a single relocs vector that it owns and writes.
RELR-eligibility is determined by checking r_offset alignment of the
collected ElfRel entries (replacing the ABS_REL_RELR kind).

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-05-04 22:53:55 +09:00
Rui Ueyama e71cef7448 Update mold.1 (automated commit) 2026-04-27 06:01:09 +00:00
Rui UeyamaandClaude Opus 4.7 6695f13dce Add --compress-debug-sections=zlib:N for configurable zlib compression level
Generalize the zstd:N syntax added in the previous commit so that the
zlib compression level can also be specified. zlib levels 0 through 9
are accepted, where 0 means no compression. Plain `zlib` remains
equivalent to `zlib:1` (the previous hardcoded default).

Also drop the objcopy/zstdcat round-trip from the zstd-level test in
favor of a simpler readelf check, matching the new zlib-level test, and
add error-message coverage for out-of-range levels.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-04-27 15:00:26 +09:00
Rui Ueyama ab704cf476 Update mold.1 (automated commit) 2026-04-27 05:47:52 +00:00
Rui Ueyama c028765bb7 Merge pull request #1569 from pierluigilenoci/feat/zstd-compression-level
Add --compress-debug-sections=zstd:N for configurable zstd level
2026-04-27 14:39:37 +09:00
Pierluigi Lenoci 04209593c1 refactor: use zstd:<level> syntax for consistency with lld
Change the compression level syntax from zstd-<level> to zstd:<level>
as requested by @rui314 for consistency with lld.

Signed-off-by: Pierluigi Lenoci <[email protected]>
2026-04-26 20:23:28 +02:00
Pierluigi Lenoci 56fc57198a Add --compress-debug-sections=zstd-N for configurable zstd compression level
Allow users to specify a zstd compression level (1-22) for debug section
compression via --compress-debug-sections=zstd-N, where N is the desired
level. Higher levels achieve better compression ratios at the cost of
compression speed. The plain 'zstd' option remains equivalent to 'zstd-3'
(the previous hardcoded default).

Closes: https://github.com/rui314/mold/issues/1290
Signed-off-by: Pierluigi Lenoci <[email protected]>
2026-04-24 17:15:01 +02:00
Rui UeyamaandClaude Opus 4.7 84c411b9a2 [LoongArch] Don't relax GOT load to pcaddi when symbol is misaligned
pcaddi computes PC + (imm << 2), so the relaxation from a GOT load
(pcalau12i + ld.d) to pcaddi is valid only when the symbol's address
is 4-byte aligned. Without this check, a reference to a misaligned
symbol was relaxed to a pcaddi that resolves to the address with its
low two bits cleared, producing a wrong pointer.

Fixes https://github.com/rui314/mold/issues/1567

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-04-19 17:23:40 +09:00
Rui UeyamaandClaude Opus 4.7 6f8d62e21c Fix non-deterministic --gdb-index output with multiple threads
The parallel writer that fills (type, cu_idx) tuples for each name
appended entries in thread-scheduling order, so names referenced from
multiple compilation units produced different .gdb_index bytes across
runs. Sort each name's tuple array after the parallel append so the
output is reproducible while preserving parallelism.

Fixes https://github.com/rui314/mold/issues/1566

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-04-18 11:28:03 +09:00
Rui Ueyama 1b2306a71b Fix test failure on SPARC
We need to ignore SPARC-specific symbol with STT_REGISTER.

Fixes https://github.com/rui314/mold/issues/1564
2026-04-14 20:50:28 +09:00
Rui Ueyama a91381b1b1 Bump mold version to 2.41.0 2026-04-13 18:31:07 +09:00
Rui Ueyama 5b8c7dc023 Merge pull request #1547 from sicherha/fix_textrel2_test_on_Fedora_44
Fix `textrel2` test on Fedora 44
2026-04-07 16:46:48 +09:00
Rui Ueyama a1ae8da0c4 Fix --omagic to pack all sections into a single PT_LOAD segment
Previously, --omagic only flipped segment permissions to RWX but
still page-aligned PT_LOAD segments and emitted PT_GNU_RELRO, so
text and data ended up in separate page-padded segments. This
mismatched the documented behavior ("makes the first data segment
not aligned to a page boundary") and GNU ld's --omagic, which also
disables shared linking.

Make --omagic imply --Bstatic, disable RELRO, and stop forcing
PT_LOAD's p_align to the page size. Also gate chunk->is_relro on
z_relro so a disabled RELRO no longer leaks into address layout.

Fixes https://github.com/rui314/mold/issues/1562
2026-04-07 16:41:35 +09:00
Rui UeyamaandClaude Opus 4.6 632a0eead1 Fix spurious "duplicate symbol" errors with LTO
When linking with LTO, archive members extracted pre-LTO to satisfy
references from IR objects could cause false duplicate symbol errors.
This happened in two ways:

1. The pre-LTO check_duplicate_symbols flagged conflicts between LTO IR
   objects and regular objects. These conflicts are handled by the LTO
   backend and should not be reported.

2. After LTO compilation, archive members that were extracted pre-LTO
   retained stale is_reachable flags even when the LTO output superseded
   all their symbols. The post-LTO duplicate check then saw both the
   archive member and the LTO output defining the same symbol.

Fix both issues:
- In check_duplicate_symbols, skip errors where one side is an LTO IR
  object and the other is not. Only IR-vs-IR duplicates need catching.
- In do_lto, reset reachability for archive members before the post-LTO
  resolve_symbols so that only genuinely needed members are re-extracted.

Fixes https://github.com/rui314/mold/issues/1421

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
2026-04-05 19:55:33 +09:00
Rui Ueyama 04430ade4b Merge pull request #1546 from jwakely/linker-script-group-as-needed
Fix getting output type from linker scripts that use AS_NEEDED
2026-04-05 14:35:21 +09:00
Rui UeyamaandClaude Opus 4.6 91a5f3c4bf Skip nightly build-all workflow when there are no new commits
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
2026-04-05 14:28:23 +09:00
Rui Ueyama e804c8caff Fix a compiler warning 2026-04-05 13:47:07 +09:00
Rui Ueyama f378b18369 Support R_RISCV_GOT32_PCREL
This change should unbreak our riscv64 buildbot by fixing
test/relative-vtables.sh test.
2026-04-05 13:47:07 +09:00
Rui Ueyama ff83f60fbc Merge pull request #1557 from akarle/ak/fix-separate-debug-file-corruption
Fix separate-debug-file strtab corruption
2026-04-04 15:58:17 +09:00
Rui Ueyama f8b49ee83e Fix FreeBSD buildbot 2026-04-04 15:54:06 +09:00
Rui Ueyama 18ea70c696 Merge pull request #1554 from karesansui-u/fix/redo-main-abort-message
Replace abort() with diagnostic message in redo_main()
2026-04-04 15:48:29 +09:00
Rui Ueyama fd95adda95 "#undef ERROR" before use for MinGW compatibility
https://github.com/rui314/mold/pul/1558
2026-04-04 15:01:32 +09:00
Rui Ueyama 4e58828670 Simplify and add tests 2026-04-04 13:55:53 +09:00
Rui Ueyama 027111315e Merge pull request #1559 from shivendra02467/fix-shlib-undefined-visibility
[ELF] Fix --no-allow-shlib-undefined for hidden static providers and LTO
2026-04-04 13:45:12 +09:00
Rui Ueyama d4d5eae43d Do not report an error on LTO unclaimed files in archives
Previously, mold eagerly report an "not claimed by the LTO plugin" error
if it finds an LTO object file that is not claimed by an LTO plugin.
2026-04-04 11:34:42 +09:00
Rui Ueyama 274a46b9f7 Revert "defer incompatible archive LTO member errors until selected"
This reverts commit 6c4701955c.
2026-04-04 11:09:05 +09:00
Rui Ueyama 906af24d25 Merge pull request #1560 from tobim/push-rpuotyoounvk
Various LTO compatibility fixes
2026-04-04 11:07:20 +09:00
Rui UeyamaandClaude Opus 4.6 be28db1241 Support R_AARCH64_GOTPCREL32 relocation for relative vtables
This relocation is used by clang's -fexperimental-relative-c++-abi-vtables
to emit 32-bit GOT-relative PC-relative offsets in vtable entries (e.g. for
RTTI pointers). It resolves to G + GOT + A - P as a 32-bit value.

Fixes https://github.com/rui314/mold/issues/1561

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
2026-04-03 19:46:19 +09:00
Tobias Mayer 6c4701955c defer incompatible archive LTO member errors until selected 2026-03-21 14:21:00 +01:00
Tobias Mayer ce0ee76550 Add regression test for mixed LLVM/GCC slim LTO archive link 2026-03-21 14:18:31 +01:00
Tobias Mayer 2a1e5c6e8d Fall back to native code for GCC fat LTO objects with LLVM plugin 2026-03-21 14:18:31 +01:00
Tobias Mayer 487b83e581 Add a test for mixed toolchain fatLTO
LTO links where the main driver is LLVM but some objects
are GCC fatLTO should succeed.
2026-03-21 14:18:31 +01:00
Tobias Mayer a660037f95 Preserve non-lto comdat members when section is shared with LTO objects 2026-03-21 10:24:17 +01:00
Tobias Mayer 10342f7c2a Add a regression test for mixed lto and assembly comdat 2026-03-21 10:02:05 +01:00
Shivendra Sharma f6bb1175eb [ELF] Fix --no-allow-shlib-undefined for hidden static providers and LTO
This patch fixes two distinct blind spots in how `mold` handles
--no-allow-shlib-undefined checks during the final link phase:

1. The Hidden Static Provider Bug (False Negative)
Previously, if a shared library requested a symbol that was provided by a
static object file with STV_HIDDEN, `mold` would silently overwrite the
global symbol visibility to STV_DEFAULT and rubber-stamp the binary,
resulting in a guaranteed `symbol lookup error` at runtime.
Fix: We now interrogate the raw `elf_syms` array of the static object
provider. If the original visibility is hidden/internal, we flag it as an
invalid provider and throw the error, matching GNU ld/lld behavior.

2. The LTO Shadowing Bug (False Positive)
When LTO is heavily utilized, GCC's plugin may optimize away symbols that
are theoretically provided by static objects if they aren't used internally.
If a shared library still needs that symbol, `mold`'s static state
registered the provider as NULL and threw an error, ignoring the fact
that a loaded DSO (like libc.so) offered a valid public fallback.
Fix: Introduced `has_public_dso_fallback()`. Before throwing an undefined
symbol error, `mold` performs a rapid scan of `ctx.dsos`. If a public
fallback exists dynamically, the error is safely suppressed.
2026-03-20 00:07:44 +05:30
Alex Karle 4cd4d5693e Fix separate-debug-file strtab corruption
Originally discussed in #1535, the debug data generated by
--separate-debug-file is being corrupted. In the best case scenario,
there's many blank/nameless undefined symbols in the symtab, but in
most production scenario I've encountered, the file is corrupt / gdb
refuses to load it:

    $ gdb main
    Reading symbols from $PWD/main.dbg...
    BFD: /$PWD/main.dbg: invalid string offset 4130582 >= 1345 for section `.strtab'
    BFD: /$PWD/main.dbg: invalid string offset 16777228 >= 1345 for section `.strtab'

    $ readelf -Ws main.dbg
    Symbol table '.symtab' contains 139 entries:
    [...]
    38: 0000000000000000     0 SECTION LOCAL  DEFAULT   38 .strtab
    39: 0000000000000000     0 SECTION LOCAL  DEFAULT   39 .symtab
    40: 0018a80200010000 0xb10000003f071500 NOTYPE  LOCAL  DEFAULT bad section index[3232] <corrupt>
    41: 0000003f07140000 0xfd02000100000cc2 NOTYPE  LOCAL  HIDDEN  bad section index[11500] <corrupt>
    42: 02000100000cd300 0x3f0712000008a3 <unknown>: 7 LOCAL  PROTECTED [<other>: 3c]   UND <corrupt>
    43: 3f07110000056202 0x100000cf5000000 NOTYPE  LOCAL  DEFAULT    1 <corrupt>

This appears to be happening because the strtab size is set to be larger
than its true value, causing garbage data to be placed in it, which
breaks the symtab.

This regression occurred in 5b71029, which fixed a different form of
corruption (the PHDR symbol escaping into the section headers, shifting
the section offsets), but created this strtab corruption. The root cause
is that `compute_section_headers` was moved prior to the wiping of
irrelevant chunks. `compute_section_headers` computes the strtab size by
adding up individual chunk strtab contributions. It needs to occur _after_
the wiping so that the strtab doesn't account for the data before the
wipe.

The core of this patch is really just a partial revert of 5b71029--the
compute_section_headers call appears to have been in the correct place
prior to this change; I've added a test that catches the issue by
intentionally bloating the size of the `.plt` section with many external
symbols. This section is wiped in the dbg data, so by bloating it, the
test can inspect for corruption.

Fixes #1535

Signed-off-by: Alex Karle <[email protected]>
2026-03-16 12:13:09 -04:00
karesansui 93e1aeaec6 Replace abort() with diagnostic message in redo_main()
When mold auto-detects an unsupported target from ELF headers,
redo_main() previously called abort() with no context. This replaces
it with a descriptive error message matching the style used by the
-m flag path in cmdline.cc.

Fixes #1553
2026-03-12 02:24:00 +09:00
Rui Ueyama fc96c1b9ef Do not call fallocate on tmpfs
Interestingly, calling fallocate on tmpfs is very slow. It took
almost a second for a fresh ~5 GiB file, even though intuititvely
it should finish very quickly.
2026-03-05 08:40:16 +09:00
Rui Ueyama 0c0c42b31d Add an entry for fedora-asahi-remix 2026-03-01 13:23:17 +09:00
Jonathan Wakely e0a6f8e1d3 Fix getting output type from linker scripts that use AS_NEEDED
Make Script<E>::get_script_output_type support AS_NEEDED inside an INPUT
or GROUP group, so that the libatomic_asneeded.so linker script used by
GCC 16 works.

Fixes #1545

Signed-off-by: Jonathan Wakely <[email protected]>
2026-02-28 09:08:49 +00:00
Rui Ueyama f8da34575f Unbreak Windows buildbot 2026-02-27 16:10:33 +09:00
Rui Ueyama a4db2382a5 Improve --gdb-index performance
This change eliminates the cost of zero-initialization on vector
resizing by using malloc/realloc instead. We also parallelize the
loops to write adddress areas and types to .gdb_index.
2026-02-27 15:45:51 +09:00
Rui Ueyama 6d6a128bf9 Handle -C and --chroot early
We read version script files in cmdline.cc, so we need to handle -C
and --chroot early. Otherwise, a version script passed by command line
can cause a "file not found" error.
2026-02-27 15:45:51 +09:00
Rui Ueyama aeb1041f1a Refactor 2026-02-27 15:45:51 +09:00
Christoph Erhardt 6e40a87aad Fix textrel2 test on Fedora 44
Tell the GNU linker to not report dynamic relocations in read-only
sections as an error.
2026-01-30 22:09:29 +01:00
Rui Ueyama 17956fdfa1 Merge pull request #1541 from mati865/push-qlyqwkunnpmx
Fix typo in test name
2025-12-12 09:43:18 +09:00
Rui Ueyama 97b43aaf0d Fix CI 2025-12-10 13:57:55 +09:00
Rui Ueyama c3bf8fd803 Attempt to fix CI 2025-12-10 12:59:41 +09:00
Rui Ueyama 4e51cfaa67 Update mold.1 (automated commit) 2025-12-10 03:29:09 +00:00
Rui Ueyama 87873cad57 Change the man page to reflect reality 2025-12-10 12:15:40 +09:00
Rui Ueyama 9f463a62ac Align the end of RELRO to a page boundary even if -z separate-loadable-segment is given
Otherwise the last page of a relro segment is not protected by the
runtime, as the runtime aligns down the end address of a RELRO segment.
2025-12-10 12:11:45 +09:00
Rui Ueyama 5b710298fc d1;10;0cAttempt to fix an issue with --separate-debug-file
https://github.com/rui314/mold/issues/1535
2025-11-17 16:01:14 +09:00
Rui Ueyama 4798f43358 Do not emit a bogus "PHDR" section for --separate-debug-file
Previously, we emit a bogus section with name "PHDR" for a separate
debug file.

Fixes https://github.com/rui314/mold/issues/1535
2025-11-15 12:42:53 +09:00
Rui Ueyama fcd5391981 Adapt to a recent readelf output format change
Fixes https://github.com/rui314/mold/issues/1533
2025-11-11 22:39:36 +09:00
Rui Ueyama ad08999d03 Refactor 2025-11-10 19:55:51 +09:00
Rui Ueyama 016bb8af6e Refactor 2025-11-05 20:13:14 +09:00
Rui Ueyama 3a5985ca14 Refactor 2025-11-04 22:06:18 +09:00
Rui Ueyama e99551c9f0 Refactor 2025-11-04 16:38:39 +09:00
Rui Ueyama 56f8eb3b92 Refactor 2025-11-04 15:54:44 +09:00
Rui Ueyama e459ed59c6 [SPARC64] Do not assume the dest reg of R_SPARC_GOTDATA_OP is always %g1
https://github.com/rui314/mold/issues/1529
2025-11-04 15:25:33 +09:00
Rui Ueyama 4d94410ad4 [SPARC64] Preserve %rd when rewriting R_SPARC_TLS_GD_ADD
https://github.com/rui314/mold/issues/1529
2025-11-04 15:21:58 +09:00
Rui Ueyama 069f4da96a [SPARC64] Change how we swap instructions for TLS_GD_CALL
https://github.com/rui314/mold/issues/1529
2025-11-04 15:21:52 +09:00
Rui Ueyama 01923e4b4b Refactor 2025-11-02 17:33:40 +09:00
Rui Ueyama 5cac744adf Merge pull request #1528 from koachan/main
[SPARC64] Preserve registers used during optimization transforms
2025-11-02 17:29:08 +09:00
Koakuma a88a2163b2 [SPARC64] Decouple rs1 and rd in R_SPARC_TLS_GD_LO10 transform
The compiler might place the two values on different registers,
so don't modify them during transforms.
2025-11-02 15:18:15 +07:00
Koakuma ca0b5973a2 [SPARC64] Preserve rd of TLS *_ADD relocations during optimization
GCC might decide to put the result of the operation in another register,
so remove hardcodings of %o0 as the destination register (`rd` field).
2025-11-02 00:40:20 +07:00
Rui Ueyama 5fb6d8aa63 Fix CI 2025-10-31 16:28:42 +09:00
Rui Ueyama 3ad34d4ec3 Do not create a PDE IFUNC entry if the ifunc is not referenced within the exe
Fixes https://github.com/rui314/mold/issues/1527
2025-10-31 15:47:06 +09:00
Rui Ueyama dce9ac1e5c Allow building mold for a specific set of targets
By popular request, this commit adds a new cmake variable that allows
building mold with a limited set of supported targets, which can help
reduce build time.

The use of this flag is discouraged unless you build mold frequently
for personal use. Otherwise, build mold with the default settings for
simplicity and maximum compatibility. In particular, distributions
should not build mold with a limited set of supported targets.

Fixes https://github.com/rui314/mold/issues/1364
2025-10-31 12:20:47 +09:00
Rui Ueyama 7cc59cc71a Fix a compiler warning 2025-10-30 15:39:21 +09:00
Rui Ueyama 98d7691c21 Refactor 2025-10-25 11:24:50 +09:00
Rui Ueyama 92adbb1d61 [SPARC64] Fix TLS GD with GCC -O2
Fixes https://github.com/rui314/mold/issues/1525
2025-10-25 11:13:21 +09:00
Rui Ueyama 8634570e56 Refactor 2025-10-23 21:05:50 +09:00
Rui Ueyama fce89c24e5 Respect --no-threads with --compress-debug-sections
Fixes https://github.com/rui314/mold/issues/1524
2025-10-23 12:28:39 +09:00
Rui Ueyama 2b36f8a982 Merge pull request #1523 from koachan/main
[SPARC64] Fix R_SPARC_TLS_GD_ADD handling
2025-10-23 10:04:45 +09:00
Rui Ueyama 7f8c50bfa7 [SPARC] Honor --no-apply-dnamic-relocs for R_SPARC_UA64
Related to: https://github.com/rui314/mold/pull/1523
2025-10-23 10:03:17 +09:00
Koakuma a16bb13a01 [SPARC64] Fix R_SPARC_TLS_GD_ADD handling
R_SPARC_TLS_GD_ADD can use any register for the base address (ELF Handling
For Thread-Local Storage, pg. 21), so remove the hardcoded %l7 usage.
2025-10-22 20:16:41 +07:00
Rui Ueyama f55ad918d4 [SPARC64] Emit dynamic relocations for R_SPARC_UA64
Related to: https://github.com/rui314/mold/pull/1523
2025-10-22 18:08:22 +09:00
Rui Ueyama 5164da8ad2 Fix CI 2025-10-09 17:20:06 +09:00
Rui Ueyama 7cc21c7657 Refactor 2025-10-09 16:30:37 +09:00
Rui Ueyama f8089ca121 Merge pull request #1522 from bmilanich/pr/eh_icf
Consider .gcc_except_table* sections for ICF.
2025-10-09 12:44:27 +09:00
Rui Ueyama 04a99003c2 Add missing newlines 2025-10-09 11:45:21 +09:00
Basil Milanich fd8659027b Consider .gcc_except_table* sections for ICF. 2025-10-08 14:12:07 -05:00
Rui Ueyama 0b20faa0cd [PPC32] Support R_PPC_DTPREL32 in non-alloc sections
Fixes https://github.com/rui314/mold/issues/1521
2025-10-03 10:47:27 +09:00
Rui Ueyama 7c82a6a00e Fix typo 2025-09-21 11:45:04 +09:00
Rui Ueyama 5148754c14 Refactor 2025-09-20 17:29:38 +09:00
Rui Ueyama 91439beea5 --section-order: Report an error if address goes backward
https://github.com/rui314/mold/issues/1514
2025-09-20 12:16:25 +09:00
Rui Ueyama 311da914d3 Refactor 2025-09-20 11:09:43 +09:00
Rui Ueyama dfb18afd69 Fix CI 2025-09-19 19:19:23 +09:00
Rui Ueyama 8dee52318b Run MemorySanitizer tests on GitHub Actions 2025-09-19 19:08:04 +09:00
Rui Ueyama 9d7f20b34e Avoid unnecessary zero-initializations 2025-09-19 19:08:04 +09:00
Rui Ueyama aa80487536 Merge pull request #1517 from etwoo/feature/msan-build
support MemorySanitizer w/ instrumented C++ stdlib
2025-09-19 15:16:32 +09:00
Eric Woo 13cec07aa3 fix msan error in compress.cc
==70801==WARNING: MemorySanitizer: use-of-uninitialized-value
0) 0x56218bab14b3 in mold::zlib_compress(std::__1::span<unsigned char, 18446744073709551615ul>) /home/ewoo/mold/lib/compress.cc:89:13
1) 0x56218bab14b3 in mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0::operator()(long) const /home/ewoo/mold/lib/compress.cc:106:17
2) 0x56218bab14b3 in decltype(std::declval<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0 const&>()(std::declval<long&>())) std::__1::__invoke[abi:nn220000]<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0 const&, long&>(mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0 const&, long&) /home/ewoo/llvm-project/build/include/c++/v1/__type_traits/invoke.h:251:25
3) 0x56218bab14b3 in std::__1::__invoke_result<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0 const&, long&>::type std::__1::invoke[abi:nn220000]<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0 const&, long&>(mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0 const&, long&) /home/ewoo/llvm-project/build/include/c++/v1/__functional/invoke.h:29:10
4) 0x56218bab14b3 in std::__1::__invoke_result<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0 const&, long&>::type tbb::detail::d0::invoke<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0 const&, long&>(mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0 const&, long&) /usr/include/tbb/../oneapi/tbb/detail/_utils.h:356:12
5) 0x56218bab14b3 in tbb::detail::d1::parallel_for_body_wrapper<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0, long>::operator()(tbb::detail::d1::blocked_range<long> const&) const /usr/include/tbb/../oneapi/tbb/parallel_for.h:206:13
6) 0x56218bab14b3 in decltype(std::declval<tbb::detail::d1::parallel_for_body_wrapper<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0, long> const&>()(std::declval<tbb::detail::d1::blocked_range<long>&>())) std::__1::__invoke[abi:nn220000]<tbb::detail::d1::parallel_for_body_wrapper<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0, long> const&, tbb::detail::d1::blocked_range<long>&>(tbb::detail::d1::parallel_for_body_wrapper<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0, long> const&, tbb::detail::d1::blocked_range<long>&) /home/ewoo/llvm-project/build/include/c++/v1/__type_traits/invoke.h:251:25
7) 0x56218bab14b3 in std::__1::__invoke_result<tbb::detail::d1::parallel_for_body_wrapper<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0, long> const&, tbb::detail::d1::blocked_range<long>&>::type std::__1::invoke[abi:nn220000]<tbb::detail::d1::parallel_for_body_wrapper<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0, long> const&, tbb::detail::d1::blocked_range<long>&>(tbb::detail::d1::parallel_for_body_wrapper<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0, long> const&, tbb::detail::d1::blocked_range<long>&) /home/ewoo/llvm-project/build/include/c++/v1/__functional/invoke.h:29:10
8) 0x56218bab14b3 in std::__1::__invoke_result<tbb::detail::d1::parallel_for_body_wrapper<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0, long> const&, tbb::detail::d1::blocked_range<long>&>::type tbb::detail::d0::invoke<tbb::detail::d1::parallel_for_body_wrapper<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0, long> const&, tbb::detail::d1::blocked_range<long>&>(tbb::detail::d1::parallel_for_body_wrapper<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0, long> const&, tbb::detail::d1::blocked_range<long>&) /usr/include/tbb/../oneapi/tbb/detail/_utils.h:356:12
9) 0x56218bab14b3 in tbb::detail::d1::start_for<tbb::detail::d1::blocked_range<long>, tbb::detail::d1::parallel_for_body_wrapper<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0, long>, tbb::detail::d1::auto_partitioner const>::run_body(tbb::detail::d1::blocked_range<long>&) /usr/include/tbb/../oneapi/tbb/parallel_for.h:117:9

Uninitialized value was created by an allocation of 'nbits.i.i.i.i.i.i.i.i.i' in the stack frame
0) 0x56218bab11e6 in mold::zlib_compress(std::__1::span<unsigned char, 18446744073709551615ul>) /home/ewoo/mold/lib/compress.cc:87:3
1) 0x56218bab11e6 in mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0::operator()(long) const /home/ewoo/mold/lib/compress.cc:106:17
2) 0x56218bab11e6 in decltype(std::declval<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0 const&>()(std::declval<long&>())) std::__1::__invoke[abi:nn220000]<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0 const&, long&>(mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0 const&, long&) /home/ewoo/llvm-project/build/include/c++/v1/__type_traits/invoke.h:251:25
3) 0x56218bab11e6 in std::__1::__invoke_result<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0 const&, long&>::type std::__1::invoke[abi:nn220000]<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0 const&, long&>(mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0 const&, long&) /home/ewoo/llvm-project/build/include/c++/v1/__functional/invoke.h:29:10
4) 0x56218bab11e6 in std::__1::__invoke_result<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0 const&, long&>::type tbb::detail::d0::invoke<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0 const&, long&>(mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0 const&, long&) /usr/include/tbb/../oneapi/tbb/detail/_utils.h:356:12
5) 0x56218bab11e6 in tbb::detail::d1::parallel_for_body_wrapper<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0, long>::operator()(tbb::detail::d1::blocked_range<long> const&) const /usr/include/tbb/../oneapi/tbb/parallel_for.h:206:13
6) 0x56218bab11e6 in decltype(std::declval<tbb::detail::d1::parallel_for_body_wrapper<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0, long> const&>()(std::declval<tbb::detail::d1::blocked_range<long>&>())) std::__1::__invoke[abi:nn220000]<tbb::detail::d1::parallel_for_body_wrapper<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0, long> const&, tbb::detail::d1::blocked_range<long>&>(tbb::detail::d1::parallel_for_body_wrapper<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0, long> const&, tbb::detail::d1::blocked_range<long>&) /home/ewoo/llvm-project/build/include/c++/v1/__type_traits/invoke.h:251:25
7) 0x56218bab11e6 in std::__1::__invoke_result<tbb::detail::d1::parallel_for_body_wrapper<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0, long> const&, tbb::detail::d1::blocked_range<long>&>::type std::__1::invoke[abi:nn220000]<tbb::detail::d1::parallel_for_body_wrapper<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0, long> const&, tbb::detail::d1::blocked_range<long>&>(tbb::detail::d1::parallel_for_body_wrapper<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0, long> const&, tbb::detail::d1::blocked_range<long>&) /home/ewoo/llvm-project/build/include/c++/v1/__functional/invoke.h:29:10
8) 0x56218bab11e6 in std::__1::__invoke_result<tbb::detail::d1::parallel_for_body_wrapper<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0, long> const&, tbb::detail::d1::blocked_range<long>&>::type tbb::detail::d0::invoke<tbb::detail::d1::parallel_for_body_wrapper<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0, long> const&, tbb::detail::d1::blocked_range<long>&>(tbb::detail::d1::parallel_for_body_wrapper<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0, long> const&, tbb::detail::d1::blocked_range<long>&) /usr/include/tbb/../oneapi/tbb/detail/_utils.h:356:12
9) 0x56218bab11e6 in tbb::detail::d1::start_for<tbb::detail::d1::blocked_range<long>, tbb::detail::d1::parallel_for_body_wrapper<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0, long>, tbb::detail::d1::auto_partitioner const>::run_body(tbb::detail::d1::blocked_range<long>&) /usr/include/tbb/../oneapi/tbb/parallel_for.h:117:9

SUMMARY: MemorySanitizer: use-of-uninitialized-value /home/ewoo/mold/lib/compress.cc:89:13 in mold::zlib_compress(std::__1::span<unsigned char, 18446744073709551615ul>)
2025-09-18 04:05:30 +00:00
Eric Woo 921649fc7d fix msan error in passes.cc
Uninitialized bytes in fwrite at offset 880 inside [0x724000002000, 7968)
==60359==WARNING: MemorySanitizer: use-of-uninitialized-value
0) 0x561279ddfd59 in mold::MallocOutputFile<mold::X86_64>::close(mold::Context<mold::X86_64>&) /home/ewoo/mold/src/mold.h:1587:5

Uninitialized value was stored to memory at
0) 0x56127521426e in __msan_memcpy.part.0 msan_interceptors.cpp.o
1) 0x56127701a499 in void mold::write_vector<unsigned char>(void*, std::__1::vector<unsigned char, std::__1::allocator<unsigned char>> const&) /home/ewoo/mold/src/../lib/lib.h:246:5
2) 0x56127701a499 in mold::BuildIdSection<mold::X86_64>::copy_buf(mold::Context<mold::X86_64>&) /home/ewoo/mold/src/output-chunks.cc:2745:3

Uninitialized value was stored to memory at
0) 0x56127521412e in __msan_memmove.part.0 msan_interceptors.cpp.o
1) 0x561277df5cca in unsigned char* std::__1::__constexpr_memmove[abi:nn220000]<unsigned char, unsigned char>(unsigned char*, unsigned char*, std::__1::__element_count) /home/ewoo/llvm-project/build/include/c++/v1/__string/constexpr_c_functions.h:228:5
2) 0x561277df5cca in std::__1::pair<unsigned char*, unsigned char*> std::__1::__copy_trivial_impl[abi:nn220000]<unsigned char, unsigned char>(unsigned char*, unsigned char*, unsigned char*) /home/ewoo/llvm-project/build/include/c++/v1/__algorithm/copy_move_common.h:64:3
3) 0x561277df5cca in std::__1::pair<unsigned char*, unsigned char*> std::__1::__copy_impl::operator()[abi:nn220000]<unsigned char, unsigned char, 0>(unsigned char*, unsigned char*, unsigned char*) const /home/ewoo/llvm-project/build/include/c++/v1/__algorithm/copy.h:234:12
4) 0x561277df5cca in std::__1::pair<unsigned char*, unsigned char*> std::__1::__copy_move_unwrap_iters[abi:nn220000]<std::__1::__copy_impl, unsigned char*, unsigned char*, unsigned char*, 0>(unsigned char*, unsigned char*, unsigned char*) /home/ewoo/llvm-project/build/include/c++/v1/__algorithm/copy_move_common.h:94:19
5) 0x561277df5cca in std::__1::pair<unsigned char*, unsigned char*> std::__1::__copy[abi:nn220000]<unsigned char*, unsigned char*, unsigned char*>(unsigned char*, unsigned char*, unsigned char*) /home/ewoo/llvm-project/build/include/c++/v1/__algorithm/copy.h:241:10
6) 0x561277df5cca in unsigned char* std::__1::copy[abi:nn220000]<unsigned char*, unsigned char*>(unsigned char*, unsigned char*, unsigned char*) /home/ewoo/llvm-project/build/include/c++/v1/__algorithm/copy.h:247:10
7) 0x561277df5cca in unsigned char* std::__1::__uninitialized_allocator_copy_impl[abi:nn220000]<std::__1::allocator<unsigned char>, unsigned char, unsigned char, 0>(std::__1::allocator<unsigned char>&, unsigned char*, unsigned char*, unsigned char*) /home/ewoo/llvm-project/build/include/c++/v1/__memory/uninitialized_algorithms.h:574:12
8) 0x561277df5cca in unsigned char* std::__1::__uninitialized_allocator_copy[abi:nn220000]<std::__1::allocator<unsigned char>, unsigned char*, unsigned char*, unsigned char*>(std::__1::allocator<unsigned char>&, unsigned char*, unsigned char*, unsigned char*) /home/ewoo/llvm-project/build/include/c++/v1/__memory/uninitialized_algorithms.h:582:28
9) 0x561277df5cca in void std::__1::vector<unsigned char, std::__1::allocator<unsigned char>>::__construct_at_end<unsigned char*, unsigned char*>(unsigned char*, unsigned char*, unsigned long) /home/ewoo/llvm-project/build/include/c++/v1/__vector/vector.h:954:17
10) 0x561277df5cca in void std::__1::vector<unsigned char, std::__1::allocator<unsigned char>>::__init_with_size[abi:nn220000]<unsigned char*, unsigned char*>(unsigned char*, unsigned char*, unsigned long) /home/ewoo/llvm-project/build/include/c++/v1/__vector/vector.h:602:7
11) 0x561277df5cca in std::__1::vector<unsigned char, std::__1::allocator<unsigned char>>::vector[abi:nn220000]<unsigned char*, 0>(unsigned char*, unsigned char*) /home/ewoo/llvm-project/build/include/c++/v1/__vector/vector.h:214:5
12) 0x561277df5cca in void mold::write_build_id<mold::X86_64>(mold::Context<mold::X86_64>&) /home/ewoo/mold/src/passes.cc:3270:29

Uninitialized value was created by an allocation of 'buf' in the stack frame
0) 0x561277df597e in void mold::write_build_id<mold::X86_64>(mold::Context<mold::X86_64>&) /home/ewoo/mold/src/passes.cc:3266:5

SUMMARY: MemorySanitizer: use-of-uninitialized-value /home/ewoo/mold/src/mold.h:1587:5 in mold::MallocOutputFile<mold::X86_64>::close(mold::Context<mold::X86_64>&)
2025-09-18 04:04:25 +00:00
Eric Woo 33545031d7 fix msan error in input-sections.cc
==45321==WARNING: MemorySanitizer: use-of-uninitialized-value
0) 0x56205a24a8f8 in char const* std::__1::__constexpr_memchr[abi:nn220000]<char const, char>(char const*, char, unsigned long) /home/ewoo/llvm-project/build/include/c++/v1/__string/constexpr_c_functions.h:149:30
1) 0x56205a24a8f8 in std::__1::char_traits<char>::find[abi:nn220000](char const*, unsigned long, char const&) /home/ewoo/llvm-project/build/include/c++/v1/__string/char_traits.h:135:12
2) 0x56205a24a8f8 in unsigned long std::__1::__str_find[abi:nn220000]<char, unsigned long, std::__1::char_traits<char>, 18446744073709551615ul>(char const*, unsigned long, char, unsigned long) /home/ewoo/llvm-project/build/include/c++/v1/__string/char_traits.h:353:23
3) 0x56205a24a8f8 in std::__1::basic_string_view<char, std::__1::char_traits<char>>::find[abi:nn220000](char, unsigned long) const /home/ewoo/llvm-project/build/include/c++/v1/string_view:509:12
4) 0x56205a24a8f8 in mold::find_null(std::__1::basic_string_view<char, std::__1::char_traits<char>>, long, long) /home/ewoo/mold/src/input-sections.cc:363:17
5) 0x56205a24a8f8 in mold::MergeableSection<mold::X86_64>::split_contents(mold::Context<mold::X86_64>&) /home/ewoo/mold/src/input-sections.cc:401:20

Uninitialized value was created by a heap allocation
0) 0x562058af198b in operator new(unsigned long) (/home/ewoo/mold/build/mold+0x23a98b) (BuildId: 29d9e90237b4b68ceff9639a3d9599d70f774d75)
1) 0x56205a2430fa in mold::InputSection<mold::X86_64>::uncompress(mold::Context<mold::X86_64>&) /home/ewoo/mold/src/input-sections.cc:65:13

SUMMARY: MemorySanitizer: use-of-uninitialized-value /home/ewoo/mold/src/input-sections.cc:363:17 in mold::find_null(std::__1::basic_string_view<char, std::__1::char_traits<char>>, long, long)
2025-09-18 04:03:09 +00:00
Eric Woo f800f5226c support MemorySanitizer w/ instrumented C++ stdlib 2025-09-18 04:02:39 +00:00
Rui Ueyama 8cf0b59e8f Update --help message 2025-09-15 08:04:28 +09:00
Rui Ueyama 729ceabd8c Update mold.1 (automated commit) 2025-09-14 14:34:10 +00:00
Rui Ueyama 131b5a9e89 Update man page 2025-09-14 23:32:00 +09:00
Rui Ueyama 056286d741 Partially revert 7dd7db0559
so that the output from --print-gc-sections and --print-icc-sections
remains the same as they were.

https://github.com/rui314/mold/pull/1507
2025-09-14 23:12:42 +09:00
Rui Ueyama 79ad0d2b4a Refactor 2025-09-14 19:13:35 +09:00
Rui Ueyama 6454b6c85a Merge pull request #1507 from pedronavf/print_sections_to_file
--print-gc-sections and --print-icf-sections can now take =FILE to s…
2025-09-14 19:11:30 +09:00
Rui Ueyama 49f9c5964a Do not convert read-only data to bss with --zero-to-data
Read-only data sections containing uninitialized variables are essentially
useless because reading from them always returns 0 at any time, so if they
exist, they are there for a reason. We do not want to automatically
convert it to a BSS.

https://github.com/rui314/mold/issues/1514
2025-09-14 10:36:03 +09:00
Rui Ueyama 3fb153e2ee Fix CI 2025-09-12 18:13:05 +09:00
Rui Ueyama 847341d208 Add --zero-to-bss
https://github.com/rui314/mold/pull/1515
2025-09-12 15:26:31 +09:00
Rui Ueyama 34c39e39f0 Refactor 2025-08-30 10:18:46 +09:00
Rui Ueyama 05827319da Fix test for system that doesn't support -static 2025-08-27 09:11:47 +09:00
Rui Ueyama ffbcfdb880 Merge pull request #1508 from PowellNGL/patch-1
Fix GCC package name for RHEL-based distros in install-build-deps.sh
2025-08-27 09:11:32 +09:00
Nick Powell 415176f530 Use correct GCC C++ package name for RHEL-based distros 2025-08-26 19:04:19 +02:00
Pedro Navarro 7dd7db0559 --print-gc-sections and --print-icf-sections can now take =FILE to save their output to a file 2025-08-26 02:26:41 -07:00
Rui Ueyama f5658e8bb4 Refactor 2025-08-26 11:24:12 +09:00
Rui Ueyama 8825ed7004 Merge pull request #1506 from minhbq-99/fix-pop-state
Correctly track --static value inside --push-state/--pop-state
2025-08-26 10:54:45 +09:00
Bui Quang Minh 001f718647 Correctly track --static value inside --push-state/--pop-state
Currently, when using --static inside --push-state and --pop-state, we
incorrectly mark the global state as static. This commit changes to the
correct behavior where global state is marked as static only when
--static is used at the outer most (outside of any --push-state,
--pop-state).
2025-08-25 23:02:40 +08:00
Rui Ueyama 4aa1a32d72 Bump required clang version from 12 to 16
Closes https://github.com/rui314/mold/issues/1505
2025-08-21 11:05:15 +09:00
Rui Ueyama 083901f42d Bump mold version to 2.40.4 2025-08-17 10:32:35 +09:00
Rui Ueyama 0a36282062 Revert "Choose the last file if we have more than one -l for the same DSO"
This reverts commit 2783e82436.

I don't want to take too much risk for 2.40.4, so I'll revert this change
for now.

https://github.com/rui314/mold/issues/1494
2025-08-16 12:21:41 +09:00
Rui Ueyama 754949f118 Revert "Upgrade bundled mimalloc to 3.0.8"
This reverts commit 1b76a11af2.

Closes https://github.com/rui314/mold/issues/1502
2025-08-15 11:48:32 +09:00
Rui Ueyama 05f5cdcf29 Add Alt Linux 2025-08-15 07:29:03 +09:00
Rui Ueyama 0ef640c95d Add Oracle Linux
Fixes https://github.com/rui314/mold/issues/1501
2025-08-15 06:58:13 +09:00
Rui Ueyama 7e0b728378 [ppc64v2] Support R_PPC64_GOT16
Fixes https://github.com/rui314/mold/issues/1497
2025-08-11 11:32:55 +09:00
Rui Ueyama d9b20a1580 [PPC64v2] Support R_PPC64_REL14
Fixes https://github.com/rui314/mold/issues/1496
2025-08-09 22:48:53 +09:00
Rui Ueyama 2783e82436 Choose the last file if we have more than one -l for the same DSO
Fixes https://github.com/rui314/mold/issues/1494

Based on https://github.com/rui314/mold/issues/1494
2025-08-04 20:33:44 +09:00
Rui Ueyama 9f5b7064dc Handle .data.rel.ro.{hot,unlikely} RELRO sections 2025-07-31 10:44:13 +09:00
Rui Ueyama f83747d6f6 Change the filename of Windows release binary 2025-07-31 06:33:29 +09:00
Rui Ueyama 282ca44e90 Bump mold version to 2.40.3 2025-07-30 13:42:59 +09:00
Rui Ueyama 201bc71b42 Revert f0b933820d
Emitting multiple zlib streams in a single .debug_info section broke
a few tools.

Fixes https://github.com/rui314/mold/issues/1491
2025-07-29 14:57:39 +09:00
Rui Ueyama de3a25f3ca [sh4] Use invalid instruction to fill gaps in executable sections
Quote from SH-4 CPU Core Architecture manual:

  The opcode 0xFFFD is guaranteed to be defined in any SH-4
  architecture revision. Other unused opcodes may be treated as
  reserved in any particular SH-4 implementation.

  The only undefined opcode which the architecture guarantees to cause
  a General Illegal Instruction Exception is 0xFFFD.
2025-07-28 10:06:57 +09:00
Rui Ueyama 5aa8809932 Refactor 2025-07-28 10:06:57 +09:00
Rui Ueyama ce04287237 [s390x] Fill gaps in executable sections with invalid insns instead of NOPs 2025-07-28 10:06:57 +09:00
Rui Ueyama 5485fdec1b [PPC64LE] Support R_PPC64_GOT16* relocations
Fixes https://github.com/rui314/mold/issues/1490
2025-07-27 18:33:25 +09:00
Rui Ueyama 69fee4966f Make --sysroot work with --chroot 2025-07-24 11:20:29 +09:00
Mateusz Mikuła afca89faaa Fix typo in test name 2025-07-23 22:07:16 +02:00
Rui Ueyama 1b76a11af2 Upgrade bundled mimalloc to 3.0.8 2025-07-22 14:40:02 +09:00
Rui Ueyama a531ce43b7 Build daily Windows archives 2025-07-19 16:41:13 +09:00
Rui Ueyama 6fb9564b4a Build a Zip file for Windows binary distribution 2025-07-18 09:44:16 +09:00
Rui Ueyama 4e44823cc0 Refactor 2025-07-15 15:33:51 +09:00
Rui Ueyama dd44e4768d Add a comment 2025-07-15 15:29:43 +09:00
Rui Ueyama 00efcf3d36 Refactor 2025-07-15 11:55:52 +09:00
Rui Ueyama 01358f680d Remove an issue template 2025-07-15 11:43:18 +09:00
Rui Ueyama 6e8852ea2a Allow partially-quoted tokens in response files
Fixes https://github.com/rui314/mold/issues/1485
2025-07-15 10:04:16 +09:00
Rui Ueyama 12e701cf32 Fix test
Closes https://github.com/rui314/mold/issues/1483
2025-07-14 21:44:44 +09:00
Rui Ueyama 5a144e161e Merge pull request #1482 from sicherha/fix_mimalloc_on_raspberry_pi
Build bundled mimalloc library without architecture-specific optimisations
2025-07-14 14:19:52 +09:00
Christoph Erhardt 278750593f Build bundled mimalloc library without architecture-specific optimisations
On 64-bit ARM, mimalloc is built with the compiler flag
`-march=armv8.1-a` by default. This flag advises GCC to make use of
ARMv8.1-A-specific instructions such as `ldaddal`. Machines based on the
older ARMv8-A instruction set - for example the Raspberry Pi 4 -
experience 'Illegal instruction (core dumped)' crashes as a result.

To ensure compatibility with machines that do not support ARMv8.1-A,
disable architecture-specific optimisations by setting the CMake option
`MI_NO_OPT_ARCH` to `ON`.

As of mimalloc 2.2.3, 64-bit ARM is the only architecture affected by
the `MI_NO_OPT_ARCH` option.
2025-07-13 18:26:57 +02:00
Rui Ueyama b862729608 Merge pull request #1481 from Ni-g-3l/add-zorin-distrib
Add Zorin Distribution to install deps script
2025-07-12 15:30:22 +09:00
Nig3l 6a842c7182 Add Zorin Distribution to install deps script 2025-07-12 07:14:58 +02:00
Rui Ueyama e61093dfd6 Bump mold version to 2.40.2 2025-07-12 10:36:11 +09:00
Rui Ueyama a01599cb4d Refactor 2025-07-08 09:38:53 +09:00
Rui Ueyama 87e4c7f552 Update toolchains 2025-07-02 13:10:41 +09:00
Rui Ueyama fa74740703 Refactor 2025-06-30 16:10:42 +09:00
Rui Ueyama 121c0ed23d Accept DSO-only linking
Fixes https://github.com/rui314/mold/issues/1478
2025-06-29 09:34:44 +09:00
Rui Ueyama ed7ccccf54 Add comments 2025-06-27 21:54:48 +09:00
Rui Ueyama 23789a7936 Add an overflow check for R_LARCH_32_PCREL 2025-06-27 09:58:38 +09:00
Rui Ueyama df91548a62 Add comments 2025-06-26 22:39:46 +09:00
Rui Ueyama bc7e4af733 Refactor 2025-06-26 10:25:40 +09:00
Rui Ueyama 2f6cf922e9 Add comments 2025-06-25 14:17:14 +09:00
Rui Ueyama 949e72307f Check certificates when downloading source archives
Pointed out at https://github.com/rui314/mold/discussions/1477
2025-06-25 10:35:29 +09:00
Rui Ueyama b3778207fc Refactor 2025-06-24 14:18:24 +09:00
Rui Ueyama 8eafdfbd0a Add a timer 2025-06-24 13:18:47 +09:00
Rui Ueyama 72c0340ca5 Ignore -fuse-ld
GCC passes the flag to the linker for some reason.
2025-06-24 13:07:49 +09:00
Rui Ueyama 934ca5bb5b Refactor 2025-06-24 10:54:05 +09:00
Rui Ueyama c9fa7111a2 Ensure that our Integer types are compiled to single load/store instructions
It turns out that at least clang-18 fails to compile our code to load
a 32-bit native-endian value from memory using shift + bitwise OR into
a single instruction when compiled with `-march=native`. It instead
compiles the code to complicated vector instructions.

It seems that the good old bswap and memcpy are the best option to
prevent the compiler from overthinking.
2025-06-23 15:09:50 +09:00
Rui Ueyama a3682ddbfd Refactor 2025-06-23 11:20:23 +09:00
Rui Ueyama 75772401cc Make loongarch64 build reproducible 2025-06-20 14:06:24 +09:00
Rui Ueyama 36f8ead511 Refactor 2025-06-20 14:06:24 +09:00
Rui Ueyama 30303d716a Fix test 2025-06-19 10:19:46 +09:00
Rui Ueyama 281d78caea Add a test 2025-06-19 09:53:58 +09:00
Rui Ueyama e9afa21adc Merge pull request #1475 from andreas-schwab/main
[RISC-V] Fix range checks for HI20/LO12 reloc addends
2025-06-19 09:18:15 +09:00
Andreas Schwab 52c698d625 [RISC-V] Fix range checks for HI20/LO12 reloc addends
Fixes #1474

Signed-off-by: Andreas Schwab <[email protected]>
2025-06-18 15:46:46 +02:00
Rui Ueyama 7eedb949b7 [RISC-V] Add additional relocation overflow checks
Fixes https://github.com/rui314/mold/issues/1474
2025-06-18 21:00:19 +09:00
Rui Ueyama 85408b4976 Refactor 2025-06-18 16:22:58 +09:00
Rui Ueyama 4cb46531d0 Refactor 2025-06-17 15:31:17 +09:00
Rui Ueyama ea98b0cb71 Refactor 2025-06-17 11:13:50 +09:00
Rui Ueyama 27edd50815 Fix CI 2025-06-16 14:11:09 +09:00
Rui Ueyama 18ecf979b8 Make the size of the thunk entry vary based on the distance to the target 2025-06-16 14:02:58 +09:00
Rui Ueyama 47f6c2839c Long range extension thunk for ARM64 2025-06-14 10:22:29 +09:00
Rui Ueyama da144e9d23 Refactor 2025-06-12 14:34:46 +09:00
Rui Ueyama f556d2b4ae Extend CI timeout 2025-06-11 22:02:41 +09:00
Rui Ueyama f2a62f70ef Set timeout to CI 2025-06-11 21:13:20 +09:00
Rui Ueyama 1956ae6ac8 Close fd when doing LTO
Fixes https://github.com/rui314/mold/issues/1362
2025-06-11 10:21:59 +09:00
Rui Ueyama 96c798d7be Bump mold version to 2.40.1 2025-06-08 10:06:35 +09:00
Rui Ueyama 6214f7be34 Refactor 2025-06-07 14:06:13 +09:00
Rui Ueyama 9b8b666da8 Update mold.1 (automated commit) 2025-06-07 04:03:43 +00:00
Rui Ueyama 6465232f0c Update a man page
Fixes https://github.com/rui314/mold/issues/1471
2025-06-07 13:02:20 +09:00
Rui Ueyama cc32a6d559 Reduce memory usage of Aho-Corasick search 2025-06-07 12:43:54 +09:00
Rui Ueyama 6214e132f8 Add a comment 2025-06-06 15:03:25 +09:00
Rui Ueyama 6ca242b8f0 Refactor 2025-06-06 14:24:13 +09:00
Rui Ueyama 3721c52d34 Refactor 2025-06-06 10:11:28 +09:00
Rui Ueyama 341c88e426 Refactor 2025-06-05 23:01:55 +09:00
Rui Ueyama 8f3f71663e Refactor 2025-06-05 18:54:39 +09:00
Rui Ueyama 1012a4804c Refactor 2025-06-05 18:13:47 +09:00
Rui Ueyama 5962014fd8 Fix CI 2025-06-05 17:40:58 +09:00
Rui Ueyama dac20fa243 Improve multi-glob pattern matcher so that it's linear-time
I asked ChatGPT how to match multiple glob patterns simultaneously with a
given input string, while avoiding the textbook-style NFA-to-DFA conversion.
Then it suggested that I implement a bitvector-based NFA simulation
algorithm that I wasn't aware. I don't think I could have come up with it
myself easily. This is impressive. ChatGPT is so good at programming and
sometimes much better than me! It may not be oto long before AI writes all
the code for me.
2025-06-05 17:14:34 +09:00
Rui Ueyama 8fa6fc86d3 Optimize glob matcher 2025-06-05 10:32:09 +09:00
Rui Ueyama 1c64d40ab8 Do not use recursion for glob pattern matching
Our glob pattern matcher was an exponential-time implementation.
This can easily be improved by avoiding recursion as explained at
https://research.swtch.com/glob.
2025-06-05 09:12:57 +09:00
Rui Ueyama 58cf042386 Refactor 2025-06-04 14:46:21 +09:00
Rui Ueyama d2f8bffdba Rename lib/common.h -> lib/lib.h 2025-06-04 14:23:43 +09:00
Rui Ueyama 60a9e013d3 Move jobs-{unix,win32}.cc from lib/ to src/ 2025-06-04 13:30:40 +09:00
Rui Ueyama a8893494ae Revert "Use std::syncostream"
This reverts commit 2479b81f8a because
it cannot be built with clang-16.
2025-06-04 12:49:52 +09:00
Rui Ueyama 2479b81f8a Use std::syncostream 2025-06-04 10:45:04 +09:00
Rui Ueyama 4dd133a31e Move {mapped-file,signal}-{unix,win32}.cc from lib/ to src/
The distinction between lib and src no longer makes much sense since
we no longer support Mach-O.
2025-06-04 10:44:54 +09:00
Rui Ueyama 02678c8af0 Refactor 2025-06-03 19:41:31 +09:00
Rui Ueyama 29e8b83886 Refactor 2025-06-03 16:56:23 +09:00
Rui Ueyama f0b933820d Emit multiple zlib streams to compressed sections
I learned that tools supporting compressed sections generally support
concatenated zlib streams within a single compressed debug section.
Examples include GNU/LLVM binutils and libbacktrace. So let's emit
compressed data that way, because it's much easier to do.

It is actually desirable to emit multiple small compressed blocks for
extremely large debug sections, because zlib doesn't support compressed
streams larger than 4 GiB on machines where the long type is 32 bits.

Context: https://sourceware.org/pipermail/binutils/2025-June/141514.html
2025-06-03 13:53:13 +09:00
Rui Ueyama 9a2f31164b Refactor 2025-06-03 08:44:29 +09:00
Rui Ueyama 8ef93b14d6 Refactor 2025-06-02 12:03:19 +09:00
Rui Ueyama 8d57a4419d Refactor 2025-06-02 11:35:21 +09:00
Rui Ueyama 0ef77ebd98 Do not use std::vector<u8> to avoid zero-initialization 2025-06-02 09:31:15 +09:00
Rui Ueyama 00d45d317f Merge pull request #1470 from a5ehren/cachyos 2025-06-02 06:57:32 +09:00
Ehren Bendler cdb63bb2a1 Update install-build-deps.sh to know what cachyos is
It's just a popular arch fork, so put it in the arch block
2025-06-01 11:52:56 -04:00
Rui Ueyama bcd77395d4 Use & instead of \0 in sed
\0 is not a portable way to represent the entire match; & is.
2025-06-01 10:52:55 +09:00
Rui Ueyama 4074707891 Fix CI 2025-06-01 10:52:55 +09:00
Rui Ueyama 079f914898 Use all available cores to compress debug sections 2025-06-01 10:03:05 +09:00
Rui Ueyama d59c55959c Avoid zero-initializing a very large vector
Chunk<E>::uncompressed_data can be very large as it contains an entire
output section contents. std::vector<u8>::resize() zero-initializes the
contents. We want to avoid that cost.

On my AMD Threadripper 7980X machine, zero-initializing 1 GiB of
memory takes ~200 ms. I can confirm that this change shaves off ~1.2s
for an executable with 5 GiB of debug info sections in uncompressed
form.
2025-06-01 10:03:05 +09:00
Rui Ueyama 5e19601a17 Fix debug section prefix 2025-05-31 11:32:50 +09:00
Rui Ueyama 3de3dcc001 Remove unused #include 2025-05-31 09:01:12 +09:00
Rui Ueyama 0d8334e5a2 Report an error if .dynsym refers to a section whose index is >65280
Unlike .symtab, .dynsym cannot have an extended symbol table, so a
section index that is larger than 65279 is not representable in an
output.

Fixes https://github.com/rui314/mold/issues/1468
2025-05-30 11:48:19 +09:00
Rui Ueyama a731b4c58c Build distribution binaries using GitHub Actions 2025-05-29 21:01:43 +09:00
Rui Ueyama 971317f3a9 Fix CI 2025-05-29 15:00:25 +09:00
Rui Ueyama 0a55058042 Do not link with the bundled TBB if BLAKE3_USE_TBB is ON
Recent versions of BLAKE3 library can be built with OneTBB for parallelization.
If that is enabled, we don't want to build and link our bundled OneTBB to avoid
conflict.

Fixes https://github.com/rui314/mold/issues/1466
2025-05-29 11:52:28 +09:00
Rui Ueyama 32746ebbb3 Upgrade bundled blake3 to v1.8.2 2025-05-29 11:51:01 +09:00
Rui Ueyama 1f44438b63 Add Manjaro Linux 2025-05-29 11:35:10 +09:00
Rui Ueyama b53197300b Bump mold version to 2.40.0 2025-05-26 11:30:00 +09:00
Rui Ueyama 3e9321b9a0 Fix test flakiness 2025-05-26 11:30:00 +09:00
Rui Ueyama 142eeaba86 Fix CI 2025-05-25 11:42:00 +09:00
Rui Ueyama 15f6b9f966 Refactor 2025-05-24 13:43:17 +09:00
Rui Ueyama a310cd27d4 Refactor 2025-05-23 18:33:04 +09:00
Rui Ueyama 2d178eb1ed Fix ASAN failure on an invalid downcast 2025-05-22 20:35:04 +09:00
Rui Ueyama 0fdffad57f Fix an assertion failure when a word-size weak abs reloc is promoted to dynamic
Fixes https://github.com/rui314/mold/issues/1462
2025-05-22 19:53:19 +09:00
Rui Ueyama d05272c4ee Fix CI 2025-05-22 12:11:22 +09:00
Rui Ueyama 1683826f2f Fix tests
Closes https://github.com/rui314/mold/issues/1461
2025-05-22 10:34:29 +09:00
Rui Ueyama 9ddec785b1 Do not sort debug info sections unless we have a mix of DWARF32 and DWARF64 2025-05-21 21:38:55 +09:00
Rui Ueyama e4f867ad77 Stabilize test 2025-05-21 21:38:55 +09:00
Rui Ueyama b7ce902e52 Add a test 2025-05-20 22:23:10 +09:00
Rui Ueyama 133613a493 Refactor 2025-05-20 21:29:37 +09:00
Rui Ueyama b026d7119b Sort debug sections in a separate debug file 2025-05-20 18:27:56 +09:00
Rui Ueyama fc0dd85d87 Add a comment 2025-05-20 18:01:27 +09:00
Rui Ueyama 6577232458 Rename a variable 2025-05-20 16:22:19 +09:00
Rui Ueyama b504cacf25 Fix CI 2025-05-20 15:45:20 +09:00
Rui Ueyama 159ce3ba67 Place strings referenced by DWARF32 at the beginning of .debug_str 2025-05-20 15:29:50 +09:00
Rui Ueyama 4c2dae0a2d Fix CI 2025-05-20 14:11:42 +09:00
Rui Ueyama 275c53d615 Merge pull request #1459 from el-remph/static-ldso
Fix linked executable segfaulting if -I and -static are both passed
2025-05-20 11:43:36 +09:00
Rui Ueyama fdd959880a Sort not only .debug_info but also other debug info sections
I believe it is unlikely that sections such as .debug_line grow beyond
4 GiB to cause relocation overflows, but doing some extra work is
trivial, so just do it.
2025-05-20 11:35:07 +09:00
Rui Ueyama 26d827d6e7 Read not only one CU but all CUs in .debug_info 2025-05-19 23:37:58 +09:00
Rui Ueyama 858fd8c495 Refactor 2025-05-19 23:35:06 +09:00
remph c13ecc99eb Fix linked executable segfaulting if -I and -static are both passed 2025-05-19 15:27:06 +01:00
Rui Ueyama 122268d147 Add a test 2025-05-19 16:22:07 +09:00
Rui Ueyama b990467c16 Upgrade bundled OneTBB to 2022.1.0
Specifically, we need https://github.com/uxlfoundation/oneTBB/commit/3c85ad77664f6ec1d50d9aebbd2d2f21adae0e21
to fix our ASAN bot.
2025-05-19 14:58:05 +09:00
Rui Ueyama 3d866bea61 Fix CI 2025-05-19 14:26:09 +09:00
Rui Ueyama 19a1bc651f Layout DWARF32 before DWARF64 to mitigate relocation overflow issues
Previously, if the combined size of DWARF32 and DWARF64 debug
information exceeded 2^32 bytes, a relocation overflow could occur.

Since not all static libraries are built with -gdwarf64, this overflow
issue prevented some very large programs from using static libraries
built without -gdwarf64. Recompiling everything with -gdwarf64 is the
"right" way to fix it, but that's not always feasible for various
reasons.

This commit mitigates the issue.

By sorting the input .debug_info sections so that DWARF32 debug info
comes before DWARF64, a relocation overflow can occur only if the size
of DWARF32 debug info alone exceeds 2^32 bytes.
2025-05-19 14:12:39 +09:00
Rui Ueyama 18c813a21c Make all tests pass on FreeBSD/ARM64 2025-05-16 18:06:21 +09:00
Rui Ueyama af36625a02 Fix bug where --as-needed did not work if --allow-shlib-undefined is given 2025-05-16 15:40:21 +09:00
Rui Ueyama 53c1758503 Align TLS segment correctly
This effectively reverts 5249edc57f.
2025-05-16 11:42:26 +09:00
Rui Ueyama 67ceb41b79 Refactor 2025-05-15 16:08:50 +09:00
Rui Ueyama 7d99824c3d Bump mold version to 2.39.1 2025-05-12 11:26:07 +09:00
Rui Ueyama 344dcff8e8 Change an error mesasge 2025-05-12 10:51:51 +09:00
Rui Ueyama d0dffd5985 Attempt to fix a use-after-free
https://github.com/rui314/mold/issues/1456
2025-05-11 11:12:57 +09:00
Rui Ueyama 251041e371 Fix comment 2025-05-06 11:13:42 +09:00
Rui Ueyama 4dd872ee8d Do not make a call to an unresolved weak symbol into an infinite loop
We don't do that other than RISC-V, so let's stop doing it entirely.
2025-05-06 10:41:39 +09:00
Rui Ueyama bb1b9dda64 Refactor 2025-05-06 10:26:15 +09:00
Rui Ueyama 78ec306cf4 Merge pull request #1453 from ziyao233/riscv/relax-undef-weak-sym
shrink-sections: Relax weak undef symbols if promotion is possible
2025-05-06 08:41:41 +09:00
Yao Zi 163c62f491 shrink-sections: Relax weak undef symbols if promotion is possible
A call to weak undefined symbol could be relaxed as long as the symbol
could be promoted to a dynamic symbol, where the relaxation is done
against the known PLT address and the address of the symbol itself
doesn't really matter.

Signed-off-by: Yao Zi <[email protected]>
2025-05-05 07:01:21 -04:00
Rui Ueyama 026fb54b9d Bump mold version to 2.39.0 2025-05-05 12:27:19 +09:00
Rui Ueyama cd162ba9f6 Remove an unnecessary constraint 2025-05-05 11:47:36 +09:00
Rui Ueyama e08e7f6aa4 Do not turn a call to a resolved weak undefined symbol into an infinite loop
Fixes https://github.com/rui314/mold/issues/1451
2025-05-05 11:35:03 +09:00
Rui Ueyama 6be2e676d2 Refactor 2025-05-04 20:07:24 +09:00
Rui Ueyama b6565025a3 Merge pull request #1450 from pcc/cargo
Update instructions to enable mold in Cargo on all architectures
2025-05-04 18:01:12 +09:00
Peter Collingbourne 118dc5de8e Update instructions to enable mold in Cargo on all architectures
Make it so that the Cargo instructions enable mold for Linux on all
architectures not just x86_64.
2025-05-04 01:21:20 -07:00
Rui Ueyama f3475d9f09 Refactor 2025-05-04 16:04:33 +09:00
Rui Ueyama 299904e56f Give the lowest priority to gentoo-test script 2025-05-04 14:21:55 +09:00
Rui Ueyama fded2d8a77 Remove redundant thunks after computing section addresses
Previously, we might mistakenly remove necessary symbols from thunks,
causing the linker to crash.

This fixes https://github.com/rui314/mold/issues/1449
2025-05-04 13:36:23 +09:00
Rui Ueyama f0e726024e Remove an unused function 2025-05-03 16:27:00 +09:00
Rui Ueyama 3d29f1678d Refactor 2025-05-03 11:44:50 +09:00
Rui Ueyama 559f23ff81 Give the lowest priority to gentoo-test script 2025-05-03 11:43:53 +09:00
Rui Ueyama 3274bcbdf0 Fix spurious --no-allow-shlib-undefined error
Fixes https://github.com/rui314/mold/issues/1446
2025-05-02 21:34:01 +09:00
Rui Ueyama 83dd353979 Prevent oscillation when computing the size of the phdr
Fixes https://github.com/rui314/mold/issues/1447
2025-05-01 09:34:27 +09:00
Rui Ueyama 8bae43b842 Make --default-symver work with version scripts
Previously, symbols specified with the `global:` label were unversioned
even if --default-symver was given. Now, such symbols are versioned with
the output file's soname.

Fixes https://github.com/rui314/mold/issues/1448
2025-04-30 20:45:09 +09:00
Rui Ueyama 4038cfd64d Refactor 2025-04-30 16:20:36 +09:00
Rui Ueyama 41079c64f5 [arm32be] Report error if --be8 is missing 2025-04-30 08:24:49 +09:00
Rui Ueyama 02e410133b Refactor 2025-04-29 14:34:25 +09:00
Rui Ueyama e70236a45a Refactor 2025-04-29 13:03:28 +09:00
Rui Ueyama 285c5bb509 Refactor 2025-04-29 09:12:43 +09:00
Rui Ueyama 975da12333 Fix CI 2025-04-28 22:12:05 +09:00
Rui Ueyama 157b16adb5 Support big-endian ARM32 target 2025-04-28 21:09:59 +09:00
Rui Ueyama 564757a0f5 [RV32] Do not report error on R_RISCV_32 2025-04-28 21:09:59 +09:00
Rui Ueyama 216b4a2325 Refactor 2025-04-28 15:01:56 +09:00
Rui Ueyama 954107e29c Bump mold version to 2.38.1 2025-04-28 11:10:38 +09:00
Rui Ueyama 21e20e01da Make --no-allow-shlib-undefined not report an error on circular dependencies
Fixes https://github.com/rui314/mold/issues/1445
2025-04-28 10:50:48 +09:00
Rui Ueyama 161cb6c99c Fix CI 2025-04-26 11:40:07 +09:00
Rui Ueyama 21cfee556a Transitively test library dependencies for --no-allow-shlib-undefined
This change is needed to build sci-libs/spqr-2.0.9-r4::gentoo
2025-04-26 11:30:38 +09:00
Rui Ueyama 06203c4a72 Make dist.sh reproducible for ARM32 on ARM64 2025-04-23 15:36:06 +09:00
Rui Ueyama 232067634f Simplify 2025-04-23 13:25:15 +09:00
Rui Ueyama 3f9aab7066 Build with -Wl,--gdb-index only for Debug build 2025-04-23 12:42:27 +09:00
Rui Ueyama 8b88595f26 Make dist.sh's output deterministic
It looks like `podman build` command does not push the newly created
image with the `latest` tag to ghcr.io, so the following `podman run`
pulled an older image. That caused an issue that the scirpt created
a different tarball than the one on my local machine.

I don't know what is the "proper" fix, but it looks like adding
`--pull=never` to `podman run` forbids it from pulling an older image
from ghcr.io, solving the issue.
2025-04-23 12:31:28 +09:00
Rui Ueyama fa09c562c9 Simplify 2025-04-23 11:53:24 +09:00
Rui Ueyama 9457d993c4 Upgrade Clang 2025-04-22 18:10:19 +09:00
Rui Ueyama 43810dfecd Make --no-allow-shlib-undefined on by default for executables
Fixes https://github.com/rui314/mold/issues/1443
2025-04-22 13:20:13 +09:00
Rui Ueyama 2d6061a233 Change how we resolve versioned symbols with default version
If a versioned symbol is a default one, it can be referred to with two
names, e.g. `foo` and `foo@VERSION`. Previously, we forward
`foo@VERSION` to `foo` if it is undefined, but it didn't always work
correctly. Now, we resolve both `foo` and `foo@VERSION` as usual and
then forward `foo@VERSION` to `foo` afterwards.

Fixes https://github.com/rui314/mold/issues/1169
2025-04-22 12:21:50 +09:00
Rui Ueyama 3d6aad65b0 Simplify 2025-04-21 13:55:31 +09:00
Rui Ueyama f7b1920f84 Make dist.sh reproducible again
mimalloc 2.2.2 started using __DATE__ and __TIME__ macros which make
the output non-reproducible. We can set the SOURCE_DATE_EPOCH environment
variable to fix the "current" timestamp.
2025-04-16 16:17:28 +09:00
Rui Ueyama b42f978212 Bump mold version to 2.38.0 2025-04-16 11:57:48 +09:00
Rui Ueyama ac6f1ec18e Do not hide foo@@VER if both foo@@VER and foo are defined
Fixes https://github.com/rui314/mold/issues/1442
2025-04-15 12:28:31 +09:00
Rui Ueyama d19f822ec0 Simplify 2025-04-15 11:51:11 +09:00
Rui Ueyama a97a628ee8 Resolve versioned symbols early
Fixes https://github.com/rui314/mold/issues/1440
2025-04-11 14:46:27 +09:00
Rui Ueyama 2888aade4c Refactor 2025-04-10 12:59:13 +09:00
Rui Ueyama 0172f22ab5 Refactor 2025-04-07 17:25:28 +09:00
Rui Ueyama 418d4ff62a Fix CI 2025-04-07 11:26:18 +09:00
Rui Ueyama 5249edc57f Simplify 2025-04-07 10:37:08 +09:00
Rui Ueyama 1870845f88 Refactor 2025-04-07 09:07:12 +09:00
Rui Ueyama d15c41a8dd Process --repro early
If mold crashes before --repro is processed, no .repro.tar file is
created. So we want to process the option as early as possible.
2025-04-07 08:50:26 +09:00
Rui Ueyama b7c8a843b2 Ignore --mmap-output-file
For the sake of completeness, --mmap-output-file is ignored just like
--no-mmap-output-file is.
2025-04-03 16:57:37 +09:00
Rui Ueyama 9b77a7ea86 Fix CI 2025-04-03 16:02:20 +09:00
Rui Ueyama af396ad5d9 Add --audit and --depaudit
These are for the sake of compatibility with GNU ld.
2025-04-03 15:36:05 +09:00
Rui Ueyama a2ee7371d5 Handle -mllvm as a synonym for -plugin-opt
Related to https://github.com/rui314/mold/pull/1437
2025-04-03 09:26:17 +09:00
Rui Ueyama c9812cf09f Merge pull request #1436 from christycylee/ignore_no_mmap_output_file
Ignore no-mmap-output-file for compatibility.
2025-04-03 08:59:31 +09:00
Christy Lee e9c070a3c2 Ignore no-mmap-output-file for compatibility. 2025-04-02 11:39:53 -07:00
Rui Ueyama 02e75be9fa Simplify 2025-04-02 21:55:17 +09:00
Rui Ueyama d5609a9bb8 Fix CI 2025-04-02 18:28:37 +09:00
Rui Ueyama 5d24db5a64 Handle COMDAT symbols in LTO IR object files
Before 9306f69a61, we completely ignored
duplciate symbol errors in IR object files. After that commit, we've
been accidentally reporting duplicate comdat symbols as errors.

Fixes https://github.com/rui314/mold/issues/1434
2025-04-02 17:54:41 +09:00
Rui Ueyama aeec8baee5 Revert "Take r_delta into account when computing distance"
This reverts commit b74a771886
because it broke buildbots.
2025-04-02 10:48:57 +09:00
Rui Ueyama b74a771886 Take r_delta into account when computing distance 2025-04-01 16:56:21 +09:00
Rui Ueyama 0124058ef7 Refactor 2025-03-26 20:56:04 +09:00
Rui Ueyama e302a2509c Fix CI 2025-03-26 20:56:02 +09:00
Rui Ueyama f70db66373 Use std::ranges 2025-03-26 20:26:51 +09:00
Rui Ueyama 9c0cbc8e6c Fix R_AARCH64_PREL16 range 2025-03-25 18:01:38 +09:00
Rui Ueyama a9acbd6b6f Refactor 2025-03-24 16:03:52 +09:00
Rui Ueyama b9d65a5787 Update RV32 toolchain 2025-03-24 15:40:06 +09:00
Rui Ueyama 50766f1ad4 Remove endbr32 support
It looks like Linux is going to support CET only on x86-64 (i.e. endbr64),
and it is very unlikely that x86-32 will get CET support. So I'll remove
the instruction from the code we generate for x86-32.
2025-03-23 18:07:21 +09:00
Rui Ueyama 3496ad1291 Update comments 2025-03-22 13:39:54 +09:00
Rui Ueyama 8a3d374251 Simplify 2025-03-22 11:20:07 +09:00
Rui Ueyama 96b2710849 Fix CI 2025-03-20 16:32:29 +09:00
Rui Ueyama a43f3953f0 Do not create redundant range extension thunks
Previously, we created thunks for all PLT function calls.
2025-03-20 13:25:05 +09:00
Rui Ueyama 2bc6ddc41c Refactor 2025-03-19 11:18:15 +09:00
Rui Ueyama f6575d53c1 Report an error if CREL contains addends for REL-type targets
Fixes https://github.com/rui314/mold/issues/1430
2025-03-18 10:52:55 +09:00
Rui Ueyama bf1ec5ac8f Allow very large offsets in SHT_CREL 2025-03-14 15:32:38 +09:00
Rui Ueyama b291100cfe Refactor 2025-03-14 15:05:16 +09:00
Rui Ueyama c43a859a90 Support CREL as input section
Fixes https://github.com/rui314/mold/issues/1427
2025-03-13 15:17:11 +09:00
Rui Ueyama 6e3d0e3a3c Fix build failure on Windows 2025-03-12 14:24:11 +09:00
Rui Ueyama 790ce3f64d Downgrade mimalloc to 2.2.2
mimalloc 3.0.1 cannot be compiled on Windows, so we don't want to stick
with the 2.x series for the time being.
2025-03-12 14:05:35 +09:00
Rui Ueyama cbd7f74c2e Upgrade bundled xxhash to 0.8.3 2025-03-12 13:38:26 +09:00
Rui Ueyama 3fe25fc69d Upgrade bundled mimalloc to 3.0.1 2025-03-12 13:34:39 +09:00
Rui Ueyama 823ef09585 Upgrade bundled blake3 to 1.6.1 2025-03-12 13:32:26 +09:00
Rui Ueyama f20f57060a Upgrade bundled zstd to 1.5.7 2025-03-12 11:44:12 +09:00
Rui Ueyama 0061d53409 Merge pull request #1425 from christycylee/buck_files
Remove extraneous build files
2025-03-11 10:35:56 +09:00
Rui Ueyama 82affb94d8 Fix out-of-bound memory access
Related to https://github.com/rui314/mold/issues/1426
2025-03-10 14:54:17 +09:00
Rui Ueyama 4f782fb7dc Refactor 2025-03-08 17:13:48 +09:00
Rui Ueyama e9eb6020a6 Skip if textrel is prohibited by default 2025-03-08 10:47:29 +09:00
Christy Lee 23a662cbb9 Remove extraneous build files 2025-03-07 14:23:22 -08:00
Rui Ueyama 32c8f6dd61 Bump mold version to 2.37.1 2025-03-07 14:32:02 +09:00
Rui Ueyama c95476d2fe Fix spurious "dupilcate symbol" error for LTO
Fixes https://github.com/rui314/mold/issues/1421
2025-03-07 13:17:37 +09:00
4014 changed files with 141757 additions and 748105 deletions

No files matched your search

+21
View File
@@ -0,0 +1,21 @@
# rustc's LoongArch targets use the medium code model, but the cc crate
# compiles C code with the C compiler's default normal code model, whose
# direct calls reach only ±128 MiB, unless -Ccode-model is given
# explicitly. A debug build of mold has more text than that, so we pass
# the model explicitly to make the cc crate pass -mcmodel=medium.
[target.'cfg(target_arch = "loongarch64")']
rustflags = ["-Ccode-model=medium"]
# LLVM's address-significance table tells the linker which symbols'
# addresses the program uses. Without it, Identical Code Folding (see
# cli/build.rs) has to assume that every piece of read-only data has a
# significant address, so it can't merge two functions that refer to
# identical constants, such as the panic locations that each crate
# instantiating the linker has its own copies of.
[target.'cfg(all())']
rustflags = ["-Cllvm-args=-addrsig"]
# Rust links musl statically by default, but a static executable can't
# dlopen() the LTO plugin.
[target.'cfg(target_env = "musl")']
rustflags = ["-Ctarget-feature=-crt-static"]
-10
View File
@@ -1,10 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
Delete this message and describe the bug here. Note that we only fix bugs in the most recent release of mold, so please make sure that you are using the most recent version.
+45 -13
View File
@@ -3,12 +3,30 @@ name: Build all tarballs
on:
schedule:
- cron: '0 0 * * *'
release:
types: [ published ]
workflow_dispatch:
jobs:
build-tarballs:
check-changes:
runs-on: ubuntu-24.04
permissions:
actions: read
contents: read
outputs:
has_changes: ${{ github.event_name == 'workflow_dispatch' || steps.check.outputs.has_changes }}
steps:
- name: Check for new commits since last successful run
id: check
env:
GH_TOKEN: ${{ github.token }}
run: |
last=$(gh run list -R ${{ github.repository }} -w build-all.yml -b main -s success -L1 --json createdAt -q '.[0].createdAt')
if [ -z "$last" ] || [ "$last" = "null" ] || [ "$(gh api repos/${{ github.repository }}/commits?sha=main\&since=$last\&per_page=1 | jq length)" -gt 0 ]; then
echo "has_changes=true" >> "$GITHUB_OUTPUT"
fi
build-linux:
needs: check-changes
if: needs.check-changes.outputs.has_changes == 'true'
strategy:
matrix:
include:
@@ -24,28 +42,42 @@ jobs:
permissions:
contents: read
packages: write
steps:
- name: Checkout Repository
uses: actions/checkout@v4
uses: actions/checkout@v7
- name: Install Podman
run: sudo apt-get update && sudo apt-get install -y podman qemu-user-static
- name: Login to GitHub Container Registry
uses: redhat-actions/podman-login@v1
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build a tarball
run: ./dist.sh ${{ matrix.target }}
- name: Upload artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: ${{ matrix.target }}
path: dist/mold-*.tar.gz
compression-level: 0
build-windows:
needs: check-changes
if: needs.check-changes.outputs.has_changes == 'true'
runs-on: windows-latest
permissions:
contents: read
steps:
- name: Checkout Repository
uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
- name: Build and Archive
shell: pwsh
run: ./.github/workflows/build-windows.ps1
- name: Upload artifact
uses: actions/upload-artifact@v7
with:
name: win-x86_64
path: dist/mold-*.*
+2 -10
View File
@@ -17,27 +17,19 @@ jobs:
permissions:
contents: read
packages: write
steps:
- name: Checkout Repository
uses: actions/checkout@v4
uses: actions/checkout@v7
- name: Install Podman
run: sudo apt-get update && sudo apt-get install -y podman qemu-user-static
- name: Login to GitHub Container Registry
uses: redhat-actions/podman-login@v1
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build a tarball
run: ./dist.sh ${{ matrix.target }}
- name: Upload artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: ${{ matrix.target }}
path: dist/mold-*.tar.gz
+16
View File
@@ -0,0 +1,16 @@
$ErrorActionPreference = 'Stop'
Set-Location "$PSScriptRoot/../.."
cargo build --release --locked --package mold
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
$metadata = cargo metadata --locked --no-deps --format-version 1 | ConvertFrom-Json
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
$version = ($metadata.packages | Where-Object name -eq 'mold').version
$stage = 'mold-install'
New-Item -ItemType Directory -Force "$stage/bin", "$stage/share/man/man1", "$stage/share/doc/mold", dist | Out-Null
Copy-Item target/release/mold.exe "$stage/bin/"
Copy-Item docs/mold.1 "$stage/share/man/man1/"
Copy-Item LICENSE "$stage/share/doc/mold/"
Compress-Archive -Path "$stage/*" -DestinationPath "dist/mold-$version-x86_64-windows.zip" -Force
+137 -93
View File
@@ -1,148 +1,192 @@
name: CI
on:
push:
pull_request:
env:
UBSAN_OPTIONS: print_stacktrace=1:halt_on_error=1
jobs:
build-sanitizers:
test-all-targets:
runs-on: ubuntu-24.04
timeout-minutes: 60
env:
# Incremental artifacts and mold's own debug info are unused in CI.
CARGO_INCREMENTAL: 0
CARGO_PROFILE_DEV_DEBUG: 0
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
- name: install test dependencies
run: |
sudo ./install-test-deps.sh
sudo .github/workflows/install-extras.sh
- run: cargo build --locked
- name: check the build for 32-bit hosts
run: |
rustup target add armv7-unknown-linux-gnueabihf i686-unknown-linux-gnu \
powerpc-unknown-linux-gnu
cargo check --locked --workspace --all-targets --target armv7-unknown-linux-gnueabihf
# i386 aligns 64-bit integers to 4 bytes
cargo check --locked --workspace --all-targets --target i686-unknown-linux-gnu
# 32-bit PowerPC has no 64-bit atomics
cargo check --locked --workspace --all-targets --target powerpc-unknown-linux-gnu
- name: verify all target toolchains are available
run: |
cargo test --locked -p mold --test integration -- --list |
tee "$RUNNER_TEMP/test-inventory.txt"
! grep -q '^unavailable:' "$RUNNER_TEMP/test-inventory.txt"
# All workspace crates were built above. Avoid linking empty test binaries
# for the target crates and command.
- run: cargo test --locked -p mold-common -p mold-elf -p mold-tests -p mold --lib --test integration
- name: archive test results
uses: actions/upload-artifact@v7
if: failure()
with:
name: test-results-all-targets
path: target/debug/mold-test/out/test
test-sanitizers:
strategy:
matrix:
target:
- ''
- '-DMOLD_USE_ASAN=On'
- '-DMOLD_USE_TSAN=On'
include:
- sanitizer: address
# Input files intentionally remain allocated until process exit.
runtime-options: detect_leaks=0:halt_on_error=1
test-prefix: ''
test-timeout: 30
- sanitizer: thread
runtime-options: halt_on_error=1
# TSan cannot model the atomic-fence synchronization in Rayon's
# work-stealing queue. Two CPUs still exercise parallel linker code.
test-prefix: taskset -c 0,1
test-timeout: 60
runs-on: ubuntu-24.04
timeout-minutes: 60
env:
CARGO_TARGET_DIR: target/${{ matrix.sanitizer }}
RUSTFLAGS: -Zsanitizer=${{ matrix.sanitizer }}
RUSTDOCFLAGS: -Zsanitizer=${{ matrix.sanitizer }}
steps:
- uses: actions/checkout@v4
- uses: rui314/setup-mold@staging
- run: sudo ./install-build-deps.sh
- name: build
run: |
sudo apt-get install -y clang-18 clang gcc-multilib gdb dwarfdump zstd
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_COMPILER=clang-18 -DCMAKE_CXX_COMPILER=clang++-18 ${{ matrix.target }} ..
cmake --build . -j$(nproc)
- run: cd build; ctest --output-on-failure -j$(nproc)
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@nightly
with:
components: rust-src
- run: sudo ./install-test-deps.sh
- run: cargo +nightly build --locked -Zbuild-std --target x86_64-unknown-linux-gnu -p mold --no-default-features --features system-allocator,x86_64
- run: |
if [ '${{ matrix.sanitizer }}' = address ]; then
export ASAN_OPTIONS='${{ matrix.runtime-options }}'
else
export TSAN_OPTIONS='${{ matrix.runtime-options }}'
fi
${{ matrix.test-prefix }} cargo +nightly test --locked -Zbuild-std --target x86_64-unknown-linux-gnu -p mold --no-default-features --features system-allocator,x86_64 --test integration -- --native --test-threads 1 --timeout ${{ matrix.test-timeout }}
- name: archive test results
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
if: failure()
with:
name: test-results-clang
path: |
build
!build/CMakeFiles
name: test-results-${{ matrix.sanitizer }}
path: target/${{ matrix.sanitizer }}/x86_64-unknown-linux-gnu/debug/mold-test/out/test
build-multi-archs:
build-msan:
runs-on: ubuntu-24.04
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
- name: install-build-deps
run: |
sudo ./install-build-deps.sh
sudo ./install-cross-tools.sh
sudo .github/workflows/install-extras.sh
- name: build
run: |
mkdir build
cd build
cmake ..
cmake --build . -j$(nproc)
- run: cd build; ctest --output-on-failure -j$(nproc)
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@nightly
with:
components: rust-src
- run: sudo ./install-test-deps.sh
- run: sudo apt-get install -y clang llvm
- run: .github/workflows/run-msan.sh
- name: archive test results
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
if: failure()
with:
name: test-results-multi-archs
path: |
build
!build/CMakeFiles
name: test-results-msan
path: target/msan/x86_64-unknown-linux-gnu/debug/mold-test/out/test
build-distros:
test-distros:
strategy:
matrix:
distro:
- alpine
- archlinux
- debian:11 # GCC 10 and CMake 3.18 - the minimum supported by mold
- debian:13
- fedora
- gentoo/stage3
- opensuse/tumbleweed
- ubuntu:22.04
- ubuntu:26.04
runs-on: ubuntu-latest
container: ${{ matrix.distro }}
timeout-minutes: 60
# Tumbleweed does not declare PATH in its container configuration.
container:
image: ${{ matrix.distro }}
env:
PATH: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
steps:
- uses: actions/checkout@v4
- run: ./install-build-deps.sh
- name: build
run: |
mkdir build
cd build
cmake ..
cmake --build . -j$(nproc)
- run: cd build; ctest --output-on-failure -j$(nproc)
- uses: actions/checkout@v7
- run: ./install-test-deps.sh
- uses: dtolnay/rust-toolchain@stable
- run: cargo build --locked -p mold --no-default-features --features x86_64
- run: cargo test --locked -p mold --no-default-features --features x86_64 --test integration -- --native
- name: sanitize distro name
if: failure()
id: sanitize
run: echo "name=$(echo '${{ matrix.distro }}' | tr '/:' '--')" >> "$GITHUB_OUTPUT"
- name: archive test results
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
if: failure()
with:
name: test-results-${{ matrix.distro }}
path: |
build
!build/CMakeFiles
name: test-results-${{ steps.sanitize.outputs.name }}
path: target/debug/mold-test/out/test
build-macos:
runs-on: macos-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
- name: build
run: |
mkdir build
cd build
cmake ..
cmake --build . -j$(sysctl -n hw.physicalcpu)
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
- run: cargo build --locked -p mold --no-default-features --features x86_64
build-windows:
runs-on: windows-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
- name: build
run: |
mkdir build
cd build
cmake -T clangcl ..
cmake --build . -j $Env:NUMBER_OF_PROCESSORS
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
- run: cargo build --locked -p mold --no-default-features --features x86_64,macho-x86_64
build-msys:
runs-on: windows-latest
timeout-minutes: 60
env:
CARGO_NET_GIT_FETCH_WITH_CLI: true
steps:
- uses: actions/checkout@v4
- name: Setup MSYS2
uses: msys2/setup-msys2@v2
- uses: actions/checkout@v7
- uses: msys2/setup-msys2@v2
with:
msystem: UCRT64
update: true
pacboy: gcc-libs:p libwinpthread-git:p tbb:p zlib:p zstd:p dlfcn:p cc:p cmake:p ninja:p
- name: build
shell: msys2 {0}
run: |
mkdir build
cd build
cmake -GNinja -DMOLD_USE_MIMALLOC=OFF -DMOLD_USE_SYSTEM_TBB=ON ..
cmake --build . -j $(nproc)
install: git
pacboy: rust:p cc:p dlfcn:p
- shell: msys2 {0}
run: cargo build --locked -p mold --no-default-features --features x86_64,macho-x86_64
build-freebsd:
runs-on: ubuntu-latest
timeout-minutes: 60
env:
# The VM has little disk space, so omit unused build artifacts.
CARGO_INCREMENTAL: 0
CARGO_PROFILE_DEV_DEBUG: 0
CARGO_PROFILE_TEST_DEBUG: 0
steps:
- uses: actions/checkout@v4
- name: Build and test
uses: vmactions/freebsd-vm@v1
- uses: actions/checkout@v7
- uses: vmactions/freebsd-vm@v1
with:
usesh: true
run: |
./install-build-deps.sh
mkdir build
cd build
cmake ..
cmake --build . -j$(nproc)
ctest --output-on-failure -j$(nproc)
pkg install -y rust
./install-test-deps.sh
cargo build --locked -p mold --no-default-features --features x86_64
cargo test --locked -p mold --no-default-features --features x86_64 --test integration -- --native
+24 -10
View File
@@ -5,7 +5,7 @@ apt-get install -y wget xz-utils
# Install a 32-bit RISC-V toolchain
mkdir /rv32
wget -O- -q https://github.com/riscv-collab/riscv-gnu-toolchain/releases/download/2023.07.07/riscv32-glibc-ubuntu-20.04-gcc-nightly-2023.07.07-nightly.tar.gz | tar -C /rv32 --strip-components=1 -xzf -
wget -O- --progress=dot:mega https://github.com/riscv-collab/riscv-gnu-toolchain/releases/download/2025.06.13/riscv32-glibc-ubuntu-24.04-gcc-nightly-2025.06.13-nightly.tar.xz | tar -C /rv32 --strip-components=1 --xz -xf -
ln -sf /rv32/sysroot /usr/riscv32-linux-gnu
echo '/rv32/bin/riscv32-unknown-linux-gnu-gcc -L/usr/riscv32-linux-gnu "$@"' > /usr/bin/riscv32-linux-gnu-gcc
@@ -18,7 +18,7 @@ done
# Install a LoongArch toolchain
mkdir /larch
wget -O- -q https://github.com/loongson/build-tools/releases/download/2025.02.21/x86_64-cross-tools-loongarch64-binutils_2.44-gcc_14.2.0-glibc_2.41.tar.xz | tar -C /larch --strip-components=1 --xz -xf -
wget -O- --progress=dot:mega https://github.com/loongson/build-tools/releases/download/2025.06.06/x86_64-cross-tools-loongarch64-binutils_2.44-gcc_15.1.0-glibc_2.41.tar.xz | tar -C /larch --strip-components=1 --xz -xf -
cp -r /larch/loongarch64-unknown-linux-gnu/lib/* /larch/target/lib64
ln -sf /larch/target /usr/loongarch64-linux-gnu
@@ -27,25 +27,39 @@ for i in gcc g++ objdump objcopy strip; do
ln -sf /larch/bin/loongarch64-unknown-linux-gnu-$i /usr/bin/loongarch64-linux-gnu-$i
done
wget -O /usr/local/bin/qemu-loongarch64 -q https://github.com/loongson/build-tools/releases/download/2024.11.01/qemu-loongarch64
wget -O /usr/local/bin/qemu-loongarch64 --progress=dot:mega https://github.com/loongson/build-tools/releases/download/2025.06.06/qemu-loongarch64
chmod 755 /usr/local/bin/qemu-loongarch64
# Install ARM32 big-endian toolchain
mkdir /armeb
wget -O- --progress=dot:mega https://toolchains.bootlin.com/downloads/releases/toolchains/armebv7-eabihf/tarballs/armebv7-eabihf--glibc--stable-2024.05-1.tar.xz | tar -C /armeb --strip-components=1 --xz -xf -
ln -sf /armeb/armeb-buildroot-linux-gnueabihf/sysroot /usr/armeb-linux-gnueabihf
echo '/armeb/bin/armeb-linux-gcc -L/usr/armeb-linux-gnu "$@"' > /usr/bin/armeb-linux-gnueabihf-gcc
echo '/armeb/bin/armeb-linux-g++ -L/usr/armeb-linux-gnu "$@"' > /usr/bin/armeb-linux-gnueabihf-g++
chmod 755 /usr/bin/armeb-linux-gnueabihf-{gcc,g++}
for i in objdump objcopy strip; do
ln -sf /armeb/bin/armeb-linux-$i /usr/bin/armeb-linux-gnueabihf-$i
done
# Install ARM64 big-endian toolchain
mkdir /aarch64be
wget -O- -q https://sources.buildroot.net/toolchain-external-arm-aarch64-be/arm-gnu-toolchain-13.3.rel1-x86_64-aarch64_be-none-linux-gnu.tar.xz | tar -C /aarch64be --strip-components=1 --xz -xf -
wget -O- --progress=dot:mega https://toolchains.bootlin.com/downloads/releases/toolchains/aarch64be/tarballs/aarch64be--glibc--stable-2024.05-1.tar.xz | tar -C /aarch64be --strip-components=1 --xz -xf -
ln -sf /aarch64be/aarch64_be-none-linux-gnu/libc /usr/aarch64_be-linux-gnu
echo '/aarch64be/bin/aarch64_be-none-linux-gnu-gcc -L/usr/aarch64_be-linux-gnu "$@"' > /usr/bin/aarch64_be-linux-gnu-gcc
echo '/aarch64be/bin/aarch64_be-none-linux-gnu-g++ -L/usr/aarch64_be-linux-gnu "$@"' > /usr/bin/aarch64_be-linux-gnu-g++
ln -sf /aarch64be/aarch64_be-buildroot-linux-gnu/sysroot /usr/aarch64_be-linux-gnu
echo '/aarch64be/bin/aarch64_be-linux-gcc -L/usr/aarch64_be-linux-gnu "$@"' > /usr/bin/aarch64_be-linux-gnu-gcc
echo '/aarch64be/bin/aarch64_be-linux-g++ -L/usr/aarch64_be-linux-gnu "$@"' > /usr/bin/aarch64_be-linux-gnu-g++
chmod 755 /usr/bin/aarch64_be-linux-gnu-{gcc,g++}
for i in objdump objcopy strip; do
ln -sf /aarch64be/bin/aarch64_be-none-linux-gnu-$i /usr/bin/aarch64_be-linux-gnu-$i
ln -sf /aarch64be/bin/aarch64_be-linux-$i /usr/bin/aarch64_be-linux-gnu-$i
done
# Install SH4 big-endian toolchain
mkdir /sh4aeb
wget -O- -q https://toolchains.bootlin.com/downloads/releases/toolchains/sh-sh4aeb/tarballs/sh-sh4aeb--glibc--stable-2024.05-1.tar.xz | tar -C /sh4aeb --strip-components=1 --xz -xf -
wget -O- --progress=dot:mega https://toolchains.bootlin.com/downloads/releases/toolchains/sh-sh4aeb/tarballs/sh-sh4aeb--glibc--stable-2024.05-1.tar.xz | tar -C /sh4aeb --strip-components=1 --xz -xf -
ln -sf /sh4aeb/sh4aeb-buildroot-linux-gnu/sysroot /usr/sh4aeb-linux-gnu
echo '/sh4aeb/bin/sh4aeb-linux-gcc -L/usr/sh4aeb-linux-gnu "$@"' > /usr/bin/sh4aeb-linux-gnu-gcc
echo '/sh4aeb/bin/sh4aeb-linux-g++ -L/usr/sh4aeb-linux-gnu "$@"' > /usr/bin/sh4aeb-linux-gnu-g++
@@ -57,5 +71,5 @@ done
# Install Intel SDE CPU emulator for CET-related tests
mkdir /sde
wget -O- -q https://downloadmirror.intel.com/843185/sde-external-9.48.0-2024-11-25-lin.tar.xz | tar -C /sde --strip-components=1 --xz -xf -
wget -O- --progress=dot:mega https://downloadmirror.intel.com/850782/sde-external-9.53.0-2025-03-16-lin.tar.xz | tar -C /sde --strip-components=1 --xz -xf -
ln -s /sde/sde64 /usr/bin
+56
View File
@@ -0,0 +1,56 @@
name: Mach-O CI
on:
push:
branches: [main]
pull_request:
# A newer push to the same branch supersedes a run still in progress.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
CARGO_TERM_COLOR: always
jobs:
test:
# The tests drive Apple's toolchain (cc, nm, otool, dyld_info, lldb),
# so they need a macOS host. An arm64 runner covers both targets:
# the harness runs every test natively and again under Rosetta.
runs-on: macos-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v7
- name: Install Rosetta 2
run: |
if ! arch -x86_64 /usr/bin/true 2>/dev/null; then
sudo softwareupdate --install-rosetta --agree-to-license
fi
- name: Update Rust
run: rustup update --no-self-update stable && rustup default stable
- uses: Swatinem/rust-cache@v2
# The unit tests, then the shell tests with twice the harness's
# default timeout, as mold's CI gives its slow configurations more:
# here the simulator's tests are slow, lldb's above all, whose lldb
# session alone takes ten seconds on a Mac, and the first of which
# ran past a minute on the hosted runners. The executable is built
# without the ELF targets, which these tests don't use.
- run: cargo test --locked -p mold-macho -p mold-tests --lib
- run: |
cargo test --locked -p mold --no-default-features --features macho-arm64,macho-x86_64 \
--test integration -- --timeout 120
# The harness reports only which tests failed; each test's log
# and its output directory are kept under the target directory.
- name: Upload test logs
if: failure()
uses: actions/upload-artifact@v7
with:
name: test-logs
path: target/debug/mold-test/out/test/
if-no-files-found: ignore
+93
View File
@@ -0,0 +1,93 @@
name: Build & attach tarballs on tag push
on:
push:
tags: ['v[0-9]*']
env:
TAG: ${{ github.ref_name }}
jobs:
build-linux:
strategy:
matrix:
include:
- { target: x86_64, os: ubuntu-24.04 }
- { target: aarch64, os: ubuntu-24.04-arm }
- { target: arm, os: ubuntu-24.04-arm }
- { target: riscv64, os: ubuntu-24.04 }
- { target: ppc64le, os: ubuntu-24.04 }
- { target: s390x, os: ubuntu-24.04 }
- { target: loongarch64, os: ubuntu-24.04 }
- { target: x86_64-musl, os: ubuntu-24.04 }
- { target: aarch64-musl, os: ubuntu-24.04-arm }
runs-on: ${{ matrix.os }}
permissions:
contents: read
steps:
- uses: actions/checkout@v7
- name: Install Podman
run: sudo apt-get update && sudo apt-get install -y podman qemu-user-static
- run: ./dist.sh ${{ matrix.target }}
- uses: actions/upload-artifact@v7
with:
name: ${{ matrix.target }}
path: dist/mold-*.tar.gz
build-windows:
runs-on: windows-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
- name: Build and Archive
shell: pwsh
run: ./.github/workflows/build-windows.ps1
- uses: actions/upload-artifact@v7
with:
name: win-x86_64
path: dist/mold-*.*
publish:
needs: [build-linux, build-windows]
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v7
- uses: actions/download-artifact@v8
with:
path: dist
pattern: '*'
merge-multiple: true
- name: Ensure release exists (create if missing)
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -e
if gh release view "$TAG" >& /dev/null; then
echo "Existing release found for $TAG"
else
echo "Creating draft release for $TAG"
gh release create "$TAG" --draft \
--title "$TAG" \
--notes "Automated draft – fill the changelog later."
fi
- name: Upload tarballs
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release upload "$TAG" dist/* --clobber
gh release view "$TAG" --json assets --jq '.assets[].name'
+28
View File
@@ -0,0 +1,28 @@
#!/bin/bash
set -euo pipefail
cd "$(dirname "$0")/../.."
# MSan requires instrumented Rust, std, and native dependencies:
# https://doc.rust-lang.org/unstable-book/compiler-flags/sanitizer.html#memorysanitizer
export CARGO_TARGET_DIR="${CARGO_TARGET_DIR:-target/msan}"
export RUSTFLAGS='-Zsanitizer=memory -Zsanitizer-memory-track-origins=2'
export RUSTDOCFLAGS="$RUSTFLAGS"
export CC_x86_64_unknown_linux_gnu=clang
export CFLAGS_x86_64_unknown_linux_gnu='-fsanitize=memory -fsanitize-memory-track-origins=2 -fno-omit-frame-pointer'
export LIBZ_SYS_STATIC=1
export MSAN_OPTIONS=halt_on_error=1
# The instrumented linker is too slow unoptimized for the heaviest tests
# to finish within the per-test timeout.
export CARGO_PROFILE_DEV_OPT_LEVEL=1
# Use instrumentable implementations instead of BLAKE3/Zstd assembly, and
# select mold-common and mold-elf as well as mold so Cargo enables their
# dependency features.
args=(
--locked -Zbuild-std --target x86_64-unknown-linux-gnu
--package mold-common --package mold-elf --package mold --no-default-features
--features mold/system-allocator,mold/x86_64,blake3/pure,zstd/no_asm
--test integration
)
cargo +nightly test "${args[@]}" -- --native --test-threads 1 --timeout 60 "$@"
+4 -1
View File
@@ -8,13 +8,16 @@ on:
- main
workflow_dispatch:
permissions:
contents: write
jobs:
update-manpage:
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v4
uses: actions/checkout@v7
- name: Install ronn
run: sudo apt-get update && sudo apt-get install -y ronn
+7 -10
View File
@@ -1,11 +1,8 @@
*.o
*.so
*~
**/.DS_Store
.cache
core
gentoo
/mold-*.tar.gz
/build*
/mold
/target
/out
/dist
/bench
/.claude
/.codex
*~
__pycache__/
-457
View File
@@ -1,457 +0,0 @@
# You can customize a build by specifying CMake options. An option may be
# given in the -Dvariable=value form. For a boolean variable, `ON` or `1`
# means true, while `OFF` or `0` means false.
#
# Here are a couple of common cmake options:
#
# -DCMAKE_C_COMPILER=<command-name>
#
# Specifies the C compiler name to use. The default value is `cc`.
#
# -DCMAKE_CXX_COMPILER=<command-name>
#
# Specifies the C++ compiler name to use. The default value is `c++`.
#
# -DCMAKE_INSTALL_PREFIX=<directory>
#
# Specifies the install target directory. The default value is `/usr/local`.
#
# -DCMAKE_BUILD_TYPE=[Debug | Release | RelWithDebInfo | MinSizeRel]
#
# Specifies the build type. The default is `Release`, which is the right
# option unless you are debugging mold.
#
# An example of a cmake command line is shown below:
#
# $ cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_CXX_COMPILER=g++-12 ..
#
# where `..` refers to this directory.
#
# With cmake, you may run `cmake --install .` instead of `make install` to
# install build artifacts to system directories. If you want to install
# artifacts to a temporary target directory, run `cmake --install . --prefix
# <dir>`.
#
# You can see the current cmake variables and their values by running
# `cmake -N -L .` in a build directory.
#
# Note that in this file, we provide various dials and knobs to configure
# how to build mold. However, as a policy, we do not provide a way to
# enable/disable any individual mold's feature. In other words, we do not
# provide options like `--enable-foo` or `--disable-foo`. The motivation
# behind this is build reproducibility. We want to guarantee that all builds
# of the mold linker of the same version will have the exact same set of
# features and behave exactly the same.
cmake_minimum_required(VERSION 3.14)
project(mold VERSION 2.37.0)
include(CMakeDependentOption)
include(CheckSymbolExists)
include(GNUInstallDirs)
add_executable(mold)
target_compile_features(mold PRIVATE cxx_std_20)
if(MINGW)
target_link_libraries(mold PRIVATE dl)
else()
target_link_libraries(mold PRIVATE ${CMAKE_DL_LIBS})
endif()
# Build mold itself using mold if -DMOLD_USE_MOLD=ON
option(MOLD_USE_MOLD "Use mold to build mold" OFF)
if(MOLD_USE_MOLD)
target_link_options(mold PRIVATE -fuse-ld=mold -Wl,--gdb-index)
if(CMAKE_BUILD_TYPE MATCHES "^Rel")
target_link_options(mold PRIVATE -Wl,--gc-sections,--icf=safe)
endif()
endif()
if(NOT "${CMAKE_CXX_COMPILER_FRONTEND_VARIANT}" STREQUAL "MSVC")
target_compile_options(mold PRIVATE
-fno-exceptions
-fno-unwind-tables
-fno-asynchronous-unwind-tables
-ffunction-sections
-fdata-sections
-Wall
-Wextra
-Wno-sign-compare
-Wno-unused-function
-Wno-unused-parameter
-Wno-missing-field-initializers
-ggnu-pubnames)
endif()
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
target_compile_options(mold PRIVATE -D_GLIBCXX_ASSERTIONS)
endif()
if(CMAKE_SYSTEM_NAME STREQUAL "OpenBSD")
set(OPENBSD ON)
endif()
# Build mold with -flto if -DMOLD_LTO=ON
option(MOLD_LTO "Build mold with link-time optimization enabled")
if(MOLD_LTO)
set_property(TARGET mold PROPERTY INTERPROCEDURAL_OPTIMIZATION ON)
endif()
# Enable AddressSanitizer if -DMOLD_USE_ASAN=ON
option(MOLD_USE_ASAN "Build mold with AddressSanitizer" OFF)
if(MOLD_USE_ASAN)
target_compile_options(mold PRIVATE -fsanitize=address -fsanitize=undefined)
target_link_options(mold PRIVATE -fsanitize=address -fsanitize=undefined)
endif()
# Enabled ThreadSanitizer if -DMOLD_USE_TSAN=ON
option(MOLD_USE_TSAN "Build mold with ThreadSanitizer" OFF)
if(MOLD_USE_TSAN)
target_compile_options(mold PRIVATE -fsanitize=thread)
target_link_options(mold PRIVATE -fsanitize=thread)
endif()
# Statically-link libstdc++ if -DMOLD_MOSTLY_STATIC=ON.
#
# This option is intended to be used by `./dist.sh` script to create a
# mold binary that works on various Linux distros. You probably don't
# need nor want to set this to ON.
option(MOLD_MOSTLY_STATIC "Statically link libstdc++ and some other libraries" OFF)
if(MOLD_MOSTLY_STATIC)
target_link_options(mold PRIVATE -static-libstdc++)
endif()
# Find zlib. If libz.so is not found, we compile a bundled one and
# statically-link it to mold.
find_package(ZLIB QUIET)
if(ZLIB_FOUND AND NOT MOLD_MOSTLY_STATIC)
target_link_libraries(mold PRIVATE ZLIB::ZLIB)
else()
set(ZLIB_BUILD_EXAMPLES OFF CACHE INTERNAL "")
add_subdirectory(third-party/zlib EXCLUDE_FROM_ALL)
target_include_directories(zlibstatic INTERFACE third-party/zlib
$<TARGET_PROPERTY:zlibstatic,BINARY_DIR>)
target_link_libraries(mold PRIVATE zlibstatic)
endif()
# Find BLAKE3 cryptographic hash library. Just like zlib, if libblkae3.so
# is not found, we compile a bundled one and statically-link it to mold.
find_package(BLAKE3 QUIET)
if(BLAKE3_FOUND AND NOT MOLD_MOSTLY_STATIC)
target_link_libraries(mold PRIVATE BLAKE3::blake3)
else()
function(mold_add_blake3)
set(BUILD_SHARED_LIBS OFF)
add_subdirectory(third-party/blake3/c EXCLUDE_FROM_ALL)
target_link_libraries(mold PRIVATE blake3)
target_include_directories(mold PUBLIC third-party/blake3/c)
endfunction()
mold_add_blake3()
endif()
# Find zstd compression library. If zstd.h is not found, we compile a
# bundled one and statically-link it to mold.
include(CheckIncludeFile)
check_include_file(zstd.h HAVE_ZSTD_H)
if(HAVE_ZSTD_H AND NOT MOLD_MOSTLY_STATIC)
target_link_libraries(mold PRIVATE zstd)
else()
add_subdirectory(third-party/zstd/build/cmake EXCLUDE_FROM_ALL)
target_compile_definitions(libzstd_static PRIVATE
ZSTD_BUILD_STATIC=1
ZSTD_BUILD_SHARED=0
ZSTD_BUILD_PROGRAMS=0
ZSTD_MULTITHREAD_SUPPORT=0
ZSTD_BUILD_TESTS=0)
target_include_directories(mold PUBLIC third-party/zstd/lib)
target_link_libraries(mold PRIVATE libzstd_static)
endif()
# Find mimalloc. mimalloc is an alternative malloc implementation
# optimized for multi-threaded applications.
#
# If you want to use the usual libc's malloc, pass -DMOLD_USE_MIMALLOC=OFF.
#
# We enable mimalloc by default for 64-bit targets. It doesn't seem to
# be stable on 32-bit targets.
cmake_dependent_option(
MOLD_USE_MIMALLOC "Use mimalloc" ON
"CMAKE_SIZEOF_VOID_P EQUAL 8; NOT APPLE; NOT ANDROID; NOT OPENBSD; NOT MOLD_USE_ASAN; NOT MOLD_USE_TSAN" OFF)
cmake_dependent_option(
MOLD_USE_SYSTEM_MIMALLOC "Use system or vendored mimalloc" OFF
MOLD_USE_MIMALLOC OFF)
# By default, we build a bundled mimalloc and statically-link it to
# mold. If you want to dynamically link to the system's
# libmimalloc.so, pass -DMOLD_USE_SYSTEM_MIMALLOC=ON.
if(MOLD_USE_MIMALLOC)
if(MOLD_USE_SYSTEM_MIMALLOC)
find_package(mimalloc REQUIRED)
target_link_libraries(mold PRIVATE mimalloc)
else()
function(mold_add_mimalloc)
set(MI_BUILD_STATIC ON CACHE INTERNAL "")
set(MI_BUILD_TESTS OFF CACHE INTERNAL "")
add_subdirectory(third-party/mimalloc EXCLUDE_FROM_ALL)
target_compile_definitions(mimalloc-static PRIVATE MI_USE_ENVIRON=0)
target_link_libraries(mold PRIVATE mimalloc-static)
endfunction()
mold_add_mimalloc()
endif()
endif()
# Find TBB. TBB (OneTBB or Intel TBB) is a high-level threading library.
# Use of this library is mandatory.
#
# By default, we build a bundled one and statically-link the library
# to mold. If you want to link to the system's libtbb2.so, pass
# -DMOLD_USE_SYSTEM_TBB=ON.
option(MOLD_USE_SYSTEM_TBB "Use system or vendored TBB" OFF)
if(MOLD_USE_SYSTEM_TBB)
find_package(TBB REQUIRED)
target_link_libraries(mold PRIVATE TBB::tbb)
else()
function(mold_add_tbb)
set(BUILD_SHARED_LIBS OFF)
set(TBB_TEST OFF CACHE INTERNAL "")
set(TBB_STRICT OFF CACHE INTERNAL "")
add_subdirectory(third-party/tbb EXCLUDE_FROM_ALL)
target_compile_definitions(tbb PRIVATE __TBB_DYNAMIC_LOAD_ENABLED=0)
target_link_libraries(mold PRIVATE TBB::tbb)
endfunction()
mold_add_tbb()
endif()
# We always use Clang to build mold on Windows. MSVC can't compile mold.
if(WIN32)
if(MSVC AND NOT CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
message(FATAL_ERROR
"Your compiler is not supported; install Clang from Visual Studio Installer and re-run cmake with '-T clangcl'")
endif()
target_compile_definitions(mold PRIVATE NOGDI NOMINMAX)
if(MINGW)
target_compile_definitions(mold PRIVATE _WIN32_WINNT=0xA00)
target_link_libraries(mold PRIVATE bcrypt)
endif()
else()
include(CheckLibraryExists)
check_library_exists(m pow "" LIBM_FOUND)
if(LIBM_FOUND)
target_link_libraries(mold PRIVATE m)
endif()
endif()
# Build mold-wrapper.so
if(NOT APPLE AND NOT WIN32)
add_library(mold-wrapper SHARED)
install(TARGETS mold-wrapper DESTINATION ${CMAKE_INSTALL_LIBDIR}/mold)
# Remove the default `lib` prefix
set_target_properties(mold-wrapper PROPERTIES PREFIX "")
target_link_libraries(mold-wrapper PRIVATE ${CMAKE_DL_LIBS})
target_sources(mold-wrapper PRIVATE src/mold-wrapper.c)
endif()
# If atomics doesn't work by default, add -latomic.
# We need the flag on riscv, armv6 and m68k.
include(CheckCXXSourceCompiles)
check_cxx_source_compiles("#include <atomic>
int main() {
std::atomic_uint8_t a;
std::atomic_uint16_t b;
std::atomic_uint32_t c;
std::atomic_uint64_t d;
return ++a + ++b + ++c + ++d;
}" HAVE_FULL_ATOMIC_SUPPORT)
if(NOT HAVE_FULL_ATOMIC_SUPPORT)
target_link_libraries(mold PRIVATE atomic)
endif()
# Add -pthread
if(NOT APPLE AND NOT MSVC)
target_compile_options(mold PRIVATE -pthread)
target_link_options(mold PRIVATE -pthread)
endif()
check_symbol_exists(madvise sys/mman.h HAVE_MADVISE)
check_symbol_exists(uname sys/utsname.h HAVE_UNAME)
# Create a .cc file containing the current git hash for `mold --version`.
add_custom_target(git_hash
COMMAND ${CMAKE_COMMAND}
-DSOURCE_DIR=${CMAKE_SOURCE_DIR}
-DOUTPUT_FILE=${CMAKE_BINARY_DIR}/git-hash.cc
-P ${CMAKE_SOURCE_DIR}/lib/update-git-hash.cmake
DEPENDS lib/update-git-hash.cmake
BYPRODUCTS git-hash.cc
VERBATIM)
add_dependencies(mold git_hash)
# Create config.h file
configure_file(lib/config.h.in config.h)
include_directories(${CMAKE_CURRENT_BINARY_DIR})
# Almost all functions are template in mold which take a target type
# (e.g. X86_64) as its type parameter. Since we suport more than 10
# targets, compiling a single source file for all the targets is very
# slow.
#
# As a workaround, we create a .cc file for each target and spawn many
# compiler instances. This is hacky but greatly reduces compile time
# on a multicore machine.
list(APPEND MOLD_ELF_TARGETS
X86_64 I386 ARM64LE ARM64BE ARM32 RV32LE RV32BE RV64LE RV64BE PPC32
PPC64V1 PPC64V2 S390X SPARC64 M68K SH4LE SH4BE LOONGARCH32 LOONGARCH64)
list(APPEND MOLD_ELF_TEMPLATE_FILES
src/arch-arm64.cc
src/arch-loongarch.cc
src/arch-riscv.cc
src/arch-sh4.cc
src/cmdline.cc
src/filetype.cc
src/gc-sections.cc
src/gdb-index.cc
src/icf.cc
src/input-files.cc
src/input-sections.cc
src/linker-script.cc
src/main.cc
src/mapfile.cc
src/output-chunks.cc
src/passes.cc
src/relocatable.cc
src/shrink-sections.cc
src/thunks.cc
src/tls.cc
)
if(WIN32 AND NOT MINGW)
list(APPEND MOLD_ELF_TEMPLATE_FILES src/lto-win32.cc)
else()
list(APPEND MOLD_ELF_TEMPLATE_FILES src/lto-unix.cc)
endif()
if(WIN32)
list(APPEND MOLD_ELF_TEMPLATE_FILES
src/output-file-win32.cc
src/subprocess-win32.cc
)
else()
list(APPEND MOLD_ELF_TEMPLATE_FILES
src/output-file-unix.cc
src/subprocess-unix.cc
)
endif()
function(mold_instantiate_templates SOURCE TARGET)
set(PATH ${CMAKE_BINARY_DIR}/${SOURCE}.${TARGET}.cc)
if(NOT EXISTS ${PATH})
file(WRITE ${PATH} "#define MOLD_${TARGET} 1
#define MOLD_TARGET ${TARGET}
#include \"${CMAKE_SOURCE_DIR}/${SOURCE}\"
")
endif()
target_sources(mold PRIVATE ${PATH})
endfunction()
foreach (SOURCE IN LISTS MOLD_ELF_TEMPLATE_FILES)
foreach(TARGET IN LISTS MOLD_ELF_TARGETS)
mold_instantiate_templates(${SOURCE} ${TARGET})
endforeach()
endforeach()
# Add other non-template source files.
target_sources(mold PRIVATE
git-hash.cc
lib/compress.cc
lib/crc32.cc
lib/demangle.cc
lib/filepath.cc
lib/glob.cc
lib/hyperloglog.cc
lib/mimalloc.cc
lib/multi-glob.cc
lib/perf.cc
lib/random.cc
lib/tar.cc
src/arch-arm32.cc
src/arch-i386.cc
src/arch-m68k.cc
src/arch-ppc32.cc
src/arch-ppc64v1.cc
src/arch-ppc64v2.cc
src/arch-s390x.cc
src/arch-sparc64.cc
src/arch-x86-64.cc
src/config.cc
src/elf.cc
third-party/rust-demangle/rust-demangle.c
)
if(WIN32)
target_sources(mold PRIVATE
lib/jobs-win32.cc
lib/mapped-file-win32.cc
lib/signal-win32.cc
)
else()
target_sources(mold PRIVATE
lib/jobs-unix.cc
lib/mapped-file-unix.cc
lib/signal-unix.cc
)
endif()
include(CTest)
if(BUILD_TESTING)
# Create the ld symlinks required for testing
if(NOT WIN32)
add_custom_command(
TARGET mold POST_BUILD
COMMAND ${CMAKE_COMMAND} -E create_symlink mold ld
BYPRODUCTS ld
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
VERBATIM)
endif()
if(${UNIX})
add_subdirectory(test)
endif()
endif()
if(NOT CMAKE_SKIP_INSTALL_RULES)
install(TARGETS mold RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
install(FILES docs/mold.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1/)
install(FILES LICENSE DESTINATION ${CMAKE_INSTALL_DOCDIR})
function(mold_install_relative_symlink OLD NEW)
install(CODE "
get_filename_component(PREFIX_ABS \${CMAKE_INSTALL_PREFIX}/ ABSOLUTE)
get_filename_component(OLD_ABS ${OLD} ABSOLUTE BASE_DIR \${PREFIX_ABS})
get_filename_component(NEW_ABS ${NEW} ABSOLUTE BASE_DIR \${PREFIX_ABS})
get_filename_component(NEW_DIR \${NEW_ABS} DIRECTORY)
file(RELATIVE_PATH OLD_REL \${NEW_DIR} \${OLD_ABS})
message(STATUS \"Installing symlink: \$ENV{DESTDIR}\${NEW_ABS} -> \${OLD_REL}\")
file(MAKE_DIRECTORY \$ENV{DESTDIR}\${NEW_DIR})
file(CREATE_LINK \${OLD_REL} \$ENV{DESTDIR}\${NEW_ABS} SYMBOLIC)")
endfunction()
mold_install_relative_symlink(${CMAKE_INSTALL_BINDIR}/mold${CMAKE_EXECUTABLE_SUFFIX}
${CMAKE_INSTALL_LIBEXECDIR}/mold/ld${CMAKE_EXECUTABLE_SUFFIX})
mold_install_relative_symlink(${CMAKE_INSTALL_BINDIR}/mold${CMAKE_EXECUTABLE_SUFFIX}
${CMAKE_INSTALL_BINDIR}/ld.mold${CMAKE_EXECUTABLE_SUFFIX})
mold_install_relative_symlink(${CMAKE_INSTALL_MANDIR}/man1/mold.1
${CMAKE_INSTALL_MANDIR}/man1/ld.mold.1)
endif()
Generated
+744
View File
@@ -0,0 +1,744 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "allocator-api2"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
[[package]]
name = "arrayvec"
version = "0.7.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56"
[[package]]
name = "blake3"
version = "1.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d9e454fc11f76977dc803893aff6304ed33d6a26efae8696573bea74baa27ae"
dependencies = [
"arrayvec",
"cc",
"cfg-if",
"constant_time_eq",
"cpufeatures",
]
[[package]]
name = "block-buffer"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa"
dependencies = [
"hybrid-array",
]
[[package]]
name = "bstr"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6bb31b46c14244e20ee9984b11bf5c992b91fb6939fea616e3512c8baecdbe5f"
dependencies = [
"memchr",
"regex-automata",
"serde_core",
]
[[package]]
name = "cc"
version = "1.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ad534f4357a5264cce5019c989cf66a4f0dc4e0d1b1d15f8aacec0ff7360273"
dependencies = [
"find-msvc-tools",
"jobserver",
"libc",
"shlex",
]
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "const-oid"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c"
[[package]]
name = "constant_time_eq"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b"
[[package]]
name = "cpp_demangle"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0667304c32ea56cb4cd6d2d7c0cfe9a2f8041229db8c033af7f8d69492429def"
dependencies = [
"cfg-if",
]
[[package]]
name = "cpufeatures"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566"
dependencies = [
"libc",
]
[[package]]
name = "crc32fast"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8498c871161e1742aaa9d52551b2d6ebdd4c3d45a3be423e3728f33b955be550"
dependencies = [
"cfg-if",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5181e0de7b61eb03a81e347d6dd8797bae9da5146707b51077e2d71a54ec0ceb"
dependencies = [
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17"
[[package]]
name = "crypto-common"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453"
dependencies = [
"hybrid-array",
]
[[package]]
name = "digest"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2"
dependencies = [
"block-buffer",
"const-oid",
"crypto-common",
]
[[package]]
name = "either"
version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34"
[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "find-msvc-tools"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890"
[[package]]
name = "flate2"
version = "1.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb"
dependencies = [
"zlib-rs",
]
[[package]]
name = "foldhash"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
[[package]]
name = "getrandom"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
dependencies = [
"cfg-if",
"libc",
"r-efi",
]
[[package]]
name = "hashbrown"
version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
dependencies = [
"allocator-api2",
"equivalent",
"foldhash",
]
[[package]]
name = "hybrid-array"
version = "0.4.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17"
dependencies = [
"typenum",
]
[[package]]
name = "itoa"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
name = "jobserver"
version = "0.1.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3"
dependencies = [
"getrandom",
"libc",
]
[[package]]
name = "libc"
version = "0.2.189"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
[[package]]
name = "libz-rs-sys"
version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "434324ee61a8e4a05dd3fdf03d51c073ea7fdaf5a35b2ca33c4ca9f3f37def4a"
dependencies = [
"zlib-rs",
]
[[package]]
name = "memchr"
version = "2.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
[[package]]
name = "memmap2"
version = "0.9.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1219ed1b7f229ee7104d281dd01d6802fe28bb6e95d292942c4daacdeb798c0"
dependencies = [
"libc",
]
[[package]]
name = "mold"
version = "3.0.0"
dependencies = [
"mold-elf",
"mold-elf-arm32",
"mold-elf-arm32be",
"mold-elf-arm64",
"mold-elf-arm64be",
"mold-elf-i386",
"mold-elf-loongarch32",
"mold-elf-loongarch64",
"mold-elf-m68k",
"mold-elf-ppc32",
"mold-elf-ppc64v1",
"mold-elf-ppc64v2",
"mold-elf-riscv32",
"mold-elf-riscv32be",
"mold-elf-riscv64",
"mold-elf-riscv64be",
"mold-elf-s390x",
"mold-elf-sh4",
"mold-elf-sh4be",
"mold-elf-sparc64",
"mold-elf-x86_64",
"mold-macho",
"mold-macho-arm64",
"mold-macho-x86_64",
"mold-mimalloc",
"mold-tests",
]
[[package]]
name = "mold-common"
version = "3.0.0"
dependencies = [
"bstr",
"cpp_demangle",
"flate2",
"libc",
"libz-rs-sys",
"memchr",
"memmap2",
"portable-atomic",
"rayon",
"rustc-demangle",
"xxhash-rust",
"zlib-rs",
"zstd",
]
[[package]]
name = "mold-elf"
version = "3.0.0"
dependencies = [
"blake3",
"bstr",
"cc",
"crc32fast",
"getrandom",
"hashbrown",
"libc",
"memchr",
"mold-common",
"portable-atomic",
"rayon",
"uuid",
"xxhash-rust",
]
[[package]]
name = "mold-elf-arm32"
version = "3.0.0"
dependencies = [
"mold-elf",
]
[[package]]
name = "mold-elf-arm32be"
version = "3.0.0"
dependencies = [
"mold-elf",
]
[[package]]
name = "mold-elf-arm64"
version = "3.0.0"
dependencies = [
"mold-elf",
]
[[package]]
name = "mold-elf-arm64be"
version = "3.0.0"
dependencies = [
"mold-elf",
]
[[package]]
name = "mold-elf-i386"
version = "3.0.0"
dependencies = [
"mold-elf",
]
[[package]]
name = "mold-elf-loongarch32"
version = "3.0.0"
dependencies = [
"mold-elf",
]
[[package]]
name = "mold-elf-loongarch64"
version = "3.0.0"
dependencies = [
"mold-elf",
]
[[package]]
name = "mold-elf-m68k"
version = "3.0.0"
dependencies = [
"mold-elf",
]
[[package]]
name = "mold-elf-ppc32"
version = "3.0.0"
dependencies = [
"mold-elf",
]
[[package]]
name = "mold-elf-ppc64v1"
version = "3.0.0"
dependencies = [
"mold-elf",
]
[[package]]
name = "mold-elf-ppc64v2"
version = "3.0.0"
dependencies = [
"mold-elf",
]
[[package]]
name = "mold-elf-riscv32"
version = "3.0.0"
dependencies = [
"mold-elf",
]
[[package]]
name = "mold-elf-riscv32be"
version = "3.0.0"
dependencies = [
"mold-elf",
]
[[package]]
name = "mold-elf-riscv64"
version = "3.0.0"
dependencies = [
"mold-elf",
]
[[package]]
name = "mold-elf-riscv64be"
version = "3.0.0"
dependencies = [
"mold-elf",
]
[[package]]
name = "mold-elf-s390x"
version = "3.0.0"
dependencies = [
"mold-elf",
]
[[package]]
name = "mold-elf-sh4"
version = "3.0.0"
dependencies = [
"mold-elf",
]
[[package]]
name = "mold-elf-sh4be"
version = "3.0.0"
dependencies = [
"mold-elf",
]
[[package]]
name = "mold-elf-sparc64"
version = "3.0.0"
dependencies = [
"mold-elf",
]
[[package]]
name = "mold-elf-x86_64"
version = "3.0.0"
dependencies = [
"mold-elf",
]
[[package]]
name = "mold-macho"
version = "3.0.0"
dependencies = [
"hashbrown",
"libc",
"memchr",
"memmap2",
"mold-common",
"portable-atomic",
"rayon",
"serde_json",
"sha1",
"sha2",
"uuid",
"xxhash-rust",
]
[[package]]
name = "mold-macho-arm64"
version = "3.0.0"
dependencies = [
"mold-macho",
]
[[package]]
name = "mold-macho-x86_64"
version = "3.0.0"
dependencies = [
"mold-macho",
]
[[package]]
name = "mold-mimalloc"
version = "0.1.52"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b7efd1b6c74e7a09c1d296af4875b79118a5ea32d70736601a8323ddcbcb4c2"
dependencies = [
"mold-mimalloc-sys",
]
[[package]]
name = "mold-mimalloc-sys"
version = "0.1.49"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66a79c372cdbf45bf4548fd4aa40a0b2805ef2ee8e25e5185e11bf2b5c5de366"
dependencies = [
"cc",
]
[[package]]
name = "mold-tests"
version = "3.0.0"
dependencies = [
"libc",
"serde_json",
]
[[package]]
name = "pkg-config"
version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548"
[[package]]
name = "portable-atomic"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85"
[[package]]
name = "proc-macro2"
version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
dependencies = [
"proc-macro2",
]
[[package]]
name = "r-efi"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
[[package]]
name = "rayon"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d"
dependencies = [
"either",
"rayon-core",
]
[[package]]
name = "rayon-core"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
dependencies = [
"crossbeam-deque",
"crossbeam-utils",
]
[[package]]
name = "regex-automata"
version = "0.4.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2"
[[package]]
name = "rustc-demangle"
version = "0.1.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b74b56ffa8bb2830709a538c2cbcae9aa062db0d2a42563bfb09bdaae44020eb"
[[package]]
name = "serde"
version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
dependencies = [
"serde_core",
]
[[package]]
name = "serde_core"
version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.151"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
dependencies = [
"itoa",
"memchr",
"serde",
"serde_core",
"zmij",
]
[[package]]
name = "sha1"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]]
name = "sha2"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]]
name = "shlex"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
[[package]]
name = "syn"
version = "3.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6275cddf4610d1775e6d1fe9469b2e77d0f39fd98fb7450901b821e0c53649f"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "typenum"
version = "1.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "uuid"
version = "1.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97277d36b9c3ace13e58fa6e753f8b0bbbf302a18dd193240d70f9e29681059a"
dependencies = [
"getrandom",
]
[[package]]
name = "xxhash-rust"
version = "0.8.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aee1b19627c7c60102ab80d3a9cbe18de90bfe03bfa6c3715447681f0e8c8af6"
[[package]]
name = "zlib-rs"
version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b268e58e7c693d7c271f93ffc4ba3b380412554231c85bf61ca7af91042a4112"
[[package]]
name = "zmij"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
[[package]]
name = "zstd"
version = "0.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a"
dependencies = [
"zstd-safe",
]
[[package]]
name = "zstd-safe"
version = "7.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d"
dependencies = [
"zstd-sys",
]
[[package]]
name = "zstd-sys"
version = "2.0.16+zstd.1.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748"
dependencies = [
"cc",
"pkg-config",
]
+51
View File
@@ -0,0 +1,51 @@
# The ELF linker in elf/ is generic over the target. Instantiating it for
# all twenty targets in one crate keeps the compiler on a single core for
# minutes, so each target is instantiated in a crate of its own under
# arch/, and the executable in cli/ dispatches to them.
[workspace]
resolver = "3"
members = ["cli", "common", "elf", "macho", "arch/*", "tests"]
[workspace.package]
version = "3.0.0"
edition = "2024"
rust-version = "1.95"
license = "MIT"
repository = "https://github.com/rui314/mold"
# The workspace crates are published together and depend on each other's
# internals, so each one requires the exact version of the others. Keep
# these versions equal to the workspace version above.
[workspace.dependencies]
libc = "0.2"
mold-common = { path = "common", version = "=3.0.0" }
mold-elf = { path = "elf", version = "=3.0.0" }
mold-elf-x86_64 = { path = "arch/elf-x86_64", version = "=3.0.0" }
mold-elf-i386 = { path = "arch/elf-i386", version = "=3.0.0" }
mold-elf-arm32 = { path = "arch/elf-arm32", version = "=3.0.0" }
mold-elf-arm32be = { path = "arch/elf-arm32be", version = "=3.0.0" }
mold-elf-arm64 = { path = "arch/elf-arm64", version = "=3.0.0" }
mold-elf-arm64be = { path = "arch/elf-arm64be", version = "=3.0.0" }
mold-elf-riscv64 = { path = "arch/elf-riscv64", version = "=3.0.0" }
mold-elf-riscv64be = { path = "arch/elf-riscv64be", version = "=3.0.0" }
mold-elf-riscv32 = { path = "arch/elf-riscv32", version = "=3.0.0" }
mold-elf-riscv32be = { path = "arch/elf-riscv32be", version = "=3.0.0" }
mold-elf-ppc64v2 = { path = "arch/elf-ppc64v2", version = "=3.0.0" }
mold-elf-ppc32 = { path = "arch/elf-ppc32", version = "=3.0.0" }
mold-elf-ppc64v1 = { path = "arch/elf-ppc64v1", version = "=3.0.0" }
mold-elf-s390x = { path = "arch/elf-s390x", version = "=3.0.0" }
mold-elf-sparc64 = { path = "arch/elf-sparc64", version = "=3.0.0" }
mold-elf-m68k = { path = "arch/elf-m68k", version = "=3.0.0" }
mold-elf-sh4 = { path = "arch/elf-sh4", version = "=3.0.0" }
mold-elf-sh4be = { path = "arch/elf-sh4be", version = "=3.0.0" }
mold-elf-loongarch64 = { path = "arch/elf-loongarch64", version = "=3.0.0" }
mold-elf-loongarch32 = { path = "arch/elf-loongarch32", version = "=3.0.0" }
mold-macho = { path = "macho", version = "=3.0.0" }
mold-macho-arm64 = { path = "arch/macho-arm64", version = "=3.0.0" }
mold-macho-x86_64 = { path = "arch/macho-x86_64", version = "=3.0.0" }
[profile.release]
panic = "abort"
# Copying generic and inline functions once per crate instead of once per
# codegen unit makes the executable about a quarter smaller.
codegen-units = 1
+145 -87
View File
@@ -1,30 +1,21 @@
# mold: A Modern Linker
mold is a faster drop-in replacement for existing Unix linkers. It is several
times quicker than the LLVM lld linker, the second-fastest open-source linker,
which I initially developed a few years ago. mold aims to enhance developer
productivity by minimizing build time, particularly in rapid
debug-edit-rebuild cycles.
mold is a high-performance drop-in replacement for existing Unix linkers,
designed to speed up builds. In our August 2026 benchmarks, it links 4.9x
faster than [LLVM lld](https://lld.llvm.org) and 1.9x faster than
[wild](https://github.com/wild-linker/wild) at the median; see
[Benchmark](#benchmark) for the full results.
Here is a performance comparison of GNU ld, GNU gold, LLVM lld, and
mold when linking final debuginfo-enabled executables for major large
programs on a simulated 16-core, 32-thread machine.
mold is written by the original developer of LLVM lld, the linker that
Android, Chrome, FreeBSD, PlayStation, Nintendo Switch, and other production
systems are built with. mold started as an effort to build an even faster
linker from scratch, free of the architectural limits its author had run into
while optimizing lld. It has been in production use since 2021, and today it
is the default linker of many large open-source projects and is used
internally by many companies.
![Link speed comparison](docs/chart.svg)
| Program (linker output size) | GNU ld | GNU gold | LLVM lld | mold
|-------------------------------|--------|----------|----------|------
| MySQL 8.3 (0.47 GiB) | 10.84s | 7.47s | 1.64s | 0.46s
| Clang 19 (1.56 GiB) | 42.07s | 33.13s | 5.20s | 1.35s
| Chromium 124 (1.35 GiB) | N/A | 27.40s | 6.10s | 1.52s
mold is so fast that it is only 2x _slower_ than the `cp` command on the same
machine. If you find that mold is not faster than other linkers, feel
free to [file a bug report](https://github.com/rui314/mold/issues).
mold supports x86-64, i386, ARM64, ARM32, 64-bit/32-bit little/big-endian
RISC-V, 32-bit PowerPC, 64-bit big-endian PowerPC ELFv1, 64-bit little-endian
PowerPC ELFv2, s390x, 64-bit/32-bit LoongArch, SPARC64, m68k, and SH-4.
mold supports x86-64, i386, ARM 32/64, RISC-V 32/64, PowerPC 32/64, s390x,
LoongArch 32/64, SPARC64, m68k, and SH-4.
## Why does linking speed matter?
@@ -38,50 +29,130 @@ speed up this process, saving you time and preventing distractions while
waiting for a lengthy build to finish. The difference is most noticeable
during rapid debug-edit-rebuild cycles.
## Benchmark
Here is a performance comparison of lld, wild, and mold when linking nine
large programs on two machines:
- AMD Ryzen Threadripper 7980X (64 cores) running Ubuntu 24.04
- Apple M1 Ultra (16 performance cores and 4 efficiency cores) running Fedora
Asahi Remix 42; the benchmark is restricted to the performance cores
The Threadripper represents many-core workstation and server processors, and
the M1 Ultra represents high-performance desktop processors.
All three linkers were built from source in release configuration as of
2026-08-28 and ran with their default options. Times are wall-clock times as
seen by the caller, the median of three runs after a warm-up. The ARM64 rows
are the ARM64 builds of the same programs. The benchmark suite, including all
linker inputs, is available on [Zenodo](https://zenodo.org/records/21882261).
**AMD Ryzen Threadripper 7980X, debug builds**
| Program (output size) | lld | wild | mold | wild/mold | lld/mold
|-----------------------------------|--------|-------|-------|-----------|---------
| Blender 5.2 (2.46 GiB) | 4.72s | 1.98s | 0.86s | 2.3x | 5.5x
| Chromium 145 (4.51 GiB) | 16.64s | 3.98s | 1.65s | 2.4x | 10.1x
| Chromium 145 ARM64 (4.76 GiB) | 20.91s | N/A | 1.83s | N/A | 11.4x
| Clang 21 (4.19 GiB) | 6.19s | 3.71s | 1.34s | 2.8x | 4.6x
| ClickHouse 26.1 (5.58 GiB) | 6.62s | 4.40s | 0.98s | 4.5x | 6.7x
| Firefox 149 (2.37 GiB) | 5.11s | N/A | 0.78s | N/A | 6.5x
| Firefox 149 ARM64 (2.43 GiB) | 6.40s | N/A | 0.93s | N/A | 6.9x
| Godot 4.6 (1.01 GiB) | 1.77s | 1.08s | 0.46s | 2.3x | 3.8x
| LibreOffice 26.2 (0.98 GiB) | 3.46s | 1.41s | 0.44s | 3.2x | 7.9x
| PyTorch 2.9 (3.51 GiB) | 4.35s | 2.48s | 0.80s | 3.1x | 5.4x
| TensorFlow 2.21 (9.55 GiB) | 50.73s | N/A | 3.15s | N/A | 16.1x
**AMD Ryzen Threadripper 7980X, release builds**
| Program (output size) | lld | wild | mold | wild/mold | lld/mold
|-----------------------------------|-------|-------|-------|-----------|---------
| Blender 5.2 (0.24 GiB) | 0.85s | 0.30s | 0.20s | 1.5x | 4.2x
| Chromium 145 (0.58 GiB) | 6.48s | N/A | 0.64s | N/A | 10.2x
| Chromium 145 ARM64 (0.60 GiB) | 7.91s | N/A | 0.73s | N/A | 10.8x
| Clang 21 (0.21 GiB) | 0.53s | 0.23s | 0.11s | 2.2x | 5.0x
| ClickHouse 26.1 (1.23 GiB) | 3.18s | 2.07s | 0.41s | 5.0x | 7.7x
| Firefox 149 (0.22 GiB) | 1.01s | 0.41s | 0.21s | 2.0x | 4.9x
| Godot 4.6 (0.15 GiB) | 0.44s | 0.21s | 0.08s | 2.7x | 5.8x
| LibreOffice 26.2 (0.19 GiB) | 1.13s | 0.57s | 0.19s | 3.0x | 6.0x
| PyTorch 2.9 (0.31 GiB) | 0.68s | 0.32s | 0.15s | 2.2x | 4.7x
| TensorFlow 2.21 (0.73 GiB) | 9.62s | N/A | 0.70s | N/A | 13.7x
**Apple M1 Ultra, debug builds**
| Program (output size) | lld | wild | mold | wild/mold | lld/mold
|-----------------------------------|--------|-------|-------|-----------|---------
| Blender 5.2 (2.46 GiB) | 3.21s | 1.81s | 1.56s | 1.2x | 2.1x
| Chromium 145 (4.51 GiB) | 9.54s | 3.49s | 2.22s | 1.6x | 4.3x
| Chromium 145 ARM64 (4.76 GiB) | 12.67s | N/A | 2.31s | N/A | 5.5x
| Clang 21 (4.19 GiB) | 4.40s | 2.78s | 2.96s | 0.9x | 1.5x
| ClickHouse 26.1 (5.58 GiB) | 4.92s | 3.50s | 1.71s | 2.1x | 2.9x
| Firefox 149 (2.37 GiB) | 3.21s | N/A | 1.12s | N/A | 2.9x
| Firefox 149 ARM64 (2.43 GiB) | 4.13s | N/A | 1.12s | N/A | 3.7x
| Godot 4.6 (1.01 GiB) | 1.12s | 0.81s | 0.62s | 1.3x | 1.8x
| LibreOffice 26.2 (0.98 GiB) | 2.08s | 0.94s | 0.63s | 1.5x | 3.3x
| PyTorch 2.9 (3.51 GiB) | 3.07s | 2.10s | 1.45s | 1.4x | 2.1x
| TensorFlow 2.21 (9.55 GiB) | 43.68s | N/A | 4.43s | N/A | 9.9x
**Apple M1 Ultra, release builds**
| Program (output size) | lld | wild | mold | wild/mold | lld/mold
|-----------------------------------|-------|-------|-------|-----------|---------
| Blender 5.2 (0.24 GiB) | 0.56s | 0.20s | 0.25s | 0.8x | 2.3x
| Chromium 145 (0.58 GiB) | 4.25s | N/A | 0.78s | N/A | 5.5x
| Chromium 145 ARM64 (0.60 GiB) | 5.17s | N/A | 0.91s | N/A | 5.7x
| Clang 21 (0.21 GiB) | 0.30s | 0.15s | 0.14s | 1.0x | 2.1x
| ClickHouse 26.1 (1.23 GiB) | 1.94s | 0.95s | 0.55s | 1.7x | 3.5x
| Firefox 149 (0.22 GiB) | 0.56s | 0.23s | 0.20s | 1.1x | 2.7x
| Godot 4.6 (0.15 GiB) | 0.26s | 0.11s | 0.11s | 1.0x | 2.3x
| LibreOffice 26.2 (0.19 GiB) | 0.62s | 0.30s | 0.23s | 1.3x | 2.7x
| PyTorch 2.9 (0.31 GiB) | 0.43s | 0.22s | 0.17s | 1.3x | 2.5x
| TensorFlow 2.21 (0.73 GiB) | 8.07s | N/A | 0.68s | N/A | 11.9x
N/A indicates that the linker cannot link that program. wild's Chromium
release links are also marked N/A because wild does not implement `--icf=all`
and links without identical code folding.
## Why is mold so fast?
mold owes its speed to pervasive parallelism and to efficient data structures
and algorithms. For details, read our paper "mold: A Massively Parallel
Linker" (ASPLOS 2027), available at https://arxiv.org/abs/2608.23228.
## Installation
Binary packages for the following systems are currently available:
[![Packaging status](https://repology.org/badge/vertical-allrepos/mold.svg)](https://repology.org/project/mold/versions)
Prebuilt binaries for Linux on x86-64, ARM64, ARM32, RISC-V, PPC64LE, s390x,
and LoongArch are also attached to each
[GitHub release](https://github.com/rui314/mold/releases).
## How to Build
mold is written in C++20, so if you build mold yourself, you will need a
recent version of a C++ compiler and a C++ standard library. We recommend GCC
10.2 or Clang 12.0.0 (or later) and libstdc++ 10 or libc++ 7 (or later).
### Install Dependencies
To install build dependencies, run `./install-build-deps.sh` in this
directory. It will detect your Linux distribution and attempt to install the
necessary packages. You may need to run it as root.
mold is written in Rust and built with Cargo. It requires Rust 1.95 or later;
if your system's Rust is older, install the stable toolchain with
[rustup](https://rustup.rs/). You also need Git and a C compiler such as GCC or
Clang, which you can install with your system's package manager.
### Compile mold
```shell
git clone --branch stable https://github.com/rui314/mold.git
git clone https://github.com/rui314/mold.git
cd mold
./install-build-deps.sh
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=c++ -B build
cmake --build build -j$(nproc)
sudo cmake --build build --target install
cargo build --release
```
You might need to pass a C++20 compiler command name to `cmake`. In the
example above, `c++` is passed. If that doesn't work for you, try a specific
version of a compiler, such as `g++-10` or `clang++-12`.
The executable is `target/release/mold`.
By default, `mold` is installed to `/usr/local/bin`. You can change the
installation location by passing `-DCMAKE_INSTALL_PREFIX=<directory>`.
For other cmake options, see the comments in `CMakeLists.txt`.
### Install mold
If you are not using a recent enough Linux distribution, or if `cmake` does
not work for you for any reason, you can use Podman to build mold in a
container. To do so, run `./dist.sh` in this directory instead of using
`cmake`. The shell script will pull a container image, build mold and auxiliary
files inside it, and package them into a single tar file named
`dist/mold-$version-$arch-linux.tar.gz`. You can extract the tar file anywhere
and use the mold executable in it.
Run `sudo ./install-mold.sh` to install mold under `/usr/local`. To install it
under a different prefix, set `PREFIX`, as in
`sudo PREFIX=/usr ./install-mold.sh`.
You can also run `target/release/mold` directly without installing it.
## How to use
@@ -103,11 +174,10 @@ to use mold instead of `/usr/bin/ld`:
valid argument, so you need to use the `-B` option instead. The `-B` option
tells GCC where to look for external commands like `ld`.
If you have installed mold with `make install`, there should be a directory
named `/usr/libexec/mold` (or `/usr/local/libexec/mold`, depending on your
`$PREFIX`), and the `ld` command should be there. The `ld` is actually a
symlink to `mold`. So, all you need is to pass `-B/usr/libexec/mold` (or
`-B/usr/local/libexec/mold`) to GCC.
If you installed mold using the script above, there is a directory named
`/usr/local/libexec/mold`, and the `ld` command there is a symlink to mold.
Pass `-B/usr/local/libexec/mold` to GCC. If you installed under another
prefix, use that prefix instead.
If you haven't installed `ld.mold` to any `$PATH`, you can still pass
`-fuse-ld=/absolute/path/to/mold` to clang to use mold. However, GCC does not
@@ -120,7 +190,7 @@ accept an absolute path as an argument for `-fuse-ld`.
Create `.cargo/config.toml` in your project directory with the following:
```toml
[target.x86_64-unknown-linux-gnu]
[target.'cfg(target_os = "linux")']
linker = "clang"
rustflags = ["-C", "link-arg=-fuse-ld=/path/to/mold"]
```
@@ -131,7 +201,7 @@ example above, we use `clang` as a linker driver since it always accepts the
may be able to remove the `linker = "clang"` line.
```toml
[target.x86_64-unknown-linux-gnu]
[target.'cfg(target_os = "linux")']
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
```
@@ -179,9 +249,10 @@ mold -run make <make-options-if-any>
```
Internally, mold invokes a given command with the `LD_PRELOAD` environment
variable set to its companion shared object file. The shared object file
variable set to a shared object file embedded in mold. The shared object file
intercepts all function calls to `exec(3)`-family functions to replace
`argv[0]` with `mold` if it is `ld`, `ld.bf`, `ld.gold`, or `ld.lld`.
`argv[0]` with `mold` if it is `ld`, `ld.bfd`, `ld.gold`, or `ld.lld`.
This feature is available on Linux and FreeBSD.
</details>
@@ -219,39 +290,26 @@ is available online. You can read the same manual by running `man mold`.
</details>
## Why is mold so fast?
## Stability
One reason is that it utilizes faster algorithms and more efficient data
structures compared to other linkers. Another reason is that mold is highly
parallelized.
Here is a side-by-side comparison of per-core CPU usage for lld (left) and
mold (right), linking the same program, a Chromium executable.
![CPU usage comparison in htop animation](docs/htop.gif)
As you can see, mold uses all available cores throughout its execution and
finishes quickly. In contrast, lld fails to utilize available cores most of
the time. In this demo, the maximum parallelism is artificially capped at 16,
so that the bars fit in the GIF.
For details, please see the [design notes](docs/design.md).
mold has been developed in the open since 2020 and has more than 140
contributors. Its test suite, which covers every linker feature, runs in CI for
all supported target CPU architectures, natively or under QEMU, and under ASAN
and TSAN. Before each release, we try to build all of Gentoo Linux's roughly
19,000 packages with mold, using GNU ld as a control, to find regressions before
they reach a release.
## Sponsors
It is taken for granted nowadays that compiler toolchains can be easily
installed and used for free, and people may not think too much about the
individuals behind these "free tools". mold supports many projects, but it
is essentially a one-person project. This situation is similar to the one
depicted in the following xkcd illustration.
mold is free to use, but keeping it maintained is continuous work: supporting
new architectures and toolchain features, keeping up with the projects that
depend on it, and making it faster. That work is funded by sponsors. If mold
saves you or your company time, please consider becoming a
[GitHub sponsor](https://github.com/sponsors/rui314).
[![xkcd 2347](https://imgs.xkcd.com/comics/dependency.png)](https://xkcd.com/2347)
If you think that the "Nebraska guy" should be rewarded, please consider
becoming our [GitHub sponsor](https://github.com/sponsors/rui314)!
We thank everyone who sponsors our project. In particular, we'd like to acknowledge
the following people and organizations who have sponsored $128/month or more:
We thank everyone who sponsors the project. In particular, we'd like to
acknowledge the following people and organizations who have sponsored
$128/month or more:
### Corporate sponsors
+13
View File
@@ -0,0 +1,13 @@
# Architecture crates
Each subdirectory is a small crate that instantiates the generic ELF or
Mach-O linker for one target architecture. Separate crates let Cargo compile
the architectures in parallel. Instantiating all targets in one crate leaves
a long serial compilation step. A directory is named after its crate without
the `mold-` prefix, e.g. `elf-x86_64` holds `mold-elf-x86_64`.
The ELF linker lives in [`elf/`](../elf/), with target-specific code in
[`elf/arch/`](../elf/arch/), and the Mach-O linker in [`macho/`](../macho/),
with target-specific code in [`macho/arm64.rs`](../macho/arm64.rs) and
[`macho/x86_64.rs`](../macho/x86_64.rs). These crates provide entry points
that the [`cli/`](../cli/) executable selects for the input files' target.
+14
View File
@@ -0,0 +1,14 @@
[package]
name = "mold-elf-arm32"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "An internal crate of the mold linker, which instantiates its ELF linker for arm32"
license.workspace = true
repository.workspace = true
[lib]
path = "lib.rs"
[dependencies]
mold-elf.workspace = true
+3
View File
@@ -0,0 +1,3 @@
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
mold_elf::driver::link::<mold_elf::arch::Arm32>(cmdline)
}
+14
View File
@@ -0,0 +1,14 @@
[package]
name = "mold-elf-arm32be"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "An internal crate of the mold linker, which instantiates its ELF linker for arm32be"
license.workspace = true
repository.workspace = true
[lib]
path = "lib.rs"
[dependencies]
mold-elf.workspace = true
+3
View File
@@ -0,0 +1,3 @@
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
mold_elf::driver::link::<mold_elf::arch::Arm32Be>(cmdline)
}
+14
View File
@@ -0,0 +1,14 @@
[package]
name = "mold-elf-arm64"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "An internal crate of the mold linker, which instantiates its ELF linker for arm64"
license.workspace = true
repository.workspace = true
[lib]
path = "lib.rs"
[dependencies]
mold-elf.workspace = true
+3
View File
@@ -0,0 +1,3 @@
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
mold_elf::driver::link::<mold_elf::arch::Arm64>(cmdline)
}
+14
View File
@@ -0,0 +1,14 @@
[package]
name = "mold-elf-arm64be"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "An internal crate of the mold linker, which instantiates its ELF linker for arm64be"
license.workspace = true
repository.workspace = true
[lib]
path = "lib.rs"
[dependencies]
mold-elf.workspace = true
+3
View File
@@ -0,0 +1,3 @@
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
mold_elf::driver::link::<mold_elf::arch::Arm64Be>(cmdline)
}
+14
View File
@@ -0,0 +1,14 @@
[package]
name = "mold-elf-i386"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "An internal crate of the mold linker, which instantiates its ELF linker for i386"
license.workspace = true
repository.workspace = true
[lib]
path = "lib.rs"
[dependencies]
mold-elf.workspace = true
+3
View File
@@ -0,0 +1,3 @@
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
mold_elf::driver::link::<mold_elf::arch::I386>(cmdline)
}
+14
View File
@@ -0,0 +1,14 @@
[package]
name = "mold-elf-loongarch32"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "An internal crate of the mold linker, which instantiates its ELF linker for loongarch32"
license.workspace = true
repository.workspace = true
[lib]
path = "lib.rs"
[dependencies]
mold-elf.workspace = true
+3
View File
@@ -0,0 +1,3 @@
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
mold_elf::driver::link::<mold_elf::arch::LoongArch32>(cmdline)
}
+14
View File
@@ -0,0 +1,14 @@
[package]
name = "mold-elf-loongarch64"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "An internal crate of the mold linker, which instantiates its ELF linker for loongarch64"
license.workspace = true
repository.workspace = true
[lib]
path = "lib.rs"
[dependencies]
mold-elf.workspace = true
+3
View File
@@ -0,0 +1,3 @@
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
mold_elf::driver::link::<mold_elf::arch::LoongArch64>(cmdline)
}
+14
View File
@@ -0,0 +1,14 @@
[package]
name = "mold-elf-m68k"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "An internal crate of the mold linker, which instantiates its ELF linker for m68k"
license.workspace = true
repository.workspace = true
[lib]
path = "lib.rs"
[dependencies]
mold-elf.workspace = true
+3
View File
@@ -0,0 +1,3 @@
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
mold_elf::driver::link::<mold_elf::arch::M68k>(cmdline)
}
+14
View File
@@ -0,0 +1,14 @@
[package]
name = "mold-elf-ppc32"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "An internal crate of the mold linker, which instantiates its ELF linker for ppc32"
license.workspace = true
repository.workspace = true
[lib]
path = "lib.rs"
[dependencies]
mold-elf.workspace = true
+3
View File
@@ -0,0 +1,3 @@
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
mold_elf::driver::link::<mold_elf::arch::Ppc32>(cmdline)
}
+14
View File
@@ -0,0 +1,14 @@
[package]
name = "mold-elf-ppc64v1"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "An internal crate of the mold linker, which instantiates its ELF linker for ppc64v1"
license.workspace = true
repository.workspace = true
[lib]
path = "lib.rs"
[dependencies]
mold-elf.workspace = true
+3
View File
@@ -0,0 +1,3 @@
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
mold_elf::driver::link::<mold_elf::arch::Ppc64V1>(cmdline)
}
+14
View File
@@ -0,0 +1,14 @@
[package]
name = "mold-elf-ppc64v2"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "An internal crate of the mold linker, which instantiates its ELF linker for ppc64v2"
license.workspace = true
repository.workspace = true
[lib]
path = "lib.rs"
[dependencies]
mold-elf.workspace = true
+3
View File
@@ -0,0 +1,3 @@
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
mold_elf::driver::link::<mold_elf::arch::Ppc64V2>(cmdline)
}
+14
View File
@@ -0,0 +1,14 @@
[package]
name = "mold-elf-riscv32"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "An internal crate of the mold linker, which instantiates its ELF linker for riscv32"
license.workspace = true
repository.workspace = true
[lib]
path = "lib.rs"
[dependencies]
mold-elf.workspace = true
+3
View File
@@ -0,0 +1,3 @@
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
mold_elf::driver::link::<mold_elf::arch::Riscv32>(cmdline)
}
+14
View File
@@ -0,0 +1,14 @@
[package]
name = "mold-elf-riscv32be"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "An internal crate of the mold linker, which instantiates its ELF linker for riscv32be"
license.workspace = true
repository.workspace = true
[lib]
path = "lib.rs"
[dependencies]
mold-elf.workspace = true
+3
View File
@@ -0,0 +1,3 @@
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
mold_elf::driver::link::<mold_elf::arch::Riscv32Be>(cmdline)
}
+14
View File
@@ -0,0 +1,14 @@
[package]
name = "mold-elf-riscv64"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "An internal crate of the mold linker, which instantiates its ELF linker for riscv64"
license.workspace = true
repository.workspace = true
[lib]
path = "lib.rs"
[dependencies]
mold-elf.workspace = true
+3
View File
@@ -0,0 +1,3 @@
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
mold_elf::driver::link::<mold_elf::arch::Riscv64>(cmdline)
}
+14
View File
@@ -0,0 +1,14 @@
[package]
name = "mold-elf-riscv64be"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "An internal crate of the mold linker, which instantiates its ELF linker for riscv64be"
license.workspace = true
repository.workspace = true
[lib]
path = "lib.rs"
[dependencies]
mold-elf.workspace = true
+3
View File
@@ -0,0 +1,3 @@
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
mold_elf::driver::link::<mold_elf::arch::Riscv64Be>(cmdline)
}
+14
View File
@@ -0,0 +1,14 @@
[package]
name = "mold-elf-s390x"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "An internal crate of the mold linker, which instantiates its ELF linker for s390x"
license.workspace = true
repository.workspace = true
[lib]
path = "lib.rs"
[dependencies]
mold-elf.workspace = true
+3
View File
@@ -0,0 +1,3 @@
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
mold_elf::driver::link::<mold_elf::arch::S390x>(cmdline)
}
+14
View File
@@ -0,0 +1,14 @@
[package]
name = "mold-elf-sh4"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "An internal crate of the mold linker, which instantiates its ELF linker for sh4"
license.workspace = true
repository.workspace = true
[lib]
path = "lib.rs"
[dependencies]
mold-elf.workspace = true
+3
View File
@@ -0,0 +1,3 @@
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
mold_elf::driver::link::<mold_elf::arch::Sh4>(cmdline)
}
+14
View File
@@ -0,0 +1,14 @@
[package]
name = "mold-elf-sh4be"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "An internal crate of the mold linker, which instantiates its ELF linker for sh4be"
license.workspace = true
repository.workspace = true
[lib]
path = "lib.rs"
[dependencies]
mold-elf.workspace = true
+3
View File
@@ -0,0 +1,3 @@
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
mold_elf::driver::link::<mold_elf::arch::Sh4Be>(cmdline)
}
+14
View File
@@ -0,0 +1,14 @@
[package]
name = "mold-elf-sparc64"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "An internal crate of the mold linker, which instantiates its ELF linker for sparc64"
license.workspace = true
repository.workspace = true
[lib]
path = "lib.rs"
[dependencies]
mold-elf.workspace = true
+3
View File
@@ -0,0 +1,3 @@
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
mold_elf::driver::link::<mold_elf::arch::Sparc64>(cmdline)
}
+14
View File
@@ -0,0 +1,14 @@
[package]
name = "mold-elf-x86_64"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "An internal crate of the mold linker, which instantiates its ELF linker for x86_64"
license.workspace = true
repository.workspace = true
[lib]
path = "lib.rs"
[dependencies]
mold-elf.workspace = true
+3
View File
@@ -0,0 +1,3 @@
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
mold_elf::driver::link::<mold_elf::arch::X86_64>(cmdline)
}
+14
View File
@@ -0,0 +1,14 @@
[package]
name = "mold-macho-arm64"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "An internal crate of the mold linker, which instantiates its Mach-O linker for arm64"
license.workspace = true
repository.workspace = true
[lib]
path = "lib.rs"
[dependencies]
mold-macho.workspace = true
+7
View File
@@ -0,0 +1,7 @@
//! The linker instantiated for arm64. Every target has a crate like this
//! one, so that the compiler can build the targets in parallel.
/// Links for this target.
pub fn link(parsed: mold_macho::driver::ParsedArgs) {
mold_macho::driver::link::<mold_macho::arch::Arm64>(parsed)
}
+14
View File
@@ -0,0 +1,14 @@
[package]
name = "mold-macho-x86_64"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "An internal crate of the mold linker, which instantiates its Mach-O linker for x86_64"
license.workspace = true
repository.workspace = true
[lib]
path = "lib.rs"
[dependencies]
mold-macho.workspace = true
+7
View File
@@ -0,0 +1,7 @@
//! The linker instantiated for x86_64. Every target has a crate like this
//! one, so that the compiler can build the targets in parallel.
/// Links for this target.
pub fn link(parsed: mold_macho::driver::ParsedArgs) {
mold_macho::driver::link::<mold_macho::arch::X86_64>(parsed)
}
+82
View File
@@ -0,0 +1,82 @@
[package]
name = "mold"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "A high-performance ELF linker, rewritten in Rust"
license.workspace = true
repository.workspace = true
readme = "../README.md"
keywords = ["linker", "elf"]
categories = ["command-line-utilities", "development-tools::build-utils"]
[[bin]]
name = "mold"
path = "main.rs"
[[test]]
name = "integration"
path = "integration.rs"
harness = false
[dependencies]
mimalloc = { package = "mold-mimalloc", version = "0.1.52" }
mold-elf.workspace = true
mold-elf-x86_64 = { workspace = true, optional = true }
mold-elf-i386 = { workspace = true, optional = true }
mold-elf-arm32 = { workspace = true, optional = true }
mold-elf-arm32be = { workspace = true, optional = true }
mold-elf-arm64 = { workspace = true, optional = true }
mold-elf-arm64be = { workspace = true, optional = true }
mold-elf-riscv64 = { workspace = true, optional = true }
mold-elf-riscv64be = { workspace = true, optional = true }
mold-elf-riscv32 = { workspace = true, optional = true }
mold-elf-riscv32be = { workspace = true, optional = true }
mold-elf-ppc64v2 = { workspace = true, optional = true }
mold-elf-ppc32 = { workspace = true, optional = true }
mold-elf-ppc64v1 = { workspace = true, optional = true }
mold-elf-s390x = { workspace = true, optional = true }
mold-elf-sparc64 = { workspace = true, optional = true }
mold-elf-m68k = { workspace = true, optional = true }
mold-elf-sh4 = { workspace = true, optional = true }
mold-elf-sh4be = { workspace = true, optional = true }
mold-elf-loongarch64 = { workspace = true, optional = true }
mold-elf-loongarch32 = { workspace = true, optional = true }
mold-macho.workspace = true
mold-macho-arm64 = { workspace = true, optional = true }
mold-macho-x86_64 = { workspace = true, optional = true }
[dev-dependencies]
mold-tests = { path = "../tests" }
# One feature per target. All are built by default; a build for one
# target alone (`--no-default-features --features x86_64`) is much faster.
[features]
default = [
"x86_64", "i386", "arm32", "arm32be", "arm64", "arm64be", "riscv64", "riscv64be", "riscv32", "riscv32be",
"ppc64v2", "ppc32", "ppc64v1", "s390x", "sparc64", "m68k", "sh4", "sh4be", "loongarch64", "loongarch32",
"macho-arm64", "macho-x86_64",
]
system-allocator = []
x86_64 = ["dep:mold-elf-x86_64"]
i386 = ["dep:mold-elf-i386"]
arm32 = ["dep:mold-elf-arm32"]
arm32be = ["dep:mold-elf-arm32be"]
arm64 = ["dep:mold-elf-arm64"]
arm64be = ["dep:mold-elf-arm64be"]
riscv64 = ["dep:mold-elf-riscv64"]
riscv64be = ["dep:mold-elf-riscv64be"]
riscv32 = ["dep:mold-elf-riscv32"]
riscv32be = ["dep:mold-elf-riscv32be"]
ppc64v2 = ["dep:mold-elf-ppc64v2"]
ppc32 = ["dep:mold-elf-ppc32"]
ppc64v1 = ["dep:mold-elf-ppc64v1"]
s390x = ["dep:mold-elf-s390x"]
sparc64 = ["dep:mold-elf-sparc64"]
m68k = ["dep:mold-elf-m68k"]
sh4 = ["dep:mold-elf-sh4"]
sh4be = ["dep:mold-elf-sh4be"]
loongarch64 = ["dep:mold-elf-loongarch64"]
loongarch32 = ["dep:mold-elf-loongarch32"]
macho-arm64 = ["dep:mold-macho-arm64"]
macho-x86_64 = ["dep:mold-macho-x86_64"]
+55
View File
@@ -0,0 +1,55 @@
//! Passes host-dependent options to the linker for the mold executable.
use std::env;
use std::ffi::OsString;
use std::io::Write;
use std::path::Path;
use std::process::{Command, Stdio};
fn main() {
println!("cargo:rerun-if-changed=build.rs");
// mimalloc uses 64-bit atomics, which C compilers implement as calls
// into libatomic on hosts without them, such as 32-bit PowerPC.
let widths = env::var("CARGO_CFG_TARGET_HAS_ATOMIC").unwrap_or_default();
if !widths.split(',').any(|w| w == "64") {
// rustc places libraries named by this crate before its dependencies
// on the linker command line, where --as-needed would drop libatomic.
// Link arguments come after all libraries.
println!("cargo:rustc-link-arg-bins=-latomic");
}
// The linker is instantiated for each target, and many functions compile
// to the same machine code for several targets. Identical Code Folding
// merges them, which roughly halves the text of a release build. GNU ld
// doesn't support it, so we pass the option only if the linker takes it.
if env::var("PROFILE").unwrap() == "release" && link_succeeds("-Wl,--icf=all") {
println!("cargo:rustc-link-arg-bins=-Wl,--icf=all");
}
}
/// Links an empty program with the given linker argument, using the same
/// compiler, linker and flags as the mold executable.
fn link_succeeds(arg: &str) -> bool {
let out_dir = env::var_os("OUT_DIR").unwrap();
let mut cmd = Command::new(env::var_os("RUSTC").unwrap());
cmd.arg("--target").arg(env::var("TARGET").unwrap());
cmd.arg("-o").arg(Path::new(&out_dir).join("link-probe"));
cmd.arg(format!("-Clink-arg={arg}"));
if let Some(linker) = env::var_os("RUSTC_LINKER") {
let mut opt = OsString::from("-Clinker=");
opt.push(linker);
cmd.arg(opt);
}
// Flags are separated by 0x1f.
let flags = env::var("CARGO_ENCODED_RUSTFLAGS").unwrap_or_default();
cmd.args(flags.split('\x1f').filter(|flag| !flag.is_empty()));
cmd.arg("-");
cmd.stdin(Stdio::piped()).stdout(Stdio::null()).stderr(Stdio::null());
let Ok(mut child) = cmd.spawn() else {
return false;
};
child.stdin.take().unwrap().write_all(b"fn main() {}").unwrap();
child.wait().is_ok_and(|status| status.success())
}
+16
View File
@@ -0,0 +1,16 @@
//! Runs the shell tests of the format the host builds programs for. The
//! Mach-O tests drive Apple's toolchain, which only macOS has, and the ELF
//! tests need a toolchain that makes ELF programs, which macOS lacks.
use std::path::Path;
use std::process::ExitCode;
fn main() -> ExitCode {
#[cfg(target_os = "macos")]
let (cases, run) = ("tests/macho", mold_tests::macho::run);
#[cfg(not(target_os = "macos"))]
let (cases, run) = ("tests/elf", mold_tests::elf::run);
let root = Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap();
run(&root.join(cases), Path::new(env!("CARGO_BIN_EXE_mold")))
}
+115
View File
@@ -0,0 +1,115 @@
//! The `mold` executable: runs the ELF linker, or the Mach-O linker if
//! invoked as `ld64.mold`, instantiated for the target the inputs are for.
//! Each target is instantiated in a crate of its own so that the compiler
//! can build them in parallel, and a feature per target decides which of
//! them are built in.
// 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 about that. The warning concerns only
// the speed of unwinding, so we keep rustc from reporting it.
#![cfg_attr(all(target_vendor = "apple", debug_assertions), allow(linker_messages))]
use mold_elf::driver::Cmdline;
use std::ffi::OsStr;
use std::path::Path;
// A Rust executable can define only one global allocator, so select mimalloc
// here rather than in the linker library.
#[cfg(not(feature = "system-allocator"))]
#[global_allocator]
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
type LinkFn = fn(Cmdline) -> Option<&'static str>;
type MachoLinkFn = fn(mold_macho::driver::ParsedArgs);
// Each target has its own monomorphized link function. The ELF linker
// starts with the first enabled target and switches to the matching
// function if the inputs are for another target. The Mach-O linker finds
// the target as it parses the options, and calls its function.
const ELF_TARGETS: &[(&str, LinkFn)] = &[
#[cfg(feature = "x86_64")]
("x86_64", mold_elf_x86_64::link),
#[cfg(feature = "i386")]
("i386", mold_elf_i386::link),
#[cfg(feature = "arm32")]
("arm32", mold_elf_arm32::link),
#[cfg(feature = "arm32be")]
("arm32be", mold_elf_arm32be::link),
#[cfg(feature = "arm64")]
("arm64", mold_elf_arm64::link),
#[cfg(feature = "arm64be")]
("arm64be", mold_elf_arm64be::link),
#[cfg(feature = "riscv64")]
("riscv64", mold_elf_riscv64::link),
#[cfg(feature = "riscv64be")]
("riscv64be", mold_elf_riscv64be::link),
#[cfg(feature = "riscv32")]
("riscv32", mold_elf_riscv32::link),
#[cfg(feature = "riscv32be")]
("riscv32be", mold_elf_riscv32be::link),
#[cfg(feature = "ppc64v2")]
("ppc64v2", mold_elf_ppc64v2::link),
#[cfg(feature = "ppc32")]
("ppc32", mold_elf_ppc32::link),
#[cfg(feature = "ppc64v1")]
("ppc64v1", mold_elf_ppc64v1::link),
#[cfg(feature = "s390x")]
("s390x", mold_elf_s390x::link),
#[cfg(feature = "sparc64")]
("sparc64", mold_elf_sparc64::link),
#[cfg(feature = "m68k")]
("m68k", mold_elf_m68k::link),
#[cfg(feature = "sh4")]
("sh4", mold_elf_sh4::link),
#[cfg(feature = "sh4be")]
("sh4be", mold_elf_sh4be::link),
#[cfg(feature = "loongarch64")]
("loongarch64", mold_elf_loongarch64::link),
#[cfg(feature = "loongarch32")]
("loongarch32", mold_elf_loongarch32::link),
];
const MACHO_TARGETS: &[(&str, MachoLinkFn)] = &[
#[cfg(feature = "macho-arm64")]
("arm64", mold_macho_arm64::link),
#[cfg(feature = "macho-x86_64")]
("x86_64", mold_macho_x86_64::link),
];
fn link_fn<F: Copy>(targets: &[(&str, F)], target: &str) -> F {
for &(name, link) in targets {
if name == target {
return link;
}
}
eprintln!(
"mold: unsupported target: {target}; rebuild mold with the appropriate target support"
);
std::process::exit(1);
}
// As with lld's ld64.lld, the name tells mold to act as Apple's linker,
// ld64. clang on macOS looks for ld64.mold when given -fuse-ld=mold.
fn is_ld64(argv0: &OsStr) -> bool {
let name = Path::new(argv0).file_name().unwrap_or_default().as_encoded_bytes();
name == b"ld64" || name.starts_with(b"ld64.")
}
fn main() {
let args: Vec<_> = std::env::args_os().collect();
if args.first().is_some_and(|argv0| is_ld64(argv0)) {
let link = |target: &str, parsed| link_fn(MACHO_TARGETS, target)(parsed);
mold_macho::driver::main(args, link);
std::process::exit(0);
}
let Some(&(initial_target, _)) = ELF_TARGETS.first() else {
eprintln!("mold: no targets enabled; rebuild mold with the appropriate target support");
std::process::exit(1);
};
let link = |target: &str, cmdline| link_fn(ELF_TARGETS, target)(cmdline);
mold_elf::driver::main(args, initial_target, link);
std::process::exit(0);
}
+27
View File
@@ -0,0 +1,27 @@
[package]
name = "mold-common"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "An internal crate of the mold linker, which holds the code its linkers share"
license.workspace = true
repository.workspace = true
[lib]
path = "lib.rs"
[dependencies]
bstr = "1"
cpp_demangle = "0.5.1"
flate2 = { version = "1", default-features = false, features = ["zlib-rs"] }
libc.workspace = true
libz-rs-sys = "0.6.8"
memchr = "2.8.3"
memmap2 = "0.9"
# std has no 64-bit atomics on hosts such as 32-bit PowerPC.
portable-atomic = "1"
rayon = "1"
rustc-demangle = "0.1"
xxhash-rust = { version = "0.8", features = ["xxh3"] }
zlib-rs = { version = "0.6.8", default-features = false }
zstd = "0.13"
+206
View File
@@ -0,0 +1,206 @@
//! This file contains functions to read an archive file (.a file).
//! An archive file is just a bundle of object files. It's similar to
//! tar or zip, but the contents are not compressed.
//!
//! An archive file is either "regular" or "thin". A regular archive
//! contains object files directly, while a thin archive contains only
//! pathnames. In the latter case, actual file contents have to be read
//! from given pathnames. A regular archive is sometimes called a "fat"
//! archive as opposed to "thin".
//!
//! If an archive file is given to the linker, the linker pulls out
//! object files that are needed to resolve undefined symbols. So,
//! bundling object files as an archive and giving that archive to the
//! linker has a different meaning than directly giving the same set of
//! object files to the linker. The former links only needed object
//! files, while the latter links all the given object files.
//!
//! Therefore, if you link libc.a for example, not all the libc
//! functions are linked to your binary. Instead, only object files
//! that provide functions and variables used in your program get
//! linked. To make this efficient, static library functions are
//! usually placed in separate object files in an archive file. You can
//! see the contents of libc.a by running `ar t
//! /usr/lib/x86_64-linux-gnu/libc.a`.
//!
//! GNU ar stores long member names in a string table member. BSD ar,
//! which macOS uses, stores a long name right after the member header
//! instead, and puts a symbol table in a member named __.SYMDEF. Both
//! forms are read here.
use std::path::{Path, PathBuf};
use crate::bytes::{cstr_at, os_str};
use crate::fatal;
use crate::mapped_file::{MappedFile, apply_chroot};
const HEADER_SIZE: usize = 60;
/// A parsed archive member header.
struct ArHeader<'a> {
name: &'a [u8],
date: u64,
size: usize,
}
impl<'a> ArHeader<'a> {
fn parse(bytes: &'a [u8]) -> Option<Self> {
let bytes = bytes.get(..HEADER_SIZE)?;
let date = parse_decimal(&bytes[16..28]) as u64;
let size = parse_decimal(&bytes[48..58]);
Some(ArHeader { name: &bytes[..16], date, size })
}
fn is_strtab(&self) -> bool {
self.name.starts_with(b"// ")
}
fn is_symtab(&self) -> bool {
self.name.starts_with(b"/ ") || self.name.starts_with(b"/SYM64/ ")
}
/// Returns the member's file name. A BSD-style long name is stored
/// right after the header, so `body` is advanced past it.
fn read_name(&self, strtab: &'a [u8], body: &mut &'a [u8]) -> &'a [u8] {
// BSD-style long filename
if let Some(rest) = self.name.strip_prefix(b"#1/") {
let len = parse_decimal(rest);
let (name, remaining) = body.split_at(len.min(body.len()));
*body = remaining;
return cstr_at(name, 0);
}
// SysV-style long filename
if let Some(rest) = self.name.strip_prefix(b"/") {
let offset = parse_decimal(rest);
let start = strtab.get(offset..).unwrap_or(&[]);
let end = memchr::memmem::find(start, b"/\n").unwrap_or(start.len());
return &start[..end];
}
// Short filename, space-padded and, in the SysV form, slash-terminated
let end = memchr::memchr(b'/', self.name).unwrap_or(self.name.len());
self.name[..end].trim_ascii_end()
}
}
/// Parses the leading decimal digits of a field, like `atoi`.
fn parse_decimal(bytes: &[u8]) -> usize {
bytes
.iter()
.skip_while(|b| b.is_ascii_whitespace())
.take_while(|b| b.is_ascii_digit())
.fold(0, |acc, &b| acc * 10 + (b - b'0') as usize)
}
/// An archive member. A thin archive's member has no contents in the
/// archive; its name is the path of the file that has them.
pub struct Member {
pub name: &'static [u8],
pub data: &'static [u8],
/// The modification time in the member header.
pub date: u64,
}
/// Iterates over the members of the archive `data`, regular or thin,
/// skipping the symbol tables and the string table. `path` is the
/// archive's name for error messages.
pub fn members<'a>(path: &'a Path, data: &'static [u8]) -> impl Iterator<Item = Member> + 'a {
let thin = data.starts_with(b"!<thin>\n");
debug_assert!(thin || data.starts_with(b"!<arch>\n"));
let mut pos = 8;
let mut strtab: &'static [u8] = &[];
std::iter::from_fn(move || {
loop {
if data.len() - pos < 2 {
return None;
}
// Each header is aligned to a 2 byte boundary.
if pos % 2 != 0 {
pos += 1;
}
let hdr = ArHeader::parse(&data[pos..])?;
let body_start = pos + HEADER_SIZE;
let body_end = (body_start + hdr.size).min(data.len());
let mut body = &data[body_start..body_end];
// Read a string table.
if hdr.is_strtab() {
strtab = body;
pos = body_end;
continue;
}
// Skip a symbol table.
if hdr.is_symtab() {
pos = body_end;
continue;
}
if thin && !hdr.name.starts_with(b"#1/") && !hdr.name.starts_with(b"/") {
fatal!("{}: filename is not stored as a long filename", path.display());
}
// Read the name field
let name = hdr.read_name(strtab, &mut body);
if thin {
// A thin archive member's contents live elsewhere; only a
// BSD-style long name occupies space after the header.
pos = body_start + (body_end - body_start - body.len());
body = &[];
} else {
pos = body_end;
}
// Skip BSD archive symbol tables (__.SYMDEF, __.SYMDEF SORTED,
// __.SYMDEF_64, ...).
if name.starts_with(b"__.SYMDEF") {
pos = body_end;
continue;
}
return Some(Member { name, data: body, date: hdr.date });
}
})
}
/// Returns the paths of the members of a thin archive, which are stored
/// outside of the archive file, without opening them.
pub fn get_thin_archive_member_paths<'a>(
chroot: &'a Path,
mf: &'static MappedFile,
) -> impl Iterator<Item = PathBuf> + 'a {
members(&mf.name, mf.data()).map(move |member| member_path(chroot, mf, member.name))
}
// An absolute member name is looked up in the --chroot directory. A relative
// one is relative to the archive, whose path is already in that directory.
fn member_path(chroot: &Path, mf: &MappedFile, name: &[u8]) -> PathBuf {
let name = Path::new(os_str(name));
if name.is_absolute() {
apply_chroot(chroot, name).into_owned()
} else {
mf.name.parent().unwrap_or(Path::new(".")).join(name)
}
}
/// Opens members as they are consumed. Parallel readers can instead schedule
/// thin-member paths on workers using get_thin_archive_member_paths().
pub fn read_archive_members<'a>(
chroot: &'a Path,
mf: &'static MappedFile,
) -> impl Iterator<Item = &'static MappedFile> + 'a {
let thin = mf.data().starts_with(b"!<thin>\n");
let base = mf.data().as_ptr() as usize;
members(&mf.name, mf.data()).map(move |member| {
if thin {
mf.open_thin_member(&member_path(chroot, mf, member.name))
} else {
let name = PathBuf::from(os_str(member.name));
mf.slice(name, member.data.as_ptr() as usize - base, member.data.len())
}
})
}
+63
View File
@@ -0,0 +1,63 @@
//! Integer and bit manipulation: alignment, bit fields and sign extension.
/// Rounds `value` up to a multiple of `align`, which must be zero or a power
/// of two. Zero means "no alignment".
#[inline]
pub fn align_to(value: u64, align: u64) -> u64 {
if align == 0 {
return value;
}
debug_assert!(align.is_power_of_two());
(value + align - 1) & !(align - 1)
}
/// Rounds `val` up to the next value congruent to `modulus` modulo
/// `align`: the smallest x >= val with x % align == modulus. ld64 places
/// every subsection this way, keeping the offset it had within its
/// section modulo the section's alignment, not merely rounding up to the
/// section's alignment.
pub fn align_to_mod(val: u64, align: u64, modulus: u64) -> u64 {
debug_assert!(align.is_power_of_two() && modulus < align);
if val <= modulus { modulus } else { align_to(val - modulus, align) + modulus }
}
/// Rounds `value` down to a multiple of `align`, which must be a power of two.
pub fn align_down(value: u64, align: u64) -> u64 {
debug_assert!(align.is_power_of_two());
value & !(align - 1)
}
/// Returns bit `pos` of `value`.
pub fn bit(value: u64, pos: u32) -> u64 {
(value >> pos) & 1
}
// Returns bits [hi:lo] of `value`.
#[inline]
pub fn bits(value: u64, hi: u32, lo: u32) -> u64 {
(value >> lo) & ((1u64 << (hi - lo + 1)) - 1)
}
// Casts `value` to a signed `n`-bit integer.
// For example, sign_extend(x, 32) == x as i32 as i64 for any x.
pub fn sign_extend(value: u64, n: u32) -> i64 {
((value << (64 - n)) as i64) >> (64 - n)
}
/// Whether `value` is representable as a signed `n`-bit integer.
pub fn is_int(value: i64, n: u32) -> bool {
sign_extend(value as u64, n) == value
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sign_extension() {
assert_eq!(sign_extend(0xff, 8), -1);
assert_eq!(sign_extend(0x7f, 8), 127);
assert!(is_int(-128, 8));
assert!(!is_int(128, 8));
}
}
+68
View File
@@ -0,0 +1,68 @@
//! Finds the git commit that mold is built from, for the linkers' version
//! strings.
use std::path::{Path, PathBuf};
use std::process::Command;
// Embed the current git commit hash into the mold executable. We ask git
// instead of parsing files under .git because the repository format varies
// (e.g. packed refs, reftable). When building from a source tarball, there's
// no .git, and the hash is simply omitted.
fn git_hash(source_dir: &Path) -> Option<String> {
let dot_git = source_dir.join(".git");
if !dot_git.exists() {
// Source archives use the package version. If Git metadata is added
// later, a clean rebuild is needed to embed the new commit hash.
return None;
}
if dot_git.is_file() {
println!("cargo:rerun-if-changed={}", dot_git.display());
}
let git_path = |name: &str| -> Option<PathBuf> {
let output = Command::new("git")
.arg("-C")
.arg(source_dir)
.args(["rev-parse", "--git-path", name])
.output()
.ok()?;
if !output.status.success() {
return None;
}
let path = PathBuf::from(String::from_utf8(output.stdout).ok()?.trim());
Some(if path.is_absolute() { path } else { source_dir.join(path) })
};
let reftable = git_path("reftable/tables.list").filter(|p| p.exists());
if let Some(head) = git_path("HEAD").filter(|p| p.exists()) {
println!("cargo:rerun-if-changed={}", head.display());
if let Ok(contents) = std::fs::read_to_string(&head)
&& let Some(reference) =
contents.strip_prefix("ref: ").map(str::trim).filter(|_| reftable.is_none())
&& let Some(path) = git_path(reference)
{
// A packed reference may acquire a loose file later.
// Watch its nearest existing directory until that happens.
if let Some(path) = path.ancestors().find(|p| p.exists()) {
println!("cargo:rerun-if-changed={}", path.display());
}
}
}
for path in git_path("packed-refs").into_iter().chain(reftable).filter(|p| p.exists()) {
println!("cargo:rerun-if-changed={}", path.display());
}
let output =
Command::new("git").arg("-C").arg(source_dir).args(["rev-parse", "HEAD"]).output().ok()?;
output.status.success().then(|| String::from_utf8_lossy(&output.stdout).trim().to_string())
}
fn main() {
println!("cargo:rerun-if-changed=build.rs");
// This crate is in common/ of the source tree.
let manifest_dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
if let Some(hash) = git_hash(manifest_dir.parent().unwrap()) {
println!("cargo:rustc-env=MOLD_GIT_HASH={hash}");
}
}
+71
View File
@@ -0,0 +1,71 @@
//! Byte strings: names that need not be UTF-8, and the text of response
//! files, linker scripts and symbol lists.
/// Whether a byte is white space as isspace() takes it in the C locale,
/// without the function call that a tokenizer would otherwise make for
/// every byte of a response file.
pub fn is_space(c: u8) -> bool {
matches!(c, b' ' | b'\t' | b'\n' | 0x0b | 0x0c | b'\r')
}
/// A byte string without the white space (see is_space) it starts and
/// ends with.
pub fn trim_space(mut bytes: &[u8]) -> &[u8] {
while let [first, rest @ ..] = bytes
&& is_space(*first)
{
bytes = rest;
}
while let [rest @ .., last] = bytes
&& is_space(*last)
{
bytes = rest;
}
bytes
}
/// The lines of a text file, as str::lines splits a string: at each
/// '\n', a '\r' before it dropped, the last line ended or not. Lists
/// of names (symbols, files) are bytes, whatever their encoding.
pub fn lines(text: &[u8]) -> impl Iterator<Item = &[u8]> {
let n = if text.is_empty() { 0 } else { usize::MAX };
let text = text.strip_suffix(b"\n").unwrap_or(text);
let lines = text.split(|&c| c == b'\n').take(n);
lines.map(|line| line.strip_suffix(b"\r").unwrap_or(line))
}
/// Splits a byte string at the first `sep`, as str::split_once does a
/// string.
pub fn split_once(bytes: &[u8], sep: u8) -> Option<(&[u8], &[u8])> {
let i = memchr::memchr(sep, bytes)?;
Some((&bytes[..i], &bytes[i + 1..]))
}
/// Writes a NUL-terminated string and returns the number of bytes written.
pub fn write_cstr(buf: &mut [u8], s: &[u8]) -> usize {
buf[..s.len()].copy_from_slice(s);
buf[s.len()] = 0;
s.len() + 1
}
/// Returns the NUL-terminated string starting at `offset` in a string table.
/// The result excludes the terminator. A missing terminator yields the rest
/// of the table.
#[inline]
pub fn cstr_at(table: &[u8], offset: usize) -> &[u8] {
let rest = table.get(offset..).unwrap_or(&[]);
let end = memchr::memchr(0, rest).unwrap_or(rest.len());
&rest[..end]
}
/// Converts bytes from a response file or linker script to an OS string.
/// Unix paths can contain arbitrary non-NUL bytes.
pub fn os_str(bytes: &[u8]) -> &std::ffi::OsStr {
use bstr::ByteSlice;
bytes.to_os_str().unwrap_or_else(|_| crate::fatal!("invalid OS string: {}", display(bytes)))
}
/// Formats a byte string for diagnostics, replacing invalid UTF-8.
pub fn display(bytes: &[u8]) -> std::borrow::Cow<'_, str> {
String::from_utf8_lossy(bytes)
}
+211
View File
@@ -0,0 +1,211 @@
//! CityHash64, as libc++'s std::hash of a string computes it: a
//! Mach-O mergeable record hashes its names with that.
//!
//! This implementation is based on CityHash v1.0.3 at
//! https://github.com/google/cityhash. libc++ differs from it in two
//! places, marked "libc++:" below.
// The license of https://github.com/google/cityhash, from its COPYING:
//
// Copyright (c) 2011 Google, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// Some primes between 2^63 and 2^64 for various uses.
const K0: u64 = 0xc3a5_c85c_97cb_3127;
const K1: u64 = 0xb492_b66f_be98_f273;
const K2: u64 = 0x9ae1_6a3b_2f90_404f;
const K3: u64 = 0xc949_d7c7_509e_6557;
fn fetch64(s: &[u8], at: usize) -> u64 {
u64::from_le_bytes(s[at..at + 8].try_into().unwrap())
}
fn fetch32(s: &[u8], at: usize) -> u32 {
u32::from_le_bytes(s[at..at + 4].try_into().unwrap())
}
fn shift_mix(val: u64) -> u64 {
val ^ (val >> 47)
}
/// Hash128to64() of the 128-bit value with `u` as its low half.
fn hash_len_16(u: u64, v: u64) -> u64 {
// Murmur-inspired hashing.
const MUL: u64 = 0x9ddf_ea08_eb38_2d69;
let mut a = (u ^ v).wrapping_mul(MUL);
a ^= a >> 47;
let mut b = (v ^ a).wrapping_mul(MUL);
b ^= b >> 47;
b.wrapping_mul(MUL)
}
fn hash_len_0_to_16(s: &[u8]) -> u64 {
let len = s.len();
if len > 8 {
let a = fetch64(s, 0);
let b = fetch64(s, len - 8);
return hash_len_16(a, b.wrapping_add(len as u64).rotate_right(len as u32)) ^ b;
}
if len >= 4 {
// libc++: `a << 3` is in 32 bits, not 64.
let a = fetch32(s, 0);
return hash_len_16((len as u64).wrapping_add((a << 3) as u64), fetch32(s, len - 4) as u64);
}
if len > 0 {
let (a, b, c) = (s[0] as u32, s[len >> 1] as u32, s[len - 1] as u32);
let y = a.wrapping_add(b << 8);
let z = (len as u32).wrapping_add(c << 2);
return shift_mix((y as u64).wrapping_mul(K2) ^ (z as u64).wrapping_mul(K3))
.wrapping_mul(K2);
}
K2
}
// This probably works well for 16-byte strings as well, but it may be overkill
// in that case.
fn hash_len_17_to_32(s: &[u8]) -> u64 {
let len = s.len();
let a = fetch64(s, 0).wrapping_mul(K1);
let b = fetch64(s, 8);
let c = fetch64(s, len - 8).wrapping_mul(K2);
let d = fetch64(s, len - 16).wrapping_mul(K0);
hash_len_16(
a.wrapping_sub(b).rotate_right(43).wrapping_add(c.rotate_right(30)).wrapping_add(d),
a.wrapping_add((b ^ K3).rotate_right(20)).wrapping_sub(c).wrapping_add(len as u64),
)
}
/// A 16-byte hash for s[0] ... s[31], `a` and `b`. Quick and dirty.
/// Callers do best to use "random-looking" values for `a` and `b`.
fn weak_hash_len_32_with_seeds(s: &[u8], a: u64, b: u64) -> (u64, u64) {
let (w, x, y, z) = (fetch64(s, 0), fetch64(s, 8), fetch64(s, 16), fetch64(s, 24));
let mut a = a.wrapping_add(w);
let mut b = b.wrapping_add(a).wrapping_add(z).rotate_right(21);
let c = a;
a = a.wrapping_add(x).wrapping_add(y);
b = b.wrapping_add(a.rotate_right(44));
(a.wrapping_add(z), b.wrapping_add(c))
}
/// An 8-byte hash for 33 to 64 bytes.
fn hash_len_33_to_64(s: &[u8]) -> u64 {
let len = s.len();
let mut z = fetch64(s, 24);
let mut a = fetch64(s, 0)
.wrapping_add((len as u64).wrapping_add(fetch64(s, len - 16)).wrapping_mul(K0));
let mut b = a.wrapping_add(z).rotate_right(52);
let mut c = a.rotate_right(37);
a = a.wrapping_add(fetch64(s, 8));
c = c.wrapping_add(a.rotate_right(7));
a = a.wrapping_add(fetch64(s, 16));
let vf = a.wrapping_add(z);
let vs = b.wrapping_add(a.rotate_right(31)).wrapping_add(c);
a = fetch64(s, 16).wrapping_add(fetch64(s, len - 32));
// libc++: z is added to, not set.
z = z.wrapping_add(fetch64(s, len - 8));
b = a.wrapping_add(z).rotate_right(52);
c = a.rotate_right(37);
a = a.wrapping_add(fetch64(s, len - 24));
c = c.wrapping_add(a.rotate_right(7));
a = a.wrapping_add(fetch64(s, len - 16));
let wf = a.wrapping_add(z);
let ws = b.wrapping_add(a.rotate_right(31)).wrapping_add(c);
let r = shift_mix(
vf.wrapping_add(ws).wrapping_mul(K2).wrapping_add(wf.wrapping_add(vs).wrapping_mul(K0)),
);
shift_mix(r.wrapping_mul(K0).wrapping_add(vs)).wrapping_mul(K2)
}
pub fn hash(s: &[u8]) -> u64 {
let len = s.len();
if len <= 16 {
return hash_len_0_to_16(s);
}
if len <= 32 {
return hash_len_17_to_32(s);
}
if len <= 64 {
return hash_len_33_to_64(s);
}
// For strings over 64 bytes we hash the end first, and then as we
// loop we keep 56 bytes of state: v, w, x, y, and z.
let n = len as u64;
let mut x = fetch64(s, len - 40);
let mut y = fetch64(s, len - 16).wrapping_add(fetch64(s, len - 56));
let mut z = hash_len_16(fetch64(s, len - 48).wrapping_add(n), fetch64(s, len - 24));
let mut v = weak_hash_len_32_with_seeds(&s[len - 64..], n, z);
let mut w = weak_hash_len_32_with_seeds(&s[len - 32..], y.wrapping_add(K1), x);
x = x.wrapping_mul(K1).wrapping_add(fetch64(s, 0));
// Decrease len to the nearest multiple of 64, and operate on 64-byte chunks.
for c in s[..(len - 1) & !63].as_chunks::<64>().0 {
x = (x.wrapping_add(y).wrapping_add(v.0).wrapping_add(fetch64(c, 8)))
.rotate_right(37)
.wrapping_mul(K1);
y = (y.wrapping_add(v.1).wrapping_add(fetch64(c, 48))).rotate_right(42).wrapping_mul(K1);
x ^= w.1;
y = y.wrapping_add(v.0).wrapping_add(fetch64(c, 40));
z = z.wrapping_add(w.0).rotate_right(33).wrapping_mul(K1);
v = weak_hash_len_32_with_seeds(c, v.1.wrapping_mul(K1), x.wrapping_add(w.0));
w = weak_hash_len_32_with_seeds(
&c[32..],
z.wrapping_add(w.1),
y.wrapping_add(fetch64(c, 16)),
);
std::mem::swap(&mut z, &mut x);
}
hash_len_16(
hash_len_16(v.0, w.0).wrapping_add(shift_mix(y).wrapping_mul(K1)).wrapping_add(z),
hash_len_16(v.1, w.1).wrapping_add(x),
)
}
#[cfg(test)]
mod tests {
use super::*;
// std::hash<std::string_view> from Xcode's libc++, of each length's
// prefix of `data`. Lengths 4 to 8 and 33 to 64 are where libc++
// differs from CityHash v1.0.3.
#[test]
fn matches_libcxx() {
let data: Vec<u8> =
(0..400).map(|i: usize| (i * 131 + (i >> 3) * 7 + 0x5a) as u8).collect();
for (len, expected) in [
(0, 0x9ae1_6a3b_2f90_404f),
(3, 0x51d1_83ca_b178_69e7),
(5, 0x06ab_c9b6_c811_7ca8),
(8, 0xed1f_6592_0960_3ce9),
(12, 0x48b7_91b0_fe9b_e040),
(17, 0x027a_8eab_ea05_2a3f),
(32, 0x1c21_a4ef_fa7b_3f54),
(33, 0x6583_3651_60f0_a2e0),
(50, 0x1e81_10d2_800d_5748),
(64, 0xd021_ebba_7723_3653),
(65, 0x73c3_2c4f_c042_52a2),
(128, 0xe173_4283_984b_218f),
(129, 0xd017_1b7c_8ed1_4630),
(400, 0x3683_4df1_0afa_73c8),
] {
assert_eq!(hash(&data[..len]), expected, "len={len}");
}
}
}
+258
View File
@@ -0,0 +1,258 @@
//! This file implements a multi-threaded zlib and zstd compression
//! routine.
//!
//! zlib-compressed data can be merged just by concatenation as long as
//! each piece of data is flushed with Z_SYNC_FLUSH. In this file, we
//! split input data into multiple shards, compress them individually
//! and concatenate them. We then append a header, a trailer and a
//! checksum so that the concatenated data is valid zlib-format data.
//!
//! zstd-compressed data can be merged in the same way.
//!
//! Using threads to compress data has a downside. Since the dictionary
//! is reset at shard boundaries, the compression ratio is sacrificed a
//! little bit. However, if the shard size is large enough, that loss is
//! negligible in practice.
use std::io::{ErrorKind, Read};
use flate2::FlushDecompress;
use rayon::prelude::*;
use zlib_rs::adler32::{adler32, adler32_combine};
use crate::worker_local::WorkerLocal;
const SHARD_SIZE: usize = 1024 * 1024;
/// Compresses data with zlib or zstd at a fixed level, using all threads.
pub struct Compressor(Method);
enum Method {
Zlib(i32),
Zstd(i32, ZstdContexts),
}
// Creating a zstd context costs nearly as much as compressing a shard at
// higher levels, so each Rayon worker reuses one for all shards.
type ZstdContexts = WorkerLocal<Option<zstd::bulk::Compressor<'static>>>;
/// Compressed data, kept as shards until it is written to the output.
pub enum CompressedData {
Zlib { shards: Vec<Vec<u8>>, checksum: u32 },
Zstd { shards: Vec<Vec<u8>> },
}
impl std::fmt::Debug for CompressedData {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "CompressedData({} bytes)", self.compressed_size())
}
}
/// Compresses a shard as a raw deflate stream ending with a sync flush,
/// so that the stream ends on a byte boundary and can be concatenated.
fn zlib_compress_shard(input: &[u8], level: i32) -> Vec<u8> {
// Initialize a zlib stream. Since debug info is generally compressed
// pretty well with lower compression levels, the default level is 1.
let mut stream = libz_rs_sys::z_stream::default();
// SAFETY: stream is a valid z_stream with the default allocator. It
// stays at the same address until deflateEnd.
let status = unsafe {
libz_rs_sys::deflateInit2_(
&mut stream,
level,
libz_rs_sys::Z_DEFLATED,
-15,
8,
libz_rs_sys::Z_DEFAULT_STRATEGY,
libz_rs_sys::zlibVersion(),
size_of::<libz_rs_sys::z_stream>() as i32,
)
};
assert_eq!(status, libz_rs_sys::Z_OK);
// Set an input buffer
stream.avail_in = input.len() as u32;
stream.next_in = input.as_ptr();
// Set an output buffer. deflateBound() returns an upper bound
// on the compression size. +16 for Z_SYNC_FLUSH.
// SAFETY: stream is initialized and remains valid through deflateEnd.
let bound = unsafe { libz_rs_sys::deflateBound(&mut stream, stream.avail_in.into()) } as usize;
let mut out = vec![0; bound + 16];
// Compress data. It writes all compressed bytes except the last
// partial byte, so up to 7 bits may be left pending without being
// written to the buffer.
stream.avail_out = out.len() as u32;
stream.next_out = out.as_mut_ptr();
// SAFETY: the input and output buffers remain alive and the output has
// deflateBound() + 16 bytes of space.
let status = unsafe { libz_rs_sys::deflate(&mut stream, libz_rs_sys::Z_BLOCK) };
assert_eq!(status, libz_rs_sys::Z_OK);
// This is a workaround for libbacktrace before 2022-04-06.
//
// Zlib is a bit stream, and what Z_SYNC_FLUSH does is to write a
// three-bit value indicating the start of an uncompressed data
// block followed by four bytes 00 00 ff ff which indicate that
// the length of the block is zero. libbacktrace uses its own zlib
// inflate routine, and it had a bug where, if that particular
// three-bit value happened to end at a byte boundary, it accidentally
// skipped the next byte.
//
// In order to avoid triggering that bug, we should avoid calling
// deflate() with Z_SYNC_FLUSH if the current bit position is 5.
// If it's 5, we insert an empty block consisting of 10 bits so
// that the bit position is 7 in the next byte.
//
// https://github.com/ianlancetaylor/libbacktrace/pull/87
let mut nbits = 0;
// SAFETY: stream is initialized and nbits is a valid output pointer.
let status =
unsafe { libz_rs_sys::deflatePending(&mut stream, std::ptr::null_mut(), &raw mut nbits) };
assert_eq!(status, libz_rs_sys::Z_OK);
if nbits == 5 {
// SAFETY: stream is initialized and has enough pending-buffer space.
let status = unsafe { libz_rs_sys::deflatePrime(&mut stream, 10, 2) };
assert_eq!(status, libz_rs_sys::Z_OK);
}
// SAFETY: stream and its input and output buffers remain valid.
let status = unsafe { libz_rs_sys::deflate(&mut stream, libz_rs_sys::Z_SYNC_FLUSH) };
assert_eq!(status, libz_rs_sys::Z_OK);
let len = out.len() - stream.avail_out as usize;
// SAFETY: stream was initialized successfully and is no longer used.
unsafe { libz_rs_sys::deflateEnd(&mut stream) };
out.truncate(len);
out
}
impl Compressor {
pub fn zlib(level: i32) -> Self {
Self(Method::Zlib(level))
}
pub fn zstd(level: i32) -> Self {
Self(Method::Zstd(level, WorkerLocal::new(|| None)))
}
pub fn compress(&self, input: &[u8]) -> CompressedData {
match &self.0 {
Method::Zlib(level) => zlib_compress(input, *level),
Method::Zstd(level, contexts) => zstd_compress(input, *level, contexts),
}
}
}
fn zlib_compress(input: &[u8], level: i32) -> CompressedData {
// Compress each shard
let (shards, adlers): (Vec<Vec<u8>>, Vec<u32>) = input
.par_chunks(SHARD_SIZE)
.map(|shard| (zlib_compress_shard(shard, level), adler32(1, shard)))
.unzip();
// Combine checksums
let mut checksum = adlers.first().copied().unwrap_or(1);
for (adler, shard) in adlers.iter().zip(input.chunks(SHARD_SIZE)).skip(1) {
checksum = adler32_combine(checksum, *adler, shard.len() as u64);
}
CompressedData::Zlib { shards, checksum }
}
fn zstd_compress(input: &[u8], level: i32, contexts: &ZstdContexts) -> CompressedData {
// Compress each shard
let shards = input
.par_chunks(SHARD_SIZE)
.map(|shard| {
let mut slot = contexts.get();
let cctx = slot.get_or_insert_with(|| {
zstd::bulk::Compressor::new(level).expect("cannot create a zstd context")
});
cctx.compress(shard).expect("zstd compression failed")
})
.collect();
CompressedData::Zstd { shards }
}
impl CompressedData {
pub fn compressed_size(&self) -> usize {
// Compute the total size
match self {
// the header and the trailer
Self::Zlib { shards, .. } => 8 + shards.iter().map(Vec::len).sum::<usize>(),
Self::Zstd { shards } => shards.iter().map(Vec::len).sum(),
}
}
pub fn write_to(&self, buf: &mut [u8]) {
match self {
Self::Zlib { shards, checksum } => {
// Write a zlib-format header
buf[0] = 0x78;
buf[1] = 0x9c;
// Copy compressed data
// +2 for the header
let mut pos = 2;
for shard in shards {
buf[pos..pos + shard.len()].copy_from_slice(shard);
pos += shard.len();
}
// Write a trailer
// An empty final block, then the Adler-32 checksum.
buf[pos] = 3;
buf[pos + 1] = 0;
buf[pos + 2..pos + 6].copy_from_slice(&checksum.to_be_bytes());
}
Self::Zstd { shards } => {
// Copy compressed data
let mut pos = 0;
for shard in shards {
buf[pos..pos + shard.len()].copy_from_slice(shard);
pos += shard.len();
}
}
}
}
}
/// Decompresses a zlib stream into a buffer of known size.
pub fn zlib_decompress(input: &[u8], out: &mut [u8]) -> Result<(), String> {
let mut decompress = flate2::Decompress::new(true);
loop {
let in_pos = decompress.total_in() as usize;
let out_pos = decompress.total_out() as usize;
if out_pos >= out.len() {
return Ok(());
}
let status = decompress
.decompress(
&input[in_pos.min(input.len())..],
&mut out[out_pos..],
FlushDecompress::None,
)
.map_err(|e| e.to_string())?;
match status {
flate2::Status::StreamEnd => break,
flate2::Status::BufError if decompress.total_in() as usize >= input.len() => break,
_ => {}
}
}
if (decompress.total_out() as usize) < out.len() {
return Err("premature end of input".to_string());
}
Ok(())
}
/// Decompresses a zstd stream, stopping when the requested output is full.
/// DWARF classification uses this to read just the unit header.
pub fn zstd_decompress(input: &[u8], out: &mut [u8]) -> Result<(), String> {
let mut decoder = zstd::stream::read::Decoder::with_buffer(input).map_err(|e| e.to_string())?;
decoder.read_exact(out).map_err(|e| {
if e.kind() == ErrorKind::UnexpectedEof {
"premature end of input".to_string()
} else {
e.to_string()
}
})
}
+475
View File
@@ -0,0 +1,475 @@
//! A fast concurrent hash map.
//!
//! Concurrent Map
//!
//! This is an implementation of a fast concurrent hash map. Unlike
//! ordinary hash tables, this impl just aborts if it becomes full.
//! So you need to give a correct estimate of the final size before
//! using it. We use this hash map to uniquify pieces of data in
//! mergeable sections.
//!
//! We've implemented this ourselves because the performance of
//! a concurrent hash map is critical for our linker.
//!
//! The map is an open-addressing table. Insertion is lock-free: a thread
//! claims an empty bucket with a compare-and-swap on its key pointer,
//! initializes the value, and then publishes the key; a thread that finds
//! a claimed bucket spins until the key appears. Probing stays within a
//! shard of the table, so the entries of a shard can be laid out
//! independently of the others. Keys are byte strings that live for the
//! whole link, such as string literals inside input files, hashed once by
//! the caller.
use rayon::prelude::*;
#[cfg(windows)]
use std::alloc::{Layout, alloc_zeroed, dealloc};
use std::cell::UnsafeCell;
use std::mem::MaybeUninit;
use std::ptr;
use std::sync::atomic::{AtomicPtr, AtomicU32, Ordering};
pub const NUM_SHARDS: usize = 64;
// MIN_NBUCKETS is chosen so that even the smallest map has
// MAX_RETRY buckets per shard; probing is confined to a shard, and
// a probe that visits MAX_RETRY distinct occupied slots aborts.
const MIN_NBUCKETS: usize = 16384;
const MAX_RETRY: usize = 256;
const _: () = assert!(MIN_NBUCKETS / NUM_SHARDS >= MAX_RETRY);
/// The key pointer of a bucket that a thread has claimed but not yet
/// published: -1 marks the slot as claimed until its value has been
/// initialized.
const CLAIMED: *mut u8 = usize::MAX as *mut u8;
// In order to avoid unnecessary cache-line false sharing, we want
// to make this object aligned to a reasonably large
// power-of-two address.
#[repr(C, align(32))]
struct Entry<T> {
key: AtomicPtr<u8>,
// Published by the release store of `key` and read after the acquire
// load that observes it, so relaxed accesses suffice.
keylen: AtomicU32,
value: UnsafeCell<MaybeUninit<T>>,
}
#[cfg(not(windows))]
fn allocate_entries<T>(bufsize: usize) -> *mut Entry<T> {
// SAFETY: this creates fresh private anonymous storage.
let entries = unsafe {
libc::mmap(
ptr::null_mut(),
bufsize,
libc::PROT_READ | libc::PROT_WRITE,
libc::MAP_ANONYMOUS | libc::MAP_PRIVATE,
-1,
0,
)
};
if entries == libc::MAP_FAILED {
let err = std::io::Error::last_os_error();
panic!("mmap of {bufsize} bytes failed: {}", crate::error::strerror(&err));
}
entries.cast()
}
#[cfg(windows)]
fn allocate_entries<T>(bufsize: usize) -> *mut Entry<T> {
let layout = Layout::from_size_align(bufsize, align_of::<Entry<T>>())
.expect("invalid concurrent-map layout");
// SAFETY: the layout has nonzero size.
let entries = unsafe { alloc_zeroed(layout).cast::<Entry<T>>() };
if entries.is_null() {
panic!("cannot allocate {bufsize} bytes for concurrent map");
}
entries
}
#[cfg(not(windows))]
unsafe fn deallocate_entries<T>(entries: *mut Entry<T>, bufsize: usize) {
// SAFETY: `entries` was mapped by `allocate_entries` with this size.
let _ = unsafe { libc::munmap(entries.cast(), bufsize) };
}
#[cfg(windows)]
unsafe fn deallocate_entries<T>(entries: *mut Entry<T>, bufsize: usize) {
let layout = Layout::from_size_align(bufsize, align_of::<Entry<T>>())
.expect("invalid concurrent-map layout");
// SAFETY: `entries` was allocated by `allocate_entries` with this layout.
unsafe { dealloc(entries.cast(), layout) };
}
/// The index of a bucket, which identifies an entry for the life of
/// the map.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct EntryId(u32);
impl EntryId {
pub fn raw(self) -> u32 {
self.0
}
#[inline]
pub fn from_raw(raw: u32) -> Self {
Self(raw)
}
}
/// A map under construction. Once concurrent insertion is complete, freeze
/// it to look up and traverse its entries.
pub struct ConcurrentMap<T> {
entries: *mut Entry<T>,
nbuckets: usize,
}
// SAFETY: entries are shared between threads only through atomics and
// values that are initialized before they become reachable.
unsafe impl<T: Send + Sync> Send for ConcurrentMap<T> {}
unsafe impl<T: Send + Sync> Sync for ConcurrentMap<T> {}
impl<T> Default for ConcurrentMap<T> {
/// A map without buckets, to be replaced before use.
fn default() -> Self {
Self { entries: ptr::null_mut(), nbuckets: 0 }
}
}
impl<T> ConcurrentMap<T> {
/// A map with room for about `nkeys` distinct keys.
pub fn with_capacity(nkeys: usize) -> Self {
let nbuckets = nkeys.next_power_of_two().max(MIN_NBUCKETS);
let bufsize = Self::bufsize(nbuckets);
// mmap is faster than malloc + memset on Unix; the platform allocator
// returns equivalently zeroed storage on Windows.
let entries = allocate_entries(bufsize);
// SAFETY: the range is the fresh allocation; the advice is only a
// hint on targets that support it.
unsafe { crate::mem::madvise_hugepage(entries.cast(), bufsize) };
Self { entries, nbuckets }
}
fn bufsize(nbuckets: usize) -> usize {
size_of::<Entry<T>>().checked_mul(nbuckets).expect("table size overflow")
}
/// Counts the number of entries.
fn len(&self) -> usize {
(0..self.nbuckets).filter(|&idx| self.is_occupied(idx)).count()
}
fn entry(&self, idx: usize) -> &Entry<T> {
debug_assert!(idx < self.nbuckets);
// SAFETY: idx is within the allocation.
unsafe { &*self.entries.add(idx) }
}
fn is_occupied(&self, idx: usize) -> bool {
let key = self.entry(idx).key.load(Ordering::Acquire);
!key.is_null() && key != CLAIMED
}
fn value_at(&self, idx: usize) -> &T {
// SAFETY: callers only ask for values of published entries, which
// were initialized before publication.
unsafe { (*self.entry(idx).value.get()).assume_init_ref() }
}
/// The probe sequence for a hash: the buckets of one shard, starting
/// from the hash's home bucket and wrapping around within the shard.
fn probe(&self, hash: u64) -> impl Iterator<Item = usize> {
let begin = hash as usize & (self.nbuckets - 1);
let mask = self.nbuckets / NUM_SHARDS - 1;
(0..MAX_RETRY).map(move |i| (begin & !mask) | ((begin + i) & mask))
}
/// Inserts `key` unless it is present, initializing the value with
/// `init`. Returns the entry, its value and whether it was inserted.
pub fn insert_with(
&self,
key: &'static [u8],
hash: u64,
init: impl FnOnce() -> T,
) -> (EntryId, &T, bool) {
self.insert_entry(
key.as_ptr(),
hash,
|ptr, len| {
// SAFETY: published keys remain live for the link.
unsafe { std::slice::from_raw_parts(ptr, len as usize) == key }
},
|| (u32::try_from(key.len()).expect("key length fits in 32 bits"), init()),
)
}
/// Inserts a NUL-terminated key without storing its length at the call
/// site. `initialize` computes the length and value only if this call
/// claims a new entry.
///
/// # Safety
///
/// `key` must point to a NUL-terminated byte string that remains live for
/// the lifetime of the map. Its length must fit in u32.
pub unsafe fn insert_cstr_with(
&self,
key: *const u8,
hash: u64,
initialize: impl FnOnce(*const u8) -> (u32, T),
) -> (EntryId, &T, bool) {
// This variant avoids storing a length alongside every caller-side key.
// Only a newly inserted key needs its length computed by `initialize`.
self.insert_entry(
key,
hash,
|ptr, _| {
// SAFETY: guaranteed for `key` by the caller and for `ptr` by
// the entry publication invariant.
unsafe { libc::strcmp(key.cast(), ptr.cast()) == 0 }
},
|| initialize(key),
)
}
fn insert_entry(
&self,
key: *const u8,
hash: u64,
equals: impl Fn(*const u8, u32) -> bool,
initialize: impl FnOnce() -> (u32, T),
) -> (EntryId, &T, bool) {
assert!(self.nbuckets > 0, "the map hasn't been sized");
let mut initialize = Some(initialize);
for idx in self.probe(hash) {
let ent = self.entry(idx);
// Avoid an atomic update when the slot is already occupied.
let mut ptr = ent.key.load(Ordering::Acquire);
if !ptr.is_null() && ptr != CLAIMED {
if equals(ptr, ent.keylen.load(Ordering::Relaxed)) {
return (EntryId(idx as u32), self.value_at(idx), false);
}
continue;
}
if ptr.is_null() {
match ent.key.compare_exchange(
ptr::null_mut(),
CLAIMED,
Ordering::Acquire,
Ordering::Acquire,
) {
Ok(_) => {
let (keylen, value) = initialize.take().expect("initialized once")();
// SAFETY: the claim gives this thread exclusive
// access to the bucket until the key is published.
unsafe { (*ent.value.get()).write(value) };
ent.keylen.store(keylen, Ordering::Relaxed);
ent.key.store(key.cast_mut(), Ordering::Release);
return (EntryId(idx as u32), self.value_at(idx), true);
}
Err(current) => ptr = current,
}
}
// Wait for the thread that claimed the slot to publish its key.
while ptr == CLAIMED {
std::hint::spin_loop();
ptr = ent.key.load(Ordering::Acquire);
}
if equals(ptr, ent.keylen.load(Ordering::Relaxed)) {
return (EntryId(idx as u32), self.value_at(idx), false);
}
}
panic!("the concurrent map is full");
}
// Prefetch the bucket where a key with the given hash would be
// probed first. Useful when a caller knows the hashes of upcoming
// insertions, as probes into a large table miss the cache almost
// every time.
#[inline]
pub fn prefetch(&self, hash: u64) {
if self.nbuckets > 0 {
let idx = hash as usize & (self.nbuckets - 1);
// SAFETY: the masked index is within the allocated table.
let ptr = unsafe { self.entries.add(idx) };
crate::prefetch(ptr.cast());
}
}
/// Finishes insertion and returns the map for lookup and traversal.
pub fn freeze(self) -> FrozenMap<T> {
FrozenMap(self)
}
}
impl<T> Drop for ConcurrentMap<T> {
fn drop(&mut self) {
if self.entries.is_null() {
return;
}
if std::mem::needs_drop::<T>() {
for idx in 0..self.nbuckets {
if self.is_occupied(idx) {
// SAFETY: the value of a published entry is initialized
// and dropped exactly once, here.
unsafe { (*self.entry(idx).value.get()).assume_init_drop() };
}
}
}
// SAFETY: allocated in with_capacity and not yet released.
unsafe { deallocate_entries(self.entries, Self::bufsize(self.nbuckets)) };
}
}
impl<T> std::fmt::Debug for ConcurrentMap<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "ConcurrentMap({} entries)", self.len())
}
}
/// A map after all insertions, for lookup and traversal. Values are
/// reachable only through shared references.
#[derive(Debug)]
pub struct FrozenMap<T>(ConcurrentMap<T>);
impl<T> Default for FrozenMap<T> {
fn default() -> Self {
Self(ConcurrentMap::default())
}
}
impl<T> FrozenMap<T> {
/// The published key of a bucket, if any.
fn key_at(&self, idx: usize) -> Option<&'static [u8]> {
let ent = self.0.entry(idx);
let key = ent.key.load(Ordering::Acquire);
if key.is_null() || key == CLAIMED {
return None;
}
// SAFETY: a published key is a live 'static slice whose length was
// written before the key pointer.
Some(unsafe {
std::slice::from_raw_parts(key, ent.keylen.load(Ordering::Relaxed) as usize)
})
}
/// Returns a published entry identified by an ID obtained from this map.
/// Linker callers only retain IDs returned by insertion or traversal of
/// the owning map; they never probe a missing key or pass another map's
/// ID here. That invariant makes the unchecked, initialized-value lookup
/// below valid without a second occupancy check on this hot path.
pub fn get(&self, id: EntryId) -> &T {
self.0.value_at(id.0 as usize)
}
#[inline]
pub fn prefetch(&self, id: EntryId) {
crate::prefetch(std::ptr::from_ref(self.0.entry(id.0 as usize)).cast());
}
pub fn key(&self, id: EntryId) -> &'static [u8] {
self.key_at(id.0 as usize).expect("an occupied bucket")
}
/// Returns the entries in the given shard in a deterministic order.
///
/// Linear probing fills the same set of buckets whatever the order
/// keys were inserted in, but which of two colliding keys got the
/// earlier bucket depends on it, so each run of adjacent occupied
/// buckets is sorted by key.
pub fn sorted_entries(&self, shard: usize) -> Vec<EntryId> {
if self.0.nbuckets == 0 {
return Vec::new();
}
let shard_size = self.0.nbuckets / NUM_SHARDS;
let begin = shard * shard_size;
let mut end = begin + shard_size;
let occupied = |idx: usize| self.0.is_occupied(idx);
let size = (begin..end).filter(|&idx| occupied(idx)).count();
let mut vec: Vec<EntryId> = Vec::with_capacity(size);
// Since the shard is circular, we need to handle the last entries
// as if they were next to the first entries.
while begin < end && occupied(end - 1) {
end -= 1;
vec.push(EntryId(end as u32));
}
let sort_run = |run: &mut [EntryId]| {
run.sort_unstable_by(|&a, &b| {
let (ka, kb) = (self.key(a), self.key(b));
ka.len().cmp(&kb.len()).then_with(|| ka.cmp(kb))
});
};
// Find entries contiguous in the buckets and sort them.
let mut last = 0;
let mut i = begin;
while i < end {
while i < end && occupied(i) {
vec.push(EntryId(i as u32));
i += 1;
}
sort_run(&mut vec[last..]);
last = vec.len();
while i < end && !occupied(i) {
i += 1;
}
}
vec
}
/// Returns all map entries in deterministic order.
pub fn sorted_entries_all(&self) -> Vec<EntryId>
where
T: Send + Sync,
{
(0..NUM_SHARDS).into_par_iter().flat_map_iter(|shard| self.sorted_entries(shard)).collect()
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn insert_and_lookup() {
let map: ConcurrentMap<u32> = ConcurrentMap::with_capacity(100);
let keys: Vec<&'static [u8]> = (0..1000)
.map(|i| &*Box::leak(format!("key{i}").into_bytes().into_boxed_slice()))
.collect();
let hash = |k: &[u8]| xxhash_rust::xxh3::xxh3_64(k);
let mut ids = Vec::with_capacity(keys.len());
for (i, &k) in keys.iter().enumerate() {
let (id, v, inserted) = map.insert_with(k, hash(k), || i as u32);
assert!(inserted);
assert_eq!(*v, i as u32);
ids.push(id);
}
for (i, &k) in keys.iter().enumerate() {
let (id, v, inserted) = map.insert_with(k, hash(k), || 99999);
assert!(!inserted);
assert_eq!(id, ids[i]);
assert_eq!(*v, i as u32);
}
let map = map.freeze();
for (i, &id) in ids.iter().enumerate() {
assert_eq!(map.key(id), keys[i]);
assert_eq!(*map.get(id), i as u32);
}
assert_eq!(map.len(), 1000);
let sorted: usize = (0..NUM_SHARDS).map(|s| map.sorted_entries(s).len()).sum();
assert_eq!(sorted, 1000);
}
}
+66
View File
@@ -0,0 +1,66 @@
//! Symbol name demangling for diagnostics.
/// Demangles an Itanium C++ ABI symbol name, if `name` is one the
/// demangler reads: its identifiers are ASCII, so a name with other
/// bytes stays as it is.
pub fn demangle_cpp(name: &[u8]) -> Option<String> {
if !name.starts_with(b"_Z") {
return None;
}
cpp_demangle::Symbol::new(name).ok()?.demangle().ok()
}
/// Demangles a Rust symbol name in either the legacy or the v0 scheme.
/// Crate disambiguator hashes are omitted, as rustc's own tools do.
pub fn demangle_rust(name: &[u8]) -> Option<String> {
let name = std::str::from_utf8(name).ok()?;
if let Ok(sym) = rustc_demangle::try_demangle(name) {
return Some(format!("{sym:#}"));
}
// A legacy name may be followed by characters that aren't part of the
// mangling, as in `_ZN2ns7versionEv`, which is `ns::version` and a
// trailing `v`. Demangle the path and keep the rest as it is.
let end = legacy_path_end(name)?;
let sym = rustc_demangle::try_demangle(&name[..end]).ok()?;
Some(format!("{sym:#}{}", &name[end..]))
}
/// The end of the legacy-mangled path `_ZN<len><ident>...E` at the start
/// of `name`, if there is one.
fn legacy_path_end(name: &str) -> Option<usize> {
let mut rest = name.strip_prefix("_ZN")?;
loop {
if rest.starts_with('E') {
return Some(name.len() - rest.len() + 1);
}
let digits = rest.bytes().take_while(u8::is_ascii_digit).count();
let len: usize = rest[..digits].parse().ok()?;
if len == 0 {
return None;
}
rest = rest.get(digits.checked_add(len)?..)?;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rust_legacy_with_suffix() {
assert_eq!(demangle_rust(b"_ZN2ns7versionEv").as_deref(), Some("ns::versionv"));
assert_eq!(demangle_rust(b"_ZN3foo3barE").as_deref(), Some("foo::bar"));
assert_eq!(demangle_rust(b"_ZN3foo").as_deref(), None);
assert_eq!(demangle_rust(b"main"), None);
}
#[test]
fn rust_legacy_length_overflow() {
// Adding the digit count to this length wraps to zero, so an
// unchecked parser would keep visiting the same identifier.
let len = usize::MAX - usize::MAX.ilog10() as usize;
let name = format!("_ZN{len}E");
assert_eq!(demangle_rust(name.as_bytes()), None);
}
}
+39
View File
@@ -0,0 +1,39 @@
//! Fixed-byte-order integer I/O on byte slices.
//!
//! These functions read and write integers of a known byte order, for code
//! that patches section contents of a known target. Target-generic code
//! uses the methods on the linker's target type, which pick the byte order
//! from the target.
macro_rules! endian_io {
($int:ty, $readb:ident, $readl:ident, $writeb:ident, $writel:ident) => {
#[inline]
pub fn $readb(bytes: &[u8]) -> $int {
let bytes = bytes[..size_of::<$int>()].try_into().unwrap();
<$int>::from_be_bytes(bytes)
}
#[inline]
pub fn $readl(bytes: &[u8]) -> $int {
let bytes = bytes[..size_of::<$int>()].try_into().unwrap();
<$int>::from_le_bytes(bytes)
}
#[inline]
pub fn $writeb(bytes: &mut [u8], value: $int) {
bytes[..size_of::<$int>()].copy_from_slice(&value.to_be_bytes());
}
#[inline]
pub fn $writel(bytes: &mut [u8], value: $int) {
bytes[..size_of::<$int>()].copy_from_slice(&value.to_le_bytes());
}
};
}
endian_io!(u16, read_ub16, read_ul16, write_ub16, write_ul16);
endian_io!(u32, read_ub32, read_ul32, write_ub32, write_ul32);
endian_io!(u64, read_ub64, read_ul64, write_ub64, write_ul64);
endian_io!(i16, read_ib16, read_il16, write_ib16, write_il16);
endian_io!(i32, read_ib32, read_il32, write_ib32, write_il32);
endian_io!(i64, read_ib64, read_il64, write_ib64, write_il64);
+190
View File
@@ -0,0 +1,190 @@
//! Error, warning and fatal-error reporting.
//!
//! Errors don't abort the link immediately: the linker keeps going so that
//! all problems are reported in one run, then exits before writing the
//! output. Fatal errors are for conditions the linker can't continue past.
use std::fmt;
use std::io::{self, Write};
use std::sync::Mutex;
use std::sync::atomic::{AtomicBool, Ordering};
/// Whether to demangle symbol names in diagnostics. This is process-wide
/// state so that `Display` implementations, which have no access to the
/// linker context, can consult it.
static DEMANGLE: AtomicBool = AtomicBool::new(true);
pub fn set_demangle(enabled: bool) {
DEMANGLE.store(enabled, Ordering::Relaxed);
}
pub fn demangle_enabled() -> bool {
DEMANGLE.load(Ordering::Relaxed)
}
// Diagnostic settings, error state and output serialization are process-wide.
static COLOR: AtomicBool = AtomicBool::new(false);
static FATAL_WARNINGS: AtomicBool = AtomicBool::new(false);
static SUPPRESS_WARNINGS: AtomicBool = AtomicBool::new(false);
static NOINHIBIT_EXEC: AtomicBool = AtomicBool::new(false);
static HAS_ERROR: AtomicBool = AtomicBool::new(false);
static OUTPUT_LOCK: Mutex<()> = Mutex::new(());
pub fn set_color(on: bool) {
COLOR.store(on, Ordering::Relaxed);
}
pub fn set_fatal_warnings(on: bool) {
FATAL_WARNINGS.store(on, Ordering::Relaxed);
}
pub fn set_suppress_warnings(on: bool) {
SUPPRESS_WARNINGS.store(on, Ordering::Relaxed);
}
pub fn set_noinhibit_exec(on: bool) {
NOINHIBIT_EXEC.store(on, Ordering::Relaxed);
}
pub fn noinhibit_exec() -> bool {
NOINHIBIT_EXEC.load(Ordering::Relaxed)
}
// Format each message before taking the lock so diagnostics from different
// threads cannot interleave.
fn emit(prefix_mono: &str, prefix_color: &str, msg: fmt::Arguments) {
let prefix = if COLOR.load(Ordering::Relaxed) { prefix_color } else { prefix_mono };
let text = format!("{prefix}{msg}\n");
let _guard = OUTPUT_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let _ = io::stderr().write_all(text.as_bytes());
}
/// Reports an unrecoverable error and exits.
pub fn fatal(msg: fmt::Arguments) -> ! {
emit("mold: fatal: ", "mold: \x1b[0;1;31mfatal:\x1b[0m ", msg);
exit_after_cleanup(1);
}
/// Reports an error. With `--noinhibit-exec` it is downgraded to a warning.
pub fn error(msg: fmt::Arguments) {
if NOINHIBIT_EXEC.load(Ordering::Relaxed) {
emit("mold: warning: ", "mold: \x1b[0;1;35mwarning:\x1b[0m ", msg);
} else {
emit("mold: error: ", "mold: \x1b[0;1;31merror:\x1b[0m ", msg);
HAS_ERROR.store(true, Ordering::Relaxed);
}
}
/// Reports a warning. With `--fatal-warnings` it is promoted to an error.
pub fn warn(msg: fmt::Arguments) {
if SUPPRESS_WARNINGS.load(Ordering::Relaxed) {
return;
}
if FATAL_WARNINGS.load(Ordering::Relaxed) {
emit("mold: error: ", "mold: \x1b[0;1;31merror:\x1b[0m ", msg);
HAS_ERROR.store(true, Ordering::Relaxed);
} else {
emit("mold: warning: ", "mold: \x1b[0;1;35mwarning:\x1b[0m ", msg);
}
}
/// Prints a message with no prefix to stderr: some reports of what the
/// link did (the Mach-O linker's -why_live, -why_load and text
/// relocations) come in lines of their own.
pub fn notice(msg: fmt::Arguments) {
emit("", "", msg);
}
/// Prints an informational message to stdout.
pub fn out(msg: fmt::Arguments) {
let text = format!("{msg}\n");
let _guard = OUTPUT_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let _ = io::stdout().write_all(text.as_bytes());
}
/// Returns the text that C's strerror gives for an I/O error. Unlike
/// `io::Error`'s `Display`, it doesn't append " (os error N)". An error that
/// doesn't come from the OS is formatted as usual.
pub fn strerror(err: &io::Error) -> String {
#[cfg(not(windows))]
if let Some(errno) = err.raw_os_error() {
let mut buf = [0u8; 256];
// SAFETY: strerror_r writes at most `buf.len()` bytes to `buf`.
let ret = unsafe { libc::strerror_r(errno, buf.as_mut_ptr().cast(), buf.len()) };
if ret == 0
&& let Ok(msg) = std::ffi::CStr::from_bytes_until_nul(&buf)
{
return msg.to_string_lossy().into_owned();
}
}
err.to_string()
}
/// Exits with a failure status if any error has been reported.
pub fn checkpoint() {
if HAS_ERROR.load(Ordering::Relaxed) {
exit_after_cleanup(1);
}
}
/// Removes a partially-written output file, then terminates the process
/// without running destructors. Input files are mapped for the process's
/// lifetime, so there is nothing else to release.
pub fn exit_after_cleanup(status: i32) -> ! {
crate::output_file::cleanup();
let _ = io::stdout().flush();
let _ = io::stderr().flush();
// SAFETY: `_exit` only terminates the process.
#[cfg(not(windows))]
unsafe {
libc::_exit(status)
}
#[cfg(windows)]
std::process::exit(status)
}
/// Makes a panic remove a partially-written output file, as a fatal error
/// does, before the default hook reports the panic. Nothing recovers from
/// a panic, so it always terminates the process.
pub fn install_panic_hook() {
let default_hook = std::panic::take_hook();
std::panic::set_hook(Box::new(move |info| {
crate::output_file::cleanup();
default_hook(info);
}));
}
#[macro_export]
macro_rules! fatal {
($($arg:tt)*) => {
$crate::error::fatal(format_args!($($arg)*))
};
}
#[macro_export]
macro_rules! error {
($($arg:tt)*) => {
$crate::error::error(format_args!($($arg)*))
};
}
#[macro_export]
macro_rules! warn {
($($arg:tt)*) => {
$crate::error::warn(format_args!($($arg)*))
};
}
#[macro_export]
macro_rules! notice {
($($arg:tt)*) => {
$crate::error::notice(format_args!($($arg)*))
};
}
#[macro_export]
macro_rules! out {
($($arg:tt)*) => {
$crate::error::out(format_args!($($arg)*))
};
}
+826
View File
@@ -0,0 +1,826 @@
//! The glob matcher used for symbol name patterns in version scripts,
//! dynamic list files and Mach-O symbol lists. Exact, prefix and suffix
//! patterns are matched directly. Simple substring patterns such as these
//!
//! *16QAccessibleCache*
//! *32QAbstractFileIconProviderPrivate*
//! *17QPixmapIconEngine*
//!
//! are combined into an Aho-Corasick matcher, essentially a trie with
//! additional links that searches for all of them in one pass over the
//! input (https://en.wikipedia.org/wiki/Aho-Corasick_algorithm). It can only
//! search substrings, so complex patterns such as `*foo*bar*` are matched
//! with the non-recursive algorithm described at
//! https://research.swtch.com/glob, and if there are many of them, a
//! bit-parallel NFA matches them together.
use std::collections::VecDeque;
#[derive(Clone, Debug)]
enum Token {
Str(Vec<u8>),
Star,
Question,
Bracket(Box<[bool; 256]>),
}
/// How a pattern spells bracket expressions and escapes: as fnmatch(3)
/// does, which is how the ELF linkers read version scripts, or as
/// ld-prime reads a Mach-O symbol list (see ld_prime_bracket).
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Syntax {
Fnmatch,
LdPrime,
}
#[derive(Clone, Debug)]
struct Pattern {
tokens: Vec<Token>,
// Searchers for the literals that follow stars, indexed like `tokens`.
// Building a searcher can take longer than searching a symbol name, so
// we build them once.
finders: Vec<Option<memchr::memmem::Finder<'static>>>,
value: i64,
}
impl Pattern {
fn compile(mut pat: &[u8], value: i64, syntax: Syntax) -> Option<Self> {
let mut tokens: Vec<Token> = Vec::new();
while let Some((&c, rest)) = pat.split_first() {
pat = rest;
match c {
b'[' if syntax == Syntax::LdPrime => {
let end = pat.iter().position(|&c| c == b']')?;
tokens.push(Token::Bracket(ld_prime_bracket(&pat[..end])?));
pat = &pat[end + 1..];
}
b'[' => {
// Here are a few bracket pattern examples:
//
// [abc]: a, b or c
// [$\]!]: $, ] or !
// [a-czg-i]: a, b, c, z, g, h, or i
// [!a-z]: Any character except lowercase letters
//
// Both `!` and `^` are accepted as negation markers. `!` is the
// POSIX/shell convention used by other linkers. `^` was mold's
// original syntax and is kept for backward compatibility.
let mut chars = Box::new([false; 256]);
let mut negate = false;
let mut closed = false;
if let Some((&b'!' | &b'^', rest)) = pat.split_first() {
negate = true;
pat = rest;
}
while let Some((&c, rest)) = pat.split_first() {
if c == b']' {
pat = rest;
closed = true;
break;
}
if c == b'\\' {
pat = rest;
if pat.is_empty() {
return None;
}
}
if pat.len() >= 3 && pat[1] == b'-' {
let start = pat[0];
let mut end = pat[2];
pat = &pat[3..];
if end == b'\\' {
end = *pat.first()?;
pat = &pat[1..];
}
if end < start {
return None;
}
chars[start as usize..=end as usize].fill(true);
} else {
chars[pat[0] as usize] = true;
pat = &pat[1..];
}
}
if !closed {
return None;
}
if negate {
for flag in chars.iter_mut() {
*flag = !*flag;
}
}
tokens.push(Token::Bracket(chars));
}
b'?' => tokens.push(Token::Question),
b'*' => {
if !matches!(tokens.last(), Some(Token::Star)) {
tokens.push(Token::Star);
}
}
// A backslash takes the next character as itself. ld-prime
// takes one at the end as itself too.
b'\\' => match pat.split_first() {
Some((&escaped, rest)) => {
pat = rest;
push_char(&mut tokens, escaped);
}
None if syntax == Syntax::LdPrime => push_char(&mut tokens, c),
None => return None,
},
_ => push_char(&mut tokens, c),
}
}
let finders = (0..tokens.len())
.map(|i| match &tokens[i] {
Token::Str(literal) if i > 0 && matches!(tokens[i - 1], Token::Star) => {
Some(memchr::memmem::Finder::new(literal).into_owned())
}
_ => None,
})
.collect();
Some(Self { tokens, finders, value })
}
fn matches(&self, s: &[u8]) -> bool {
if let Some(Token::Str(suffix)) = self.tokens.last()
&& !s.ends_with(suffix)
{
return false;
}
let mut x = 0;
let mut y = 0;
let mut next: Option<(usize, usize)> = None;
while x < s.len() || y < self.tokens.len() {
if y < self.tokens.len() {
let tok = &self.tokens[y];
match tok {
Token::Str(literal) => {
if s[x..].starts_with(literal) {
x += literal.len();
y += 1;
continue;
}
}
Token::Star => {
next = Some((x + 1, y));
y += 1;
if let Some(Some(finder)) = self.finders.get(y) {
let Some(pos) = finder.find(&s[x..]) else {
return false;
};
let pos = x + pos;
next = Some((pos + 1, y - 1));
x = pos + finder.needle().len();
y += 1;
}
continue;
}
Token::Question => {
if x < s.len() {
x += 1;
y += 1;
continue;
}
}
Token::Bracket(chars) => {
if x < s.len() && chars[s[x] as usize] {
x += 1;
y += 1;
continue;
}
}
}
}
// Retry the last star after assigning one more input byte to it.
match next {
Some((nx, ny)) if nx <= s.len() => {
x = nx;
y = ny;
}
_ => return false,
}
}
true
}
}
/// The characters a bracket expression matches, given what is between
/// its brackets, as ld-prime reads one: it ends at the first `]` (so
/// `[]` matches nothing) and knows no negation or escapes - `[!a]`
/// matches `!` or `a`, `[\a]` `\` or `a`. A `-` between two characters
/// makes a range from the one before it, which after a range is that
/// `-` itself (`[a-b-d]` is `a`-`b` and `-`-`d`), to the one after it;
/// a reversed range is empty. A `-` first or last makes the expression,
/// and so its pattern, malformed (None): it matches nothing.
fn ld_prime_bracket(class: &[u8]) -> Option<Box<[bool; 256]>> {
let mut chars = Box::new([false; 256]);
let mut prev = None;
let mut i = 0;
while i < class.len() {
let c = class[i];
if c == b'-' {
let (Some(start), Some(&end)) = (prev, class.get(i + 1)) else {
return None;
};
for x in start..=end {
chars[x as usize] = true;
}
i += 1;
} else {
chars[c as usize] = true;
}
prev = Some(c);
i += 1;
}
Some(chars)
}
fn push_char(tokens: &mut Vec<Token>, c: u8) {
if let Some(Token::Str(literal)) = tokens.last_mut() {
literal.push(c);
} else {
tokens.push(Token::Str(vec![c]));
}
}
// Nfa matches many glob patterns in parallel by representing each state
// with one bit. It is used only for large pattern sets; matching individual
// patterns is faster when there are only a few of them.
#[derive(Debug, Default)]
struct Nfa {
initial_states: Vec<u64>,
star_states: Vec<u64>,
accept_states: Vec<u64>,
char_masks: Vec<u64>,
values: Vec<i64>,
}
impl Nfa {
fn compile(patterns: &[Pattern]) -> Self {
let mut num_states = 0;
for pattern in patterns {
num_states += 1;
for tok in &pattern.tokens {
match tok {
Token::Str(literal) => num_states += literal.len(),
Token::Star => {}
_ => num_states += 1,
}
}
}
let num_words = num_states.div_ceil(64);
let mut nfa = Self {
initial_states: vec![0; num_words],
star_states: vec![0; num_words],
accept_states: vec![0; num_words],
char_masks: vec![0; 256 * num_words],
values: vec![-1; num_states],
};
let set_bit = |vec: &mut [u64], pos: usize| vec[pos / 64] |= 1 << (pos % 64);
let mut state = 0;
for pattern in patterns {
set_bit(&mut nfa.initial_states, state);
for tok in &pattern.tokens {
match tok {
Token::Str(literal) => {
for &c in literal {
state += 1;
nfa.char_masks[c as usize * num_words + state / 64] |=
1 << (state % 64);
}
}
Token::Star => set_bit(&mut nfa.star_states, state),
Token::Question => {
state += 1;
for c in 0..256 {
nfa.char_masks[c * num_words + state / 64] |= 1 << (state % 64);
}
}
Token::Bracket(chars) => {
state += 1;
for c in 0..256 {
if chars[c] {
nfa.char_masks[c * num_words + state / 64] |= 1 << (state % 64);
}
}
}
}
}
set_bit(&mut nfa.accept_states, state);
nfa.values[state] = pattern.value;
state += 1;
}
nfa
}
fn is_empty(&self) -> bool {
self.initial_states.is_empty()
}
fn matches(&self, s: &[u8]) -> i64 {
let num_words = self.initial_states.len();
let mut states = self.initial_states.clone();
for &c in s {
let mask = &self.char_masks[c as usize * num_words..][..num_words];
let mut carry = 0;
for i in 0..num_words {
let old = states[i];
let next = (old << 1) | carry;
states[i] = (old & self.star_states[i]) | (next & mask[i]);
carry = old >> 63;
}
}
let mut value = -1;
for (i, &state) in states.iter().enumerate() {
let mut word = state & self.accept_states[i];
while word != 0 {
let bit = word.trailing_zeros() as usize;
value = value.max(self.values[i * 64 + bit]);
word &= word - 1;
}
}
value
}
}
/// An Aho-Corasick automaton for patterns that are plain substring
/// searches, such as `*foo*`, `foo*` or `*foo`. Anchors are represented by
/// a NUL byte at the beginning or end of the pattern.
#[derive(Clone, Debug, Default)]
struct AhoCorasick {
// Most trie nodes have only one child. The root uses a dense table because
// it is visited for almost every input byte; other edges are stored sparsely.
root_children: Vec<i32>,
nodes: Vec<TrieNode>,
max_value: i64,
}
#[derive(Clone, Debug)]
struct TrieNode {
value: i64,
suffix_link: i32,
first_child: i32,
next_sibling: i32,
ch: u8,
}
impl Default for TrieNode {
fn default() -> Self {
Self { value: -1, suffix_link: -1, first_child: -1, next_sibling: -1, ch: 0 }
}
}
impl AhoCorasick {
fn can_handle(pat: &[u8]) -> bool {
let pat = pat.strip_prefix(b"*").unwrap_or(pat);
let pat = pat.strip_suffix(b"*").unwrap_or(pat);
!pat.is_empty() && !pat.iter().any(|&c| matches!(c, b'*' | b'?' | b'[' | b'\\'))
}
fn find_child(&self, node: i32, ch: u8) -> i32 {
if node == 0 {
return self.root_children[ch as usize];
}
let mut i = self.nodes[node as usize].first_child;
while i != -1 {
if self.nodes[i as usize].ch == ch {
return i;
}
i = self.nodes[i as usize].next_sibling;
}
-1
}
fn add_child(&mut self, node: i32, ch: u8) -> i32 {
let child = self.find_child(node, ch);
if child != -1 {
return child;
}
let child = self.nodes.len() as i32;
let sibling = self.nodes[node as usize].first_child;
self.nodes.push(TrieNode { next_sibling: sibling, ch, ..TrieNode::default() });
self.nodes[node as usize].first_child = child;
if node == 0 {
self.root_children[ch as usize] = child;
}
child
}
fn add(&mut self, pat: &[u8], value: i64) {
debug_assert!(Self::can_handle(pat));
self.max_value = self.max_value.max(value);
if self.nodes.is_empty() {
self.root_children = vec![-1; 256];
self.nodes.push(TrieNode::default());
}
// We handle "foo" as if "\0foo\0", "*foo" as if "foo\0", "foo*" as
// if "\0foo", and "*foo*" as if "foo". Aho-Corasick can do only
// substring matching, so we use \0 as a beginning/end-of-string
// marker.
let mut idx = 0;
if !pat.starts_with(b"*") {
idx = self.add_child(idx, 0);
}
for &c in pat {
if c != b'*' {
idx = self.add_child(idx, c);
}
}
if !pat.ends_with(b"*") {
idx = self.add_child(idx, 0);
}
let node = &mut self.nodes[idx as usize];
node.value = node.value.max(value);
}
fn compile(&mut self) {
if self.nodes.is_empty() {
return;
}
// A failure link may refer to any shallower node, so failure links
// must be constructed breadth-first.
let mut queue = VecDeque::new();
let mut child = self.nodes[0].first_child;
while child != -1 {
self.nodes[child as usize].suffix_link = 0;
queue.push_back(child);
child = self.nodes[child as usize].next_sibling;
}
while let Some(idx) = queue.pop_front() {
let mut child = self.nodes[idx as usize].first_child;
while child != -1 {
let ch = self.nodes[child as usize].ch;
let mut suffix = self.nodes[idx as usize].suffix_link;
while suffix != 0 && self.find_child(suffix, ch) == -1 {
suffix = self.nodes[suffix as usize].suffix_link;
}
let next = self.find_child(suffix, ch);
if next != -1 {
suffix = next;
}
self.nodes[child as usize].suffix_link = suffix;
let suffix_value = self.nodes[suffix as usize].value;
let node = &mut self.nodes[child as usize];
node.value = node.value.max(suffix_value);
queue.push_back(child);
child = self.nodes[child as usize].next_sibling;
}
}
}
// This runs once per byte while matching symbol names. Keep the state
// in the caller's registers instead of outlining the loop body.
#[inline(always)]
fn walk(&self, c: u8, idx: &mut i32, value: &mut i64) -> i64 {
let mut j = *idx;
while j != -1 {
let child = self.find_child(j, c);
if child != -1 {
*idx = child;
*value = (*value).max(self.nodes[child as usize].value);
return *value;
}
j = self.nodes[j as usize].suffix_link;
}
*idx = 0;
*value
}
fn find(&self, s: &[u8]) -> i64 {
if self.nodes.is_empty() {
return -1;
}
let mut idx = 0;
let mut value = -1;
self.walk(0, &mut idx, &mut value);
for &c in s {
if self.walk(c, &mut idx, &mut value) == self.max_value {
return self.max_value;
}
}
self.walk(0, &mut idx, &mut value)
}
}
#[derive(Clone, Debug)]
struct Literal {
pat: Vec<u8>,
value: i64,
}
/// A set of glob patterns, each associated with a value. Looking up a
/// string returns the largest value among the matching patterns, or -1.
#[derive(Debug)]
pub struct Glob {
// Patterns that need only a literal string comparison are kept out
// of the automaton-based matchers below, which scan the entire input
// string per query. Real version scripts consist almost entirely of
// such patterns (e.g. `local: *;` or `v8dbg_*;`), and we match them
// against every defined symbol name.
match_all: i64, // "*"
max_value: i64,
exacts: Vec<Literal>,
prefixes: Vec<Literal>,
suffixes: Vec<Literal>,
patterns: Vec<Pattern>, // "foo*bar"
aho_corasick: AhoCorasick,
nfa: Nfa,
}
/// Collects patterns before compiling an immutable matcher.
#[derive(Debug, Default)]
pub struct GlobBuilder {
glob: Glob,
}
fn is_literal(pat: &[u8]) -> bool {
!pat.iter().any(|&c| matches!(c, b'*' | b'?' | b'[' | b'\\'))
}
impl Default for Glob {
fn default() -> Self {
Self {
match_all: -1,
max_value: -1,
exacts: Vec::new(),
prefixes: Vec::new(),
suffixes: Vec::new(),
patterns: Vec::new(),
aho_corasick: AhoCorasick::default(),
nfa: Nfa::default(),
}
}
}
impl GlobBuilder {
/// Adds a pattern. Returns false if the pattern is malformed.
pub fn add(&mut self, pat: &[u8], value: i64) -> bool {
self.add_pattern(pat, value, Syntax::Fnmatch)
}
/// Adds a pattern of a Mach-O symbol list, whose bracket expressions
/// and escapes are read as ld-prime reads them (see ld_prime_bracket).
/// Returns false if the pattern is malformed.
pub fn add_ld_prime(&mut self, pat: &[u8], value: i64) -> bool {
self.add_pattern(pat, value, Syntax::LdPrime)
}
/// Adds a string that matches itself alone, wildcards and all.
pub fn add_literal(&mut self, s: &[u8], value: i64) {
debug_assert!(value >= 0);
self.glob.max_value = self.glob.max_value.max(value);
self.glob.exacts.push(Literal { pat: s.to_vec(), value });
}
fn add_pattern(&mut self, pat: &[u8], value: i64, syntax: Syntax) -> bool {
debug_assert!(value >= 0);
self.glob.max_value = self.glob.max_value.max(value);
// Match-all, exact, prefix and suffix patterns are handled with
// plain string comparisons instead of the matchers below, which
// have to scan the entire input string on every query.
if pat == b"*" {
self.glob.match_all = self.glob.match_all.max(value);
return true;
}
if is_literal(pat) {
self.glob.exacts.push(Literal { pat: pat.to_vec(), value });
return true;
}
if let Some(prefix) = pat.strip_suffix(b"*").filter(|p| is_literal(p)) {
self.glob.prefixes.push(Literal { pat: prefix.to_vec(), value });
return true;
}
if let Some(suffix) = pat.strip_prefix(b"*").filter(|p| is_literal(p)) {
self.glob.suffixes.push(Literal { pat: suffix.to_vec(), value });
return true;
}
// If the pattern requires only a single substring search, the
// Aho-Corasick algorithm is even faster than our glob matcher.
if AhoCorasick::can_handle(pat) {
self.glob.aho_corasick.add(pat, value);
return true;
}
match Pattern::compile(pat, value, syntax) {
Some(pattern) => {
self.glob.patterns.push(pattern);
true
}
None => false,
}
}
/// Consumes the construction state. The result supports parallel queries.
pub fn build(self) -> Glob {
let mut glob = self.glob;
if glob.match_all == glob.max_value {
return glob;
}
// For duplicate names, retain the largest value.
glob.exacts.sort_unstable_by(|a, b| a.pat.cmp(&b.pat).then(b.value.cmp(&a.value)));
glob.exacts.dedup_by(|a, b| a.pat == b.pat);
if glob.patterns.len() >= 64 {
glob.nfa = Nfa::compile(&glob.patterns);
glob.patterns = Vec::new();
}
glob.aho_corasick.compile();
glob
}
}
impl Glob {
pub fn new() -> Self {
Self::default()
}
pub fn is_empty(&self) -> bool {
self.max_value < 0
}
/// Returns the largest value of a matching pattern, or -1 if none match.
pub fn find(&self, s: &[u8]) -> i64 {
let mut value = self.match_all;
if value == self.max_value {
return value;
}
if let Ok(i) = self.exacts.binary_search_by(|l| l.pat.as_slice().cmp(s)) {
value = value.max(self.exacts[i].value);
}
for p in &self.prefixes {
if value < p.value && s.starts_with(&p.pat) {
value = p.value;
}
}
for p in &self.suffixes {
if value < p.value && s.ends_with(&p.pat) {
value = p.value;
}
}
if value == self.max_value {
return value;
}
value = value.max(self.aho_corasick.find(s));
if value == self.max_value {
return value;
}
if !self.nfa.is_empty() {
value = value.max(self.nfa.matches(s));
}
for p in &self.patterns {
if value < p.value && p.matches(s) {
value = p.value;
}
}
value
}
}
#[cfg(test)]
mod tests {
use super::*;
fn glob(pats: &[&str]) -> Glob {
let mut g = GlobBuilder::default();
for (i, p) in pats.iter().enumerate() {
assert!(g.add(p.as_bytes(), i as i64));
}
g.build()
}
#[test]
fn default_has_no_matches() {
let g = Glob::default();
assert!(g.is_empty());
assert_eq!(g.find(b"missing"), -1);
let mut g = GlobBuilder::default();
assert!(g.add(b"present", 7));
let g = g.build();
assert!(!g.is_empty());
assert_eq!(g.find(b"present"), 7);
assert_eq!(g.find(b"missing"), -1);
}
#[test]
fn shared_priorities_preserve_highest_match() {
let mut g = GlobBuilder::default();
for (pattern, priority) in
[("*", 0), ("*inner*", 2), ("prefix*", 2), ("*suffix", 2), ("exact", 3), ("p?efix*", 4)]
{
assert!(g.add(pattern.as_bytes(), priority));
}
let g = g.build();
for (name, expected) in
[("none", 0), ("hasinnersuffix", 2), ("suffix", 2), ("exact", 3), ("prefixsuffix", 4)]
{
assert_eq!(g.find(name.as_bytes()), expected);
}
let mut g = GlobBuilder::default();
assert!(g.add(b"*inner*", 0));
assert!(g.add(b"*suffix", 0));
let g = g.build();
assert_eq!(g.find(b"innersuffix"), 0);
assert_eq!(g.find(b"none"), -1);
}
#[test]
fn literals() {
let g = glob(&["foo", "bar*", "*baz", "*"]);
assert_eq!(g.find(b"foo"), 3);
assert_eq!(g.find(b"barx"), 3);
assert_eq!(g.find(b"xbaz"), 3);
let g = glob(&["foo", "bar*", "*baz"]);
assert_eq!(g.find(b"foo"), 0);
assert_eq!(g.find(b"barx"), 1);
assert_eq!(g.find(b"xbaz"), 2);
assert_eq!(g.find(b"nothing"), -1);
}
#[test]
fn complex() {
let g = glob(&["a*b*c", "x?z", "[a-c]d", "*mid*", "[!x]y"]);
assert_eq!(g.find(b"axxbyyc"), 0);
assert_eq!(g.find(b"xyz"), 1);
assert_eq!(g.find(b"bd"), 2);
assert_eq!(g.find(b"dd"), -1);
assert_eq!(g.find(b"leftmidright"), 3);
assert_eq!(g.find(b"ay"), 4);
assert_eq!(g.find(b"xy"), -1);
}
#[test]
fn ld_prime_brackets() {
let mut g = GlobBuilder::default();
for (i, pat) in
["a[a-b-d]", "b[c-a]", "c[\\a]", "d[]x", "e\\*", "f\\", "g[!x]y"].iter().enumerate()
{
assert!(g.add_ld_prime(pat.as_bytes(), i as i64));
}
let g = g.build();
for (name, expected) in [
("ab", 0),
("a-", 0),
("aA", 0),
("ac", 0),
("a,", -1),
("bc", 1),
("bb", -1),
("c\\", 2),
("ca", 2),
("d]x", -1),
("dx", -1),
("e*", 4),
("ex", -1),
("f\\", 5),
("g!y", 6),
("gxy", 6),
("gay", -1),
] {
assert_eq!(g.find(name.as_bytes()), expected, "{name}");
}
let mut g = GlobBuilder::default();
for pat in ["x[", "y[a-]", "z[-a]"] {
assert!(!g.add_ld_prime(pat.as_bytes(), 0));
}
assert!(!g.add(b"f\\", 0));
}
#[test]
fn literal_matches_only_itself() {
let mut g = GlobBuilder::default();
g.add_literal(b"a*b", 0);
let g = g.build();
assert_eq!(g.find(b"a*b"), 0);
assert_eq!(g.find(b"axb"), -1);
}
#[test]
fn many_patterns_use_nfa() {
let pats: Vec<String> = (0..100).map(|i| format!("p{i}*q?r")).collect();
let refs: Vec<&str> = pats.iter().map(String::as_str).collect();
let g = glob(&refs);
assert_eq!(g.find(b"p42xxqyr"), 42);
assert_eq!(g.find(b"p42xxqr"), -1);
}
}
+70
View File
@@ -0,0 +1,70 @@
//! This file implements the HyperLogLog algorithm, which estimates
//! the number of unique items in a given multiset.
//!
//! For more info, read
//! https://engineering.fb.com/2018/12/13/data-infrastructure/hyperloglog
/// Estimates the number of distinct values in a stream of hashes.
///
/// Each hash selects a register by its low bits; the register keeps the
/// longest run of leading zeros seen, plus one. The estimate is a scaled
/// harmonic mean of the registers.
#[derive(Clone)]
pub struct HyperLogLog {
registers: [u8; Self::NUM_REGISTERS],
}
impl HyperLogLog {
const NUM_REGISTERS: usize = 2048;
const ALPHA: f64 = 0.79402;
#[inline]
pub fn insert(&mut self, hash: u64) {
let register = &mut self.registers[hash as usize & (Self::NUM_REGISTERS - 1)];
*register = (*register).max(hash.leading_zeros() as u8 + 1);
}
/// Combines two estimators, as if every value had been inserted into
/// one of them.
#[inline]
pub fn merged(mut self, other: &Self) -> Self {
for (a, b) in self.registers.iter_mut().zip(&other.registers) {
*a = (*a).max(*b);
}
self
}
#[inline]
pub fn cardinality(&self) -> u64 {
let z: f64 = self.registers.iter().map(|&r| 2f64.powi(-i32::from(r))).sum();
(Self::ALPHA * (Self::NUM_REGISTERS * Self::NUM_REGISTERS) as f64 / z) as u64
}
}
impl Default for HyperLogLog {
fn default() -> Self {
Self { registers: [0; Self::NUM_REGISTERS] }
}
}
impl Extend<u64> for HyperLogLog {
fn extend<I: IntoIterator<Item = u64>>(&mut self, hashes: I) {
for hash in hashes {
self.insert(hash);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn estimates_within_a_few_percent() {
let mut hll = HyperLogLog::default();
hll.extend((0..100_000u64).map(|i| xxhash_rust::xxh3::xxh3_64(&i.to_le_bytes())));
let n = hll.cardinality();
// The scale constant follows mold, whose estimates run about 10% high.
assert!((95_000..125_000).contains(&n), "estimate {n}");
}
}
+70
View File
@@ -0,0 +1,70 @@
//! Limits the number of concurrent mold processes.
// Many build systems attempt to invoke as many linker processes as there
// are cores, based on the assumption that the linker is single-threaded.
// However, since mold is multi-threaded, such build systems' behavior is
// not beneficial and just increases the overall peak memory usage.
// On machines with limited memory, this could lead to an out-of-memory
// error.
//
// This file implements a feature that limits the number of concurrent
// mold processes to just 1 for each user. It is intended to be used as
// `MOLD_JOBS=1 ninja` or `MOLD_JOBS=1 make -j$(nproc)`.
#[cfg(not(windows))]
use std::ffi::CStr;
#[cfg(not(windows))]
use std::fs::{File, OpenOptions};
#[cfg(not(windows))]
use std::os::unix::{fs::OpenOptionsExt, io::AsRawFd};
#[cfg(not(windows))]
use std::sync::Mutex;
#[cfg(not(windows))]
static LOCK_FILE: Mutex<Option<File>> = Mutex::new(None);
#[cfg(not(windows))]
pub fn acquire_global_lock() {
if std::env::var_os("MOLD_JOBS").as_deref() != Some(std::ffi::OsStr::new("1")) {
return;
}
// Like other XDG variables, an empty XDG_RUNTIME_DIR counts as unset.
let path = if let Some(dir) = std::env::var_os("XDG_RUNTIME_DIR").filter(|d| !d.is_empty()) {
std::path::PathBuf::from(dir).join("mold-lock")
} else {
// SAFETY: getpwuid returns either null or a pointer to process-global
// storage. We copy the name before making another libc call.
let name = unsafe {
let pwd = libc::getpwuid(libc::getuid());
if pwd.is_null() || (*pwd).pw_name.is_null() {
return;
}
CStr::from_ptr((*pwd).pw_name).to_bytes().to_vec()
};
let name = String::from_utf8_lossy(&name);
std::path::PathBuf::from(format!("/tmp/mold-lock-{name}"))
};
let Ok(file) =
OpenOptions::new().write(true).create(true).truncate(false).mode(0o600).open(path)
else {
return;
};
// SAFETY: file owns a valid descriptor. Keep lockf for compatibility with
// other mold processes; flock uses a different locking protocol.
if unsafe { libc::lockf(file.as_raw_fd(), libc::F_LOCK, 0) } == -1 {
return;
}
*LOCK_FILE.lock().unwrap() = Some(file);
}
#[cfg(windows)]
pub fn acquire_global_lock() {}
#[cfg(not(windows))]
pub fn release_global_lock() {
drop(LOCK_FILE.lock().unwrap().take());
}
#[cfg(windows)]
pub fn release_global_lock() {}
+132
View File
@@ -0,0 +1,132 @@
//! LEB128, the variable-length integer encoding of DWARF and of other
//! tables in object files.
use crate::bits::sign_extend;
/// Appends `value` in unsigned LEB128 encoding.
pub fn encode_uleb(out: &mut Vec<u8>, mut value: u64) {
loop {
let byte = (value & 0x7f) as u8;
value >>= 7;
if value == 0 {
out.push(byte);
return;
}
out.push(byte | 0x80);
}
}
/// Appends `value` in signed LEB128 encoding.
pub fn encode_sleb(out: &mut Vec<u8>, mut value: i64) {
loop {
let byte = (value & 0x7f) as u8;
value >>= 7;
let negative = byte & 0x40 != 0;
if (value == 0 && !negative) || (value == -1 && negative) {
out.push(byte);
return;
}
out.push(byte | 0x80);
}
}
/// Writes `value` in unsigned LEB128 encoding at the start of `buf`,
/// returning its length.
pub fn write_uleb(buf: &mut [u8], mut value: u64) -> usize {
let mut i = 0;
loop {
let mut byte = (value & 0x7f) as u8;
value >>= 7;
if value != 0 {
byte |= 0x80;
}
buf[i] = byte;
i += 1;
if value == 0 {
return i;
}
}
}
/// The length of `value` in unsigned LEB128 encoding.
pub fn uleb_size(mut value: u64) -> usize {
let mut len = 1;
while value >= 0x80 {
value >>= 7;
len += 1;
}
len
}
/// Overwrites an existing unsigned LEB128 value in place, keeping its length.
pub fn overwrite_uleb(buf: &mut [u8], mut value: u64) {
let mut i = 0;
while buf[i] & 0x80 != 0 {
buf[i] = 0x80 | (value & 0x7f) as u8;
value >>= 7;
i += 1;
}
buf[i] = (value & 0x7f) as u8;
}
/// Reads an unsigned LEB128 value, advancing `bytes` past it.
#[inline]
pub fn read_uleb(bytes: &mut &[u8]) -> u64 {
let mut value = 0;
let mut shift = 0;
loop {
let (&byte, rest) = bytes.split_first().expect("truncated LEB128");
*bytes = rest;
if shift < 64 {
value |= ((byte & 0x7f) as u64) << shift;
}
shift += 7;
if byte & 0x80 == 0 {
return value;
}
}
}
/// Reads a signed LEB128 value, advancing `bytes` past it.
#[inline]
pub fn read_sleb(bytes: &mut &[u8]) -> i64 {
let mut value = 0u64;
let mut shift = 0;
loop {
let (&byte, rest) = bytes.split_first().expect("truncated LEB128");
*bytes = rest;
if shift < 64 {
value |= ((byte & 0x7f) as u64) << shift;
}
shift += 7;
if byte & 0x80 == 0 {
return if shift < 64 { sign_extend(value, shift) } else { value as i64 };
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn leb128_roundtrip() {
for &value in &[0u64, 1, 127, 128, 300, u32::MAX as u64, u64::MAX] {
let mut buf = Vec::new();
encode_uleb(&mut buf, value);
assert_eq!(uleb_size(value), buf.len());
let mut written = [0xff; 10];
assert_eq!(write_uleb(&mut written, value), buf.len());
assert_eq!(&written[..buf.len()], buf);
let mut slice = buf.as_slice();
assert_eq!(read_uleb(&mut slice), value);
assert!(slice.is_empty());
}
for &value in &[0i64, -1, 63, 64, -64, -65, i64::MIN, i64::MAX] {
let mut buf = Vec::new();
encode_sleb(&mut buf, value);
let mut slice = buf.as_slice();
assert_eq!(read_sleb(&mut slice), value);
}
}
}
+35
View File
@@ -0,0 +1,35 @@
//! Code that mold's linkers share. Nothing here depends on the format of
//! object files.
pub mod archive_file;
pub mod bits;
pub mod bytes;
pub mod cityhash;
pub mod compress;
pub mod concurrent_map;
pub mod demangle;
pub mod endian;
pub mod error;
pub mod glob;
pub mod hyperloglog;
pub mod jobs;
pub mod leb128;
pub mod mapped_file;
pub mod mem;
pub mod output_file;
pub mod parallel;
pub mod path;
pub mod perf;
mod prefetch;
pub mod record;
pub mod response_file;
pub mod siphash;
pub mod subprocess;
pub mod tar;
pub mod worker_local;
pub use prefetch::prefetch;
/// The git commit that mold is built from, or None if the source tree has
/// no git metadata, as in a source tarball.
pub const GIT_HASH: Option<&str> = option_env!("MOLD_GIT_HASH");
+308
View File
@@ -0,0 +1,308 @@
//! Input file access.
//!
//! Every input file is read once and kept in memory for the whole link,
//! because sections, symbol names and relocation tables of object files
//! are used until the output is written. Files are therefore leaked with a
//! `'static` lifetime rather than tracked with reference counts, which
//! keeps lifetimes out of every data structure that refers to file
//! contents.
use std::borrow::Cow;
use std::fs::File;
use std::io::{self, Read};
use std::ops::Range;
#[cfg(not(windows))]
use std::os::unix::fs::MetadataExt;
use std::path::{Path, PathBuf};
use std::sync::Mutex;
use std::sync::atomic::{AtomicBool, Ordering};
#[cfg(not(windows))]
use rayon::prelude::*;
use crate::error::strerror;
use crate::fatal;
use crate::mem::leak;
use crate::parallel::SyncUnsafeCell;
use crate::path::clean_path;
/// All files opened during this link.
static FILE_POOL: Mutex<Vec<&'static MappedFile>> = Mutex::new(Vec::new());
/// The files that are memory-mapped, for [`drop_mappings`].
static MMAPPED_FILES: Mutex<Vec<&'static MappedFile>> = Mutex::new(Vec::new());
/// The device and inode numbers of the memory-mapped files, for
/// [`is_mmapped`].
#[cfg(not(windows))]
static MMAPPED_INODES: Mutex<Vec<(u64, u64)>> = Mutex::new(Vec::new());
/// Returns all files opened during this link, sorted by name. Input
/// files are opened in parallel, so the order in which they were opened
/// changes from run to run.
pub fn file_pool() -> Vec<&'static MappedFile> {
let mut files = FILE_POOL.lock().unwrap().clone();
files.sort_by(|a, b| a.name.cmp(&b.name));
files
}
/// Drops the page table entries of the mapped input files, in parallel.
/// This makes process exit faster, as the kernel otherwise reclaims them
/// in a single thread on exit. The files' contents stay in the page cache,
/// but the relocation records rewritten in place through these private
/// mappings are discarded, so this must be the last use of the input data.
pub fn drop_mappings() {
let files = std::mem::take(&mut *MMAPPED_FILES.lock().unwrap());
#[cfg(windows)]
let _ = files;
#[cfg(not(windows))]
files.par_iter().for_each(|mf| {
// SAFETY: the range is a whole mapping of the file that lives for
// the rest of the process; MADV_DONTNEED only discards the pages,
// which are faulted in again if they are ever read.
unsafe {
libc::madvise(
mf.data().as_ptr() as *mut libc::c_void,
mf.data().len(),
libc::MADV_DONTNEED,
)
};
});
}
/// Returns true if the file at `path` is memory-mapped as an input. Such a
/// file must not be written in place, because a private mapping reflects
/// changes to the file in the pages it has not copied yet.
#[cfg(not(windows))]
pub fn is_mmapped(path: &Path) -> bool {
std::fs::metadata(path)
.is_ok_and(|m| MMAPPED_INODES.lock().unwrap().contains(&(m.dev(), m.ino())))
}
/// Output files are never written in place on Windows.
#[cfg(windows)]
pub fn is_mmapped(_path: &Path) -> bool {
false
}
// Files up to this size are read into malloc'ed 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. read(2) takes no such lock,
// but copying costs memory bandwidth in proportion to the file size, so
// large files are still mmap'ed. On a Chromium link, files below this
// threshold are 64% of the inputs by count but 16% by size.
//
// The buffers add up to hundreds of megabytes on such a link, so they
// need to be backed by huge pages; with 4 KiB pages, faulting them in
// costs more than the mmap calls did. mimalloc does that by default.
const READ_THRESHOLD: u64 = 32 * 1024;
// MappedFile represents an input file that is either mmap'ed or read into
// memory. Either way, its contents are accessible through `data`.
#[derive(Debug)]
pub struct MappedFile {
/// The path this file was opened by, which already has --chroot
/// applied, or the member name for a slice of an archive.
pub name: PathBuf,
/// The contents, in an allocation that is deliberately leaked with the
/// input file. The bytes are cells because the linker modifies private
/// relocation records in place, as C++ mold does.
pub data: &'static [SyncUnsafeCell<u8>],
/// False if the file was found by searching library paths (`-l`),
/// which affects how a shared library's soname defaults.
pub given_fullpath: bool,
/// The archive this file is a member of.
pub parent: Option<&'static Self>,
/// The thin archive this file is a member of.
pub thin_parent: Option<&'static Self>,
// For --dependency-file
pub is_dependency: AtomicBool,
}
impl MappedFile {
fn open_impl(path: &Path) -> io::Result<&'static Self> {
let file = File::open(path)?;
let display = path.display();
let metadata =
file.metadata().unwrap_or_else(|e| fatal!("{display}: fstat failed: {}", strerror(&e)));
let size = metadata.len();
// True if `data` is a memory mapping of the file rather than a copy of
// its contents in anonymous memory.
let mut is_mmapped = false;
let data: &'static [SyncUnsafeCell<u8>] = if size == 0 {
&[]
} else if size <= READ_THRESHOLD && metadata.is_file() {
let mut buf = Vec::with_capacity(size as usize);
(&file)
.take(size)
.read_to_end(&mut buf)
.unwrap_or_else(|e| fatal!("{display}: read failed: {}", strerror(&e)));
if buf.len() as u64 != size {
fatal!("{display}: file is shorter than its reported size");
}
SyncUnsafeCell::from_mut(Vec::leak(buf))
} else {
// C++ mold maps inputs MAP_PRIVATE with write permission so it
// can redirect ordinary relocation records in place without
// changing the input file.
//
// SAFETY: the mapping is private and lives for the rest of the
// process. Input files are not expected to change while the
// linker runs.
let map_len = usize::try_from(size)
.unwrap_or_else(|_| fatal!("{display}: file is too large to map"));
let map = unsafe { memmap2::MmapOptions::new().len(map_len).map_copy(&file) }
.unwrap_or_else(|e| fatal!("{display}: mmap failed: {}", strerror(&e)));
is_mmapped = true;
SyncUnsafeCell::from_mut(Box::leak(Box::new(map)).as_mut())
};
let mf = leak(Self {
name: path.to_path_buf(),
data,
given_fullpath: true,
parent: None,
thin_parent: None,
is_dependency: AtomicBool::new(true),
});
FILE_POOL.lock().unwrap().push(mf);
if is_mmapped {
MMAPPED_FILES.lock().unwrap().push(mf);
#[cfg(not(windows))]
MMAPPED_INODES.lock().unwrap().push((metadata.dev(), metadata.ino()));
}
Ok(mf)
}
/// Opens a file, returning `None` if it doesn't exist.
pub fn open(path: impl AsRef<Path>) -> Option<&'static Self> {
let path = path.as_ref();
match Self::open_impl(path) {
Ok(mf) => Some(mf),
Err(e) if e.kind() == io::ErrorKind::NotFound => None,
Err(e) => fatal!("opening {} failed: {}", path.display(), strerror(&e)),
}
}
/// Opens a file that must exist.
pub fn must_open(path: impl AsRef<Path>) -> &'static Self {
let path = path.as_ref();
Self::open_impl(path)
.unwrap_or_else(|e| fatal!("cannot open {}: {}", path.display(), strerror(&e)))
}
/// Returns a view of a member of this archive.
pub fn slice(&'static self, name: PathBuf, start: usize, size: usize) -> &'static Self {
let mf = leak(Self {
name,
data: &self.data[start..start + size],
given_fullpath: true,
parent: Some(self),
thin_parent: None,
is_dependency: AtomicBool::new(true),
});
FILE_POOL.lock().unwrap().push(mf);
mf
}
/// Opens a member whose bytes are stored outside this thin archive.
pub fn open_thin_member(&'static self, path: &Path) -> &'static Self {
let member = Self::must_open(path);
leak(Self {
name: member.name.clone(),
data: member.data,
given_fullpath: true,
parent: None,
thin_parent: Some(self),
is_dependency: AtomicBool::new(true),
})
}
pub fn size(&self) -> usize {
self.data.len()
}
#[inline]
pub fn data(&self) -> &[u8] {
// SAFETY: the only writes are to relocation ranges during exclusive
// linker phases, which use no shared view of those ranges.
unsafe { SyncUnsafeCell::as_slice(self.data) }
}
/// Returns a relocation range for modification.
///
/// # Safety
///
/// The caller must have exclusive access to `range` while the slice is
/// alive, and no shared reference to an overlapping range may be used
/// during that time.
#[allow(clippy::mut_from_ref)]
pub unsafe fn data_mut(&self, range: Range<usize>) -> &mut [u8] {
// SAFETY: guaranteed by the caller.
unsafe { SyncUnsafeCell::as_mut_slice(&self.data[range]) }
}
/// The offset of this slice within its top-level archive file.
pub fn offset(&self) -> usize {
match self.parent {
Some(parent) => {
self.data.as_ptr() as usize - parent.data.as_ptr() as usize + parent.offset()
}
None => 0,
}
}
// Returns a string that uniquely identifies a file that is possibly
// in an archive.
pub fn identifier(&self) -> std::ffi::OsString {
if let Some(parent) = self.parent {
// Archive members may have the same name, so use the file offset.
let mut name = parent.name.as_os_str().to_os_string();
name.push(format!(":{}", self.offset()));
return name;
}
if let Some(parent) = self.thin_parent {
// Thin archive members have unique filenames.
let mut name = parent.name.as_os_str().to_os_string();
name.push(":");
name.push(&self.name);
return name;
}
self.name.as_os_str().to_os_string()
}
pub fn is_dependency(&self) -> bool {
self.is_dependency.load(Ordering::Relaxed)
}
pub fn set_dependency(&self, value: bool) {
self.is_dependency.store(value, Ordering::Relaxed);
}
}
pub fn apply_chroot<'a>(chroot: &Path, path: &'a Path) -> Cow<'a, Path> {
if path.is_absolute() && !chroot.as_os_str().is_empty() {
Cow::Owned(chroot.join(clean_path(path).strip_prefix("/").unwrap_or(path)))
} else {
Cow::Borrowed(path)
}
}
/// Opens an input file, applying `--chroot` to absolute paths.
pub fn open_file(chroot: &Path, path: impl AsRef<Path>) -> Option<&'static MappedFile> {
MappedFile::open(apply_chroot(chroot, path.as_ref()))
}
/// Opens an input file that must exist, applying `--chroot` to absolute paths.
pub fn must_open_file(chroot: &Path, path: impl AsRef<Path>) -> &'static MappedFile {
let path = path.as_ref();
MappedFile::open_impl(&apply_chroot(chroot, path))
.unwrap_or_else(|e| fatal!("cannot open {}: {}", path.display(), strerror(&e)))
}
+75
View File
@@ -0,0 +1,75 @@
//! Memory: transparent huge pages for large tables, and allocations that
//! are never freed.
/// Requests transparent huge pages for a mapped byte range on Linux-based
/// targets.
///
/// # Safety
///
/// `data..data + size` must describe a live mapping.
#[cfg(any(target_os = "android", target_os = "linux"))]
pub(crate) unsafe fn madvise_hugepage(data: *mut u8, size: usize) {
// SAFETY: guaranteed by the caller; MADV_HUGEPAGE is only a kernel hint.
let _ = unsafe { libc::madvise(data.cast(), size, libc::MADV_HUGEPAGE) };
}
/// No-op on targets that do not support Linux's `MADV_HUGEPAGE` advice.
///
/// # Safety
///
/// Kept identical to the supported-target signature.
#[cfg(not(any(target_os = "android", target_os = "linux")))]
pub(crate) unsafe fn madvise_hugepage(_data: *mut u8, _size: usize) {}
/// Requests transparent huge pages for the whole pages strictly inside an
/// allocation, leaving possible allocator metadata in its boundary pages
/// untouched.
///
/// # Safety
///
/// `data..data + size` must describe a live allocation.
#[cfg(any(target_os = "android", target_os = "linux"))]
pub unsafe fn madvise_hugepage_interior(data: *const u8, size: usize) {
let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) };
if size == 0 || page_size <= 0 {
return;
}
let page_size = page_size as usize;
let start = data.addr();
let Some(end) = start.checked_add(size) else {
return;
};
let Some(rounded) = start.checked_add(page_size - 1) else {
return;
};
let first_page = rounded / page_size * page_size;
let last_page = end / page_size * page_size;
if first_page < last_page {
// SAFETY: these complete pages lie strictly inside the caller's
// allocation.
unsafe { madvise_hugepage(data.with_addr(first_page).cast_mut(), last_page - first_page) };
}
}
/// No-op on targets that do not support Linux's `MADV_HUGEPAGE` advice.
///
/// # Safety
///
/// Kept identical to the supported-target signature.
#[cfg(not(any(target_os = "android", target_os = "linux")))]
pub unsafe fn madvise_hugepage_interior(_data: *const u8, _size: usize) {}
/// Leaks a value for the rest of the process's lifetime.
///
/// Input files, symbol names and a few other objects must outlive every
/// data structure of a link, and the process exits as soon as the link is
/// done, so never freeing them is both simplest and cheapest.
pub fn leak<T>(value: T) -> &'static T {
Box::leak(Box::new(value))
}
/// Leaks a byte string for the rest of the process's lifetime.
pub fn leak_bytes(bytes: Vec<u8>) -> &'static [u8] {
Vec::leak(bytes)
}
+574
View File
@@ -0,0 +1,574 @@
//! The output file and helpers for writing to it from many threads.
use std::fs::{File, OpenOptions};
use std::io::{self, Write};
use std::ops::Range;
#[cfg(not(windows))]
use std::os::unix::fs::{OpenOptionsExt, PermissionsExt};
#[cfg(not(windows))]
use std::os::unix::io::AsRawFd;
use std::path::{Path, PathBuf};
#[cfg(windows)]
use std::sync::Mutex;
#[cfg(not(windows))]
use std::sync::atomic::{AtomicPtr, AtomicUsize, Ordering};
use memmap2::MmapMut;
#[cfg(not(windows))]
use memmap2::MmapOptions;
use crate::error::strerror;
use crate::fatal;
/// The temporary file being written, removed on a fatal error.
#[cfg(windows)]
static TMPFILE: Mutex<Option<PathBuf>> = Mutex::new(None);
#[cfg(not(windows))]
static TMPFILE: AtomicPtr<libc::c_char> = AtomicPtr::new(std::ptr::null_mut());
/// Registers the file that cleanup() removes. The Mach-O linker doesn't
/// use OutputFile but registers its output, which it writes in place.
pub fn set_tmpfile(path: Option<&Path>) {
#[cfg(not(windows))]
{
// Published paths live until process exit: a signal handler on another
// thread may still be using the old pointer when this registration
// changes.
let ptr = path.map_or(std::ptr::null_mut(), |path| {
std::ffi::CString::new(path.as_os_str().as_encoded_bytes())
.expect("temporary path contains NUL")
.into_raw()
});
TMPFILE.store(ptr, Ordering::Release);
}
#[cfg(windows)]
{
*TMPFILE.lock().unwrap() = path.map(Path::to_path_buf);
}
}
#[cfg(not(windows))]
static OUTPUT_BUFFER_START: AtomicUsize = AtomicUsize::new(0);
#[cfg(not(windows))]
static OUTPUT_BUFFER_END: AtomicUsize = AtomicUsize::new(0);
#[cfg(not(windows))]
fn set_output_buffer_range(start: usize, len: usize) {
OUTPUT_BUFFER_END.store(0, Ordering::SeqCst);
OUTPUT_BUFFER_START.store(start, Ordering::SeqCst);
OUTPUT_BUFFER_END.store(start + len, Ordering::SeqCst);
}
#[cfg(not(windows))]
pub fn output_buffer_contains(addr: usize) -> bool {
let start = OUTPUT_BUFFER_START.load(Ordering::SeqCst);
let end = OUTPUT_BUFFER_END.load(Ordering::SeqCst);
start != 0 && start <= addr && addr < end
}
#[cfg(not(windows))]
fn open_options(mode: u32) -> OpenOptions {
let mut options = OpenOptions::new();
options.mode(mode);
options
}
#[cfg(windows)]
fn open_options(_mode: u32) -> OpenOptions {
OpenOptions::new()
}
fn set_permissions(file: &File, perm: u32) -> io::Result<()> {
#[cfg(not(windows))]
{
file.set_permissions(std::fs::Permissions::from_mode(perm & !umask()))
}
#[cfg(windows)]
{
let mut permissions = file.metadata()?.permissions();
permissions.set_readonly(perm & 0o200 == 0);
file.set_permissions(permissions)
}
}
/// Removes a partially written output file.
pub fn cleanup() {
#[cfg(not(windows))]
{
let path = TMPFILE.swap(std::ptr::null_mut(), Ordering::AcqRel);
if !path.is_null() {
// SAFETY: path is a published, NUL-terminated string that is never
// freed. This function is also called from a signal handler, so it
// must not lock, allocate or drop owned storage. unlink is
// signal-safe.
unsafe { libc::unlink(path) };
}
}
#[cfg(windows)]
if let Ok(mut guard) = TMPFILE.lock() {
if let Some(path) = guard.take() {
let _ = std::fs::remove_file(path);
}
}
}
enum Storage {
File {
file: File,
// Empty files and a locked file awaiting resize have no mapping.
// A mapping may reserve more address space than the file's `len`.
map: Option<MmapMut>,
len: usize,
},
Memory(Vec<u8>),
}
/// An output file, either memory-mapped or buffered in memory.
pub struct OutputFile {
path: PathBuf,
tmp_path: Option<PathBuf>,
storage: Storage,
perm: u32,
}
/// Calling fallocate speeds up later linking passes on ext4 by taking disk
/// block allocation out of the page-fault handler. On tmpfs, it instead
/// makes things much slower: the kernel allocates and zeroes every page of
/// the range inside the syscall, on one thread, which takes ~1.8 s for a
/// 5 GiB file.
#[cfg(any(target_os = "android", target_os = "linux"))]
fn preallocate(file: &File, offset: u64, size: u64) {
// SAFETY: fstatfs and fallocate only inspect and act on a valid open
// descriptor; the statfs buffer is fully written before it is read.
unsafe {
let mut fs: libc::statfs = std::mem::zeroed();
if libc::fstatfs(file.as_raw_fd(), &raw mut fs) != 0 || fs.f_type != libc::TMPFS_MAGIC as _
{
libc::fallocate(file.as_raw_fd(), 0, offset as libc::off_t, size as libc::off_t);
}
}
}
#[cfg(not(any(target_os = "android", target_os = "linux")))]
fn preallocate(_file: &File, _offset: u64, _size: u64) {}
/// Returns the length of the buffer that holds a file of the given size.
/// No Rust object can be larger than isize::MAX bytes, which limits the
/// output to 2 GiB on 32-bit hosts.
fn buffer_len(path: &Path, size: u64) -> usize {
if isize::try_from(size).is_err() {
fatal!("{}: output file is too large for this host: {size} bytes", path.display());
}
size as usize
}
fn map_file(file: &File, len: usize) -> io::Result<Option<MmapMut>> {
if len == 0 {
return Ok(None);
}
// We map the file with twice as much address space as its size, so
// that extend() can grow the file into the mapping in place. Touching
// the mapping beyond the end of the file is not allowed, but growing
// the file with ftruncate makes the tail of the mapping accessible
// without any further mmap call.
//
// SAFETY: the file is private to this process until it's closed.
#[cfg(not(windows))]
let map = unsafe { MmapOptions::new().len(len * 2).map_mut(file) }
// If the address space is too tight, map just the file.
.or_else(|_| unsafe { MmapMut::map_mut(file) });
#[cfg(windows)]
let map = unsafe { MmapMut::map_mut(file) };
let mut map = map?;
// Enable transparent huge pages for a memory-mapped output file
// when the target provides the required advice.
// Linking a Chromium debug build is ~20% faster with this madvise call.
//
// Without this, every 4 KiB page of the output takes its own
// page fault when it is first written, and the faults of the
// many copying threads serialize on the file's page cache. With
// it, the kernel backs the mapping with large folios and the
// number of faults drops by an order of magnitude.
// SAFETY: the range is the mapping; the advice is only a hint.
unsafe { crate::mem::madvise_hugepage(map.as_mut_ptr(), map.len()) };
Ok(Some(map))
}
impl OutputFile {
#[cfg(not(windows))]
fn publish_output_buffer(&self) {
match &self.storage {
Storage::File { map: Some(map), len, .. } => {
set_output_buffer_range(map.as_ptr() as usize, *len);
}
_ => set_output_buffer_range(0, 0),
}
}
/// Opens an output file of the given size.
///
/// A regular file is written through a memory mapping of a temporary
/// file that is renamed into place on close, so that a failed link
/// doesn't leave a truncated output behind and a running executable
/// isn't modified underneath the kernel. Anything else, such as a
/// device, a pipe or standard output, is assembled in memory and
/// written out at the end. If `mmap` is false, a regular file is
/// assembled in memory too and then written out to the temporary file.
pub fn open(path: &Path, size: u64, perm: u32, overwrite_in_place: bool, mmap: bool) -> Self {
let len = buffer_len(path, size);
let is_special =
path == Path::new("-") || std::fs::metadata(path).is_ok_and(|m| !m.is_file());
if is_special {
return Self {
path: path.to_path_buf(),
tmp_path: None,
storage: Storage::Memory(vec![0; len]),
perm,
};
}
let dir = Path::new(path)
.parent()
.filter(|p| !p.as_os_str().is_empty())
.unwrap_or(Path::new("."));
let mut name = std::ffi::OsString::from(".");
name.push(path.file_name().unwrap_or_default());
name.push(format!(".{}", std::process::id()));
let tmp = dir.join(name);
if !mmap {
return Self {
path: path.to_path_buf(),
tmp_path: Some(tmp),
storage: Storage::Memory(vec![0; len]),
perm,
};
}
// Reuse an existing file if it exists and is writable because on
// Linux, writing to an existing file is much faster than creating a
// fresh file and writing to it. A file mapped as an input is not reused, as
// the linker is still reading it.
let reuse_existing = || -> Option<File> {
if !overwrite_in_place
|| crate::mapped_file::is_mmapped(path)
|| std::fs::rename(path, &tmp).is_err()
{
return None;
}
match open_options(perm).read(true).write(true).create(true).open(&tmp) {
Ok(file) => Some(file),
Err(_) => {
let _ = std::fs::remove_file(&tmp);
None
}
}
};
let file = reuse_existing().unwrap_or_else(|| {
open_options(perm)
.read(true)
.write(true)
.create(true)
.truncate(true)
.open(&tmp)
.unwrap_or_else(|e| fatal!("cannot open {}: {}", tmp.display(), strerror(&e)))
});
set_tmpfile(Some(&tmp));
set_permissions(&file, perm)
.unwrap_or_else(|e| fatal!("{}: fchmod failed: {}", tmp.display(), strerror(&e)));
file.set_len(size)
.unwrap_or_else(|e| fatal!("{}: ftruncate failed: {}", tmp.display(), strerror(&e)));
preallocate(&file, 0, size);
let map = map_file(&file, len)
.unwrap_or_else(|e| fatal!("{}: mmap failed: {}", path.display(), strerror(&e)));
let output = Self {
path: path.to_path_buf(),
tmp_path: Some(tmp),
storage: Storage::File { file, map, len },
perm,
};
#[cfg(not(windows))]
output.publish_output_buffer();
output
}
/// Opens a file that is written in place rather than renamed and holds an
/// exclusive `flock`. This is used for a separate debug file that a
/// debugger may wait on. The file is made unusable right away so that a
/// stale one is not picked up by accident; [`Self::resize`] gives it its
/// size.
#[cfg(not(windows))]
pub fn open_locked(path: &Path, perm: u32) -> Self {
let mut file = open_options(perm)
.read(true)
.write(true)
.create(true)
.open(path)
.unwrap_or_else(|e| fatal!("cannot open {}: {}", path.display(), strerror(&e)));
// SAFETY: flock on a valid descriptor.
unsafe {
libc::flock(file.as_raw_fd(), libc::LOCK_EX);
}
// We may be overwriting an existing debug info file. We want to
// make the file unusable so that gdb won't use it by accident until
// it's ready.
file.write_all(&[0; 256])
.unwrap_or_else(|e| fatal!("{}: write failed: {}", path.display(), strerror(&e)));
Self {
path: path.to_path_buf(),
tmp_path: None,
storage: Storage::File { file, map: None, len: 0 },
perm,
}
}
#[cfg(windows)]
pub fn open_locked(_path: &Path, _perm: u32) -> OutputFile {
fatal!("LockingOutputFile is not supported on Windows");
}
/// Sets the size of a file opened with [`Self::open_locked`].
pub fn resize(&mut self, size: u64) {
let new_len = buffer_len(&self.path, size);
let Storage::File { file, map, len } = &mut self.storage else {
panic!("resizing an output file that isn't a file");
};
file.set_len(size).unwrap_or_else(|e| {
fatal!("{}: ftruncate failed: {}", self.path.display(), strerror(&e))
});
*map = map_file(file, new_len)
.unwrap_or_else(|e| fatal!("{}: mmap failed: {}", self.path.display(), strerror(&e)));
*len = new_len;
#[cfg(not(windows))]
self.publish_output_buffer();
}
/// Whether the buffer is a mapping of the file rather than memory.
pub fn is_mmapped(&self) -> bool {
matches!(self.storage, Storage::File { map: Some(_), .. })
}
pub fn buf(&mut self) -> &mut [u8] {
match &mut self.storage {
Storage::File { map: Some(map), len, .. } => &mut map[..*len],
Storage::File { map: None, .. } => &mut [],
Storage::Memory(vec) => &mut vec[..],
}
}
pub fn len(&self) -> usize {
match &self.storage {
Storage::File { len, .. } => *len,
Storage::Memory(vec) => vec.len(),
}
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
// Extend the file so the caller can fill the appended data through the tail
// of the mapping. This is called at most once per output file, for
// .gdb_index, whose size is not known until the other sections have been
// written. The new space is zero-initialized, and the output buffer may
// move.
pub fn extend(&mut self, size: usize) {
let new_len = buffer_len(&self.path, self.len() as u64 + size as u64);
match &mut self.storage {
Storage::Memory(vec) => vec.resize(new_len, 0),
Storage::File { file, map, len } => {
file.set_len(new_len as u64).unwrap_or_else(|e| {
fatal!("{}: ftruncate failed: {}", self.path.display(), strerror(&e))
});
// Allocate only the appended range. Reallocating the already
// written prefix can flush dirty extents on filesystems such
// as btrfs and serialize a large part of .gdb_index output.
preallocate(file, *len as u64, size as u64);
if map.as_ref().is_none_or(|map| new_len > map.len()) {
// The appended data does not fit in the existing mapping, so map
// the grown file again. Release the old mapping first, as the
// address space may be too tight for both. The data written
// through it stays in the file.
*map = None;
*map = map_file(file, new_len).unwrap_or_else(|e| {
fatal!("{}: mmap failed: {}", self.path.display(), strerror(&e))
});
}
*len = new_len;
}
}
#[cfg(not(windows))]
self.publish_output_buffer();
}
/// Finishes writing and moves the file into place. The mapping is
/// released without waiting for the data to reach the disk.
pub fn close(self) {
#[cfg(not(windows))]
set_output_buffer_range(0, 0);
// Hold the file (and any lock) through publication of the output.
let _file = match self.storage {
Storage::File { file, map, .. } => {
drop(map);
Some(file)
}
Storage::Memory(vec) => {
if self.path == Path::new("-") {
let mut stdout = std::io::stdout().lock();
stdout
.write_all(&vec)
.and_then(|()| stdout.flush())
.unwrap_or_else(|e| fatal!("write failed: {}", strerror(&e)));
// Close the descriptor too: the parent process may
// already have exited, and a program that then runs
// the output would fail with ETXTBSY while this
// process still holds it open for writing.
// SAFETY: nothing else writes to stdout after this.
#[cfg(not(windows))]
unsafe {
libc::close(libc::STDOUT_FILENO);
}
return;
}
// A regular file is written to the temporary file, which is
// renamed below.
let dest = self.tmp_path.as_deref().unwrap_or(&self.path);
set_tmpfile(self.tmp_path.as_deref());
let mut file = open_options(self.perm)
.write(true)
.create(true)
.truncate(true)
.open(dest)
.unwrap_or_else(|e| fatal!("cannot open {}: {}", dest.display(), strerror(&e)));
file.write_all(&vec).unwrap_or_else(|e| {
fatal!("{}: write failed: {}", dest.display(), strerror(&e))
});
None
}
};
if let Some(tmp) = self.tmp_path {
// If an output file already exists, open it and then remove it.
// This is the fastest way to unlink a file, as it does not make the
// system immediately release disk blocks occupied by the file.
// The descriptor is kept until the process exits.
if let Ok(old) = File::open(&self.path) {
let _ = std::fs::remove_file(&self.path);
std::mem::forget(old);
}
std::fs::rename(&tmp, &self.path).unwrap_or_else(|e| {
fatal!(
"cannot rename {} to {}: {}",
tmp.display(),
self.path.display(),
strerror(&e)
)
});
set_tmpfile(None);
}
}
}
#[cfg(not(windows))]
fn umask() -> u32 {
// SAFETY: umask is thread-safe in the sense that it just swaps a
// process-wide value; restoring it immediately keeps it unchanged.
unsafe {
let mask = libc::umask(0);
libc::umask(mask);
mask as u32
}
}
/// Splits a buffer into consecutive slices at the given ascending
/// offsets. The first slice starts at `offsets[0]`; bytes before it are
/// not returned. The last slice extends to the end of the buffer.
pub fn split_at_offsets<'a, T>(buf: &'a mut [T], offsets: &[u64]) -> Vec<&'a mut [T]> {
let mut slices = Vec::with_capacity(offsets.len());
let mut rest = buf;
let mut pos = 0usize;
for (i, &start) in offsets.iter().enumerate() {
let start = start as usize;
let end = offsets.get(i + 1).map_or(rest.len() + pos, |&e| e as usize);
debug_assert!(pos <= start && start <= end);
rest.split_off_mut(..start - pos).unwrap();
let slice = rest.split_off_mut(..end - start).unwrap();
slices.push(slice);
pos = end;
}
slices
}
/// Borrows several disjoint ranges of a buffer mutably at once.
pub fn split_ranges<'a>(buf: &'a mut [u8], ranges: &[Range<u64>]) -> Vec<&'a mut [u8]> {
let mut order: Vec<usize> = (0..ranges.len()).collect();
order.sort_by_key(|&i| ranges[i].start);
let mut result: Vec<Option<&'a mut [u8]>> = (0..ranges.len()).map(|_| None).collect();
let mut rest = buf;
let mut pos = 0u64;
for i in order {
let r = &ranges[i];
assert!(r.start >= pos, "overlapping output ranges");
rest.split_off_mut(..(r.start - pos) as usize).unwrap();
let slice = rest.split_off_mut(..(r.end - r.start) as usize).unwrap();
result[i] = Some(slice);
pos = r.end;
}
result.into_iter().map(|s| s.unwrap()).collect()
}
#[cfg(all(test, not(windows)))]
mod tests {
use super::*;
#[test]
fn tracks_output_buffer_addresses() {
set_output_buffer_range(0x1000, 0x100);
assert!(!output_buffer_contains(0x0fff));
assert!(output_buffer_contains(0x1000));
assert!(output_buffer_contains(0x10ff));
assert!(!output_buffer_contains(0x1100));
set_output_buffer_range(0, 0);
let path = std::env::temp_dir().join(format!("mold-output-{}", std::process::id()));
let mut file = OutputFile::open(&path, 8192, 0o600, true, true);
let start = file.buf().as_ptr() as usize;
assert!(output_buffer_contains(start));
assert!(output_buffer_contains(start + 8191));
assert!(!output_buffer_contains(start + 8192));
file.close();
assert!(!output_buffer_contains(start));
// Cover empty regular files, remapping on growth, buffering in
// memory, and the locked staging state used by separate debug
// output. Keep this in the same test because output publication
// uses process-global state.
for mmap in [true, false] {
for initial in [0, 8] {
let mut file = OutputFile::open(&path, initial, 0o600, true, mmap);
file.buf().fill(7);
file.extend(65536);
assert!(file.buf()[..initial as usize].iter().all(|&b| b == 7));
assert!(file.buf()[initial as usize..].iter().all(|&b| b == 0));
file.close();
let contents = std::fs::read(&path).unwrap();
assert_eq!(contents.len() as u64, initial + 65536);
assert!(contents[..initial as usize].iter().all(|&b| b == 7));
}
}
let mut file = OutputFile::open_locked(&path, 0o600);
assert!(file.buf().is_empty());
file.resize(0);
assert!(!file.is_mmapped());
file.resize(4);
file.buf().copy_from_slice(b"test");
file.close();
assert_eq!(std::fs::read(&path).unwrap(), b"test");
std::fs::remove_file(path).unwrap();
}
}
+190
View File
@@ -0,0 +1,190 @@
use rayon::prelude::*;
/// An owned Rayon job whose result is needed by a later linker pass.
pub struct Background<T> {
receiver: std::sync::mpsc::Receiver<T>,
name: &'static str,
}
impl<T: Send + 'static> Background<T> {
pub fn spawn(name: &'static str, run: impl FnOnce() -> T + Send + 'static) -> Self {
let (sender, receiver) = std::sync::mpsc::sync_channel(1);
rayon::spawn(move || {
let _ = sender.send(run());
});
Self { receiver, name }
}
pub fn join(self) -> T {
use std::sync::mpsc::TryRecvError;
loop {
match self.receiver.try_recv() {
Ok(value) => return value,
Err(TryRecvError::Disconnected) => panic!("{} task failed", self.name),
Err(TryRecvError::Empty) => {
// A blocking receive would deadlock a one-worker pool.
if !matches!(rayon::yield_now(), Some(rayon::Yield::Executed)) {
std::thread::yield_now();
}
}
}
}
}
}
/// Stably partitions a slice, returning the number of matching elements.
/// Each block scatters into disjoint ranges computed from its match count.
pub fn stable_partition<T: Copy + Send + Sync>(
values: &mut [T],
pred: impl Fn(&T) -> bool + Sync,
) -> usize {
const BLOCK: usize = 16384;
let matches: Vec<bool> = if values.len() <= BLOCK {
values.iter().map(&pred).collect()
} else {
values.par_iter().map(&pred).collect()
};
let counts: Vec<usize> =
matches.par_chunks(BLOCK).map(|chunk| chunk.iter().filter(|&&v| v).count()).collect();
let num_matches = counts.iter().sum();
if num_matches == 0 || num_matches == values.len() {
return num_matches;
}
let mut output = values.to_vec();
let (mut yes, mut no) = output.split_at_mut(num_matches);
let parts: Vec<_> = counts
.iter()
.zip(matches.chunks(BLOCK))
.map(|(&count, flags)| {
let matched = yes.split_off_mut(..count).unwrap();
let unmatched = no.split_off_mut(..flags.len() - count).unwrap();
(matched, unmatched)
})
.collect();
parts.into_par_iter().zip(values.par_chunks(BLOCK)).zip(matches.par_chunks(BLOCK)).for_each(
|(((yes, no), input), flags)| {
let (mut y, mut n) = (0, 0);
for (&value, &matched) in input.iter().zip(flags) {
if matched {
yes[y] = value;
y += 1;
} else {
no[n] = value;
n += 1;
}
}
},
);
values
.par_chunks_mut(BLOCK)
.zip(output.par_chunks(BLOCK))
.for_each(|(dst, src)| dst.copy_from_slice(src));
num_matches
}
/// An `UnsafeCell` that may be shared between threads, like std's unstable
/// type of the same name. A parallel pass views a slice as cells so that
/// each task can write the elements it owns through a shared reference,
/// with the slice's bounds checks still applied to every access.
#[repr(transparent)]
pub struct SyncUnsafeCell<T>(std::cell::UnsafeCell<T>);
// SAFETY: the unsafe methods below require callers to keep concurrent
// accesses to one cell disjoint, which is what `Sync` promises.
unsafe impl<T: Send + Sync> Sync for SyncUnsafeCell<T> {}
impl<T> SyncUnsafeCell<T> {
pub fn new(value: T) -> Self {
Self(std::cell::UnsafeCell::new(value))
}
/// Views an exclusively borrowed slice as shared cells.
pub fn from_mut(slice: &mut [T]) -> &[Self] {
// SAFETY: a cell has the same layout as its content, and the
// exclusive borrow of the slice is given up for the shared view.
unsafe { &*(std::ptr::from_mut(slice) as *const [Self]) }
}
/// A raw pointer to the content.
pub fn get(&self) -> *mut T {
self.0.get()
}
pub fn get_mut(&mut self) -> &mut T {
self.0.get_mut()
}
/// Views cells as their contents.
///
/// # Safety
///
/// No cell in `cells` may be written while the returned slice is alive.
pub(crate) unsafe fn as_slice(cells: &[Self]) -> &[T] {
// SAFETY: a cell has the same layout as its content; the caller
// guarantees that the contents do not change.
unsafe { &*(std::ptr::from_ref(cells) as *const [T]) }
}
/// Views cells as their exclusively borrowed contents.
///
/// # Safety
///
/// No cell in `cells` may be accessed otherwise while the returned slice
/// is alive.
#[allow(clippy::mut_from_ref)]
pub unsafe fn as_mut_slice(cells: &[Self]) -> &mut [T] {
let ptr = std::cell::UnsafeCell::raw_get(cells.as_ptr().cast());
// SAFETY: a cell has the same layout as its content; the caller
// guarantees exclusive access.
unsafe { std::slice::from_raw_parts_mut(ptr, cells.len()) }
}
}
impl<T> std::fmt::Debug for SyncUnsafeCell<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SyncUnsafeCell").finish_non_exhaustive()
}
}
/// Sets flag bits without taking exclusive ownership of a cache line when
/// all requested bits are already set. Callers needing the previous value
/// must use `fetch_or` directly.
#[inline]
pub fn atomic_or(atomic: &std::sync::atomic::AtomicU8, bits: u8) {
use std::sync::atomic::Ordering::Relaxed;
if atomic.load(Relaxed) & bits != bits {
atomic.fetch_or(bits, Relaxed);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn background_job_joins_on_one_worker() {
rayon::ThreadPoolBuilder::new().num_threads(1).build().unwrap().install(|| {
let job = Background::spawn("test", || (0..100usize).into_par_iter().sum::<usize>());
assert_eq!(job.join(), 4950);
});
}
#[test]
fn preserves_both_groups_across_blocks() {
for len in [0, 1, 16383, 16384, 16385, 65539] {
for divisor in [1, 2, 7, 100000] {
let mut values: Vec<_> = (0..len).collect();
let (yes, no): (Vec<_>, Vec<_>) =
values.iter().copied().partition(|v| v % divisor == 0);
let count = stable_partition(&mut values, |v| v % divisor == 0);
assert_eq!(count, yes.len());
assert_eq!(&values[..count], yes);
assert_eq!(&values[count..], no);
assert_eq!(stable_partition(&mut values, |_| false), 0);
}
}
}
}
+50
View File
@@ -0,0 +1,50 @@
//! Lexical path manipulation, which doesn't consult the file system.
/// Normalizes a path lexically, resolving `.` and `..` components without
/// consulting the file system.
pub fn path_clean(path: &str) -> String {
clean_path(std::path::Path::new(path)).to_string_lossy().into_owned()
}
/// The bytes of a path, as the file system and Mach-O load commands
/// hold them.
pub fn path_bytes(path: &std::path::Path) -> &[u8] {
path.as_os_str().as_encoded_bytes()
}
/// Normalizes an OS path without resolving symlinks.
pub fn clean_path(path: &std::path::Path) -> std::path::PathBuf {
use std::path::{Component, PathBuf};
let mut out = PathBuf::new();
for component in path.components() {
match component {
Component::CurDir => {}
Component::ParentDir => match out.components().next_back() {
Some(Component::RootDir) => {}
None | Some(Component::ParentDir) => out.push(".."),
_ => {
out.pop();
}
},
other => out.push(other),
}
}
if out.as_os_str().is_empty() { PathBuf::from(".") } else { out }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn clean_paths() {
assert_eq!(path_clean("a/./b/../c"), "a/c");
assert_eq!(path_clean("/a/../.."), "/");
assert_eq!(path_clean("../a"), "../a");
assert_eq!(path_clean("../../a/b"), "../../a/b");
assert_eq!(path_clean("a/../../b"), "../b");
assert_eq!(path_clean("a/b/../../../c"), "../c");
assert_eq!(path_clean(".."), "..");
assert_eq!(path_clean("/.."), "/");
}
}
+347
View File
@@ -0,0 +1,347 @@
//! Statistics counters and wall-clock and CPU time accounting.
// Counter is used to collect statistics.
use std::io::Write;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex, Once};
use std::time::Instant;
use portable_atomic::AtomicI64;
use crate::out;
static COUNTERS_ENABLED: AtomicBool = AtomicBool::new(false);
static COUNTERS: Mutex<Vec<&'static Counter>> = Mutex::new(Vec::new());
pub struct Counter {
name: &'static str,
value: AtomicI64,
registered: Once,
}
impl Counter {
pub const fn new(name: &'static str) -> Self {
Self { name, value: AtomicI64::new(0), registered: Once::new() }
}
pub fn enable() {
COUNTERS_ENABLED.store(true, Ordering::Relaxed);
}
#[inline]
pub fn increment(&'static self) {
self.add(1);
}
#[inline]
pub fn add(&'static self, delta: i64) {
if !COUNTERS_ENABLED.load(Ordering::Relaxed) {
return;
}
self.registered.call_once(|| {
COUNTERS.lock().unwrap().push(self);
});
self.value.fetch_add(delta, Ordering::Relaxed);
}
pub fn print(stats: impl IntoIterator<Item = (&'static str, i64)>) {
let mut counters: Vec<_> = COUNTERS
.lock()
.unwrap()
.iter()
.map(|counter| (counter.name, counter.value.load(Ordering::Relaxed)))
.collect();
counters.extend(stats);
counters.sort_by_key(|&(_, value)| value);
for (name, value) in counters {
out!("{:>20}={}", name, value);
}
}
}
#[derive(Debug)]
struct Record {
name: String,
start: Instant,
end: Option<Instant>,
user_start: f64,
sys_start: f64,
user: f64,
sys: f64,
parent: Option<usize>,
children: Vec<usize>,
}
// Infer implicit parents from complete intervals, as C++ mold does. Overlapping
// timers need not be nested, and explicitly named parents take precedence.
fn nest_records(records: &mut [Record]) {
for record in records.iter_mut() {
record.children.clear();
}
for i in 0..records.len() {
if records[i].parent.is_none() {
let inner = &records[i];
records[i].parent = records[..i].iter().rposition(|outer| {
outer.start <= inner.start && inner.end.unwrap() <= outer.end.unwrap()
});
}
if let Some(parent) = records[i].parent {
records[parent].children.push(i);
}
}
}
/// Collects wall-clock and CPU timing records for the passes of a link.
/// Cloning shares the underlying records.
#[derive(Clone, Debug)]
pub struct Timers {
records: Option<Arc<Mutex<Vec<Record>>>>,
}
/// A running timer, stopped when dropped.
#[must_use = "a timer records its interval when dropped"]
pub struct Timer {
timers: Timers,
index: usize,
stopped: bool,
}
/// A clonable handle for starting timers beneath a running timer.
#[derive(Clone)]
pub struct TimerHandle {
timers: Timers,
parent: usize,
}
#[cfg(windows)]
#[repr(C)]
struct FileTime {
low: u32,
high: u32,
}
#[cfg(windows)]
#[link(name = "kernel32")]
unsafe extern "system" {
fn GetCurrentProcess() -> *mut std::ffi::c_void;
fn GetProcessTimes(
process: *mut std::ffi::c_void,
creation: *mut FileTime,
exit: *mut FileTime,
kernel: *mut FileTime,
user: *mut FileTime,
) -> i32;
}
#[cfg(not(windows))]
fn rusage() -> (f64, f64) {
// SAFETY: `usage` is a valid, writable rusage struct.
let mut usage: libc::rusage = unsafe { std::mem::zeroed() };
unsafe { libc::getrusage(libc::RUSAGE_SELF, &raw mut usage) };
let to_secs = |t: libc::timeval| t.tv_sec as f64 + t.tv_usec as f64 / 1_000_000.0;
(to_secs(usage.ru_utime), to_secs(usage.ru_stime))
}
#[cfg(windows)]
fn rusage() -> (f64, f64) {
let mut creation = FileTime { low: 0, high: 0 };
let mut exit = FileTime { low: 0, high: 0 };
let mut kernel = FileTime { low: 0, high: 0 };
let mut user = FileTime { low: 0, high: 0 };
// SAFETY: all FILETIME pointers are valid outputs and the pseudo-handle
// returned by GetCurrentProcess is always valid in this process.
unsafe {
GetProcessTimes(GetCurrentProcess(), &mut creation, &mut exit, &mut kernel, &mut user);
}
let to_secs = |time: FileTime| {
let ticks = (u64::from(time.high) << 32) | u64::from(time.low);
ticks as f64 / 10_000_000.0
};
(to_secs(user), to_secs(kernel))
}
impl Default for Timers {
fn default() -> Self {
Self::new()
}
}
impl Timers {
pub fn new() -> Self {
Self { records: Some(Arc::new(Mutex::new(Vec::new()))) }
}
/// Skips clock reads, system calls and shared recording when --perf (or
/// the Mach-O linker's -print_statistics) is off.
pub fn disabled() -> Self {
Self { records: None }
}
fn inactive(&self) -> Timer {
Timer { timers: self.clone(), index: 0, stopped: true }
}
/// Starts a timer whose nesting is inferred from completed time intervals.
pub fn start(&self, name: &str) -> Timer {
self.start_child(name, None)
}
fn start_child(&self, name: impl std::fmt::Display, parent: Option<usize>) -> Timer {
let Some(records) = &self.records else {
return self.inactive();
};
let (user, sys) = rusage();
let mut records = records.lock().unwrap();
let index = records.len();
records.push(Record {
name: name.to_string(),
start: Instant::now(),
end: None,
user_start: user,
sys_start: sys,
user: 0.0,
sys: 0.0,
parent,
children: Vec::new(),
});
Timer { timers: self.clone(), index, stopped: false }
}
fn stop(&self, index: usize) {
let (user, sys) = rusage();
let mut records = self.records.as_ref().unwrap().lock().unwrap();
let record = &mut records[index];
if record.end.is_none() {
record.end = Some(Instant::now());
record.user = user - record.user_start;
record.sys = sys - record.sys_start;
}
}
/// Prints the timer tree to `out`: stdout for the ELF linker, as C++
/// mold does, and stderr for the Mach-O linker, where ld64 reports its
/// -print_statistics figures.
pub fn print(&self, out: &mut dyn Write) {
let Some(records) = &self.records else {
return;
};
let mut records = records.lock().unwrap();
let now = Instant::now();
for r in records.iter_mut() {
if r.end.is_none() {
r.end = Some(now);
}
}
nest_records(&mut records);
fn print_rec(out: &mut dyn Write, records: &[Record], i: usize, indent: usize) {
let r = &records[i];
let real = r.end.unwrap().duration_since(r.start).as_secs_f64();
let _ = writeln!(
out,
" {:8.3} {:8.3} {:8.3} {}{}",
r.user,
r.sys,
real,
" ".repeat(indent * 2),
r.name
);
// nest_records adds children in their recorded start order.
for &child in &r.children {
print_rec(out, records, child, indent + 1);
}
}
let _ = writeln!(out, " User System Real Name");
for i in 0..records.len() {
if records[i].parent.is_none() {
print_rec(out, &records, i, 0);
}
}
}
}
impl Timer {
/// Starts a timer nested in this one. Timers started from parallel
/// tasks name their parent this way, because in parallel code, the
/// timer that started last is not necessarily the enclosing one.
pub fn child(&self, name: impl std::fmt::Display) -> Self {
self.timers.start_child(name, Some(self.index))
}
pub fn handle(&self) -> TimerHandle {
TimerHandle { timers: self.timers.clone(), parent: self.index }
}
pub fn stop(&mut self) {
if !self.stopped {
self.stopped = true;
self.timers.stop(self.index);
}
}
}
impl TimerHandle {
pub fn child(&self, name: impl std::fmt::Display) -> Timer {
self.timers.start_child(name, Some(self.parent))
}
}
impl Drop for Timer {
fn drop(&mut self) {
self.stop();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn implicit_timer_parents_follow_completed_intervals() {
let timers = Timers::new();
let root = timers.start("root");
let _running = [
timers.start("foreground"),
timers.start("nested"),
timers.start("background"),
timers.start("next foreground"),
root.child("explicit child"),
];
let mut records = timers.records.as_ref().unwrap().lock().unwrap();
let start = records[0].start;
for (record, (begin, end)) in
records.iter_mut().zip([(0, 100), (10, 40), (15, 25), (20, 70), (50, 80), (60, 65)])
{
record.start = start + std::time::Duration::from_millis(begin);
record.end = Some(start + std::time::Duration::from_millis(end));
}
nest_records(&mut records);
let children: Vec<_> = records.iter().map(|r| r.children.clone()).collect();
drop(records);
assert_eq!(children, [vec![1, 3, 4, 5], vec![2], vec![], vec![], vec![], vec![]]);
}
#[test]
fn optional_recording_includes_nested_and_background_timers() {
for timers in [Timers::new(), Timers::disabled()] {
let mut root = timers.start("root");
let child = root.child("child");
let handle = root.handle();
std::thread::spawn(move || drop(handle.child("background"))).join().unwrap();
drop(child);
root.stop();
root.stop();
if let Some(records) = &timers.records {
let mut records = records.lock().unwrap();
nest_records(&mut records);
assert_eq!(records.len(), 3);
assert!(records.iter().all(|record| record.end.is_some()));
assert_eq!(records[0].children, [1, 2]);
} else {
assert!(root.timers.records.is_none());
}
}
}
}
+77
View File
@@ -0,0 +1,77 @@
/// Prefetches a cache line for a future read, preferring the L1 cache.
/// Targets without a supported prefetch instruction ignore the hint.
#[inline]
pub fn prefetch(_ptr: *const u8) {
#[cfg(any(all(target_arch = "x86", target_feature = "sse"), target_arch = "x86_64"))]
// SAFETY: PREFETCHT0 is an SSE instruction, which x86-64 always has and
// the cfg requires on 32-bit x86. It is a non-faulting hint and changes
// no registers or memory.
unsafe {
std::arch::asm!(
"prefetcht0 [{ptr}]",
ptr = in(reg) _ptr,
options(readonly, nostack, preserves_flags),
);
}
#[cfg(any(target_arch = "aarch64", target_arch = "arm64ec"))]
// SAFETY: PRFM is a non-faulting hint and changes no registers or memory.
unsafe {
std::arch::asm!(
"prfm pldl1keep, [{ptr}]",
ptr = in(reg) _ptr,
options(readonly, nostack, preserves_flags),
);
}
#[cfg(target_arch = "arm")]
// SAFETY: PLD is a non-faulting hint and changes no registers or memory.
unsafe {
std::arch::asm!(
"pld [{ptr}]",
ptr = in(reg) _ptr,
options(readonly, nostack, preserves_flags),
);
}
#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))]
// SAFETY: PREFETCH.R is an ORI hint, which is a no-op without Zicbop.
// Spell it as ORI so the assembler does not require that extension.
unsafe {
std::arch::asm!(
"ori zero, {ptr}, 1",
ptr = in(reg) _ptr,
options(readonly, nostack, preserves_flags),
);
}
#[cfg(any(target_arch = "powerpc", target_arch = "powerpc64"))]
// SAFETY: DCBT is a non-faulting hint and changes no registers or memory.
unsafe {
std::arch::asm!(
"dcbt 0, {ptr}",
ptr = in(reg) _ptr,
options(readonly, nostack, preserves_flags),
);
}
#[cfg(target_arch = "s390x")]
// SAFETY: PFD is a non-faulting hint and changes no registers or memory.
unsafe {
std::arch::asm!(
"pfd 1, 0({ptr})",
ptr = in(reg_addr) _ptr,
options(readonly, nostack, preserves_flags),
);
}
#[cfg(target_arch = "loongarch64")]
// SAFETY: PRELD is a non-faulting hint and changes no registers or memory.
unsafe {
std::arch::asm!(
"preld 0, {ptr}, 0",
ptr = in(reg) _ptr,
options(readonly, nostack, preserves_flags),
);
}
}
+79
View File
@@ -0,0 +1,79 @@
//! Records of object files, read, written and viewed in their file
//! representation. A record's fields are bytes, or integers backed by
//! bytes that read and write in the file's byte order, so a record has
//! alignment one: one in a possibly unaligned archive member is read in
//! place, without depending on the host's byte order.
/// A record stored in its file representation.
///
/// # Safety
///
/// Implementations must have alignment one, contain no padding or references,
/// and accept every bit pattern. These requirements let records in possibly
/// unaligned archive members be read and written without host dependencies.
pub unsafe trait FileRecord: Clone + Copy + Default + Send + Sync + 'static {
fn parse(bytes: &[u8]) -> Self {
const { assert!(align_of::<Self>() == 1) };
assert!(bytes.len() >= size_of::<Self>());
// SAFETY: the trait guarantees that every bit pattern is valid, and
// the length check proves that a complete record is available.
unsafe { bytes.as_ptr().cast::<Self>().read_unaligned() }
}
fn write(&self, buf: &mut [u8]) {
assert!(buf.len() >= size_of::<Self>());
// SAFETY: `buf` has room for the complete record, and copying bytes
// does not depend on its alignment.
unsafe {
std::ptr::copy_nonoverlapping(
std::ptr::from_ref(self).cast::<u8>(),
buf.as_mut_ptr(),
size_of::<Self>(),
);
}
}
/// The record's bytes, as a file holds them.
fn as_bytes(&self) -> &[u8] {
// SAFETY: a record has no padding, so all of its bytes are
// initialized.
unsafe {
std::slice::from_raw_parts(std::ptr::from_ref(self).cast::<u8>(), size_of::<Self>())
}
}
fn write_all(records: &[Self], buf: &mut [u8]) {
for (record, slot) in records.iter().zip(buf.chunks_exact_mut(size_of::<Self>())) {
record.write(slot);
}
}
}
/// Views one record directly in its file representation.
pub fn record_from_bytes<R: FileRecord>(data: &[u8]) -> &R {
const { assert!(align_of::<R>() == 1) };
assert!(data.len() >= size_of::<R>());
// SAFETY: FileRecord requires alignment one and every bit pattern to be
// valid. The length check proves that one complete record is present.
unsafe { &*data.as_ptr().cast() }
}
/// Views records directly in their file representation.
pub fn records_from_bytes<R: FileRecord>(data: &[u8]) -> &[R] {
const { assert!(align_of::<R>() == 1 && size_of::<R>() != 0) };
let size = size_of::<R>();
assert!(data.len().is_multiple_of(size));
// SAFETY: FileRecord requires alignment one and every bit pattern to be
// valid. The resulting slice covers exactly `data`.
unsafe { std::slice::from_raw_parts(data.as_ptr().cast(), data.len() / size) }
}
/// Mutably views records directly in their file representation.
pub fn records_from_bytes_mut<R: FileRecord>(data: &mut [u8]) -> &mut [R] {
const { assert!(align_of::<R>() == 1 && size_of::<R>() != 0) };
let size = size_of::<R>();
assert!(data.len().is_multiple_of(size));
// SAFETY: FileRecord requires alignment one and every bit pattern to be
// valid. `data` is exclusively borrowed for the returned slice.
unsafe { std::slice::from_raw_parts_mut(data.as_mut_ptr().cast(), data.len() / size) }
}
+121
View File
@@ -0,0 +1,121 @@
//! Response files. If a command line argument is in the form of
//! `@path/to/some/file` (i.e. it starts with an at sign), the linker reads
//! the given file and interprets its contents as a list of command line
//! arguments. A file containing command line arguments is called a
//! "response file".
//!
//! A response file is often used to pass a very large number of arguments
//! to the linker without exceeding the kernel's command line length limit.
use std::borrow::Cow;
use std::ffi::{OsStr, OsString};
use std::path::Path;
use bstr::ByteVec;
use crate::bytes::{display, is_space, os_str};
use crate::fatal;
use crate::mapped_file::MappedFile;
/// Replaces each "@path/to/some/text/file" argument with the arguments
/// the file holds. For the Mach-O linker (`macho`), an argument starting
/// with "@rpath", "@loader_path" or "@executable_path" is a dylib path,
/// not a response file, wherever it is, even an option's argument.
pub fn expand_response_files(argv: Vec<OsString>, macho: bool) -> Vec<Cow<'static, OsStr>> {
let mut args = Vec::new();
for arg in argv {
if let Some(path) = response_file(arg.as_encoded_bytes(), macho) {
args.extend(read_response_file(path, 1, macho));
} else {
args.push(Cow::Owned(arg));
}
}
args
}
/// The response file an argument names, if it names one (see
/// expand_response_files).
fn response_file(arg: &[u8], macho: bool) -> Option<&Path> {
const DYLIB_PATHS: [&[u8]; 3] = [b"@rpath", b"@loader_path", b"@executable_path"];
if macho && DYLIB_PATHS.iter().any(|prefix| arg.starts_with(prefix)) {
return None;
}
arg.strip_prefix(b"@").map(|path| Path::new(os_str(path)))
}
/// Opens a response file, tokenizes its contents, and returns the tokens.
fn read_response_file(path: &Path, depth: usize, macho: bool) -> Vec<Cow<'static, OsStr>> {
if depth > 10 {
fatal!("{}: response file nesting too deep", path.display());
}
let mf = MappedFile::must_open(path);
mf.set_dependency(false);
let data = mf.data();
// Arguments are passed on as C strings, e.g. to the LTO plugin, so they
// must not contain a NUL byte. Arguments given by the OS never do.
if data.contains(&0) {
fatal!("{}: response file contains a NUL byte", path.display());
}
let mut expanded = Vec::new();
let mut i = 0;
while i < data.len() {
if is_space(data[i]) {
i += 1;
continue;
}
// Plain tokens can borrow the mapping, which lives for the complete
// link. Copy only when removing quotes or backslashes.
let start = i;
while i < data.len() && !is_space(data[i]) && !matches!(data[i], b'\\' | b'\'' | b'"') {
i += 1;
}
let mut tok = Cow::Borrowed(&data[start..i]);
let mut quote = None;
while i < data.len() {
let c = data[i];
if c == b'\\' {
if i + 1 == data.len() {
fatal!("{}: premature end of input", path.display());
}
tok.to_mut().push(data[i + 1]);
i += 2;
} else if let Some(q) = quote {
if c == q {
quote = None;
} else {
tok.to_mut().push(c);
}
i += 1;
} else if c == b'\'' || c == b'"' {
quote = Some(c);
i += 1;
} else if is_space(c) {
break;
} else {
tok.to_mut().push(c);
i += 1;
}
}
if quote.is_some() {
fatal!("{}: premature end of input", path.display());
}
if let Some(nested) = response_file(&tok, macho) {
expanded.extend(read_response_file(nested, depth + 1, macho));
} else {
expanded.push(match tok {
Cow::Borrowed(bytes) => Cow::Borrowed(os_str(bytes)),
Cow::Owned(bytes) => Cow::Owned(
bytes
.into_os_string()
.unwrap_or_else(|e| fatal!("invalid OS string: {}", display(e.as_bytes()))),
),
});
}
}
expanded
}
+166
View File
@@ -0,0 +1,166 @@
//! A configurable implementation of SipHash.
//!
//! The compression rounds, finalization rounds and 64- or 128-bit output are
//! selected with const generic parameters. This implementation is based on
//! the reference implementation at https://github.com/rui314/siphash.
pub struct SipHashTmpl<const C_ROUNDS: usize, const D_ROUNDS: usize, const OUTLEN: usize> {
v0: u64,
v1: u64,
v2: u64,
v3: u64,
buf: [u8; 8],
buflen: u8,
sum: u8,
}
impl<const C_ROUNDS: usize, const D_ROUNDS: usize, const OUTLEN: usize>
SipHashTmpl<C_ROUNDS, D_ROUNDS, OUTLEN>
{
#[inline]
pub fn new(key: &[u8; 16]) -> Self {
const { assert!(OUTLEN == 64 || OUTLEN == 128) };
let k0 = u64::from_le_bytes(key[..8].try_into().unwrap());
let k1 = u64::from_le_bytes(key[8..].try_into().unwrap());
let mut hasher = Self {
v0: 0x736f_6d65_7073_6575 ^ k0,
v1: 0x646f_7261_6e64_6f6d ^ k1,
v2: 0x6c79_6765_6e65_7261 ^ k0,
v3: 0x7465_6462_7974_6573 ^ k1,
buf: [0; 8],
buflen: 0,
sum: 0,
};
if OUTLEN == 128 {
hasher.v1 ^= 0xee;
}
hasher
}
// ICF repeatedly hashes fixed-size digests. Inlining lets the optimizer
// remove the buffering paths for these word-aligned updates.
#[inline(always)]
pub fn update(&mut self, mut msg: &[u8]) {
self.sum = self.sum.wrapping_add(msg.len() as u8);
if self.buflen != 0 {
let buflen = self.buflen as usize;
if buflen + msg.len() < 8 {
self.buf[buflen..buflen + msg.len()].copy_from_slice(msg);
self.buflen += msg.len() as u8;
return;
}
let n = 8 - buflen;
self.buf[buflen..].copy_from_slice(&msg[..n]);
self.compress(u64::from_le_bytes(self.buf));
msg = &msg[n..];
self.buflen = 0;
}
let (words, msg) = msg.as_chunks::<8>();
for &word in words {
self.compress(u64::from_le_bytes(word));
}
self.buf[..msg.len()].copy_from_slice(msg);
self.buflen = msg.len() as u8;
}
/// Hashes a word in little-endian byte order. The common aligned case
/// bypasses the byte buffer, including in loops of fixed-size updates.
#[inline(always)]
pub fn update_u64(&mut self, word: u64) {
if self.buflen != 0 {
self.update(&word.to_le_bytes());
return;
}
self.sum = self.sum.wrapping_add(8);
self.compress(word);
}
#[inline]
pub fn finish(mut self, out: &mut [u8]) {
assert_eq!(out.len() * 8, OUTLEN);
self.buf[self.buflen as usize..].fill(0);
self.compress((u64::from(self.sum) << 56) | u64::from_le_bytes(self.buf));
self.v2 ^= if OUTLEN == 128 { 0xee } else { 0xff };
self.finalize();
out[..8].copy_from_slice(&(self.v0 ^ self.v1 ^ self.v2 ^ self.v3).to_le_bytes());
if OUTLEN == 128 {
self.v1 ^= 0xdd;
self.finalize();
out[8..].copy_from_slice(&(self.v0 ^ self.v1 ^ self.v2 ^ self.v3).to_le_bytes());
}
}
#[inline]
fn round(&mut self) {
self.v0 = self.v0.wrapping_add(self.v1);
self.v1 = self.v1.rotate_left(13);
self.v1 ^= self.v0;
self.v0 = self.v0.rotate_left(32);
self.v2 = self.v2.wrapping_add(self.v3);
self.v3 = self.v3.rotate_left(16);
self.v3 ^= self.v2;
self.v0 = self.v0.wrapping_add(self.v3);
self.v3 = self.v3.rotate_left(21);
self.v3 ^= self.v0;
self.v2 = self.v2.wrapping_add(self.v1);
self.v1 = self.v1.rotate_left(17);
self.v1 ^= self.v2;
self.v2 = self.v2.rotate_left(32);
}
#[inline]
fn compress(&mut self, m: u64) {
self.v3 ^= m;
for _ in 0..C_ROUNDS {
self.round();
}
self.v0 ^= m;
}
#[inline]
fn finalize(&mut self) {
for _ in 0..D_ROUNDS {
self.round();
}
}
}
pub type SipHash13_128 = SipHashTmpl<1, 3, 128>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn word_updates_match_bytes_with_partial_buffers_and_length_wraparound() {
let key = std::array::from_fn(|i| i as u8);
let data: Vec<u8> = (0..1040).map(|i| (i * 97) as u8).collect();
for prefix in 0..8 {
for words in 0..128 {
let end = prefix + words * 8;
let mut bytes = SipHash13_128::new(&key);
bytes.update(&data[..end + 3]);
let mut mixed = SipHash13_128::new(&key);
mixed.update(&data[..prefix]);
for &word in data[prefix..end].as_chunks::<8>().0 {
mixed.update_u64(u64::from_le_bytes(word));
}
mixed.update(&data[end..end + 3]);
let (mut expected, mut actual) = ([0; 16], [0; 16]);
bytes.finish(&mut expected);
mixed.finish(&mut actual);
assert_eq!(actual, expected, "prefix={prefix}, words={words}");
}
}
}
}
+140
View File
@@ -0,0 +1,140 @@
//! Process management: forking a child to hide exit latency, and signal
//! handling for disk-full errors.
#[cfg(not(windows))]
use std::fs::File;
#[cfg(not(windows))]
use std::io::{Read, Write};
#[cfg(not(windows))]
use std::os::unix::io::{FromRawFd, OwnedFd};
#[cfg(not(windows))]
use std::sync::Mutex;
#[cfg(not(windows))]
static PIPE_WRITER: Mutex<Option<OwnedFd>> = Mutex::new(None);
// Exiting from a program with large memory usage is slow --
// it may take a few hundred milliseconds. To hide the latency,
// we fork a child and let it do the actual linking work.
#[cfg(not(windows))]
pub fn fork_child() {
let mut pipefd = [0i32; 2];
// Keep the pipe's descriptors inheritable across the LTO restart.
// SAFETY: pipe initializes both descriptors on success; each then has
// exactly one owner in this process.
let (reader, writer) = unsafe {
if libc::pipe(pipefd.as_mut_ptr()) == -1 {
eprintln!("mold: pipe failed");
std::process::exit(1);
}
(OwnedFd::from_raw_fd(pipefd[0]), OwnedFd::from_raw_fd(pipefd[1]))
};
// SAFETY: this runs before the linker starts its worker threads. The
// parent only waits for completion and exits; the child continues linking.
unsafe {
let pid = libc::fork();
if pid == -1 {
eprintln!("mold: fork failed");
std::process::exit(1);
}
if pid > 0 {
// Parent
drop(writer);
if File::from(reader).read_exact(&mut [0u8]).is_ok() {
libc::_exit(0);
}
// If SIGCHLD is ignored, which is inherited across exec, the
// child is reaped automatically and waitpid fails. Its exit
// status is then lost, so report a failure.
let mut status = 0;
if libc::waitpid(pid, &raw mut status, 0) == -1 {
libc::_exit(1);
}
if libc::WIFEXITED(status) {
libc::_exit(libc::WEXITSTATUS(status));
}
if libc::WIFSIGNALED(status) {
libc::raise(libc::WTERMSIG(status));
}
libc::_exit(1);
}
}
// Child
drop(reader);
*PIPE_WRITER.lock().unwrap() = Some(writer);
}
#[cfg(windows)]
pub fn fork_child() {}
/// Tells the parent that the output is complete.
#[cfg(not(windows))]
pub fn notify_parent() {
let Some(writer) = PIPE_WRITER.lock().unwrap().take() else {
return;
};
let _ = File::from(writer).write_all(&[1]);
}
#[cfg(windows)]
pub fn notify_parent() {}
#[cfg(not(windows))]
extern "C" fn on_signal(
signo: libc::c_int,
info: *mut libc::siginfo_t,
_context: *mut libc::c_void,
) {
// mold mmap's an output file, and the mmap succeeds even if there's
// not enough space left on the filesystem. The actual disk blocks are
// not allocated on the mmap call but when the program writes to it
// for the first time.
//
// If a disk becomes full as a result of a write to an mmap'ed memory
// region, the failure of the write is reported as a SIGBUS. This
// signal handler catches that signal and prints out a user-friendly
// error message. Without this, it is very hard to realize that the
// disk might be full.
let addr = if info.is_null() {
0
} else {
// SAFETY: SA_SIGINFO gives the handler a valid siginfo_t pointer.
unsafe { (*info).si_addr() as usize }
};
if (signo == libc::SIGSEGV || signo == libc::SIGBUS)
&& crate::output_file::output_buffer_contains(addr)
{
// Handle disk full error
let msg = b"mold: failed to write to an output file. Disk full?\n";
// SAFETY: write is async-signal-safe.
unsafe {
libc::write(libc::STDERR_FILENO, msg.as_ptr().cast(), msg.len());
}
}
crate::output_file::cleanup();
// Re-throw the signal
// SAFETY: restoring the default handlers and re-raising.
unsafe {
libc::signal(libc::SIGSEGV, libc::SIG_DFL);
libc::signal(libc::SIGBUS, libc::SIG_DFL);
libc::raise(signo);
}
}
#[cfg(not(windows))]
pub fn install_signal_handler() {
// SAFETY: installing a signal handler with the three-argument SA_SIGINFO
// calling convention.
unsafe {
let mut action: libc::sigaction = std::mem::zeroed();
action.sa_sigaction = on_signal as *const () as libc::sighandler_t;
libc::sigemptyset(&raw mut action.sa_mask);
action.sa_flags = libc::SA_SIGINFO;
libc::sigaction(libc::SIGSEGV, &raw const action, std::ptr::null_mut());
libc::sigaction(libc::SIGBUS, &raw const action, std::ptr::null_mut());
}
}
#[cfg(windows)]
pub fn install_signal_handler() {}
+109
View File
@@ -0,0 +1,109 @@
//! This file contains functions to create a tar file.
use std::fs::File;
use std::io::{self, Seek, SeekFrom, Write};
use std::ffi::{OsStr, OsString};
use std::path::Path;
use crate::bits::align_to;
use crate::path::clean_path;
const BLOCK_SIZE: u64 = 512;
// TarWriter creates the archive used by --repro.
//
// If you pass `--repro` to mold, mold collects all input files and puts them
// into `<output-file-path>.repro.tar`, making it easy to run the same command
// with the same command-line arguments.
//
/// A tar file consists of one or more Ustar headers followed by data.
/// Each Ustar header represents a single file in an archive.
///
/// tar is an old file format, and its `name` field is only 100 bytes long.
/// If `name` is longer than 100 bytes, we can emit a PAX header before a
/// Ustar header to store a long filename.
///
/// For simplicity, we always emit a PAX header even for a short filename.
pub struct TarWriter {
out: File,
basedir: OsString,
}
fn ustar_header(name: &[u8], mode: &[u8], size: u64, typeflag: u8) -> [u8; 512] {
let mut hdr = [0u8; 512];
hdr[..name.len()].copy_from_slice(name);
hdr[100..100 + mode.len()].copy_from_slice(mode);
let size = format!("{size:011o}");
hdr[124..124 + size.len()].copy_from_slice(size.as_bytes());
hdr[148..156].fill(b' ');
hdr[156] = typeflag;
hdr[257..262].copy_from_slice(b"ustar");
hdr[263..265].copy_from_slice(b"00");
// Compute checksum. The field is six octal digits, a NUL byte and a
// trailing space.
let checksum: u32 = hdr.iter().map(|&b| b as u32).sum();
let checksum = format!("{checksum:06o}\0");
hdr[148..148 + checksum.len()].copy_from_slice(checksum.as_bytes());
hdr
}
/// Construct a string which contains something like
/// "16 path=foo/bar\n" where 16 is the size of the string
/// including the size string itself.
fn encode_path(basedir: &OsStr, path: &Path) -> Vec<u8> {
let mut name = basedir.to_os_string();
name.push("/");
name.push(path);
let path = clean_path(Path::new(&name));
let bytes = path.as_os_str().as_encoded_bytes();
let len = b" path=\n".len() + bytes.len();
let total = len + len.to_string().len();
let total = len + total.to_string().len();
// PAX defines the `path` value as UTF-8. If a filename isn't valid
// UTF-8, we have to declare that we are storing raw bytes, or bsdtar
// (the tar of FreeBSD and macOS) fails to extract the archive. We
// don't declare it for every file because GNU tar doesn't know the
// keyword and prints a warning for each occurrence.
let mut out = Vec::new();
if path.to_str().is_none() {
out.extend_from_slice(b"21 hdrcharset=BINARY\n");
}
out.extend_from_slice(format!("{total} path=").as_bytes());
out.extend_from_slice(bytes);
out.push(b'\n');
out
}
impl TarWriter {
pub fn open(output_path: &Path, basedir: &OsStr) -> io::Result<Self> {
Ok(Self { out: File::create(output_path)?, basedir: basedir.to_os_string() })
}
pub fn append(&mut self, path: &Path, data: &[u8]) -> io::Result<()> {
let attr = encode_path(&self.basedir, path);
// Write PAX header
self.out.write_all(&ustar_header(b"/", b"", attr.len() as u64, b'x'))?;
// Write pathname
self.out.write_all(&attr)?;
self.pad()?;
// Write Ustar header
self.out.write_all(&ustar_header(b"", b"0000664", data.len() as u64, b'0'))?;
// Write file contents
self.out.write_all(data)?;
self.pad()?;
// A tar file must end with two empty blocks
let pos = self.out.stream_position()?;
self.out.set_len(pos + BLOCK_SIZE * 2)
}
fn pad(&mut self) -> io::Result<()> {
let pos = self.out.stream_position()?;
self.out.seek(SeekFrom::Start(align_to(pos, BLOCK_SIZE)))?;
Ok(())
}
}
+55
View File
@@ -0,0 +1,55 @@
use std::sync::{Mutex, MutexGuard};
// Keep independent workers' locks and buffers off the same cache line.
#[repr(align(128))]
struct Worker<T>(Mutex<T>);
/// Persistent scratch storage per Rayon worker, plus a slot for callers
/// outside the pool. Look up and lock once per file, not once per symbol.
pub struct WorkerLocal<T>(Vec<Worker<T>>);
impl<T> WorkerLocal<T> {
pub fn new(init: impl Fn() -> T) -> Self {
Self((0..=rayon::current_num_threads()).map(|_| Worker(Mutex::new(init()))).collect())
}
pub fn get(&self) -> MutexGuard<'_, T> {
let fallback = self.0.len() - 1;
let worker = rayon::current_thread_index().unwrap_or(fallback).min(fallback);
self.0[worker].0.lock().unwrap()
}
pub fn into_values(self) -> impl Iterator<Item = T> {
self.0.into_iter().map(|worker| worker.0.into_inner().unwrap())
}
pub fn take(&mut self) -> impl Iterator<Item = T> + '_
where
T: Default,
{
self.0.iter_mut().map(|worker| std::mem::take(worker.0.get_mut().unwrap()))
}
}
#[cfg(test)]
mod tests {
use super::*;
use rayon::prelude::*;
#[test]
fn reuses_worker_storage_and_keeps_an_outside_slot() {
let pool = rayon::ThreadPoolBuilder::new().num_threads(2).build().unwrap();
let mut slots = pool.install(|| {
let slots = WorkerLocal::new(Vec::new);
for batch in 0..3 {
(0..100).into_par_iter().for_each(|i| slots.get().push(batch * 100 + i));
}
slots
});
slots.get().push(300);
let mut values: Vec<_> = slots.take().flatten().collect();
values.sort_unstable();
assert_eq!(values, (0..301).collect::<Vec<_>>());
assert!(slots.into_values().all(|slot| slot.is_empty()));
}
}
+184 -167
View File
@@ -1,219 +1,236 @@
#!/bin/bash
#
# This script creates a mold binary distribution. The output is written in
# `dist` directory as `mold-$version-$arch-linux.tar.gz` (e.g.,
# `mold-1.0.3-x86_64-linux.tar.gz`).
# This script creates a mold binary distribution. The output is written to
# the `dist` directory as `mold-$version-$arch-linux.tar.gz` (e.g.
# `mold-2.42.0-x86_64-linux.tar.gz`). The builds for musl-based systems,
# `x86_64-musl` and `aarch64-musl`, are written as
# `mold-$version-$arch-linux-musl.tar.gz` instead.
#
# The mold executable created by this script is statically linked to
# libstdc++ but dynamically linked to libc, libm, libz, and librt, as
# these libraries almost always exist on any Linux system. We can't
# statically link libc because doing so would disable dlopen(), which is
# necessary to open the LTO linker plugin.
#
# This script aims to produce reproducible outputs. That means if you run
# the script twice on the same git commit, it should produce bit-by-bit
# identical binary files. This property is crucial as a countermeasure
# against supply chain attacks. With this, you can verify that the binary
# files distributed on the GitHub release pages are created from the
# commit with release tags by rebuilding the binaries yourself.
# This script aims to produce reproducible outputs. The container images,
# Rust toolchain, Cargo dependencies and file timestamps are pinned so that
# the same git commit can produce a bit-for-bit identical binary file. This
# property serves as a strong safeguard against supply chain attacks. With
# a reproducible build, anyone can independently verify that the binary
# files published on our GitHub release page were built from the git commit
# tagged for release by rebuilding the binaries themselves.
#
# Debian provides snapshot.debian.org to host all historical binary
# packages. We use it to construct Podman images pinned to a
# particular timestamp.
# packages. Distro package repositories must be pinned as well as the base
# image before a build is fully reproducible. The loongarch64 build still
# uses the live Debian sid repository, so it does not yet have that property.
# Neither do the musl builds, which are built on Alpine Linux. Alpine keeps
# only the latest version of each package, so its packages can't be pinned.
#
# We aim to use a reasonably old Debian version because we'll dynamically
# link glibc to mold, and a binary linked against a newer version of glibc
# won't work on a system with an older version of glibc.
# The mold executable created by this script is dynamically linked to the
# system C runtime and other standard system libraries. We can't statically
# link glibc or musl because doing so would disable dlopen(), which is
# required to load the LTO linker plugin.
#
# We want to build mold with Clang rather than GCC because mold's
# Identical Code Folding works best with the LLVM address significance
# table (.llvm_addrsig). Building a release binary with GCC yields a
# slightly larger binary than Clang's.
# We use a reasonably old Debian version for the build environment because
# a binary dynamically linked against a newer version of glibc won't work
# on a system with an older version of glibc. Likewise, a binary built on a
# newer Alpine may use functions that an older musl or libgcc doesn't have,
# so the musl builds use the oldest Alpine release that is still supported.
#
# We need a recent version of Clang to build mold. If it's not available
# via apt-get, we'll build it ourselves.
# The Rust toolchain is downloaded from the official Rust distribution
# site. Its version and SHA-256 hash are recorded below, so the toolchain
# is another pinned build input.
#
# You may need to install qemu-user-static package to build non-native
# binaries.
# This script can be used to create non-native binaries (e.g., building
# aarch64 binary on x86-64) because Podman automatically runs everything
# under QEMU if the container image is not native. To use this script for
# non-native builds, you may need to install the qemu-user-static package.
set -e -x
cd "$(dirname $0)"
cd "$(dirname "$0")"
# The executable embeds the hash of the Git commit it is built from, and
# the commit's timestamp is used as the file timestamp, so this script
# must run in a Git clone. A worktree doesn't work either because its
# `.git` file refers to a directory outside the build container.
if [ ! -d .git ]; then
echo "$0: must be run in a Git clone of mold" >&2
exit 1
fi
usage() {
echo "Usage: $0 [ x86_64 | aarch64 | arm | riscv64 | ppc64le | s390x | loongarch64 ]"
echo "Usage: $0 [ x86_64 | aarch64 | arm | riscv64 | ppc64le | s390x | loongarch64 | x86_64-musl | aarch64-musl ]"
exit 1
}
case $# in
0)
arch=$(uname -m)
if [ $arch = arm64 ]; then
if [ "$arch" = arm64 ]; then
arch=aarch64
elif [[ $arch = arm* ]]; then
elif [[ "$arch" = arm* ]]; then
arch=arm
fi
;;
1)
arch="$1"
arch=$1
;;
*)
usage
;;
esac
# Create a Podman image.
if [ "$GITHUB_REPOSITORY" = '' ]; then
image=mold-builder-$arch
image_build="podman build --arch $arch -t $image -"
else
# If this script is running on GitHub Actions, we want to cache
# the created container image in GitHub's container repostiory.
image=ghcr.io/$GITHUB_REPOSITORY/mold-builder-$arch
image_build="podman build --arch $arch -t $image --output=type=registry --layers --cache-to $image --cache-from $image -"
fi
rust_version=1.97.1
# Switch to the pinned snapshot.debian.org sources that the Debian images
# ship commented out; the live mirrors no longer carry every architecture.
apt_setup="sed -i -e '/^deb/d' -e 's/^# deb /deb /g' /etc/apt/sources.list"
case $arch in
x86_64)
# Debian 9 (Stretch) released in June 2017.
# We use a Google-provided mirror (gcr.io) of the official Docker hub
# (docker.io) because docker.io has a strict rate limit policy.
cat <<EOF | $image_build
FROM mirror.gcr.io/library/debian:stretch@sha256:c5c5200ff1e9c73ffbf188b4a67eb1c91531b644856b4aefe86a58d2f0cb05be
ENV DEBIAN_FRONTEND=noninteractive TZ=UTC
RUN sed -i -e '/^deb/d' -e 's/^# deb /deb [trusted=yes] /g' /etc/apt/sources.list && \
echo 'Acquire::Retries "10"; Acquire::http::timeout "10"; Acquire::Check-Valid-Until "false";' > /etc/apt/apt.conf.d/80-retries && \
apt-get update && \
apt-get install -y --no-install-recommends wget file make gcc g++ zlib1g-dev libssl-dev && \
rm -rf /var/lib/apt/lists
# Build CMake 3.27
RUN mkdir /build && \
cd /build && \
wget -O- --no-check-certificate --progress=dot:mega https://cmake.org/files/v3.27/cmake-3.27.7.tar.gz | tar xzf - --strip-components=1 && \
./bootstrap --parallel=\$(nproc) && \
make -j\$(nproc) && \
make install && \
rm -rf /build
# Build GCC 14
RUN mkdir /build && \
cd /build && \
wget -O- --no-check-certificate --progress=dot:mega https://ftpmirror.gnu.org/gcc/gcc-14.2.0/gcc-14.2.0.tar.gz | tar xzf - --strip-components=1 && \
mkdir gmp mpc mpfr && \
wget -O- --no-check-certificate --progress=dot:mega https://ftpmirror.gnu.org/gmp/gmp-6.3.0.tar.gz | tar xzf - --strip-components=1 -C gmp && \
wget -O- --no-check-certificate --progress=dot:mega https://ftpmirror.gnu.org/mpc/mpc-1.3.1.tar.gz | tar xzf - --strip-components=1 -C mpc && \
wget -O- --no-check-certificate --progress=dot:mega https://ftpmirror.gnu.org/mpfr/mpfr-4.2.1.tar.gz | tar xzf - --strip-components=1 -C mpfr && \
./configure --prefix=/usr --enable-languages=c,c++ --disable-bootstrap --disable-multilib && \
make -j\$(nproc) && \
make install && \
ln -sf /usr/lib64/libstdc++.so.6 /usr/lib/x86_64-linux-gnu/libstdc++.so.6 && \
rm -rf /build
# Build GNU binutils 2.43
RUN mkdir /build && \
cd /build && \
wget -O- --no-check-certificate --progress=dot:mega https://ftpmirror.gnu.org/binutils/binutils-2.43.tar.gz | tar xzf - --strip-components=1 && \
./configure --prefix=/usr && \
make -j\$(nproc) && \
make install && \
rm -fr /build
# Build Python 3.12.7
RUN mkdir /build && \
cd /build && \
wget -O- --no-check-certificate --progress=dot:mega https://www.python.org/ftp/python/3.12.7/Python-3.12.7.tgz | tar xzf - --strip-components=1 && \
./configure && \
make -j\$(nproc) && \
make install && \
rm -rf /build
# Build LLVM 19
RUN mkdir /build && \
cd /build && \
wget -O- --no-check-certificate --progress=dot:mega https://github.com/llvm/llvm-project/archive/refs/tags/llvmorg-19.1.7.tar.gz | tar xzf - --strip-components=1 && \
mkdir b && \
cd b && \
cmake -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_PROJECTS=clang ../llvm && \
cmake --build . -j\$(nproc) && \
cmake --install . --strip && \
rm -rf /build
EOF
;;
aarch64 | arm | ppc64le | s390x)
# Debian 11 (Bullseye) released in August 2021
#
# We don't want to build Clang for these targets with Qemu becuase
# that'd take extremely long time. Also I believe old build machines
# are usually x86-64.
cat <<EOF | $image_build
FROM mirror.gcr.io/library/debian:bullseye-20240904@sha256:8ccc486c29a3ad02ad5af7f1156e2152dff3ba5634eec9be375269ef123457d8
ENV DEBIAN_FRONTEND=noninteractive TZ=UTC
RUN sed -i -e '/^deb/d' -e 's/^# deb /deb [trusted=yes] /g' /etc/apt/sources.list && \
echo 'Acquire::Retries "10"; Acquire::http::timeout "10"; Acquire::Check-Valid-Until "false";' > /etc/apt/apt.conf.d/80-retries && \
apt-get update && \
apt-get install -y --no-install-recommends build-essential gcc-10 g++-10 clang-16 cmake && \
ln -sf /usr/bin/clang-16 /usr/bin/clang && \
ln -sf /usr/bin/clang++-16 /usr/bin/clang++ && \
rm -rf /var/lib/apt/lists
EOF
# We use a Google-provided mirror (mirror.gcr.io) instead of the official
# Docker Hub (docker.io) because docker.io has a strict rate limit policy.
base_image=mirror.gcr.io/library/debian:stretch@sha256:c5c5200ff1e9c73ffbf188b4a67eb1c91531b644856b4aefe86a58d2f0cb05be
rust_target=x86_64-unknown-linux-gnu
rust_sha256=b4cdbc7cc6b0ee0a2666b1872769fdb2ad8393b28b63952f6493b4b400e4832b
;;
aarch64)
# Debian 11 (Bullseye) released in August 2021.
base_image=mirror.gcr.io/library/debian:bullseye-20240904@sha256:8ccc486c29a3ad02ad5af7f1156e2152dff3ba5634eec9be375269ef123457d8
rust_target=aarch64-unknown-linux-gnu
rust_sha256=2f2496c70bd336a66a4c8baf2d303ba161f3552f192444c3639ba903c7c1e2c5
;;
arm)
# Debian 11 (Bullseye) released in August 2021.
base_image=mirror.gcr.io/library/debian:bullseye-20240904@sha256:8ccc486c29a3ad02ad5af7f1156e2152dff3ba5634eec9be375269ef123457d8
rust_target=armv7-unknown-linux-gnueabihf
rust_sha256=e89c5e33aaddc6ef56857000c9117875c2997e9a1a500bd7b16277c9874b002f
;;
riscv64)
cat <<EOF | $image_build
FROM mirror.gcr.io/riscv64/debian:unstable-20240926@sha256:25654919c2926f38952cdd14b3300d83d13f2d820715f78c9f4b7a1d9399bf48
ENV DEBIAN_FRONTEND=noninteractive TZ=UTC
RUN sed -i -e '/^URIs/d' -e 's/^# http/URIs: http/' /etc/apt/sources.list.d/debian.sources && \
echo 'Acquire::Retries "10"; Acquire::http::timeout "10"; Acquire::Check-Valid-Until "false";' > /etc/apt/apt.conf.d/80-retries && \
apt-get update && \
apt-get install -y --no-install-recommends build-essential gcc-14 g++-14 clang-18 cmake && \
ln -sf /usr/bin/clang-18 /usr/bin/clang && \
ln -sf /usr/bin/clang++-18 /usr/bin/clang++ && \
rm -rf /var/lib/apt/lists
EOF
base_image=mirror.gcr.io/riscv64/debian:unstable-20240926@sha256:25654919c2926f38952cdd14b3300d83d13f2d820715f78c9f4b7a1d9399bf48
apt_setup="sed -i -e '/^URIs/d' -e 's/^# http/URIs: http/' /etc/apt/sources.list.d/debian.sources"
rust_target=riscv64gc-unknown-linux-gnu
rust_sha256=59bec35d8febb2ab918fa41cffbaa5b07146a63bdc33f029ff756d70a3151ece
;;
ppc64le)
# Debian 11 (Bullseye) released in August 2021.
base_image=mirror.gcr.io/library/debian:bullseye-20240904@sha256:8ccc486c29a3ad02ad5af7f1156e2152dff3ba5634eec9be375269ef123457d8
rust_target=powerpc64le-unknown-linux-gnu
rust_sha256=ff524eef5a59d801df09ccad5cdaf9ea1f0a07d75cbed2a7e9f013a9eb76a3c1
;;
s390x)
# Debian 11 (Bullseye) released in August 2021.
base_image=mirror.gcr.io/library/debian:bullseye-20240904@sha256:8ccc486c29a3ad02ad5af7f1156e2152dff3ba5634eec9be375269ef123457d8
rust_target=s390x-unknown-linux-gnu
rust_sha256=808268af9e880d41b8cb32b242e38c9bd3ea7aba6409b02fbffa0fbc5370c538
;;
loongarch64)
# LoongArch build is not reproducible yet
cat <<EOF | $image_build
FROM mirror.gcr.io/loongarch64/debian:sid
ENV DEBIAN_FRONTEND=noninteractive TZ=UTC
RUN apt-get update && \
apt-get install -y --no-install-recommends build-essential gcc-14 g++-14 clang-19 cmake && \
ln -sf /usr/bin/clang-19 /usr/bin/clang && \
ln -sf /usr/bin/clang++-19 /usr/bin/clang++ && \
rm -rf /var/lib/apt/lists
EOF
base_image=mirror.gcr.io/loongarch64/debian:sid@sha256:0356df4e494bbb86bb469377a00789a5b42bbf67d5ff649a3f9721b745cbef77
apt_setup="echo 'deb http://deb.debian.org/debian sid main' > /etc/apt/sources.list"
rust_target=loongarch64-unknown-linux-gnu
rust_sha256=d5a925962854730ae7641420d8337af93988ea4ff47b503a856ec53776c87841
;;
x86_64-musl)
# Alpine 3.21 released in December 2024.
base_image=mirror.gcr.io/library/alpine:3.21.8@sha256:ce64758a109eb420d874a118f87920e625e12d3634e03b4a5573fd9f6e5d3507
rust_target=x86_64-unknown-linux-musl
rust_sha256=40dbea28193cf2b488cf3e4a89274ccfb60efa50883f19917a382f84fd05bdc4
;;
aarch64-musl)
# Alpine 3.21 released in December 2024.
base_image=mirror.gcr.io/library/alpine:3.21.8@sha256:ce64758a109eb420d874a118f87920e625e12d3634e03b4a5573fd9f6e5d3507
rust_target=aarch64-unknown-linux-musl
rust_sha256=c5f45b5c6eb7f8fdb277c54c08402b7c931516740fbd4eccc26ba148f7cd5d57
;;
*)
usage
;;
esac
version=$(sed -n 's/^project(mold VERSION \(.*\))/\1/p' CMakeLists.txt)
dest=mold-$version-$arch-linux
# Create a Podman image containing the native C tools and a pinned Rust
# toolchain. The downloaded archive is checked before it is unpacked.
image=mold-rust-builder-$arch
archive=rust-$rust_version-$rust_target.tar.gz
os=linux
if [[ $arch = *-musl ]]; then
arch=${arch%-musl}
os=linux-musl
fi
# Source tarballs available on GitHub don't contain .git history.
# Clone the repo if missing.
[ -d .git ] || git clone --branch v$version --depth 1 --bare https://github.com/rui314/mold .git
podman build --arch "$arch" -t "$image" - <<EOF
FROM $base_image
ENV DEBIAN_FRONTEND=noninteractive TZ=UTC
RUN if [ -f /etc/alpine-release ]; then \
apk add --no-cache bash build-base coreutils git gzip linux-headers tar wget; \
else \
$apt_setup && \
echo 'Acquire::Retries "10"; Acquire::http::timeout "10"; Acquire::Check-Valid-Until "false";' > /etc/apt/apt.conf.d/80-retries && \
apt-get update && \
apt-get install -y --no-install-recommends build-essential ca-certificates git wget && \
rm -rf /var/lib/apt/lists; \
fi
RUN mkdir /tmp/rust && \
cd /tmp/rust && \
wget --progress=dot:mega https://static.rust-lang.org/dist/$archive && \
echo '$rust_sha256 $archive' | sha256sum -c && \
tar xf $archive && \
./${archive%.tar.gz}/install.sh --prefix=/usr/local --disable-ldconfig && \
cd / && \
rm -rf /tmp/rust
EOF
version=$(sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml)
dest=mold-$version-$arch-$os
# We use the timestamp of the last Git commit as the file timestamp
# for build artifacts.
timestamp="$(git log -1 --format=%ci)"
timestamp=$(git log -1 --format=%ct)
# `uname -m` in an ARM32 container running on an ARM64 host reports it
# not as ARM32 but as ARM64. Keep the reported machine consistent with
# the ARM32 userspace for any build tool that inspects it.
setarch=
[ "$arch" = arm ] && setarch='setarch linux32'
mkdir -p dist "target/dist-vendor-$arch"
# Cargo verifies registry packages against the checksums in Cargo.lock and
# checks out Git dependencies at the commit recorded there. Vendor them in a
# separate networked step so that the actual build can run without a network.
# Cargo's own cache is a tmpfs because the libgit2 inside a 32-bit cargo can't
# read a bind-mounted ext4 directory (readdir fails with EOVERFLOW under QEMU).
podman run --arch "$arch" -it --rm --userns=host --pids-limit=-1 \
--pull=never --env CARGO_HOME=/cargo --tmpfs /cargo -v "$(pwd):/mold:ro" \
-v "$(pwd)/target/dist-vendor-$arch:/vendor" "$image" $setarch \
bash -c 'cd /mold && cargo vendor --locked /vendor/sources > /vendor/config.toml'
# Build mold in a container.
mkdir -p dist
podman run --arch $arch -it --rm --userns=host --pids-limit=-1 --network=none \
-v "$(pwd):/mold:ro" -v "$(pwd)/dist:/dist" $image bash -c "
#
# SOURCE_DATE_EPOCH is a standardized environment variable that allows
# build artifacts to appear as if they were built at a specific time.
# Fixed source, vendor and target paths keep embedded build paths stable.
podman run --arch "$arch" -it --rm --userns=host --pids-limit=-1 --network=none \
--pull=never --env SOURCE_DATE_EPOCH="$timestamp" --env DEST="$dest" \
-v "$(pwd):/mold:ro" -v "$(pwd)/dist:/dist" \
-v "$(pwd)/target/dist-vendor-$arch:/vendor:ro" "$image" \
$setarch bash -c '
set -e
mkdir /build
export CARGO_TARGET_DIR=/build/target
cd /mold
cargo build --release --frozen --config /vendor/config.toml --package mold
# Link mold again with the mold just built, so that every release binary
# gets Identical Code Folding (see cli/build.rs). The default linker for
# most targets here is GNU ld, which does not support it. The first mold
# runs from a copy because the link replaces the files in the target
# directory.
mkdir /build/stage1
cp $CARGO_TARGET_DIR/release/mold /build/stage1
/build/stage1/mold -run cargo rustc --release --frozen --config /vendor/config.toml \
--package mold --bin mold -- -Clink-arg=-Wl,--icf=all
stage=/build/$DEST
DESTDIR=/build PREFIX=/$DEST ./install-mold.sh
strip --strip-unneeded "$stage/bin/mold"
find "$stage" -print | xargs touch --no-dereference --date="@$SOURCE_DATE_EPOCH"
cd /build
cmake -DCMAKE_BUILD_TYPE=Release -DMOLD_MOSTLY_STATIC=1 -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ /mold
cmake --build . -j\$(nproc)
cmake --install .
cmake -DMOLD_USE_MOLD=1 .
cmake --build . -j\$(nproc)
ctest --output-on-failure -j\$(nproc)
cmake --install . --prefix $dest --strip
find $dest -print | xargs touch --no-dereference --date='$timestamp'
find $dest -print | sort | tar -cf - --no-recursion --files-from=- | gzip -9nc > /dist/$dest.tar.gz
cp mold /dist
sha256sum /dist/$dest.tar.gz
"
find "$DEST" -print | sort | tar -cf - --no-recursion --files-from=- | gzip -9nc > "/dist/$DEST.tar.gz"
sha256sum "/dist/$DEST.tar.gz"
'
-150
View File
@@ -1,150 +0,0 @@
This is a note about interesting bugs that I met during the
development of the mold linker.
## GNU IFUNC
Problem: A statically-linked "hello world" program mysteriously
crashed in `__libc_start_main` function which is called just after
`_start`.
Investigation: I opened up gdb and found that the program reads a
bogus value from some array. It looks like `memcpy` failed to copy
proper data there. After some investigation, I noticed that `memcpy`
did't copy data at all but instead returned the address of
`__memcpy_avx_unaligned` function, which is a real `memcpy` function
optimized for machines with the AVX registers.
This odd issue was caused by the GNU IFUNC mechanism. That is, if a
function symbol has type `STT_GNU_IFUNC`, the function does not do
what its name suggests to do but instead returns a pointer to a
function that does the actual job. In this case, `memcpy` is an IFUNC
function, and it returns an address of `__memcpy_avx_unaligned` which
is a real `memcpy` function.
IFUNC function addresses are stored to `.got` section in an ELF
executable. The dynamic loader executes all IFUNC functions at
startup to replace their GOT entries with their return values. This
mechanism allows programs to choose the best implementation among
variants of the same function at runtime based on the machine info.
If a program is statically-linked, there's no dynamic loader that
rewrites the GOT entries. Therefore, if a program is
statically-linked, a libc's startup routine does that on behalf of the
dynamic loader. Concretely, a startup routine interprets all dynamic
relocations between `__rela_iplt_start` and `__rela_iplt_stop`
symbols. It is linker's responsibility to emit dynamic relocations
for IFUNC symbols even if it is linking a statically-linked program
and mark the beginning and the ending of a `.rela.dyn` section with
the symbols, so that the startup routine can find the relocations.
The bug was my linker didn't define `__rela_iplt_start` and
`__rela_iplt_stop` symbols. Since these symbols are weak, they are
initialized to zero. From the point of the initializer function,
there's no dynamic relocations between `__rela_iplt_start` and
`__rela_iplt_stop` symbols. That left GOT entries for IFUNC symbols
untouched.
The proper fix was to emit dynamic relocations for IFUNC symbols and
define the linker-synthesized symbols. I did that, and the bug was
fixed.
## stdio buffering
Problem: A statically-linked "Hello world" program prints out the
message if executed as `./hello`, but it doesn't output anything if
executed as `./hello | cat`.
Investigation: I knew that the default buffering mode for stdout is
line buffering (buffer is flushed on every '\n'), but if it is not
connected to the terminal (i.e. `isatty(2)` returns 0 on
`STDOUT_FILENO`), it automatically switches to full buffering (buffer
is flushed when it becomes full). So, it looks like libc failed to
flush the stdout on program exit for some reason.
I traced all function calls using gdb and noticed that `__libc_atexit`
was not called. That function seemed to be responsible for buffer
flushing. I don't know how exactly I found the root cause, but after
spending an hour or two, I found that `__start___libc_atexit` and
`__stop___libc_atexit` have value 0 in my linker's output while they
mark a section containing the address of `__libc_atexit` in GNU ld's
output.
So, libc doesn't directly call `__libc_atexit` but instead call all
function pointers between `__start___libc_atexit` and
`__stop___libc_atexit` symbols. libc puts `__libc_atexit` address in
`_libc_atexit` section, expecting that the linker automatically
creates the start and the end marker symbols for the section.
There's an obscure linker feature: if a section name is valid as a C
identifier (e.g. `foo` or `_foo_bar` but not `.foo`), the linker
automatically creates marker symbols by prepending `__start_` and
`__stop_` to the section name. My linker lacked the feature.
I implemented the feature, and the bug was fixed.
## TLS variable initialization
Problem: A statically-linked "hello world" program crashes after
reading a thread-local variable.
Investigation: Thread-local variables are very different from other
types of varaibles because there may be more than one instance of the
same variable in memory. Each thread has its copy of thread-local
varaibles. `%fs` segment register points the end of the variable area
for the current thread, and the variables are accessed as an offset
from `%fs`.
Thread-local variables may be initialized (e.g. `thread_local int x =
5;`). The linker gathers all thread-local variables and put them into
`PT_TLS` segment. At runtime, the contents of the segment is used as
an "initialization image" for new threads. When a new thread is
created, the image is memcpy'ed to the new thread's thread-local
variable area. The initialization image itself is read-only at
runtime.
It took more than a day to find out that memcpy copies the
initialization image to a different place than the thread-local
variables reside. That means, thread-local variables had garbage as
initial values, and the program crashed when using them.
The problem is that I set a very large value (4096) to the alignment
of `PT_TLS` segment. All `PT_LOAD` segments are naturally aligned to
the page boundary, so I use the same value for `PT_TLS`, but that was
a mistake. When a thread initialization routine sets a value to `%fs`,
it first aligns the end of the thread-local variable area address to
`PT_TLS` alignment value. So, if you set a large value to `PT_TLS`
alignment, `%fs` is set to a wrong place.
I fixed `PT_TLS` alignment, and the bug was gone.
## stdio buffering (another issue)
I noticed that a dynamically-linked "hello world" program didn't
flush its stdout buffer on exit. The cause of the problem was that the
executable had more than one DT_NEEDED entry for `libc.so`.
DT_NEEDED entries in `.dynamic` section specify a list of shared
object file names which need to be linked at runtime. I added one
DT_NEEDED entry for each library specified with the `-l` option.
The pitfall is, unlike object files, libraries are allowed to
appear more than once in a command line, and the linker has to
de-duplicate them before processing. Adding more than one DT_NEEDED
entry for the same shared object causes mysterious issues like this.
# Copy relocations and symbol aliases
environ, _environ and __environ point to the same location in libc.so,
so when we create a copy relocation for one of the symbols, we need to
do that for all of them. Otherwise, they'll end up pointing to different
places which causes a very mysterious issue.
# DT_DEBUG and gdb
If you forget to add an entry with type DT_DEBUG to .dynamic, gdb's
`info sharedlibrary` command doesn't print out a list of shared
libraries loaded to memory. The value of the entry doesn't matter, so
it can be just zero. The existence of it is important.
#
__EH_FRAME_BEGIN__ in libgcc/crtstuff.c
-1
View File
@@ -1 +0,0 @@
<svg version="1.1" viewBox="0.0 0.0 600.0 371.0" fill="none" stroke="none" stroke-linecap="square" stroke-miterlimit="10" width="600" height="371" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg"><path fill="#ffffff" d="M0 0L600.0 0L600.0 371.0L0 371.0L0 0Z" fill-rule="nonzero"/><path stroke="#333333" stroke-width="1.0" stroke-linecap="butt" d="M82.5 275.5L581.5 275.5" fill-rule="nonzero"/><path stroke="#cccccc" stroke-width="1.0" stroke-linecap="butt" d="M82.5 231.5L581.5 231.5" fill-rule="nonzero"/><path stroke="#cccccc" stroke-width="1.0" stroke-linecap="butt" d="M82.5 186.5L581.5 186.5" fill-rule="nonzero"/><path stroke="#cccccc" stroke-width="1.0" stroke-linecap="butt" d="M82.5 142.5L581.5 142.5" fill-rule="nonzero"/><path stroke="#cccccc" stroke-width="1.0" stroke-linecap="butt" d="M82.5 98.5L581.5 98.5" fill-rule="nonzero"/><path stroke="#cccccc" stroke-width="1.0" stroke-linecap="butt" d="M82.5 53.5L581.5 53.5" fill-rule="nonzero"/><clipPath id="id_0"><path d="M82.55 53.916668L581.45 53.916668L581.45 275.45L82.55 275.45L82.55 53.916668Z" clip-rule="nonzero"/></clipPath><path stroke="#000000" stroke-width="2.0" stroke-linecap="butt" stroke-opacity="0.0" clip-path="url(#id_0)" d="M158.0 275.0L136.0 275.0L136.0 229.0C136.0 227.89543 136.89543 227.0 138.0 227.0L156.0 227.0C157.10457 227.0 158.0 227.89543 158.0 229.0Z" fill-rule="nonzero"/><path fill="#4285f4" clip-path="url(#id_0)" d="M158.0 275.0L136.0 275.0L136.0 229.0C136.0 227.89543 136.89543 227.0 138.0 227.0L156.0 227.0C157.10457 227.0 158.0 227.89543 158.0 229.0Z" fill-rule="nonzero"/><path stroke="#000000" stroke-width="2.0" stroke-linecap="butt" stroke-opacity="0.0" clip-path="url(#id_0)" d="M307.0 275.0L285.0 275.0L285.0 91.0C285.0 89.89543 285.89542 89.0 287.0 89.0L305.0 89.0C306.10458 89.0 307.0 89.89543 307.0 91.0Z" fill-rule="nonzero"/><path fill="#4285f4" clip-path="url(#id_0)" d="M307.0 275.0L285.0 275.0L285.0 91.0C285.0 89.89543 285.89542 89.0 287.0 89.0L305.0 89.0C306.10458 89.0 307.0 89.89543 307.0 91.0Z" fill-rule="nonzero"/><path stroke="#000000" stroke-width="2.0" stroke-linecap="butt" stroke-opacity="0.0" clip-path="url(#id_0)" d="M182.0 275.0L160.0 275.0L160.0 244.0C160.0 242.89543 160.89543 242.0 162.0 242.0L180.0 242.0C181.10457 242.0 182.0 242.89543 182.0 244.0Z" fill-rule="nonzero"/><path fill="#ea4335" clip-path="url(#id_0)" d="M182.0 275.0L160.0 275.0L160.0 244.0C160.0 242.89543 160.89543 242.0 162.0 242.0L180.0 242.0C181.10457 242.0 182.0 242.89543 182.0 244.0Z" fill-rule="nonzero"/><path stroke="#000000" stroke-width="2.0" stroke-linecap="butt" stroke-opacity="0.0" clip-path="url(#id_0)" d="M331.0 275.0L309.0 275.0L309.0 131.0C309.0 129.89543 309.89542 129.0 311.0 129.0L329.0 129.0C330.10458 129.0 331.0 129.89543 331.0 131.0Z" fill-rule="nonzero"/><path fill="#ea4335" clip-path="url(#id_0)" d="M331.0 275.0L309.0 275.0L309.0 131.0C309.0 129.89543 309.89542 129.0 311.0 129.0L329.0 129.0C330.10458 129.0 331.0 129.89543 331.0 131.0Z" fill-rule="nonzero"/><path stroke="#000000" stroke-width="2.0" stroke-linecap="butt" stroke-opacity="0.0" clip-path="url(#id_0)" d="M480.0 275.0L458.0 275.0L458.0 156.0C458.0 154.89543 458.89542 154.0 460.0 154.0L478.0 154.0C479.10458 154.0 480.0 154.89543 480.0 156.0Z" fill-rule="nonzero"/><path fill="#ea4335" clip-path="url(#id_0)" d="M480.0 275.0L458.0 275.0L458.0 156.0C458.0 154.89543 458.89542 154.0 460.0 154.0L478.0 154.0C479.10458 154.0 480.0 154.89543 480.0 156.0Z" fill-rule="nonzero"/><path stroke="#000000" stroke-width="2.0" stroke-linecap="butt" stroke-opacity="0.0" clip-path="url(#id_0)" d="M206.0 275.0L184.0 275.0L184.0 270.0C184.0 268.89542 184.89543 268.0 186.0 268.0L204.0 268.0C205.10457 268.0 206.0 268.89542 206.0 270.0Z" fill-rule="nonzero"/><path fill="#fbbc04" clip-path="url(#id_0)" d="M206.0 275.0L184.0 275.0L184.0 270.0C184.0 268.89542 184.89543 268.0 186.0 268.0L204.0 268.0C205.10457 268.0 206.0 268.89542 206.0 270.0Z" fill-rule="nonzero"/><path stroke="#000000" stroke-width="2.0" stroke-linecap="butt" stroke-opacity="0.0" clip-path="url(#id_0)" d="M355.0 275.0L333.0 275.0L333.0 254.0C333.0 252.89543 333.89542 252.0 335.0 252.0L353.0 252.0C354.10458 252.0 355.0 252.89543 355.0 254.0Z" fill-rule="nonzero"/><path fill="#fbbc04" clip-path="url(#id_0)" d="M355.0 275.0L333.0 275.0L333.0 254.0C333.0 252.89543 333.89542 252.0 335.0 252.0L353.0 252.0C354.10458 252.0 355.0 252.89543 355.0 254.0Z" fill-rule="nonzero"/><path stroke="#000000" stroke-width="2.0" stroke-linecap="butt" stroke-opacity="0.0" clip-path="url(#id_0)" d="M504.0 275.0L482.0 275.0L482.0 250.0C482.0 248.89543 482.89542 248.0 484.0 248.0L502.0 248.0C503.10458 248.0 504.0 248.89543 504.0 250.0Z" fill-rule="nonzero"/><path fill="#fbbc04" clip-path="url(#id_0)" d="M504.0 275.0L482.0 275.0L482.0 250.0C482.0 248.89543 482.89542 248.0 484.0 248.0L502.0 248.0C503.10458 248.0 504.0 248.89543 504.0 250.0Z" fill-rule="nonzero"/><path stroke="#000000" stroke-width="2.0" stroke-linecap="butt" stroLine truncated

Before

Width:  |  Height:  |  Size: 99 KiB

-46
View File
@@ -1,46 +0,0 @@
# Coding Guidelines
mold is written in C++20, but as is the case with every C++ project,
it has local coding rules. In this document, I'll explain some of them
and try to give justifications for why I chose such rules.
## DOs
- Always use `i64` (which is a type alias for `int64_t` in mold) for
integers unless you have a reason not to. For example, even if you know
that a loop counter won't exceed 100, you should stop thinking about it
and just use `i64`.
Justification: Local variables are usually on CPU registers, so on
64-bit CPUs, there's no performance peanlty on choosing `i64` over
`i32`. Even if a compiler has to spill register values to the stack,
I don't think there's an observable difference between `i32` and
`i64`. Therefore, extra 32 bits are essentially free. On 32-bit CPUs,
they are not free, but that's OK because we are writing mold for modern
computers. mold will still run on 32-bit computers but a bit slowly.
By always using `i64`, we can eliminate the need to think about the
"right" size for each variable. It also reduces the risk of integer
overflow.
Exceptions: If you have to allocate a very large number (e.g. millions)
of the same object, its size matters. In that case, use a smaller type.
## DON'Ts
- Don't use `auto` unless its actual type is obvious in the very narrow
context. Currently, we use `auto` only for lambdas.
Justification: I think `auto` makes code writing easier but code reading
harder, because readers have to make a guess as to what is the actual
type of `auto`. If you are already familiar with the existing codebase,
you may be able to guess it easily, but that's not always the case.
I want to keep the mold codebase friendly to first-time readers.
- Don't over-use inheritance. In mold, most classes don't have parents,
and even if they do, their class hierarchy is very shallow. Currently
its height is just two (i.e. abstract classes and their implementations).
Justification: Designing class hierarchies is fun as it feels like
taxonomy, but I don't think that always help writing code. It looks like
simpler class hierarchy makes its code simpler.
-491
View File
@@ -1,491 +0,0 @@
[This document was written in 2020, and the contents are outdated.
Specifically, we no longer believe that object preloading is a good
idea. That being said, most of the points in this document still hold
even today. Therefore, I'll keep this document as-is.]
## Design and implementation of mold
For the rest of this documentation, I'll explain the design and the
implementation of mold. If you are only interested in using mold, you
don't need to read the below.
### Motivation
Here is why I'm writing a new linker:
- Even though lld has significantly improved the situation, linking is
still one of the slowest steps in a build. It is especially
annoying when I changed one line of code and had to wait for a few
seconds or even more for a linker to complete. It should be
instantaneous. There's a need for a faster linker.
- The number of cores on a PC has increased a lot lately, and this
trend is expected to continue. However, the existing linkers can't
take the advantage of the trend because they don't scale well for more
cores. I have a 64-core/128-thread machine, so my goal is to create
a linker that uses the CPU nicely. mold should be much faster than
other linkers on 4 or 8-core machines too, though.
- It looks to me that the designs of the existing linkers are somewhat
too similar, and I believe there are a lot of drastically different
designs that haven't been explored yet. Developers generally don't
care about linkers as long as they work correctly, and they don't
even think about creating a new one. So there may be lots of low
hanging fruits there in this area.
### Basic design
- In order to achieve a `cp`-like performance, the most important
thing is to fix the layout of an output file as quickly as possible, so
that we can start copying actual data from input object files to an
output file as soon as possible.
- Copying data from input files to an output file is I/O-bounded, so
there should be room for doing computationally-intensive tasks while
copying data from one file to another.
- We should allow the linker to preload object files from disk and
parse them in memory before a complete set of input object files
is ready. To do so, we need
to split the linker into two in such a way that the latter half of
the process finishes as quickly as possible by speculatively parsing
and preprocessing input files in the first half of the process.
- One of the most computationally-intensive stage among linker stages
is symbol resolution. To resolve symbols, we basically have to throw
all symbol strings into a hash table to match undefined symbols with
defined symbols. But this can be done in the preloading stage using
[string interning](https://en.wikipedia.org/wiki/String_interning).
- Object files may contain a special section called a mergeable string
section. The section contains lots of null-terminated strings, and
the linker is expected to gather all mergeable string sections and
merge their contents. So, if two object files contain the same
string literal, for example, the resulting output will contain a
single merged string. This step is computationally intensive, but string
merging can be done in the preloading stage using string interning.
- Static archives (.a files) contain object files, but the static
archive's string table contains only defined symbols of member
object files and lacks other types of symbols. That makes static
archives unsuitable for speculative parsing. Therefore, the linker
should ignore the symbol table of static archive and directly read
static archive members.
- If there's a relocation that uses a GOT of a symbol, then we have to
create a GOT entry for that symbol. Otherwise, we shouldn't. That
means we need to scan all relocation tables to fix the length and
the contents of a .got section. This is computationally intensive,
but this step is parallelizable.
### Linker Script
Linker script is an embedded language for the linker. It is mainly
used to control how input sections are mapped to output sections and
the layout of the output, but it can also do a lot of tricky stuff.
Its feature is useful especially for embedded programming, but it's
also an awfully underdocumented and complex language.
We have to implement a subset of the linker script language anwyay,
because on Linux, /usr/lib/x86_64-linux-gnu/libc.so is (despite its
name) not a shared object file but actually an ASCII file containing
linker script code to load the _actual_ libc.so file. But the feature
set for this purpose is very limited, and it is okay to implement them
to mold.
Besides that, we really don't want to implement the linker script
language. But at the same time, we want to satisfy the user needs that
are currently satisfied with the linker script language. So, what
should we do? Here is my observation:
- Linker script allows doing a lot of tricky stuff, such as specifying
the exact layout of a file, inserting arbitrary bytes between
sections, etc. But most of them can be done with a post-link binary
editing tool (such as `objcopy`).
- It looks like there are two things that truly cannot be done by a
post-link editing tool: (a) mapping input sections to output
sections, and (b) applying relocations.
From the above observation, I believe we need to provide only the
following features instead of the entire linker script language:
- A method to specify how input sections are mapped to output
sections, and
- a method to set addresses to output sections, so that relocations
are applied based on desired addresses.
I believe everything else can be done with a post-link binary editing
tool.
### Details
- As we aim to the 1-second goal for Chromium, every millisecond
counts. We can't ignore the latency of process exit. If we mmap a
lot of files, \_exit(2) is not instantaneous but takes a few hundred
milliseconds because the kernel has to clean up a lot of
resources. As a workaround, we should organize the linker command as
two processes; the first process forks the second process, and the
second process does the actual work. As soon as the second process
writes a result file to a filesystem, it notifies the first process,
and the first process exits. The second process can take time to
exit, because it is not an interactive process.
- At least on Linux, it looks like the filesystem's performance to
allocate new blocks to a new file is the limiting factor when
creating a new large file and filling its contents using mmap.
If you already have a large file in the buffer cache, writing to it is
much faster than creating a new fresh file and writing to it.
Based on this observation, mold overwrites to an existing
executable file if exists. My quick benchmark showed that I could
save 300 milliseconds when creating a 2 GiB output file.
Linux doesn't allow to open an executable for writing if it is
running (you'll get a "text busy" error if you attempt). mold
falls back to the usual way if it fails to open an output file.
- The output from the linker should be deterministic for the sake of
[build reproducibility](https://en.wikipedia.org/wiki/Reproducible_builds)
and ease of debugging. This might add a little bit of overhead to
the linker, but that shouldn't be too much.
- A .build-id, a unique ID embedded to an output file, is usually
computed by applying a cryptographic hash function (e.g. SHA-1) to
an output file. This is a slow step, but we can speed it up by
splitting a file into small chunks, computing SHA-1 for each chunk,
and then computing SHA-1 of the concatenated SHA-1 hashes
(i.e. constructing a [Merkle
Tree](https://en.wikipedia.org/wiki/Merkle_tree) of height 2).
Modern x86 processors have purpose-built instructions for SHA-1 and
can compute SHA-1 pretty quickly at about 2 GiB/s. Using 16
cores, a build-id for a 2 GiB executable can be computed in 60 to 70
milliseconds.
- BFD, gold, and lld support section garbage collection. That is, a
linker runs a mark-sweep garbage collection on an input graph, where
sections are vertices and relocations are edges, to discard all
sections that are not reachable from the entry point symbol
(i.e. `_start`) or a few other root sections. In mold, we are using
multiple threads to mark sections concurrently.
- Similarly, BFD, gold an lld support Identical Comdat Folding (ICF)
as yet another size optimization. ICF merges two or more read-only
sections that happen to have the same contents and relocations.
To do that, we have to find isomorphic subgraphs from larger graphs.
I implemented a new algorithm for mold, which is 5x faster than lld
to do ICF for Chromium (from 5 seconds to 1 second).
- [Intel Threading Building
Blocks](https://github.com/oneapi-src/oneTBB) (TBB) is a good
library for parallel execution and has several concurrent
containers. We are particularly interested in using
`parallel_for_each` and `concurrent_hash_map`.
- TBB provides `tbbmalloc` which works better for multi-threaded
applications than the glib'c malloc, but it looks like
[jemalloc](https://github.com/jemalloc/jemalloc) and
[mimalloc](https://github.com/microsoft/mimalloc) are a little bit
more scalable than `tbbmalloc`.
### Size of the problem
When linking Chrome, a linker reads 3,430,966,844 bytes of data in
total. The data contains the following items:
| Data item | Number
| ------------------------ | ------
| Object files | 30,723
| Public undefined symbols | 1,428,149
| Mergeable strings | 1,579,996
| Comdat groups | 9,914,510
| Regular sections¹ | 10,345,314
| Public defined symbols | 10,512,135
| Symbols | 23,953,607
| Sections | 27,543,225
| Relocations against SHF_ALLOC sections | 39,496,375
| Relocations | 62,024,719
¹ Sections that have to be copied from input object files to an
output file. Sections that contain relocations or symbols are for
example excluded.
### Internals
In this section, I'll explain the internals of mold linker.
#### A brief history of Unix and the Unix linker
Conceptually, what a linker does is pretty simple. A compiler compiles
a fragment of a program (a single source file) into a fragment of
machine code and data (an object file, which typically has the .o
extension), and a linker stitches them together into a single
executable or a shared library image.
In reality, modern linkers for Unix-like systems are much more
complicated than the naive understanding because they have gradually
gained one feature at a time over the 50 years history of Unix, and
they are now something like a bag of lots of miscellaneous features in
which none of the features is more important than the others. It is
very easy to miss the forest for the trees, since for those who don't
know the details of the Unix linker, it is not clear which feature is
essential and which is not.
That being said, one thing is clear that at any point of Unix history,
a Unix linker has a coherent feature set for the Unix of that age. So,
let me entangle the history to see how the operating system, runtime,
and linker have gained features that we see today. That should give
you an idea of why a particular feature has been added to a linker in the
first place.
1. Original Unix didn't support shared libraries, and a program was
always loaded to a fixed address. An executable was something like
a memory dump that was just loaded to a particular address by the
kernel. After loading, the kernel started executing the program by
setting the instruction pointer to a particular address.
The most essential feature for any linker is relocation processing.
The original Unix linker of course supported that. Let me explain
what that is.
Individual object files are inevitably incomplete as a program,
because when a compiler created them, it only see a part of an
entire program. For example, if an object file contains a function
call that refers to another object file, the `call` instruction in the
object cannot be complete, as the compiler has no idea as to what
is the called function's address. To deal with this, the compiler
emits a placeholder value (typically just zero) instead of a real
address and leaves metadata in an object file saying "fix offset X
of this file with an address of Y". That metadata is called
"relocation". Relocations are typically processed by the linker.
It is easy for a linker to apply relocations for the original Unix
because a program is always loaded to a fixed address. It exactly
knows the addresses of all functions and data when linking a
program.
Static library support, which is still an important feature of Unix
linker, also dates back to this early period of Unix history.
To understand what it is, imagine that you are trying to compile
a program for the early Unix. You don't want to waste time to
compile libc functions every time you compile your program (the
computers of the era were incredibly slow), so you have already
placed each libc function into a separate source file and compiled
them individually. That means you have object files for each libc
function, e.g., printf.o, scanf.o, atoi.o, write.o, etc.
Given this configuration, all you have to do to link your program
against libc functions is to pick up the right set of libc object
files and give them to the linker along with the object files of your
program. But, keeping the linker command line in sync with the
libc functions you are using in your program is bothersome. You can
be conservative; you can specify all libc object files to the
command line, but that leads to program bloat because the linker
unconditionally link all object files given to it no matter whether
they are used or not. So, a new feature was added to the linker to
fix the problem. That is the static library, which is also called
the archive file.
An archive file is just a bundle of object files, just like zip
file but in an uncompressed form. An archive file typically has the
.a file extension and named after its contents. For example, the
archive file containing all libc objects is named `libc.a`.
If you pass an archive file along with other object files to the
linker, the linker pulls out an object file from the archive _only
when_ it is referenced by other object files. In other words,
unlike object files directly given to a linker, object files
wrapped in an archive are not linked to the output by default.
An archive works as a supplement to complete your program.
Even today, you can still find a libc archive file. Run `ar t
/usr/lib/x86_64-linux-gnu/libc.a` on Linux should give you a list
of object files in the libc archive.
2. In the '80s, Sun Microsystems, a leading commercial Unix vendor at the
time, added shared library support to their Unix variant, SunOS.
(This section is incomplete.)
### Concurrency strategy
In this section, I'll explain the high-level concurrency strategy of
mold.
In most places, mold adopts data parallelism. That is, we have a huge
number of pieces of data of the same kind, and we process each of them
individually using parallel for-loop. For example, after identifying
the exact set of input object files, we need to scan all relocation
tables to determine the sizes of .got and .plt sections. We do that
using a parallel for-loop. The granularity of parallel processing in
this case is the relocation table.
Data parallelism is very efficient and scalable because there's no
need for threads to communicate with each other while working on each
element of data. In addition to that, data parallelism is easy to
understand, as it is just a for-loop in which multiple iterations may
be executed in parallel. We don't use high-level communication or
synchronization mechanisms such as channels, futures, promises,
latches or something like that in mold.
In some cases, we need to share a little bit of data between threads
while executing a parallel for-loop. For example, the loop to scan
relocations turns on "requires GOT" or "requires PLT" flags in a
symbol. Symbol is a shared resource, and writing to them from multiple
threads without synchronization is unsafe. To deal with it, we made
the flag an atomic variable.
The other common pattern you can find in mold which is build on top of
the parallel for-loop is the map-reduce pattern. That is, we run a
parallel for-loop on a large data set to produce a small data set and
process the small data set with a single thread. Let me take a
build-id computation as an example. Build-id is typically computed by
applying a cryptographic hash function such as SHA-1 on a linker's
output file. To compute it, we first consider an output as a sequence
of 1 MiB blocks and compute a SHA-1 hash for each block in parallel.
Then, we concatenate the SHA-1 hashes and compute a SHA-1 hash on the
hashes to get a final build-id.
Finally, we use concurrent hashmap at a few places in mold. Concurrent
hashmap is a hashmap to which multiple threads can safely insert items
in parallel. We use it in the symbol resolution stage, for example.
To resolve symbols, we basically have to throw in all defined symbols
into a hash table, so that we can find a matching defined symbol for
an undefined symbol by name. We do the hash table insertion from a
parallel for-loop which iterates over a list of input files.
Overall, even though mold is highly scalable, it succeeded to avoid
complexties you often find in complex parallel programs. From high
level, mold just serially executes the linker's internal passes one by
one. Each pass is parallelized using parallel for-loops.
### Rejected ideas
In this section, I'll explain the alternative designs I currently do
not plan to implement and why I turned them down.
- Placing variable-length sections at end of an output file and start
copying file contents before fixing the output file layout
Idea: Fixing the layout of regular sections seems easy, and if we
place them at beginning of a file, we can start copying their
contents from their input files to an output file. While copying
file contents, we can compute the sizes of variable-length sections
such as .got or .plt and place them at end of the file.
Reason for rejection: I did not choose this design because I doubt
if it could actually shorten link time and I think I don't need it
anyway.
The linker has to de-duplicate comdat sections (i.e. inline
functions that are included in multiple object files), so we
cannot compute the layout of regular sections until we resolve all
symbols and de-duplicate comdats. That takes a few hundred
milliseconds. After that, we can compute the sizes of
variable-length sections in less than 100 milliseconds. It's quite
fast, so it doesn't seem to make much sense to proceed without
fixing the final file layout.
The other reason to reject this idea is because there's good a
chance for this idea to have a negative impact on linker's overall
performance. If we copy file contents before fixing the layout, we
can't apply relocations to them while copying because symbol
addresses are not available yet. If we fix the file layout first, we
can apply relocations while copying, which is effectively zero-cost
due to a very good data locality. On the other hand, if we apply
relocations long after we copy file contents, it's pretty expensive
because section contents are very likely to have been evicted from
CPU cache.
- Incremental linking
Idea: Incremental linking is a technique to patch a previous linker's
output file so that only functions or data that are updated from the
previous build are written to it. It is expected to significantly
reduce the amount of data copied from input files to an output file
and thus speed up linking. GNU BFD and gold linkers support it.
Reason for rejection: I turned it down because it (1) is
complicated, (2) doesn't seem to speed it up that much and (3) has
several practical issues. Let me explain each of them.
First, incremental linking for real C/C++ programs is not as easy as
one might think. Let me take malloc as an example. malloc is usually
defined by libc, but you can implement it in your program, and if
that's the case, the symbol `malloc` will be resolved to your
function instead of the one in libc. If you include a library that
defines malloc (such as libjemalloc or libtbbmallc) before libc,
their malloc will override libc's malloc.
Assume that you are using a nonstandard malloc. What if you remove
your malloc from your code, or remove `-ljemalloc` from your
Makefile? The linker has to include a malloc from libc, which may
include more object files to satisfy its dependencies. Such code
change can affect the entire program rather than just replacing one
function. The same is true for adding malloc to your program. Making
a local change doesn't necessarily result in a local change in the
binary level. It can easily have cascading effects.
Some ELF fancy features make incremental linking even harder to
implement. Take the weak symbol as an example. If you define `atoi`
as a weak symbol in your program, and if you are not using `atoi`
at all in your program, that symbol will be resolved to address
0. But if you start using some libc function that indirectly calls
`atoi`, then `atoi` will be included in your program, and your weak
symbol will be resolved to that function. I don't know how to
efficiently fix up a binary for this case.
This is a hard problem, so existing linkers don't try too hard to
solve it. For example, IIRC, gold falls back to full link if any
function is removed from a previous build. If you want to not annoy
users in the fallback case, you need to make full link fast anyway.
Second, incremental linking itself has an overhead. It has to detect
updated files, patch an existing output file and write additional
data to an output file for future incremental linking. GNU gold, for
instance, takes almost 30 seconds on my machine to do a null
incremental link (i.e. no object files are updated from a previous
build) for chrome. It's just too slow.
Third, there are other practical issues in incremental linking. It's
not reproducible, so your binary isn't going to be the same as other
binaries even if you are compiling the same source tree using the
same compiler toolchain. Or, it is complex and there might be a bug
in it. If something doesn't work correctly, "remove --incremental
from your Makefile and try again" could be a piece of advice, but
that isn't ideal.
So, all in all, incremental linking is tricky. I wanted to make full
link as fast as possible, so that we don't have to think about how
to work around the slowness of full link.
- Defining a completely new file format and use it
Idea: Sometimes, the ELF file format itself seems to be a limiting
factor in improving the linker's performance. We might be able to make a
far better one if we create a new file format.
Reason for rejection: I rejected the idea because it apparently has
a practical issue (backward compatibility issue) and also doesn't
seem to improve the performance of linkers that much. As clearly
demonstrated by mold, we can create a fast linker for ELF. I believe
ELF isn't that bad, after all. The semantics of the existing Unix
linkers, such as the name resolution algorithm or the linker script,
have slowed the linkers down, but that's not a problem of the file
format itself.
- Watching object files using inotify(2)
Idea: When mold is running as a daemon for preloading, use
inotify(2) to watch file system updates so that it can reload files
as soon as they are updated.
Reason for rejection: Just like the maximum number of files you can
simultaneously open, the maximum number of files you can watch using
inotify(2) isn't that large. Maybe just a single instance of mold is
fine with inotify(2), but it may fail if you run multiple of it.
The other reason for not doing it is because mold is quite fast
without it anyway. Invoking stat(2) on each file for file update
check takes less than 100 milliseconds for Chrome, and if most of
the input files are not updated, parsing updated files takes almost
no time.
-55
View File
@@ -1,55 +0,0 @@
This page explains the following warning message and how to fix it.
mold emits this message when it sees an object file that may not be
compatible with mold.
```
mold: warning: foo.o: this file may cause a segmentation fault because it requires an executable stack. See https://github.com/rui314/mold/tree/main/docs/execstack.md for more info.
```
# Background
On modern computers, the stack area (to which local variables are
stored) cannot contain executable code. If the control reaches the
stack area, the CPU refuses to execute any code there and the program
is usually terminated due to segmentation fault.
This is a security measure. The stack area used to be executable (old
CPUs generally execute any code as long as it is in a readable memory
region), but that provided an easy attack vector to a malicious user.
They wrote executable code to the stack area using some buffer
overflow bug and jumped there to run arbitrary code in a remote server
process.
To prevent this type of attack, the stack area is no longer executable
since the early 2000s. On Linux, the stack's executable-ness is
controlled by a bit in an executable, and the loader respects that
bit. The bit is set by the linker.
GCC had (and still has) a feature that depends on the executable
stack, so they invented a way to tell the linker to mark the stack
executable. Specifically, if an object file contains a
`.note.GNU-stack` section with the `SHF_EXECSTACK` bit, GNU linker
silently makes the stack of an output file executable.
But the GNU linker's behavior is dangerous. If you accidentally link
an object file that has that marker section, the entire stack area
silently becomes executable, disabling the security mechanism.
Therefore, mold simply ignores that marker section. If you are using
mold, you need to explicitly pass `-z execstack` to the linker to make
the stack executable.
# What caused this issue?
You are likely to use GCC's [Nested
Functions](https://gcc.gnu.org/onlinedocs/gcc/Nested-Functions.html)
feature which still depends on the executable stack.
# How to fix it?
If you know what you are doing, pass `-z execstack` to mold. Beware
that this will significantly weaken your program's security.
If you don't want to pass `-z execstack`, rewrite your code so that
your code does not depend on the executable stack.
Loaded 100 of 4014 files, more files were not shown because too many files have changed in this diff. Show more