Skip to content

Commit

Permalink
start, add experimental_cross_language_lto feature
Browse files Browse the repository at this point in the history
  • Loading branch information
ParkMyCar committed Jan 14, 2025
1 parent bb74a65 commit 281bf70
Show file tree
Hide file tree
Showing 5 changed files with 126 additions and 7 deletions.
47 changes: 41 additions & 6 deletions rust/private/lto.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ rust_lto_flag = rule(
)

def _determine_lto_object_format(ctx, toolchain, crate_info):
"""Determines if we should run LTO and what bitcode should get included in a built artifact.
"""Determines what bitcode should get included in a built artifact.
Args:
ctx (ctx): The calling rule's context object.
Expand Down Expand Up @@ -76,13 +76,39 @@ def _determine_lto_object_format(ctx, toolchain, crate_info):
# generating object files entirely.
return "only_bitcode"
elif crate_info.type in ["dylib", "proc-macro"]:
# If we're a dylib and we're running LTO, then only emit object code
# because 'rustc' doesn't currently support LTO with dylibs.
# proc-macros do not benefit from LTO, and cannot be dynamically linked with LTO.
# If we're a dylib or a proc-macro and we're running LTO, then only emit
# object code because 'rustc' doesn't currently support LTO for these targets.
return "only_object"
else:
return "object_and_bitcode"

def _determine_experimental_xlang_lto(ctx, toolchain, crate_info):
"""Determines if we should use Linker-plugin-based LTO, to enable cross language optimizations.
'rustc' has a `linker-plugin-lto` codegen option which delays LTO to the actual linking step.
If your C/C++ code is built with an LLVM toolchain (e.g. clang) and was built with LTO enabled,
then the linker can perform optimizations across programming language boundaries.
See <https://doc.rust-lang.org/rustc/linker-plugin-lto.html>
Args:
ctx (ctx): The calling rule's context object.
toolchain (rust_toolchain): The current target's `rust_toolchain`.
crate_info (CrateInfo): The CrateInfo provider of the target crate.
Returns:
bool: Whether or not to specify `-Clinker-plugin-lto` when building this crate.
"""

feature_enabled = toolchain._experimental_cross_language_lto
rust_lto_enabled = toolchain.lto.mode in ["thin", "fat"]
correct_crate_type = crate_info.type in ["bin"]

# TODO(parkmycar): We could try to detect if LTO is enabled for C code using
# `ctx.fragments.cpp.copts` but I'm not sure how reliable that is.

return feature_enabled and rust_lto_enabled and correct_crate_type and not is_exec_configuration(ctx)

