| # Copyright 2024 The Bazel Authors. All rights reserved. | |
| # | |
| # Licensed under the Apache License, Version 2.0 (the "License"); | |
| # you may not use this file except in compliance with the License. | |
| # You may obtain a copy of the License at | |
| # | |
| # http://www.apache.org/licenses/LICENSE-2.0 | |
| # | |
| # Unless required by applicable law or agreed to in writing, software | |
| # distributed under the License is distributed on an "AS IS" BASIS, | |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| # See the License for the specific language governing permissions and | |
| # limitations under the License. | |
| """Utilities for creating cc debug package information outputs""" | |
| load("//cc:action_names.bzl", "ACTION_NAMES") | |
| load("//cc:find_cc_toolchain.bzl", "CC_TOOLCHAIN_TYPE") | |
| load(":cc_common.bzl", "cc_common") | |
| load(":cc_helper.bzl", "linker_mode") | |
| load(":visibility.bzl", "INTERNAL_VISIBILITY") | |
| visibility(INTERNAL_VISIBILITY) | |
| def create_debug_packager_actions( | |
| ctx, | |
| cc_toolchain, | |
| dwp_output, | |
| *, | |
| cc_compilation_outputs, | |
| cc_debug_context, | |
| feature_configuration, | |
| linking_mode, | |
| use_pic = True, | |
| lto_artifacts = [], | |
| dwp_exec_group = None): | |
| """Creates intermediate and final dwp creation action(s) | |
| Args: | |
| ctx: (RuleContext) the rule context | |
| cc_toolchain: (CcToolchainInfo) the cc toolchain | |
| dwp_output: (File) the output of the final dwp action | |
| cc_compilation_outputs: (CcCompilationOutputs) | |
| cc_debug_context: (DebugContext) | |
| feature_configuration: (FeatureConfiguration) | |
| linking_mode: (str) See cc_helper.bzl%linker_mode | |
| use_pic: (bool) | |
| lto_artifacts: ([CcLtoBackendArtifacts]) | |
| dwp_exec_group: (str) the exec group to use for the final dwp action | |
| Returns: | |
| dwo_files: (depset) The dwo_files used to generate the dwp_output | |
| """ | |
| dwo_files = _collect_transitive_dwo_artifacts( | |
| cc_compilation_outputs, | |
| cc_debug_context, | |
| linking_mode, | |
| use_pic, | |
| lto_artifacts, | |
| ) | |
| # No inputs? Just generate a trivially empty .dwp. | |
| # | |
| # Note this condition automatically triggers for any build where fission is disabled. | |
| # Because rules referencing .dwp targets may be invoked with or without fission, we need | |
| # to support .dwp generation even when fission is disabled. Since no actual functionality | |
| # is expected then, an empty file is appropriate. | |
| dwo_files_list = dwo_files.to_list() | |
| if len(dwo_files_list) == 0: | |
| ctx.actions.write(dwp_output, "", False) | |
| return dwo_files | |
| # We apply a hierarchical action structure to limit the maximum number of inputs to any | |
| # single action. | |
| # | |
| # While the dwp tool consumes .dwo files, it can also consume intermediate .dwp files, | |
| # allowing us to split a large input set into smaller batches of arbitrary size and order. | |
| # Aside from the parallelism performance benefits this offers, this also reduces input | |
| # size requirements: if a.dwo, b.dwo, c.dwo, and e.dwo are each 1 KB files, we can apply | |
| # two intermediate actions DWP(a.dwo, b.dwo) --> i1.dwp and DWP(c.dwo, e.dwo) --> i2.dwp. | |
| # When we then apply the final action DWP(i1.dwp, i2.dwp) --> finalOutput.dwp, the inputs | |
| # to this action will usually total far less than 4 KB. | |
| # | |
| # The actions form an n-ary tree with n == MAX_INPUTS_PER_DWP_ACTION. The tree is fuller | |
| # at the leaves than the root, but that both increases parallelism and reduces the final | |
| # action's input size. | |
| packager = _create_intermediate_dwp_packagers( | |
| ctx, | |
| dwp_output, | |
| cc_toolchain, | |
| feature_configuration, | |
| cc_toolchain._dwp_files, | |
| dwo_files_list, | |
| 1, | |
| ) | |
| packager["outputs"].append(dwp_output) | |
| packager["arguments"].add("-o", dwp_output) | |
| ctx.actions.run( | |
| mnemonic = "CcGenerateDwp", | |
| tools = packager["tools"], | |
| executable = packager["executable"], | |
| toolchain = CC_TOOLCHAIN_TYPE, | |
| arguments = [packager["arguments"]], | |
| inputs = packager["inputs"], | |
| outputs = packager["outputs"], | |
| exec_group = dwp_exec_group, | |
| env = packager["env"], | |
| use_default_shell_env = True, | |
| ) | |
| return dwo_files | |
| def _collect_transitive_dwo_artifacts(cc_compilation_outputs, cc_debug_context, linking_mode, use_pic, lto_backend_artifacts): | |
| dwo_files = [] | |
| transitive_dwo_files = depset() | |
| if use_pic: | |
| if hasattr(cc_compilation_outputs, "pic_dwo_files"): | |
| dwo_files.extend(cc_compilation_outputs.pic_dwo_files()) | |
| else: | |
| dwo_files.extend(cc_compilation_outputs._pic_dwo_files) | |
| elif hasattr(cc_compilation_outputs, "dwo_files"): | |
| dwo_files.extend(cc_compilation_outputs.dwo_files()) | |
| else: | |
| dwo_files.extend(cc_compilation_outputs._dwo_files) | |
| if lto_backend_artifacts != None: | |
| for lto_backend_artifact in lto_backend_artifacts: | |
| if hasattr(lto_backend_artifact, "dwo_file") and lto_backend_artifact.dwo_file() != None: | |
| dwo_files.append(lto_backend_artifact.dwo_file()) | |
| if hasattr(lto_backend_artifact, "_dwo_file") and lto_backend_artifact._dwo_file != None: | |
| dwo_files.append(lto_backend_artifact._dwo_file) | |
| if linking_mode != linker_mode.LINKING_DYNAMIC: | |
| if use_pic: | |
| transitive_dwo_files = cc_debug_context.pic_files | |
| else: | |
| transitive_dwo_files = cc_debug_context.files | |
| return depset(dwo_files, transitive = [transitive_dwo_files]) | |
| def _get_intermediate_dwp_file(ctx, dwp_output, order_number): | |
| output_path = dwp_output.short_path | |
| # Since it is a dwp_output we can assume that it always | |
| # ends with .dwp suffix, because it is declared so in outputs | |
| # attribute. | |
| extension_stripped_output_path = output_path[0:len(output_path) - 4] | |
| intermediate_path = extension_stripped_output_path + "-" + str(order_number) + ".dwp" | |
| return ctx.actions.declare_file("_dwps/" + intermediate_path) | |
| def _create_intermediate_dwp_packagers( | |
| ctx, | |
| dwp_output, | |
| cc_toolchain, | |
| feature_configuration, | |
| dwp_files, | |
| dwo_files, | |
| intermediate_dwp_count): | |
| intermediate_outputs = dwo_files | |
| # This long loop is a substitution for recursion, which is not currently supported in Starlark. | |
| for _ in range(2147483647): | |
| packagers = [] | |
| current_packager = _new_dwp_action( | |
| ctx, | |
| cc_toolchain, | |
| feature_configuration, | |
| dwp_files, | |
| ) | |
| inputs_for_current_packager = 0 | |
| # Step 1: generate our batches. We currently break into arbitrary batches of fixed maximum | |
| # input counts, but we can always apply more intelligent heuristics if the need arises. | |
| for dwo_file in intermediate_outputs: | |
| if inputs_for_current_packager == 100: | |
| packagers.append(current_packager) | |
| current_packager = _new_dwp_action( | |
| ctx, | |
| cc_toolchain, | |
| feature_configuration, | |
| dwp_files, | |
| ) | |
| inputs_for_current_packager = 0 | |
| current_packager["inputs"].append(dwo_file) | |
| # add_all expands all directories to their contained files, see | |
| # https://bazel.build/rules/lib/builtins/Args#add_all. add doesn't | |
| # do that, so using add_all on the one-item list here allows us to | |
| # find dwo files in directories. | |
| current_packager["arguments"].add_all([dwo_file]) | |
| inputs_for_current_packager += 1 | |
| packagers.append(current_packager) | |
| # Step 2: given the batches, create the actions. | |
| if len(packagers) > 1: | |
| # If we have multiple batches, make them all intermediate actions, then pipe their outputs | |
| # into an additional level. | |
| intermediate_outputs = [] | |
| for packager in packagers: | |
| intermediate_output = _get_intermediate_dwp_file(ctx, dwp_output, intermediate_dwp_count) | |
| intermediate_dwp_count += 1 | |
| packager["outputs"].append(intermediate_output) | |
| packager["arguments"].add("-o", intermediate_output) | |
| ctx.actions.run( | |
| mnemonic = "CcGenerateIntermediateDwp", | |
| tools = packager["tools"], | |
| executable = packager["executable"], | |
| toolchain = CC_TOOLCHAIN_TYPE, | |
| arguments = [packager["arguments"]], | |
| inputs = packager["inputs"], | |
| outputs = packager["outputs"], | |
| env = packager["env"], | |
| use_default_shell_env = True, | |
| ) | |
| intermediate_outputs.append(intermediate_output) | |
| else: | |
| return packagers[0] | |
| # This is to fix buildifier errors, even though we should never reach this part of the code. | |
| return None | |
| def _new_dwp_action(ctx, cc_toolchain, feature_configuration, dwp_tools): | |
| variables = cc_common.create_compile_variables( | |
| feature_configuration = feature_configuration, | |
| cc_toolchain = cc_toolchain, | |
| ) | |
| return { | |
| "tools": dwp_tools, | |
| # Old toolchains use tool_paths. But, those same old toolchains don't support the new | |
| # action configuration solution, so we have to try both. | |
| "executable": cc_toolchain._tool_paths.get("dwp", None) or cc_common.get_tool_for_action( | |
| feature_configuration = feature_configuration, | |
| action_name = ACTION_NAMES.dwp, | |
| ), | |
| "arguments": ctx.actions.args(), | |
| "inputs": [], | |
| "outputs": [], | |
| "env": cc_common.get_environment_variables( | |
| feature_configuration = feature_configuration, | |
| action_name = ACTION_NAMES.dwp, | |
| variables = variables, | |
| ), | |
| } # buildifier: disable=unsorted-dict-items |