use super::target_info::TargetInfo;
pub(crate) enum FilteredArg {
Keep(Vec<String>),
Skip,
SkipWithNext,
}
pub(crate) fn filter_linker_args(
args: impl IntoIterator<Item = String>,
rustc_ver: &rustc_version::Version,
zig_version: &semver::Version,
target_info: &TargetInfo,
) -> Vec<String> {
let mut result = Vec::new();
let mut skip_next = false;
for arg in args {
if skip_next {
skip_next = false;
continue;
}
match filter_linker_arg(&arg, rustc_ver, zig_version, target_info) {
FilteredArg::Keep(filtered) => result.extend(filtered),
FilteredArg::Skip => {}
FilteredArg::SkipWithNext => {
skip_next = true;
}
}
}
if target_info.is_apple_platform() {
result = dedup_apple_link_libs(result);
}
result
}
pub(crate) fn dedup_apple_link_libs(args: Vec<String>) -> Vec<String> {
let mut seen = std::collections::HashSet::new();
args.into_iter()
.filter(|arg| {
if arg.starts_with("-l") && arg.len() > 2 {
seen.insert(arg.clone())
} else {
true
}
})
.collect()
}
pub(crate) fn filter_linker_arg(
arg: &str,
rustc_ver: &rustc_version::Version,
zig_version: &semver::Version,
target_info: &TargetInfo,
) -> FilteredArg {
if arg == "-lgcc_s" {
return FilteredArg::Keep(vec!["-lunwind".to_string()]);
} else if arg.starts_with("--target=") {
return FilteredArg::Skip;
} else if arg.starts_with("-e") && arg.len() > 2 && !arg.starts_with("-export") {
let entry = &arg[2..];
return FilteredArg::Keep(vec![format!("-Wl,--entry={}", entry)]);
} else if let Some(sym) = arg.strip_prefix("-Wl,--undefined=") {
return FilteredArg::Keep(vec![format!("-Wl,-u,{sym}")]);
}
if (target_info.is_arm() || target_info.is_windows_gnu())
&& arg.ends_with(".rlib")
&& arg.contains("libcompiler_builtins-")
{
return FilteredArg::Skip;
}
if target_info.is_windows_gnu() {
#[allow(clippy::if_same_then_else)]
if arg == "-lgcc_eh"
&& ((zig_version.major, zig_version.minor) < (0, 14) || target_info.is_i686())
{
return FilteredArg::Keep(vec!["-lc++".to_string()]);
} else if arg.ends_with("rsbegin.o") || arg.ends_with("rsend.o") {
if target_info.is_i686() {
return FilteredArg::Skip;
}
} else if arg == "-Wl,-Bdynamic" && (zig_version.major, zig_version.minor) >= (0, 11) {
return FilteredArg::Keep(vec!["-Wl,-search_paths_first".to_owned()]);
} else if arg == "-lwindows" || arg == "-l:libpthread.a" || arg == "-lgcc" {
return FilteredArg::Skip;
} else if arg == "-Wl,--disable-auto-image-base"
|| arg == "-Wl,--dynamicbase"
|| arg == "-Wl,--large-address-aware"
|| (arg.starts_with("-Wl,")
&& (arg.ends_with("/list.def") || arg.ends_with("\\list.def")))
{
return FilteredArg::Skip;
} else if arg == "-lmsvcrt" {
return FilteredArg::Skip;
}
} else if arg == "-Wl,--no-undefined-version"
|| arg == "-Wl,-znostart-stop-gc"
|| arg == "-Wl,--fix-cortex-a53-843419"
|| arg.starts_with("-Wl,-plugin-opt")
{
return FilteredArg::Skip;
}
if target_info.is_musl() || target_info.is_ohos() {
if (arg.ends_with(".o") && arg.contains("self-contained") && arg.contains("crt"))
|| arg == "-Wl,-melf_i386"
{
return FilteredArg::Skip;
}
if rustc_ver.major == 1
&& rustc_ver.minor < 59
&& arg.ends_with(".rlib")
&& arg.contains("liblibc-")
{
return FilteredArg::Skip;
}
if arg == "-lc" {
return FilteredArg::Skip;
}
}
if arg.starts_with("-Wp,")
&& !arg.starts_with("-Wp,-MD")
&& !arg.starts_with("-Wp,-MMD")
&& !arg.starts_with("-Wp,-MT")
{
return FilteredArg::Skip;
}
if arg.starts_with("-march=") {
if target_info.is_arm() || target_info.is_i386() {
return FilteredArg::Skip;
} else if target_info.is_riscv64() {
return FilteredArg::Keep(vec!["-march=generic_rv64".to_string()]);
} else if target_info.is_riscv32() {
return FilteredArg::Keep(vec!["-march=generic_rv32".to_string()]);
} else if arg.starts_with("-march=armv")
&& (target_info.is_aarch64() || target_info.is_aarch64_be())
{
let march_value = arg.strip_prefix("-march=").unwrap();
let base_cpu = if target_info.is_apple_platform() {
target_info.apple_cpu()
} else {
"generic"
};
let (features, has_crypto) = map_aarch64_features(march_value);
let mut result = vec![format!("-mcpu={base_cpu}{features}")];
if has_crypto {
result.append(&mut vec!["-Xassembler".to_owned(), arg.to_string()]);
}
return FilteredArg::Keep(result);
} else {
let value = arg.strip_prefix("-march=").unwrap();
return FilteredArg::Keep(vec![format!("-march={}", map_cpu_name(value, target_info))]);
}
}
if let Some((flag @ ("-mcpu" | "-mtune"), value)) = arg.split_once('=') {
if target_info.is_aarch64() || target_info.is_aarch64_be() {
let cpu = value.split('+').next().unwrap();
let (features, _) = map_aarch64_features(value);
return FilteredArg::Keep(vec![format!(
"{flag}={}{features}",
map_cpu_name(cpu, target_info)
)]);
}
return FilteredArg::Keep(vec![format!("{flag}={}", map_cpu_name(value, target_info))]);
}
if target_info.is_apple_platform() {
if (zig_version.major, zig_version.minor) < (0, 16) {
if arg.starts_with("-Wl,-exported_symbols_list,") {
return FilteredArg::Skip;
}
if arg == "-Wl,-exported_symbols_list" {
return FilteredArg::SkipWithNext;
}
}
if arg == "-Wl,-dylib" {
return FilteredArg::Skip;
}
}
if (zig_version.major, zig_version.minor) < (0, 16) {
if arg == "-Wl,-exported_symbols_list" || arg == "-Wl,--dynamic-list" {
return FilteredArg::SkipWithNext;
}
if arg.starts_with("-Wl,-exported_symbols_list,") || arg.starts_with("-Wl,--dynamic-list,")
{
return FilteredArg::Skip;
}
}
if target_info.is_freebsd() {
let ignored_libs = ["-lkvm", "-lmemstat", "-lprocstat", "-ldevstat"];
if ignored_libs.contains(&arg) {
return FilteredArg::Skip;
}
}
FilteredArg::Keep(vec![arg.to_string()])
}
fn map_cpu_name(value: &str, target_info: &TargetInfo) -> String {
if target_info.is_s390x() {
return value.to_string();
}
let (cpu, features) = match value.find('+') {
Some(pos) => value.split_at(pos),
None => (value, ""),
};
if target_info.is_powerpc() {
let cpu = match cpu {
"powerpc" => "ppc".to_string(),
"powerpc64" => "ppc64".to_string(),
"powerpc64le" => "ppc64le".to_string(),
_ => match cpu.strip_prefix("power") {
Some(n) if n.starts_with(|c: char| c.is_ascii_digit()) => format!("pwr{n}"),
_ => cpu.to_string(),
},
};
return format!("{cpu}{features}");
}
format!("{}{features}", cpu.replace('-', "_"))
}
fn map_aarch64_features(value: &str) -> (String, bool) {
let mut extensions = value.split('+');
let _cpu_or_arch = extensions.next();
let mut features = String::new();
let mut has_crypto = false;
for ext in extensions.filter(|e| !e.is_empty() && *e != "none") {
has_crypto |= ext == "crypto";
let (sign, name) = match ext.strip_prefix("no") {
Some(name) if !name.is_empty() => ('-', name),
_ => ('+', ext),
};
features.push(sign);
for c in map_aarch64_arch_extension(name).chars() {
features.push(if c == '-' { '_' } else { c });
}
}
(features, has_crypto)
}
fn map_aarch64_arch_extension(ext: &str) -> &str {
match ext {
"fp" => "fp-armv8",
"fp16" => "fullfp16",
"simd" => "neon",
"profile" => "spe",
"rng" => "rand",
"memtag" => "mte",
"pmuv3" => "perfmon",
"jscvt" => "jsconv",
"fcma" => "complxnum",
"predres2" => "specres2",
_ => ext,
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_rustc_ver(major: u64, minor: u64, patch: u64) -> rustc_version::Version {
rustc_version::Version::new(major, minor, patch)
}
fn make_zig_ver(major: u64, minor: u64, patch: u64) -> semver::Version {
semver::Version::new(major, minor, patch)
}
fn run_filter(args: &[&str], target: Option<&str>, zig_ver: (u64, u64)) -> Vec<String> {
let rustc_ver = make_rustc_ver(1, 80, 0);
let zig_version = make_zig_ver(0, zig_ver.0, zig_ver.1);
let target_info = TargetInfo::new(target.map(|s| s.to_string()).as_ref());
filter_linker_args(
args.iter().map(|s| s.to_string()),
&rustc_ver,
&zig_version,
&target_info,
)
}
fn run_filter_one(arg: &str, target: Option<&str>, zig_ver: (u64, u64)) -> Vec<String> {
run_filter(&[arg], target, zig_ver)
}
fn run_filter_one_rustc(
arg: &str,
target: Option<&str>,
zig_ver: (u64, u64),
rustc_minor: u64,
) -> Vec<String> {
let rustc_ver = make_rustc_ver(1, rustc_minor, 0);
let zig_version = make_zig_ver(0, zig_ver.0, zig_ver.1);
let target_info = TargetInfo::new(target.map(|s| s.to_string()).as_ref());
filter_linker_args(
std::iter::once(arg.to_string()),
&rustc_ver,
&zig_version,
&target_info,
)
}
#[test]
fn test_filter_common_replacements() {
let linux = Some("x86_64-unknown-linux-gnu");
assert_eq!(run_filter_one("-lgcc_s", linux, (13, 0)), vec!["-lunwind"]);
assert!(run_filter_one("--target=x86_64-unknown-linux-gnu", linux, (13, 0)).is_empty());
assert_eq!(
run_filter_one("-emain", linux, (13, 0)),
vec!["-Wl,--entry=main"]
);
assert_eq!(
run_filter_one("-export-dynamic", linux, (13, 0)),
vec!["-export-dynamic"]
);
}
#[test]
fn test_filter_compiler_builtins_removed() {
for target in &["armv7-unknown-linux-gnueabihf", "x86_64-pc-windows-gnu"] {
let result = run_filter_one(
"/path/to/libcompiler_builtins-abc123.rlib",
Some(target),
(13, 0),
);
assert!(
result.is_empty(),
"compiler_builtins should be removed for {target}"
);
}
}
#[test]
fn test_filter_windows_gnu_args() {
let gnu = Some("x86_64-pc-windows-gnu");
let removed: &[&str] = &[
"-lwindows",
"-l:libpthread.a",
"-lgcc",
"-Wl,--disable-auto-image-base",
"-Wl,--dynamicbase",
"-Wl,--large-address-aware",
"-Wl,/path/to/list.def",
"-Wl,C:\\path\\to\\list.def",
"-lmsvcrt",
];
for arg in removed {
let result = run_filter_one(arg, gnu, (13, 0));
assert!(result.is_empty(), "{arg} should be removed for windows-gnu");
}
let replaced: &[(&str, (u64, u64), &str)] = &[
("-lgcc_eh", (13, 0), "-lc++"),
("-Wl,-Bdynamic", (13, 0), "-Wl,-search_paths_first"),
];
for (arg, zig_ver, expected) in replaced {
let result = run_filter_one(arg, gnu, *zig_ver);
assert_eq!(result, vec![*expected], "filter({arg})");
}
let result = run_filter_one("-lgcc_eh", gnu, (14, 0));
assert_eq!(result, vec!["-lgcc_eh"]);
}
#[test]
fn test_filter_windows_gnu_rsbegin() {
let result = run_filter_one("/path/to/rsbegin.o", Some("i686-pc-windows-gnu"), (13, 0));
assert!(result.is_empty());
let result = run_filter_one("/path/to/rsbegin.o", Some("x86_64-pc-windows-gnu"), (13, 0));
assert_eq!(result, vec!["/path/to/rsbegin.o"]);
}
#[test]
fn test_filter_unsupported_linker_args() {
let linux = Some("x86_64-unknown-linux-gnu");
let removed: &[&str] = &[
"-Wl,--no-undefined-version",
"-Wl,-znostart-stop-gc",
"-Wl,--fix-cortex-a53-843419",
"-Wl,-plugin-opt=O2",
];
for arg in removed {
let result = run_filter_one(arg, linux, (13, 0));
assert!(result.is_empty(), "{arg} should be removed");
}
}
#[test]
fn test_filter_undefined_arg() {
let linux = Some("x86_64-unknown-linux-gnu");
let result = run_filter_one("-Wl,--undefined=AUDITABLE_VERSION_INFO", linux, (13, 0));
assert_eq!(result, vec!["-Wl,-u,AUDITABLE_VERSION_INFO"]);
let result = run_filter_one(
"-Wl,-undefined=dynamic_lookup",
Some("aarch64-apple-darwin"),
(13, 0),
);
assert_eq!(result, vec!["-Wl,-undefined=dynamic_lookup"]);
}
#[test]
fn test_filter_wp_args() {
let linux = Some("x86_64-unknown-linux-gnu");
for arg in &[
"-Wp,-U_FORTIFY_SOURCE",
"-Wp,-DFOO=1",
"-Wp,-MF,/tmp/t.d",
"-Wp,-MQ,foo",
"-Wp,-MP",
] {
let result = run_filter_one(arg, linux, (13, 0));
assert!(result.is_empty(), "{arg} should be removed");
}
for arg in &["-Wp,-MD,/tmp/test.d", "-Wp,-MMD,/tmp/test.d", "-Wp,-MT,foo"] {
let result = run_filter_one(arg, linux, (13, 0));
assert_eq!(result, vec![*arg], "{arg} should be kept");
}
let result = run_filter_one("-U_FORTIFY_SOURCE", linux, (13, 0));
assert_eq!(result, vec!["-U_FORTIFY_SOURCE"]);
let result = run_filter_one("-DFOO=1", linux, (13, 0));
assert_eq!(result, vec!["-DFOO=1"]);
}
#[test]
fn test_filter_musl_args() {
let musl = Some("x86_64-unknown-linux-musl");
let removed: &[&str] = &["/path/self-contained/crt1.o", "-lc"];
for arg in removed {
let result = run_filter_one(arg, musl, (13, 0));
assert!(result.is_empty(), "{arg} should be removed for musl");
}
let result = run_filter_one("-Wl,-melf_i386", Some("i686-unknown-linux-musl"), (13, 0));
assert!(result.is_empty());
let result = run_filter_one_rustc("/path/to/liblibc-abc123.rlib", musl, (13, 0), 58);
assert!(result.is_empty());
let result = run_filter_one_rustc("/path/to/liblibc-abc123.rlib", musl, (13, 0), 59);
assert_eq!(result, vec!["/path/to/liblibc-abc123.rlib"]);
}
#[test]
fn test_filter_march_args() {
let cases: &[(&str, &str, &[&str])] = &[
("-march=armv7-a", "armv7-unknown-linux-gnueabihf", &[]),
(
"-march=rv64gc",
"riscv64gc-unknown-linux-gnu",
&["-march=generic_rv64"],
),
(
"-march=rv32imac",
"riscv32imac-unknown-none-elf",
&["-march=generic_rv32"],
),
(
"-march=armv8.4-a",
"aarch64-unknown-linux-gnu",
&["-mcpu=generic"],
),
(
"-march=armv8.4-a+crypto",
"aarch64-unknown-linux-gnu",
&[
"-mcpu=generic+crypto",
"-Xassembler",
"-march=armv8.4-a+crypto",
],
),
(
"-march=armv8.4-a",
"aarch64-apple-darwin",
&["-mcpu=apple_m1"],
),
(
"-march=armv8.2-a+sve+fp16",
"aarch64-unknown-linux-musl",
&["-mcpu=generic+sve+fullfp16"],
),
(
"-march=armv8.2-a+simd+profile+rng+memtag",
"aarch64-unknown-linux-gnu",
&["-mcpu=generic+neon+spe+rand+mte"],
),
(
"-march=armv9-a+sve2-aes+fp",
"aarch64-unknown-linux-gnu",
&["-mcpu=generic+sve2_aes+fp_armv8"],
),
(
"-march=armv8.2-a+nofp16+nosimd",
"aarch64-unknown-linux-gnu",
&["-mcpu=generic-fullfp16-neon"],
),
(
"-march=x86-64-v3",
"x86_64-unknown-linux-gnu",
&["-march=x86_64_v3"],
),
(
"-march=haswell",
"x86_64-unknown-linux-gnu",
&["-march=haswell"],
),
];
for (input, target, expected) in cases {
let result = run_filter_one(input, Some(target), (13, 0));
assert_eq!(&result, expected, "filter({input}, {target})");
}
}
#[test]
fn test_filter_mcpu_mtune_args() {
let cases: &[(&str, &str, &[&str])] = &[
(
"-mcpu=cortex-a53",
"aarch64-unknown-linux-gnu",
&["-mcpu=cortex_a53"],
),
(
"-mcpu=cortex-a7",
"arm-unknown-linux-gnueabihf",
&["-mcpu=cortex_a7"],
),
(
"-mtune=x86-64-v3",
"x86_64-unknown-linux-gnu",
&["-mtune=x86_64_v3"],
),
(
"-mcpu=cortex-a53+crypto+nofp16",
"aarch64-unknown-linux-gnu",
&["-mcpu=cortex_a53+crypto-fullfp16"],
),
(
"-mcpu=power9",
"powerpc64le-unknown-linux-gnu",
&["-mcpu=pwr9"],
),
(
"-mcpu=powerpc64le",
"powerpc64le-unknown-linux-gnu",
&["-mcpu=ppc64le"],
),
(
"-mcpu=generic+v6+strict_align+vfp2-d32",
"arm-unknown-linux-gnueabihf",
&["-mcpu=generic+v6+strict_align+vfp2-d32"],
),
(
"-mcpu=z10-vector",
"s390x-unknown-linux-gnu",
&["-mcpu=z10-vector"],
),
];
for (input, target, expected) in cases {
let result = run_filter_one(input, Some(target), (13, 0));
assert_eq!(&result, expected, "filter({input}, {target})");
}
}
#[test]
fn test_filter_apple_args() {
let darwin = Some("aarch64-apple-darwin");
let result = run_filter_one("-Wl,-dylib", darwin, (13, 0));
assert!(result.is_empty());
}
#[test]
fn test_dedup_apple_duplicate_libs() {
let darwin = Some("aarch64-apple-darwin");
let result = run_filter(
&[
"-lobjc",
"-framework",
"AppKit",
"-lobjc",
"-liconv",
"-lobjc",
"main.o",
],
darwin,
(16, 0),
);
assert_eq!(
result,
vec!["-lobjc", "-framework", "AppKit", "-liconv", "main.o"]
);
let result = run_filter(
&["-lfoo", "-lfoo"],
Some("x86_64-unknown-linux-gnu"),
(16, 0),
);
assert_eq!(result, vec!["-lfoo", "-lfoo"]);
}
#[test]
fn test_filter_freebsd_libs_removed() {
for lib in &["-lkvm", "-lmemstat", "-lprocstat", "-ldevstat"] {
let result = run_filter_one(lib, Some("x86_64-unknown-freebsd"), (13, 0));
assert!(result.is_empty(), "{lib} should be removed for freebsd");
}
}
#[test]
fn test_filter_exported_symbols_list_two_arg_apple() {
let result = run_filter(
&[
"-arch",
"arm64",
"-Wl,-exported_symbols_list",
"-Wl,/tmp/rustcXXX/list",
"-o",
"output.dylib",
],
Some("aarch64-apple-darwin"),
(13, 0),
);
assert_eq!(result, vec!["-arch", "arm64", "-o", "output.dylib"]);
}
#[test]
fn test_filter_exported_symbols_list_two_arg_cross_platform() {
let result = run_filter(
&[
"-arch",
"arm64",
"-Wl,-exported_symbols_list",
"-Wl,C:\\Users\\RUNNER~1\\AppData\\Local\\Temp\\rustcXXX\\list",
"-o",
"output.dylib",
],
None,
(13, 0),
);
assert_eq!(result, vec!["-arch", "arm64", "-o", "output.dylib"]);
}
#[test]
fn test_filter_exported_symbols_list_single_arg_comma() {
let result = run_filter(
&[
"-Wl,-exported_symbols_list,/tmp/rustcXXX/list",
"-o",
"output.dylib",
],
Some("aarch64-apple-darwin"),
(13, 0),
);
assert_eq!(result, vec!["-o", "output.dylib"]);
}
#[test]
fn test_filter_exported_symbols_list_not_filtered_zig_016() {
let result = run_filter(
&[
"-Wl,-exported_symbols_list",
"-Wl,/tmp/rustcXXX/list",
"-o",
"output.dylib",
],
Some("aarch64-apple-darwin"),
(16, 0),
);
assert_eq!(
result,
vec![
"-Wl,-exported_symbols_list",
"-Wl,/tmp/rustcXXX/list",
"-o",
"output.dylib"
]
);
}
#[test]
fn test_filter_dynamic_list_two_arg() {
let result = run_filter(
&[
"-Wl,--dynamic-list",
"-Wl,/tmp/rustcXXX/list",
"-o",
"output.so",
],
Some("x86_64-unknown-linux-gnu"),
(13, 0),
);
assert_eq!(result, vec!["-o", "output.so"]);
}
#[test]
fn test_filter_dynamic_list_single_arg_comma() {
let result = run_filter(
&["-Wl,--dynamic-list,/tmp/rustcXXX/list", "-o", "output.so"],
Some("x86_64-unknown-linux-gnu"),
(13, 0),
);
assert_eq!(result, vec!["-o", "output.so"]);
}
#[test]
fn test_filter_preserves_normal_args() {
let result = run_filter(
&["-arch", "arm64", "-lSystem", "-lc", "-o", "output"],
Some("aarch64-apple-darwin"),
(13, 0),
);
assert_eq!(
result,
vec!["-arch", "arm64", "-lSystem", "-lc", "-o", "output"]
);
}
#[test]
fn test_filter_skip_next_at_end_of_args() {
let result = run_filter(
&["-o", "output", "-Wl,-exported_symbols_list"],
Some("aarch64-apple-darwin"),
(13, 0),
);
assert_eq!(result, vec!["-o", "output"]);
}
}