• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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