From cfdef01d4ee8e4bc5fc69e938b33bf7c07d10e79 Mon Sep 17 00:00:00 2001 From: Parker Timmerman Date: Sun, 5 Jan 2025 22:43:51 -0500 Subject: [PATCH] start, add experimental_cross_language_lto feature --- rust/private/lto.bzl | 47 +++++++++++++++++++++++++++++++++----- rust/settings/BUILD.bazel | 3 +++ rust/settings/settings.bzl | 11 +++++++++ rust/toolchain.bzl | 4 ++++ 4 files changed, 59 insertions(+), 6 deletions(-) diff --git a/rust/private/lto.bzl b/rust/private/lto.bzl index 18fe307515..65f5d58bdd 100644 --- a/rust/private/lto.bzl +++ b/rust/private/lto.bzl @@ -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. @@ -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 + + 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. @@ -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 format != "only_object" and not is_exec_configuration(ctx): args.append("lto={}".format(mode)) if format == "object_and_bitcode": @@ -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] diff --git a/rust/settings/BUILD.bazel b/rust/settings/BUILD.bazel index 1dbef8f9b2..69e9706c8a 100644 --- a/rust/settings/BUILD.bazel +++ b/rust/settings/BUILD.bazel @@ -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", @@ -60,6 +61,8 @@ codegen_units() error_format() +experimental_cross_language_lto() + experimental_link_std_dylib() experimental_per_crate_rustc_flag() diff --git a/rust/settings/settings.bzl b/rust/settings/settings.bzl index 1d323ca360..bda9b4fd15 100644 --- a/rust/settings/settings.bzl +++ b/rust/settings/settings.bzl @@ -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: + """ + 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. diff --git a/rust/toolchain.bzl b/rust/toolchain.bzl index 97d9e07f4c..8bf276d026 100644 --- a/rust/toolchain.bzl +++ b/rust/toolchain.bzl @@ -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, @@ -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"), ),