Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: cross language LTO #3162

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
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
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":
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did you revert this intentionally?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I was experimenting, but I switched this back to how it was before!

# 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",
],
)
Loading