# 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:find_cc_toolchain.bzl", "CC_TOOLCHAIN_TYPE") 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, linking_mode, use_pic = True, lto_artifacts = []): """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) linking_mode: (str) See cc_helper.bzl%linker_mode use_pic: (bool) lto_artifacts: ([CcLtoBackendArtifacts]) """ 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 # 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, 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"], ) 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: dwo_files.extend(cc_compilation_outputs.pic_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 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, 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, 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, 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"], ) 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, dwp_tools): return { "arguments": ctx.actions.args(), "executable": cc_toolchain._tool_paths.get("dwp", None), "inputs": [], "outputs": [], "tools": dwp_tools, }