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]>
This commit is contained in:
Rui UeyamaandClaude Opus 5.5 committed 2026-10-11 13:40:30 +09:00
1 parent 55137f5427
commit 4e43149228
6 files changed
+82 -104

No files matched your search

+3 -4
View File
@@ -1,8 +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, or reports the target the inputs are actually
/// for.
pub fn link(cmdline: mold_macho::driver::Cmdline) -> mold_macho::driver::LinkResult {
mold_macho::driver::link::<mold_macho::arch::Arm64>(cmdline)
/// Links for this target.
pub fn link(parsed: mold_macho::driver::ParsedArgs) {
mold_macho::driver::link::<mold_macho::arch::Arm64>(parsed)
}
+3 -4
View File
@@ -1,8 +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, or reports the target the inputs are actually
/// for.
pub fn link(cmdline: mold_macho::driver::Cmdline) -> mold_macho::driver::LinkResult {
mold_macho::driver::link::<mold_macho::arch::X86_64>(cmdline)
/// Links for this target.
pub fn link(parsed: mold_macho::driver::ParsedArgs) {
mold_macho::driver::link::<mold_macho::arch::X86_64>(parsed)
}
+16 -16
View File
@@ -22,10 +22,12 @@ use std::path::Path;
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
type LinkFn = fn(Cmdline) -> LinkResult;
type MachoLinkFn = fn(mold_macho::driver::ParsedArgs);
// Each target has its own monomorphized link function. Start with the first
// enabled target and switch to the matching function if the inputs are for
// another target.
// 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),
@@ -69,17 +71,17 @@ const ELF_TARGETS: &[(&str, LinkFn)] = &[
("loongarch32", mold_elf_loongarch32::link),
];
const MACHO_TARGETS: &[(&str, LinkFn)] = &[
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_for_target(targets: &[(&str, LinkFn)], target: &str, cmdline: Cmdline) -> LinkResult {
fn link_fn<F: Copy>(targets: &[(&str, F)], target: &str) -> F {
for &(name, link) in targets {
if name == target {
return link(cmdline);
return link;
}
}
eprintln!(
@@ -97,18 +99,16 @@ fn is_ld64(argv0: &OsStr) -> bool {
fn main() {
let args: Vec<_> = std::env::args_os().collect();
let macho = args.first().is_some_and(|argv0| is_ld64(argv0));
let targets = if macho { MACHO_TARGETS } else { ELF_TARGETS };
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, _)) = targets.first() else {
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_for_target(targets, target, cmdline);
let status = if macho {
mold_macho::driver::main(args, initial_target, link)
} else {
mold_elf::driver::main(args, initial_target, link)
};
std::process::exit(status);
let link = |target: &str, cmdline| link_fn(ELF_TARGETS, target)(cmdline);
std::process::exit(mold_elf::driver::main(args, initial_target, link));
}
+2 -2
View File
@@ -316,8 +316,8 @@ pub trait Target: Copy + Default + Send + Sync + 'static {
fn apply_optimization_hints(_ctx: &Context<Self>, _buf: &mut [u8]) {}
}
/// The canonical name of a target named on the command line, borrowed
/// from static storage so that a restart for that target can carry it.
/// The canonical name of a target named on the command line, if mold
/// links for it.
pub fn canonical_name(name: &str) -> Option<&'static str> {
[Arm64::NAME, X86_64::NAME].into_iter().find(|&canonical| canonical == name)
}
+43 -36
View File
@@ -1363,8 +1363,8 @@ fn read_filelist(arg: &OsStr) -> (PathBuf, Vec<PathBuf>) {
(path.to_path_buf(), files)
}
/// What option parsing needs to know about the target: the driver
/// parses once per speculated target, as mold does.
/// What resolving the options needs to know about the target (see
/// resolve_args).
pub struct TargetTraits {
pub name: &'static str,
pub page_size: u64,
@@ -1394,16 +1394,16 @@ enum OutputKind {
/// the kind of output (None for one not given), the symbol lists, and
/// what the checks of the options need.
#[derive(Default)]
struct ParseState<'a> {
struct ParseState {
kind: OutputKind,
/// -target's triple, which overrides -arch and -platform_version.
target_triple: Option<&'a str>,
target_triple: Option<String>,
/// The first two platforms the options name that cannot go
/// together (see set_platform).
incompatible_platforms: Option<(u32, u32)>,
arch_variant: bool,
/// -no_allow_dylib_sub_type_mismatches's architectures.
dylib_subtype_list: Option<&'a [u8]>,
dylib_subtype_list: Option<Vec<u8>>,
/// Whether -e names the entry point.
explicit_entry: bool,
pie: Option<bool>,
@@ -1870,7 +1870,9 @@ fn initial_args() -> Args {
}
}
/// Parses all options. `cmdline` includes the program name.
/// Parses all options, and settles the target they are for. What depends
/// on the target is left to resolve_args. `cmdline` includes the program
/// name.
///
/// Options are matched as bytes and their arguments keep the bytes they
/// were given in: paths, install names and rpaths pass through to the
@@ -1878,7 +1880,7 @@ fn initial_args() -> Args {
/// symbols, segments and sections are bytes, UTF-8 or not, as the
/// inputs' are. Arguments that are text by nature (versions, numbers,
/// the -undefined treatment) must be UTF-8.
pub fn parse_args(target: &TargetTraits, cmdline: &[Cow<'_, OsStr>]) -> Args {
pub fn parse_args(cmdline: &[Cow<'_, OsStr>]) -> ParsedArgs {
let mut args = initial_args();
let mut st = ParseState::default();
@@ -1975,7 +1977,7 @@ pub fn parse_args(target: &TargetTraits, cmdline: &[Cow<'_, OsStr>]) -> Args {
.unwrap_or_else(|| fatal!("unknown -arch name: {arch}")),
);
}
b"-target" => st.target_triple = Some(cur.next_text(name)),
b"-target" => st.target_triple = Some(cur.next_text(name).to_owned()),
b"-platform_version" => read_platform_version(&mut cur, &mut args, &mut st, name),
// The minimum versions of macOS and iOS, under their old
// names too; ld-prime says the iOS one was renamed.
@@ -2002,7 +2004,7 @@ pub fn parse_args(target: &TargetTraits, cmdline: &[Cow<'_, OsStr>]) -> Args {
b"-arch_errors_fatal" => args.arch_errors_fatal = true,
b"-allow_sub_type_mismatches" => args.allow_sub_type_mismatches = true,
b"-no_allow_dylib_sub_type_mismatches" => {
st.dylib_subtype_list = Some(cur.next_arg(name).as_encoded_bytes());
st.dylib_subtype_list = Some(cur.next_arg(name).as_encoded_bytes().to_vec());
}
b"-deployment_target_mismatches" => {
args.deployment_target_mismatches = parse_treatment(name, cur.next_arg(name), true);
@@ -2629,18 +2631,44 @@ pub fn parse_args(target: &TargetTraits, cmdline: &[Cow<'_, OsStr>]) -> Args {
finish_options(&mut args);
set_output_kind(&mut args, st.kind);
if let Some(triple) = st.target_triple {
if let Some(triple) = &st.target_triple {
apply_target_triple(&mut args, triple);
}
if args.inputs.is_empty() {
exit_without_inputs(&args);
}
// A parse for another target than this one is redone by the driver,
// so what depends on the target is left to that parse.
if !resolve_target(target, &mut args) {
return args;
// Without -arch, the first object file names the architecture;
// without -platform_version (or the like), the objects name the
// platform too.
if args.arch.is_none() {
args.arch = Some(crate::reader::detect_machine_type(&args));
}
if args.platform == 0 {
crate::reader::infer_platform(&mut args);
}
check_deployment_target(&args);
ParsedArgs { args, st }
}
/// The command line as parse_args reads it: what the options say, with
/// the target they are for settled, but not yet what depends on the
/// target (see resolve_args).
pub struct ParsedArgs {
args: Args,
st: ParseState,
}
impl ParsedArgs {
/// The target the link is for.
pub fn arch(&self) -> &'static str {
self.args.arch.unwrap()
}
}
/// Resolves what depends on the target: the defaults of many options, and
/// the checks of the options against the target and one another.
pub fn resolve_args(target: &TargetTraits, parsed: ParsedArgs) -> Args {
let ParsedArgs { mut args, mut st } = parsed;
check_arch_options(target, &mut args, &st);
resolve_defaults(target, &mut args, &st);
std::mem::take(&mut st.lists).build(&mut args);
@@ -2721,27 +2749,6 @@ fn exit_without_inputs(args: &Args) -> ! {
fatal!("no object files specified");
}
/// Settles what the image is linked for, and returns whether that is
/// `target`. Without -arch, the first object file names the
/// architecture (see reader::detect_machine_type); without
/// -platform_version (or the like), the parse for the target looks for
/// the platform in the objects too (see reader::infer_platform). mold's
/// driver detects the machine type after the parse, but here the
/// defaults of many options depend on the target and the platform.
fn resolve_target(target: &TargetTraits, args: &mut Args) -> bool {
if args.arch.is_none() {
args.arch = Some(crate::reader::detect_machine_type(args));
}
if args.arch != Some(target.name) {
return false;
}
if args.platform == 0 {
crate::reader::infer_platform(args);
}
check_deployment_target(args);
true
}
/// Refuses an iOS deployment target older than 4.3, the first iOS to
/// slide a main executable, whatever the link's options or objects
/// name it by: ld-prime makes every image position-independent, and
@@ -2767,7 +2774,7 @@ fn check_arch_options(target: &TargetTraits, args: &mut Args, st: &ParseState) {
}
let env_subtypes = std::env::var_os("LD_DYLIB_CPU_SUBTYPES_MUST_MATCH");
let env_subtypes = env_subtypes.as_deref().map(OsStr::as_encoded_bytes);
if let Some(list) = st.dylib_subtype_list.or(env_subtypes) {
if let Some(list) = st.dylib_subtype_list.as_deref().or(env_subtypes) {
args.dylib_subtypes_must_match = names_cpu_family(list, target.name);
}
}
+15 -42
View File
@@ -1,9 +1,7 @@
//! The linker driver: runs the passes in order.
use std::borrow::Cow;
use std::ffi::{OsStr, OsString};
use std::ffi::OsString;
use std::ops::Range;
use std::sync::Arc;
use rayon::prelude::*;
@@ -19,50 +17,26 @@ use crate::passes;
use crate::reader;
use crate::symbol_moves;
/// The fully expanded command line.
pub type Cmdline = Arc<[Cow<'static, OsStr>]>;
pub use crate::cmdline::ParsedArgs;
/// The exit status of a link, or the name of the target the inputs are
/// actually for.
pub type LinkResult = Result<i32, &'static str>;
/// Runs the linker with the given command line. Returns the exit status.
/// Runs the linker with the given command line. A link that fails exits
/// in the middle (see mold_common::error::checkpoint).
///
/// `initial_target` is an enabled target used for the initial argument
/// parsing. `link_for_target` links for a named target, or reports the
/// target the inputs are actually for; the executable provides both, as
/// the targets are instantiated in crates of their own.
pub fn main(
argv: Vec<OsString>,
initial_target: &str,
link_for_target: impl Fn(&str, Cmdline) -> LinkResult,
) -> i32 {
let cmdline: Cmdline = mold_common::response_file::expand_response_files(argv, true).into();
// Parse with an enabled target's defaults; if the target turns out to
// be different, start over with the right one.
let mut target = initial_target;
loop {
match link_for_target(target, Arc::clone(&cmdline)) {
Ok(status) => return status,
Err(actual) => target = actual,
}
}
/// `link_for_target` links for a named target; the executable provides
/// it, as the targets are instantiated in crates of their own.
pub fn main(argv: Vec<OsString>, link_for_target: impl Fn(&str, ParsedArgs)) {
let cmdline = mold_common::response_file::expand_response_files(argv, true);
let parsed = cmdline::parse_args(&cmdline);
link_for_target(parsed.arch(), parsed)
}
fn target_traits<E: Target>() -> cmdline::TargetTraits {
cmdline::TargetTraits { name: E::NAME, page_size: E::PAGE_SIZE }
}
/// Links for the target `E`, or reports the target the inputs are
/// actually for.
pub fn link<E: Target>(cmdline: Cmdline) -> LinkResult {
let args = cmdline::parse_args(&target_traits::<E>(), &cmdline);
// Redo if the target does not match with our speculation.
if args.arch != Some(E::NAME) {
return Err(args.arch.unwrap());
}
/// Links for the target `E`.
pub fn link<E: Target>(parsed: ParsedArgs) {
let args = cmdline::resolve_args(&target_traits::<E>(), parsed);
// Fork so exit latency (unmapping every input) hides behind the
// parent's return, as in mold; MOLD_NO_FORK=1 keeps one process
@@ -117,7 +91,7 @@ pub fn link<E: Target>(cmdline: Cmdline) -> LinkResult {
mold_common::error::checkpoint();
mold_common::subprocess::notify_parent();
drop(t_all);
return Ok(0);
return;
}
// If there's a bitcode file, do link-time optimization.
@@ -181,7 +155,7 @@ pub fn link<E: Target>(cmdline: Cmdline) -> LinkResult {
mold_common::subprocess::notify_parent();
drop(t_all);
passes::show_stats(&ctx);
return Ok(0);
return;
}
// Merge the initializers, literals and Objective-C references, and
@@ -375,7 +349,6 @@ pub fn link<E: Target>(cmdline: Cmdline) -> LinkResult {
mold_common::subprocess::notify_parent();
drop(t_all);
passes::show_stats(&ctx);
Ok(0)
}
/// Copies all chunks to the output buffer and applies relocations, in