diff --git a/rust/private/lto.bzl b/rust/private/lto.bzl index 18fe307515..531129944e 100644 --- a/rust/private/lto.bzl +++ b/rust/private/lto.bzl @@ -1,5 +1,6 @@ """A module defining Rust link time optimization (lto) rules""" +load("//rust/private:providers.bzl", "IsProcMacroDepInfo") load("//rust/private:utils.bzl", "is_exec_configuration") _LTO_MODES = [ @@ -43,6 +44,21 @@ rust_lto_flag = rule( build_setting = config.string(flag = True), ) +def _is_proc_macro_dep(ctx): + """Determines if the current crate is a dependency of a proc-macro. + + Relies on a transition being applied to dependencies of `rust_proc_macro` and providing + `IsProcMacroDepInfo`. + + Args: + ctx (ctx): The calling rule's context object. + + Returns: + boolean: Whether or not the current crate is a dependency of a proc-macro. + """ + return hasattr(ctx.attr, "_is_proc_macro_dep") and \ + ctx.attr._is_proc_macro_dep[IsProcMacroDepInfo].is_proc_macro_dep + 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. @@ -57,7 +73,7 @@ def _determine_lto_object_format(ctx, toolchain, crate_info): # Even if LTO is enabled don't use it for actions being built in the exec # configuration, e.g. build scripts and proc-macros. This mimics Cargo. - if is_exec_configuration(ctx): + if is_exec_configuration(ctx) or _is_proc_macro_dep(ctx): return "only_object" mode = toolchain.lto.mode @@ -103,8 +119,14 @@ def construct_lto_arguments(ctx, toolchain, crate_info): format = _determine_lto_object_format(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": + # We don't want to set LTO for actions being built in the exec configuration, e.g. build + # scripts or proc-macros. But also if we're building a proc-macro directly (or its + # dependencies), we want to skip LTO since `rustc` itself doesn't support it. + # + # See . + if is_exec_configuration(ctx) or crate_info.type == "proc-macro" or _is_proc_macro_dep(ctx): + pass + elif mode in ["thin", "fat", "off"]: args.append("lto={}".format(mode)) if format == "object_and_bitcode": diff --git a/rust/private/providers.bzl b/rust/private/providers.bzl index 0556ebef5f..0a20c3d3c4 100644 --- a/rust/private/providers.bzl +++ b/rust/private/providers.bzl @@ -186,3 +186,8 @@ LintsInfo = provider( "rustdoc_lint_flags": "List[String]: rustc flags to specify when building rust_doc targets.", }, ) + +IsProcMacroDepInfo = provider( + doc = "Records if this is a transitive dependency of a proc-macro.", + fields = {"is_proc_macro_dep": "Boolean"}, +) diff --git a/rust/private/rustc.bzl b/rust/private/rustc.bzl index 98e005dc84..e148d12a68 100644 --- a/rust/private/rustc.bzl +++ b/rust/private/rustc.bzl @@ -25,7 +25,7 @@ load( load(":common.bzl", "rust_common") load(":compat.bzl", "abs") load(":lto.bzl", "construct_lto_arguments") -load(":providers.bzl", "LintsInfo", "RustcOutputDiagnosticsInfo", _BuildInfo = "BuildInfo") +load(":providers.bzl", "IsProcMacroDepInfo", "LintsInfo", "RustcOutputDiagnosticsInfo", _BuildInfo = "BuildInfo") load(":rustc_resource_set.bzl", "get_rustc_resource_set", "is_codegen_units_enabled") load(":stamp.bzl", "is_stamping_enabled") load( @@ -80,11 +80,6 @@ PerCrateRustcFlagsInfo = provider( fields = {"per_crate_rustc_flags": "List[string] Extra flags to pass to rustc in non-exec configuration"}, ) -IsProcMacroDepInfo = provider( - doc = "Records if this is a transitive dependency of a proc-macro.", - fields = {"is_proc_macro_dep": "Boolean"}, -) - def _is_proc_macro_dep_impl(ctx): return IsProcMacroDepInfo(is_proc_macro_dep = ctx.build_setting_value) diff --git a/test/unit/lto/lto_test_suite.bzl b/test/unit/lto/lto_test_suite.bzl index 8ccf47da18..7ea0f90d59 100644 --- a/test/unit/lto/lto_test_suite.bzl +++ b/test/unit/lto/lto_test_suite.bzl @@ -11,11 +11,23 @@ load( "assert_argv_contains_prefix_not", ) -def _lto_test_impl(ctx, lto_setting, embed_bitcode, linker_plugin): +def _lto_test_impl(ctx, lto_setting, embed_bitcode, linker_plugin, name = None): env = analysistest.begin(ctx) target = analysistest.target_under_test(env) - action = target.actions[0] + # Find the action we want to assert on. + action = None + if name: + for a in target.actions: + if name in str(a): + action = a + break + else: + action = target.actions[0] + + if not action: + fail("no action found") + assert_action_mnemonic(env, action, "Rustc") # Check if LTO is enabled. @@ -79,13 +91,24 @@ _lto_level_fat_test = analysistest.make( ) def _lto_proc_macro(ctx): - return _lto_test_impl(ctx, None, "no", False) + return _lto_test_impl(ctx, None, "no", False, "my_proc_macro") _lto_proc_macro_test = analysistest.make( _lto_proc_macro, config_settings = {str(Label("//rust/settings:lto")): "thin"}, ) +def _lto_proc_macro_dep(ctx): + return _lto_test_impl(ctx, None, "no", False, "my_proc_macro_dep") + +_lto_proc_macro_dep_test = analysistest.make( + _lto_proc_macro_dep, + config_settings = { + str(Label("//rust/settings:lto")): "thin", + str(Label("//rust/private:is_proc_macro_dep_enabled")): True, + }, +) + def lto_test_suite(name): """Entry-point macro called from the BUILD file. @@ -108,9 +131,16 @@ def lto_test_suite(name): edition = "2021", ) + rust_library( + name = "my_proc_macro_dep", + srcs = [":lib.rs"], + edition = "2021", + ) + rust_proc_macro( - name = "proc_macro", + name = "my_proc_macro", srcs = [":lib.rs"], + deps = [":my_proc_macro_dep"], edition = "2021", ) @@ -141,7 +171,12 @@ def lto_test_suite(name): _lto_proc_macro_test( name = "lto_proc_macro_test", - target_under_test = ":proc_macro", + target_under_test = ":my_proc_macro", + ) + + _lto_proc_macro_dep_test( + name = "lto_proc_macro_dep_test", + target_under_test = ":my_proc_macro_dep", ) native.test_suite(