Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions rust/private/runtime_linker_flags.bzl
Original file line number Diff line number Diff line change
@@ -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",
)]
20 changes: 19 additions & 1 deletion rust/private/rustc.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions test/unit/runtime_linker_flags/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -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")
1 change: 1 addition & 0 deletions test/unit/runtime_linker_flags/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
fn main() {}
88 changes: 88 additions & 0 deletions test/unit/runtime_linker_flags/runtime_linker_flags_test.bzl
Original file line number Diff line number Diff line change
@@ -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)
Loading