1# Copyright 2024 The Bazel Authors. All rights reserved. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14"""Utilities for creating cc debug package information outputs""" 15 16load("//cc:find_cc_toolchain.bzl", "CC_TOOLCHAIN_TYPE") 17load(":cc_helper.bzl", "linker_mode") 18load(":visibility.bzl", "INTERNAL_VISIBILITY") 19 20visibility(INTERNAL_VISIBILITY) 21 22def create_debug_packager_actions( 23 ctx, 24 cc_toolchain, 25 dwp_output, 26 *, 27 cc_compilation_outputs, 28 cc_debug_context, 29 linking_mode, 30 use_pic = True, 31 lto_artifacts = []): 32 """Creates intermediate and final dwp creation action(s) 33 34 Args: 35 ctx: (RuleContext) the rule context 36 cc_toolchain: (CcToolchainInfo) the cc toolchain 37 dwp_output: (File) the output of the final dwp action 38 cc_compilation_outputs: (CcCompilationOutputs) 39 cc_debug_context: (DebugContext) 40 linking_mode: (str) See cc_helper.bzl%linker_mode 41 use_pic: (bool) 42 lto_artifacts: ([CcLtoBackendArtifacts]) 43 """ 44 dwo_files = _collect_transitive_dwo_artifacts( 45 cc_compilation_outputs, 46 cc_debug_context, 47 linking_mode, 48 use_pic, 49 lto_artifacts, 50 ) 51 52 # No inputs? Just generate a trivially empty .dwp. 53 # 54 # Note this condition automatically triggers for any build where fission is disabled. 55 # Because rules referencing .dwp targets may be invoked with or without fission, we need 56 # to support .dwp generation even when fission is disabled. Since no actual functionality 57 # is expected then, an empty file is appropriate. 58 dwo_files_list = dwo_files.to_list() 59 if len(dwo_files_list) == 0: 60 ctx.actions.write(dwp_output, "", False) 61 return 62 63 # We apply a hierarchical action structure to limit the maximum number of inputs to any 64 # single action. 65 # 66 # While the dwp tool consumes .dwo files, it can also consume intermediate .dwp files, 67 # allowing us to split a large input set into smaller batches of arbitrary size and order. 68 # Aside from the parallelism performance benefits this offers, this also reduces input 69 # size requirements: if a.dwo, b.dwo, c.dwo, and e.dwo are each 1 KB files, we can apply 70 # two intermediate actions DWP(a.dwo, b.dwo) --> i1.dwp and DWP(c.dwo, e.dwo) --> i2.dwp. 71 # When we then apply the final action DWP(i1.dwp, i2.dwp) --> finalOutput.dwp, the inputs 72 # to this action will usually total far less than 4 KB. 73 # 74 # The actions form an n-ary tree with n == MAX_INPUTS_PER_DWP_ACTION. The tree is fuller 75 # at the leaves than the root, but that both increases parallelism and reduces the final 76 # action's input size. 77 packager = _create_intermediate_dwp_packagers(ctx, dwp_output, cc_toolchain, cc_toolchain._dwp_files, dwo_files_list, 1) 78 packager["outputs"].append(dwp_output) 79 packager["arguments"].add("-o", dwp_output) 80 ctx.actions.run( 81 mnemonic = "CcGenerateDwp", 82 tools = packager["tools"], 83 executable = packager["executable"], 84 toolchain = CC_TOOLCHAIN_TYPE, 85 arguments = [packager["arguments"]], 86 inputs = packager["inputs"], 87 outputs = packager["outputs"], 88 ) 89 90def _collect_transitive_dwo_artifacts(cc_compilation_outputs, cc_debug_context, linking_mode, use_pic, lto_backend_artifacts): 91 dwo_files = [] 92 transitive_dwo_files = depset() 93 if use_pic: 94 dwo_files.extend(cc_compilation_outputs.pic_dwo_files()) 95 else: 96 dwo_files.extend(cc_compilation_outputs.dwo_files()) 97 98 if lto_backend_artifacts != None: 99 for lto_backend_artifact in lto_backend_artifacts: 100 if lto_backend_artifact.dwo_file() != None: 101 dwo_files.append(lto_backend_artifact.dwo_file()) 102 103 if linking_mode != linker_mode.LINKING_DYNAMIC: 104 if use_pic: 105 transitive_dwo_files = cc_debug_context.pic_files 106 else: 107 transitive_dwo_files = cc_debug_context.files 108 return depset(dwo_files, transitive = [transitive_dwo_files]) 109 110def _get_intermediate_dwp_file(ctx, dwp_output, order_number): 111 output_path = dwp_output.short_path 112 113 # Since it is a dwp_output we can assume that it always 114 # ends with .dwp suffix, because it is declared so in outputs 115 # attribute. 116 extension_stripped_output_path = output_path[0:len(output_path) - 4] 117 intermediate_path = extension_stripped_output_path + "-" + str(order_number) + ".dwp" 118 119 return ctx.actions.declare_file("_dwps/" + intermediate_path) 120 121def _create_intermediate_dwp_packagers(ctx, dwp_output, cc_toolchain, dwp_files, dwo_files, intermediate_dwp_count): 122 intermediate_outputs = dwo_files 123 124 # This long loop is a substitution for recursion, which is not currently supported in Starlark. 125 for _ in range(2147483647): 126 packagers = [] 127 current_packager = _new_dwp_action(ctx, cc_toolchain, dwp_files) 128 inputs_for_current_packager = 0 129 130 # Step 1: generate our batches. We currently break into arbitrary batches of fixed maximum 131 # input counts, but we can always apply more intelligent heuristics if the need arises. 132 for dwo_file in intermediate_outputs: 133 if inputs_for_current_packager == 100: 134 packagers.append(current_packager) 135 current_packager = _new_dwp_action(ctx, cc_toolchain, dwp_files) 136 inputs_for_current_packager = 0 137 current_packager["inputs"].append(dwo_file) 138 139 # add_all expands all directories to their contained files, see 140 # https://bazel.build/rules/lib/builtins/Args#add_all. add doesn't 141 # do that, so using add_all on the one-item list here allows us to 142 # find dwo files in directories. 143 current_packager["arguments"].add_all([dwo_file]) 144 inputs_for_current_packager += 1 145 146 packagers.append(current_packager) 147 148 # Step 2: given the batches, create the actions. 149 if len(packagers) > 1: 150 # If we have multiple batches, make them all intermediate actions, then pipe their outputs 151 # into an additional level. 152 intermediate_outputs = [] 153 for packager in packagers: 154 intermediate_output = _get_intermediate_dwp_file(ctx, dwp_output, intermediate_dwp_count) 155 intermediate_dwp_count += 1 156 packager["outputs"].append(intermediate_output) 157 packager["arguments"].add("-o", intermediate_output) 158 ctx.actions.run( 159 mnemonic = "CcGenerateIntermediateDwp", 160 tools = packager["tools"], 161 executable = packager["executable"], 162 toolchain = CC_TOOLCHAIN_TYPE, 163 arguments = [packager["arguments"]], 164 inputs = packager["inputs"], 165 outputs = packager["outputs"], 166 ) 167 intermediate_outputs.append(intermediate_output) 168 else: 169 return packagers[0] 170 171 # This is to fix buildifier errors, even though we should never reach this part of the code. 172 return None 173 174def _new_dwp_action(ctx, cc_toolchain, dwp_tools): 175 return { 176 "arguments": ctx.actions.args(), 177 "executable": cc_toolchain._tool_paths.get("dwp", None), 178 "inputs": [], 179 "outputs": [], 180 "tools": dwp_tools, 181 } 182