diff --git a/rust/private/runtime_linker_flags.bzl b/rust/private/runtime_linker_flags.bzl new file mode 100644 index 0000000000..2598192dd2 --- /dev/null +++ b/rust/private/runtime_linker_flags.bzl @@ -0,0 +1,57 @@ +"""Compiler-driver runtime options that are redundant for rustc-owned linking.""" + +def can_omit_runtime_selection(flags): + """Whether known, ordered rustc flags leave default linker libraries disabled. + + Opaque Args, response files and malformed options are conservative: preserve + the C toolchain's flags rather than changing an unknown link configuration. + + Args: + flags: Effective rustc options in precedence order, or an opaque Args. + + Returns: + True only when automatic default linker libraries are disabled. + """ + if type(flags) != "list": + return False + enabled = False # rustc's default for -C default-linker-libraries + codegen_next = False + for flag in flags: + if type(flag) != "string" or flag.startswith("@"): + return False + if flag == "--target" or flag.startswith("--target="): + return False + option = None + if codegen_next: + option = flag + codegen_next = False + elif flag in ("-C", "--codegen"): + codegen_next = True + elif flag.startswith("-C"): + option = flag[2:].removeprefix("=") + elif flag.startswith("--codegen="): + option = flag[len("--codegen="):] + if option == None: + continue + if option.startswith("linker="): + return False # The final compiler driver is no longer known. + if option == "default-linker-libraries": + enabled = True + elif option.startswith("default-linker-libraries="): + value = option.split("=", 1)[1] + if value in ("yes", "true", "on", "y", "1"): + enabled = True + elif value in ("no", "false", "off", "n", "0"): + enabled = False + else: + return False + return not enabled and not codegen_next + +def omit_unused_runtime_selection(link_args): + """Remove only the two redundant automatic runtime choices, preserving order.""" + return [arg for arg in link_args if arg not in ( + "--unwindlib=none", + "-unwindlib=none", + "-rtlib=compiler-rt", + "--rtlib=compiler-rt", + )] diff --git a/rust/private/rustc.bzl b/rust/private/rustc.bzl index a7006fb429..ca69ace45c 100644 --- a/rust/private/rustc.bzl +++ b/rust/private/rustc.bzl @@ -43,6 +43,7 @@ load( "UnstableRustFeaturesInfo", _BuildInfo = "BuildInfo", ) +load(":runtime_linker_flags.bzl", "can_omit_runtime_selection", "omit_unused_runtime_selection") load(":rustc_resource_set.bzl", "get_rustc_resource_set", "is_codegen_units_enabled") load(":stamp.bzl", "is_stamping_enabled") load( @@ -406,7 +407,7 @@ def get_linker_and_args(ctx, crate_type, toolchain, cc_toolchain, feature_config _get_linker_env(linker_config), ) -def _get_linker_config(ctx, crate_type, toolchain, cc_toolchain, feature_configuration, rpaths, add_flags_for_binary): +def _get_linker_config(ctx, crate_type, toolchain, cc_toolchain, feature_configuration, rpaths, add_flags_for_binary, omit_runtime_selection = False): user_link_flags = get_cc_user_link_flags(ctx) cc_linker = None @@ -459,6 +460,8 @@ def _get_linker_config(ctx, crate_type, toolchain, cc_toolchain, feature_configu is_direct_driver = use_rust_linker and toolchain.linker_type == "direct", target_arch = toolchain.target_arch, rpaths = rpaths, + omit_runtime_selection = omit_runtime_selection and not use_rust_linker and + cc_linker.replace("\\", "/").split("/")[-1] in ("clang", "clang++", "clang.exe", "clang++.exe"), ) def _get_linker_path(linker_config): @@ -487,6 +490,9 @@ def _get_linker_args(linker_config): variables = linker_config.variables, )) + if linker_config.omit_runtime_selection: + link_args = omit_unused_runtime_selection(link_args) + if linker_config.rust_linker: # Make sure we include RPATHs for Rust ABI dylibs even when no cc_toolchain. if linker_config.variables == None and linker_config.rpaths: @@ -1496,6 +1502,18 @@ def construct_arguments( feature_configuration, rpaths, add_flags_for_binary = add_flags_for_binary, + # rustc adds -nodefaultlibs on these targets unless explicitly + # overridden. Do not copy redundant Clang runtime-selection flags + # into that link. Opaque flags/build-script files stay untouched. + omit_runtime_selection = toolchain.target_os in ("linux", "darwin", "macos") and + not toolchain.target_flag_value.endswith(".json") and + not build_flags_files and + type(rust_flags) == "list" and + can_omit_runtime_selection( + collect_extra_rustc_flags(ctx, toolchain, crate_info.root, crate_info.type) + + rust_flags + + getattr(attr, "rustc_flags", []), + ), ) ld_is_direct_driver = linker_config.is_direct_driver diff --git a/test/unit/runtime_linker_flags/BUILD.bazel b/test/unit/runtime_linker_flags/BUILD.bazel new file mode 100644 index 0000000000..f719fedaaa --- /dev/null +++ b/test/unit/runtime_linker_flags/BUILD.bazel @@ -0,0 +1,3 @@ +load(":runtime_linker_flags_test.bzl", "runtime_linker_flags_test_suite") + +runtime_linker_flags_test_suite(name = "runtime_linker_flags_test_suite") diff --git a/test/unit/runtime_linker_flags/main.rs b/test/unit/runtime_linker_flags/main.rs new file mode 100644 index 0000000000..f328e4d9d0 --- /dev/null +++ b/test/unit/runtime_linker_flags/main.rs @@ -0,0 +1 @@ +fn main() {} diff --git a/test/unit/runtime_linker_flags/runtime_linker_flags_test.bzl b/test/unit/runtime_linker_flags/runtime_linker_flags_test.bzl new file mode 100644 index 0000000000..d62fcd138e --- /dev/null +++ b/test/unit/runtime_linker_flags/runtime_linker_flags_test.bzl @@ -0,0 +1,88 @@ +"""Regression tests for redundant Clang runtime-selection flags.""" + +load("@bazel_skylib//lib:unittest.bzl", "analysistest", "asserts", "unittest") +load("//rust:defs.bzl", "rust_binary") + +# buildifier: disable=bzl-visibility +load("//rust/private:runtime_linker_flags.bzl", "can_omit_runtime_selection", "omit_unused_runtime_selection") + +def _default_libraries_test_impl(ctx): + env = unittest.begin(ctx) + for flags in [ + [], + ["--edition=2021", "-Copt-level=2"], + ["-Cdefault-linker-libraries=no"], + ["-C", "default-linker-libraries=false"], + ["--codegen=default-linker-libraries=off"], + ["--codegen", "default-linker-libraries=0"], + ["-Cdefault-linker-libraries=yes", "-Cdefault-linker-libraries=no"], + ]: + asserts.true(env, can_omit_runtime_selection(flags), str(flags)) + for flags in [ + ["-Cdefault-linker-libraries"], + ["-C", "default-linker-libraries=yes"], + ["--codegen=default-linker-libraries=true"], + ["--codegen", "default-linker-libraries=on"], + ["-C=default-linker-libraries=1"], + ["-Cdefault-linker-libraries=no", "-Cdefault-linker-libraries=yes"], + ["-Cdefault-linker-libraries=invalid"], + ["-C"], + ["@flags.txt"], + ["--target=custom.json"], + [("--cfg=%s", "file")], + ["-Clinker=other-driver"], + ]: + asserts.false(env, can_omit_runtime_selection(flags), str(flags)) + asserts.false(env, can_omit_runtime_selection(ctx.actions.args())) + return unittest.end(env) + +def _filter_test_impl(ctx): + env = unittest.begin(ctx) + kept = ["-target", "x86_64-linux-gnu", "--sysroot=/dev/null", "-fuse-ld=lld", "-Lruntime", "-nostdlib++", "-Wl,--as-needed", "-lunwind", "--unwindlib=libunwind", "-rtlib=libgcc"] + original = kept[:4] + ["--unwindlib=none", "-rtlib=compiler-rt", "-unwindlib=none", "--rtlib=compiler-rt"] + kept[4:] + asserts.equals(env, kept, omit_unused_runtime_selection(original)) + return unittest.end(env) + +default_libraries_test = unittest.make(_default_libraries_test_impl) +filter_test = unittest.make(_filter_test_impl) + +def _link_action_test_impl(ctx): + env = analysistest.begin(ctx) + action = [a for a in analysistest.target_actions(env) if a.mnemonic == "Rustc"][0] + asserts.true(env, any([arg.startswith("--codegen=linker=") and arg.endswith("/clang++") for arg in action.argv])) + for flag in ("--unwindlib=none", "-rtlib=compiler-rt"): + asserts.equals(env, ctx.attr.preserve, "--codegen=link-arg=" + flag in action.argv, flag) + asserts.true(env, any([arg.startswith("--codegen=link-arg=-fuse-ld=") for arg in action.argv])) + return analysistest.end(env) + +def _link_action_test(platform): + return analysistest.make( + _link_action_test_impl, + attrs = {"preserve": attr.bool()}, + config_settings = { + "//command_line_option:extra_toolchains": [str(Label("@llvm//toolchain:all"))], + "//command_line_option:platforms": str(Label(":" + platform)), + "//command_line_option:linkopt": ["--unwindlib=none", "-rtlib=compiler-rt"], + }, + ) + +linux_link_action_test = _link_action_test("linux") +macos_link_action_test = _link_action_test("macos") + +def runtime_linker_flags_test_suite(name): + """Define pure flag tests and real Clang toolchain action regressions. + + Args: + name: Name of the pure flag test suite. + """ + native.platform(name = "macos", constraint_values = ["@platforms//os:macos", "@platforms//cpu:aarch64"]) + native.platform(name = "linux", constraint_values = ["@platforms//os:linux", "@platforms//cpu:x86_64"]) + for suffix, flags, preserve in [ + ("default", [], False), + ("enabled", ["-Cdefault-linker-libraries=yes"], True), + ("disabled_last", ["-Cdefault-linker-libraries=yes", "-C", "default-linker-libraries=no"], False), + ]: + rust_binary(name = suffix + "_bin", srcs = ["main.rs"], rustc_flags = flags, tags = ["manual"]) + linux_link_action_test(name = suffix + "_linux_action_test", target_under_test = ":" + suffix + "_bin", preserve = preserve) + macos_link_action_test(name = suffix + "_macos_action_test", target_under_test = ":" + suffix + "_bin", preserve = preserve) + unittest.suite(name, default_libraries_test, filter_test)