def construct_lto_arguments(ctx, toolchain, crate_info):
"""Returns a list of 'rustc' flags to configure link time optimization.
Expand All @@ -101,10 +127,16 @@ def construct_lto_arguments(ctx, toolchain, crate_info):
return []

format = _determine_lto_object_format(ctx, toolchain, crate_info)
xlang_enabled = _determine_experimental_xlang_lto(ctx, toolchain, crate_info)
args = []

# proc-macros do not benefit from LTO, and cannot be dynamically linked with LTO.
if mode in ["thin", "fat", "off"] and not is_exec_configuration(ctx) and crate_info.type != "proc-macro":
# Only tell `rustc` to use LTO if it's enabled, the crate we're currently building has bitcode
# embeded, and we're not building in the exec configuration.
#
# We skip running LTO when building for the exec configuration because the exec config is used
# for local tools, like build scripts or proc-macros, and LTO isn't really needed in those
# scenarios. Note, this also mimics Cargo's behavior.
if mode in ["thin", "fat", "off"] and crate_info.type != "proc-macro" and not is_exec_configuration(ctx):
args.append("lto={}".format(mode))

if format == "object_and_bitcode":
Expand All @@ -117,4 +149,7 @@ def construct_lto_arguments(ctx, toolchain, crate_info):
else:
fail("unrecognized LTO object format {}".format(format))

if xlang_enabled:
args.append("linker-plugin-lto")

return ["-C{}".format(arg) for arg in args]
3 changes: 3 additions & 0 deletions rust/settings/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ load(
"clippy_toml",
"codegen_units",
"error_format",
"experimental_cross_language_lto",
"experimental_link_std_dylib",
"experimental_per_crate_rustc_flag",
"experimental_use_cc_common_link",
Expand Down Expand Up @@ -60,6 +61,8 @@ codegen_units()

error_format()

experimental_cross_language_lto()

experimental_link_std_dylib()

experimental_per_crate_rustc_flag()
Expand Down
11 changes: 11 additions & 0 deletions rust/settings/settings.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,17 @@ def lto():
build_setting_default = "unspecified",
)

def experimental_cross_language_lto():
"""A build setting which specifies whether or not to specify `linker-plugin-lto` and perform \
cross language optimizations.
See: <https://doc.rust-lang.org/rustc/linker-plugin-lto.html>
"""
bool_flag(
name = "experimental_cross_language_lto",
build_setting_default = False,
)

def rename_first_party_crates():
"""A flag controlling whether to rename first-party crates such that their names \
encode the Bazel package and target name, instead of just the target name.
Expand Down
4 changes: 4 additions & 0 deletions rust/toolchain.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -698,6 +698,7 @@ def _rust_toolchain_impl(ctx):
_rename_first_party_crates = rename_first_party_crates,
_third_party_dir = third_party_dir,
_pipelined_compilation = pipelined_compilation,
_experimental_cross_language_lto = ctx.attr._experimental_cross_language_lto[BuildSettingInfo].value,
_experimental_link_std_dylib = _experimental_link_std_dylib(ctx),
_experimental_use_cc_common_link = _experimental_use_cc_common_link(ctx),
_experimental_use_global_allocator = experimental_use_global_allocator,
Expand Down Expand Up @@ -885,6 +886,9 @@ rust_toolchain = rule(
"_codegen_units": attr.label(
default = Label("//rust/settings:codegen_units"),
),
"_experimental_cross_language_lto": attr.label(
default = Label("//rust/settings:experimental_cross_language_lto"),
),
"_experimental_use_coverage_metadata_files": attr.label(
default = Label("//rust/settings:experimental_use_coverage_metadata_files"),
),
Expand Down
68 changes: 67 additions & 1 deletion test/unit/lto/lto_test_suite.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

load("@bazel_skylib//lib:unittest.bzl", "analysistest")
load("@bazel_skylib//rules:write_file.bzl", "write_file")
load("//rust:defs.bzl", "rust_library", "rust_proc_macro")
load("//rust:defs.bzl", "rust_library", "rust_proc_macro", "rust_binary")
load(
"//test/unit:common.bzl",
"assert_action_mnemonic",
Expand Down Expand Up @@ -86,6 +86,39 @@ _lto_proc_macro_test = analysistest.make(
config_settings = {str(Label("//rust/settings:lto")): "thin"},
)

def _lto_xlang_bin_off(ctx):
return _lto_test_impl(ctx, "off", "no", False)

_lto_xlang_bin_off_test = analysistest.make(
_lto_xlang_bin_off,
config_settings = {
str(Label("//rust/settings:lto")): "off",
str(Label("//rust/settings:experimental_cross_language_lto")): True,
},
)

def _lto_xlang_bin_thin(ctx):
return _lto_test_impl(ctx, "thin", None, True)

_lto_xlang_bin_thin_test = analysistest.make(
_lto_xlang_bin_thin,
config_settings = {
str(Label("//rust/settings:lto")): "thin",
str(Label("//rust/settings:experimental_cross_language_lto")): True,
},
)

def _lto_xlang_lib_off(ctx):
return _lto_test_impl(ctx, "off", "no", False)

_lto_xlang_lib_off_test = analysistest.make(
_lto_xlang_lib_off,
config_settings = {
str(Label("//rust/settings:lto")): "off",
str(Label("//rust/settings:experimental_cross_language_lto")): False,
},
)

def lto_test_suite(name):
"""Entry-point macro called from the BUILD file.
Expand All @@ -102,6 +135,15 @@ def lto_test_suite(name):
],
)

write_file(
name = "crate_bin",
out = "main.rs",
content = [
"fn main() {}",
"",
],
)

rust_library(
name = "lib",
srcs = [":lib.rs"],
Expand All @@ -114,6 +156,12 @@ def lto_test_suite(name):
edition = "2021",
)

rust_binary(
name = "binary",
srcs = [":main.rs"],
edition = "2021",
)

_lto_level_default_test(
name = "lto_level_default_test",
target_under_test = ":lib",
Expand Down Expand Up @@ -144,6 +192,21 @@ def lto_test_suite(name):
target_under_test = ":proc_macro",
)

_lto_xlang_bin_off_test(
name = "lto_xlang_bin_off_test",
target_under_test = ":binary",
)

_lto_xlang_bin_thin_test(
name = "lto_xlang_bin_thin_test",
target_under_test = ":binary",
)

_lto_xlang_lib_off_test(
name = "lto_xlang_lib_off_test",
target_under_test = ":lib",
)

native.test_suite(
name = name,
tests = [
Expand All @@ -153,5 +216,8 @@ def lto_test_suite(name):
":lto_level_thin_test",
":lto_level_fat_test",
":lto_proc_macro_test",
":lto_xlang_bin_off_test",
":lto_xlang_bin_thin_test",
":lto_xlang_lib_off_test",
],
)

0 comments on commit 281bf70

Please sign in to comment.