xref: /aosp_15_r20/external/emboss/compiler/front_end/dependency_checker.py (revision 99e0aae7469b87d12f0ad23e61142c2d74c1ef70)
1*99e0aae7SDavid Rees# Copyright 2019 Google LLC
2*99e0aae7SDavid Rees#
3*99e0aae7SDavid Rees# Licensed under the Apache License, Version 2.0 (the "License");
4*99e0aae7SDavid Rees# you may not use this file except in compliance with the License.
5*99e0aae7SDavid Rees# You may obtain a copy of the License at
6*99e0aae7SDavid Rees#
7*99e0aae7SDavid Rees#     https://www.apache.org/licenses/LICENSE-2.0
8*99e0aae7SDavid Rees#
9*99e0aae7SDavid Rees# Unless required by applicable law or agreed to in writing, software
10*99e0aae7SDavid Rees# distributed under the License is distributed on an "AS IS" BASIS,
11*99e0aae7SDavid Rees# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*99e0aae7SDavid Rees# See the License for the specific language governing permissions and
13*99e0aae7SDavid Rees# limitations under the License.
14*99e0aae7SDavid Rees
15*99e0aae7SDavid Rees"""Checks for dependency cycles in Emboss IR."""
16*99e0aae7SDavid Rees
17*99e0aae7SDavid Reesfrom compiler.util import error
18*99e0aae7SDavid Reesfrom compiler.util import ir_data
19*99e0aae7SDavid Reesfrom compiler.util import ir_util
20*99e0aae7SDavid Reesfrom compiler.util import traverse_ir
21*99e0aae7SDavid Rees
22*99e0aae7SDavid Rees
23*99e0aae7SDavid Reesdef _add_reference_to_dependencies(reference, dependencies, name,
24*99e0aae7SDavid Rees                                   source_file_name, errors):
25*99e0aae7SDavid Rees  if reference.canonical_name.object_path[0] in {"$is_statically_sized",
26*99e0aae7SDavid Rees                                                 "$static_size_in_bits",
27*99e0aae7SDavid Rees                                                 "$next"}:
28*99e0aae7SDavid Rees    # This error is a bit opaque, but given that the compiler used to crash on
29*99e0aae7SDavid Rees    # this case -- for a couple of years -- and no one complained, it seems
30*99e0aae7SDavid Rees    # safe to assume that this is a rare error.
31*99e0aae7SDavid Rees    errors.append([
32*99e0aae7SDavid Rees        error.error(source_file_name, reference.source_location,
33*99e0aae7SDavid Rees                    "Keyword `" + reference.canonical_name.object_path[0] +
34*99e0aae7SDavid Rees                    "` may not be used in this context."),
35*99e0aae7SDavid Rees    ])
36*99e0aae7SDavid Rees    return
37*99e0aae7SDavid Rees  dependencies[name] |= {ir_util.hashable_form_of_reference(reference)}
38*99e0aae7SDavid Rees
39*99e0aae7SDavid Rees
40*99e0aae7SDavid Reesdef _add_field_reference_to_dependencies(reference, dependencies, name):
41*99e0aae7SDavid Rees  dependencies[name] |= {ir_util.hashable_form_of_reference(reference.path[0])}
42*99e0aae7SDavid Rees
43*99e0aae7SDavid Rees
44*99e0aae7SDavid Reesdef _add_name_to_dependencies(proto, dependencies):
45*99e0aae7SDavid Rees  name = ir_util.hashable_form_of_reference(proto.name)
46*99e0aae7SDavid Rees  dependencies.setdefault(name, set())
47*99e0aae7SDavid Rees  return {"name": name}
48*99e0aae7SDavid Rees
49*99e0aae7SDavid Rees
50*99e0aae7SDavid Reesdef _find_dependencies(ir):
51*99e0aae7SDavid Rees  """Constructs a dependency graph for the entire IR."""
52*99e0aae7SDavid Rees  dependencies = {}
53*99e0aae7SDavid Rees  errors = []
54*99e0aae7SDavid Rees  traverse_ir.fast_traverse_ir_top_down(
55*99e0aae7SDavid Rees      ir, [ir_data.Reference], _add_reference_to_dependencies,
56*99e0aae7SDavid Rees      # TODO(bolms): Add handling for references inside of attributes, once
57*99e0aae7SDavid Rees      # there are attributes with non-constant values.
58*99e0aae7SDavid Rees      skip_descendants_of={
59*99e0aae7SDavid Rees          ir_data.AtomicType, ir_data.Attribute, ir_data.FieldReference
60*99e0aae7SDavid Rees      },
61*99e0aae7SDavid Rees      incidental_actions={
62*99e0aae7SDavid Rees          ir_data.Field: _add_name_to_dependencies,
63*99e0aae7SDavid Rees          ir_data.EnumValue: _add_name_to_dependencies,
64*99e0aae7SDavid Rees          ir_data.RuntimeParameter: _add_name_to_dependencies,
65*99e0aae7SDavid Rees      },
66*99e0aae7SDavid Rees      parameters={
67*99e0aae7SDavid Rees          "dependencies": dependencies,
68*99e0aae7SDavid Rees          "errors": errors,
69*99e0aae7SDavid Rees      })
70*99e0aae7SDavid Rees  traverse_ir.fast_traverse_ir_top_down(
71*99e0aae7SDavid Rees      ir, [ir_data.FieldReference], _add_field_reference_to_dependencies,
72*99e0aae7SDavid Rees      skip_descendants_of={ir_data.Attribute},
73*99e0aae7SDavid Rees      incidental_actions={
74*99e0aae7SDavid Rees          ir_data.Field: _add_name_to_dependencies,
75*99e0aae7SDavid Rees          ir_data.EnumValue: _add_name_to_dependencies,
76*99e0aae7SDavid Rees          ir_data.RuntimeParameter: _add_name_to_dependencies,
77*99e0aae7SDavid Rees      },
78*99e0aae7SDavid Rees      parameters={"dependencies": dependencies})
79*99e0aae7SDavid Rees  return dependencies, errors
80*99e0aae7SDavid Rees
81*99e0aae7SDavid Rees
82*99e0aae7SDavid Reesdef _find_dependency_ordering_for_fields_in_structure(
83*99e0aae7SDavid Rees    structure, type_definition, dependencies):
84*99e0aae7SDavid Rees  """Populates structure.fields_in_dependency_order."""
85*99e0aae7SDavid Rees  # For fields which appear before their dependencies in the original source
86*99e0aae7SDavid Rees  # text, this algorithm moves them to immediately after their dependencies.
87*99e0aae7SDavid Rees  #
88*99e0aae7SDavid Rees  # This is one of many possible schemes for constructing a dependency ordering;
89*99e0aae7SDavid Rees  # it has the advantage that all of the generated fields (e.g., $size_in_bytes)
90*99e0aae7SDavid Rees  # stay at the end of the ordering, which makes testing easier.
91*99e0aae7SDavid Rees  order = []
92*99e0aae7SDavid Rees  added = set()
93*99e0aae7SDavid Rees  for parameter in type_definition.runtime_parameter:
94*99e0aae7SDavid Rees    added.add(ir_util.hashable_form_of_reference(parameter.name))
95*99e0aae7SDavid Rees  needed = list(range(len(structure.field)))
96*99e0aae7SDavid Rees  while True:
97*99e0aae7SDavid Rees    for i in range(len(needed)):
98*99e0aae7SDavid Rees      field_number = needed[i]
99*99e0aae7SDavid Rees      field = ir_util.hashable_form_of_reference(
100*99e0aae7SDavid Rees          structure.field[field_number].name)
101*99e0aae7SDavid Rees      assert field in dependencies, "dependencies = {}".format(dependencies)
102*99e0aae7SDavid Rees      if all(dependency in added for dependency in dependencies[field]):
103*99e0aae7SDavid Rees        order.append(field_number)
104*99e0aae7SDavid Rees        added.add(field)
105*99e0aae7SDavid Rees        del needed[i]
106*99e0aae7SDavid Rees        break
107*99e0aae7SDavid Rees    else:
108*99e0aae7SDavid Rees      break
109*99e0aae7SDavid Rees  # If a non-local-field dependency were in dependencies[field], then not all
110*99e0aae7SDavid Rees  # fields would be added to the dependency ordering.  This shouldn't happen.
111*99e0aae7SDavid Rees  assert len(order) == len(structure.field), (
112*99e0aae7SDavid Rees      "order: {}\nlen(structure.field: {})".format(order, len(structure.field)))
113*99e0aae7SDavid Rees  del structure.fields_in_dependency_order[:]
114*99e0aae7SDavid Rees  structure.fields_in_dependency_order.extend(order)
115*99e0aae7SDavid Rees
116*99e0aae7SDavid Rees
117*99e0aae7SDavid Reesdef _find_dependency_ordering_for_fields(ir):
118*99e0aae7SDavid Rees  """Populates the fields_in_dependency_order fields throughout ir."""
119*99e0aae7SDavid Rees  dependencies = {}
120*99e0aae7SDavid Rees  # TODO(bolms): This duplicates work in _find_dependencies that could be
121*99e0aae7SDavid Rees  # shared.
122*99e0aae7SDavid Rees  traverse_ir.fast_traverse_ir_top_down(
123*99e0aae7SDavid Rees      ir, [ir_data.FieldReference], _add_field_reference_to_dependencies,
124*99e0aae7SDavid Rees      skip_descendants_of={ir_data.Attribute},
125*99e0aae7SDavid Rees      incidental_actions={
126*99e0aae7SDavid Rees          ir_data.Field: _add_name_to_dependencies,
127*99e0aae7SDavid Rees          ir_data.EnumValue: _add_name_to_dependencies,
128*99e0aae7SDavid Rees          ir_data.RuntimeParameter: _add_name_to_dependencies,
129*99e0aae7SDavid Rees      },
130*99e0aae7SDavid Rees      parameters={"dependencies": dependencies})
131*99e0aae7SDavid Rees  traverse_ir.fast_traverse_ir_top_down(
132*99e0aae7SDavid Rees      ir, [ir_data.Structure],
133*99e0aae7SDavid Rees      _find_dependency_ordering_for_fields_in_structure,
134*99e0aae7SDavid Rees      parameters={"dependencies": dependencies})
135*99e0aae7SDavid Rees
136*99e0aae7SDavid Rees
137*99e0aae7SDavid Reesdef _find_module_import_dependencies(ir):
138*99e0aae7SDavid Rees  """Constructs a dependency graph of module imports."""
139*99e0aae7SDavid Rees  dependencies = {}
140*99e0aae7SDavid Rees  for module in ir.module:
141*99e0aae7SDavid Rees    foreign_imports = set()
142*99e0aae7SDavid Rees    for foreign_import in module.foreign_import:
143*99e0aae7SDavid Rees      # The prelude gets an automatic self-import that shouldn't cause any
144*99e0aae7SDavid Rees      # problems.  No other self-imports are allowed, however.
145*99e0aae7SDavid Rees      if foreign_import.file_name.text or module.source_file_name:
146*99e0aae7SDavid Rees        foreign_imports |= {(foreign_import.file_name.text,)}
147*99e0aae7SDavid Rees    dependencies[module.source_file_name,] = foreign_imports
148*99e0aae7SDavid Rees  return dependencies
149*99e0aae7SDavid Rees
150*99e0aae7SDavid Rees
151*99e0aae7SDavid Reesdef _find_cycles(graph):
152*99e0aae7SDavid Rees  """Finds cycles in graph.
153*99e0aae7SDavid Rees
154*99e0aae7SDavid Rees  The graph does not need to be fully connected.
155*99e0aae7SDavid Rees
156*99e0aae7SDavid Rees  Arguments:
157*99e0aae7SDavid Rees    graph: A dictionary whose keys are node labels.  Values are sets of node
158*99e0aae7SDavid Rees      labels, representing edges from the key node to the value nodes.
159*99e0aae7SDavid Rees
160*99e0aae7SDavid Rees  Returns:
161*99e0aae7SDavid Rees    A set of sets of nodes which form strongly-connected components (subgraphs
162*99e0aae7SDavid Rees    where every node is directly or indirectly reachable from every other node).
163*99e0aae7SDavid Rees    No node will be included in more than one strongly-connected component, by
164*99e0aae7SDavid Rees    definition.  Strongly-connected components of size 1, where the node in the
165*99e0aae7SDavid Rees    component does not have a self-edge, are not included in the result.
166*99e0aae7SDavid Rees
167*99e0aae7SDavid Rees    Note that a strongly-connected component may have a more complex structure
168*99e0aae7SDavid Rees    than a single loop.  For example:
169*99e0aae7SDavid Rees
170*99e0aae7SDavid Rees        +-- A <-+ +-> B --+
171*99e0aae7SDavid Rees        |       | |       |
172*99e0aae7SDavid Rees        v        C        v
173*99e0aae7SDavid Rees        D       ^ ^       E
174*99e0aae7SDavid Rees        |       | |       |
175*99e0aae7SDavid Rees        +-> F --+ +-- G <-+
176*99e0aae7SDavid Rees  """
177*99e0aae7SDavid Rees  # This uses Tarjan's strongly-connected components algorithm, as described by
178*99e0aae7SDavid Rees  # Wikipedia.  This is a depth-first traversal of the graph with a node stack
179*99e0aae7SDavid Rees  # that is independent of the call stack; nodes are added to the stack when
180*99e0aae7SDavid Rees  # they are first encountered, but not removed until all nodes they can reach
181*99e0aae7SDavid Rees  # have been checked.
182*99e0aae7SDavid Rees  next_index = [0]
183*99e0aae7SDavid Rees  node_indices = {}
184*99e0aae7SDavid Rees  node_lowlinks = {}
185*99e0aae7SDavid Rees  nodes_on_stack = set()
186*99e0aae7SDavid Rees  stack = []
187*99e0aae7SDavid Rees  nontrivial_components = set()
188*99e0aae7SDavid Rees
189*99e0aae7SDavid Rees  def strong_connect(node):
190*99e0aae7SDavid Rees    """Implements the STRONGCONNECT routine of Tarjan's algorithm."""
191*99e0aae7SDavid Rees    node_indices[node] = next_index[0]
192*99e0aae7SDavid Rees    node_lowlinks[node] = next_index[0]
193*99e0aae7SDavid Rees    next_index[0] += 1
194*99e0aae7SDavid Rees    stack.append(node)
195*99e0aae7SDavid Rees    nodes_on_stack.add(node)
196*99e0aae7SDavid Rees
197*99e0aae7SDavid Rees    for destination_node in graph[node]:
198*99e0aae7SDavid Rees      if destination_node not in node_indices:
199*99e0aae7SDavid Rees        strong_connect(destination_node)
200*99e0aae7SDavid Rees        node_lowlinks[node] = min(node_lowlinks[node],
201*99e0aae7SDavid Rees                                  node_lowlinks[destination_node])
202*99e0aae7SDavid Rees      elif destination_node in nodes_on_stack:
203*99e0aae7SDavid Rees        node_lowlinks[node] = min(node_lowlinks[node],
204*99e0aae7SDavid Rees                                  node_indices[destination_node])
205*99e0aae7SDavid Rees
206*99e0aae7SDavid Rees    strongly_connected_component = []
207*99e0aae7SDavid Rees    if node_lowlinks[node] == node_indices[node]:
208*99e0aae7SDavid Rees      while True:
209*99e0aae7SDavid Rees        popped_node = stack.pop()
210*99e0aae7SDavid Rees        nodes_on_stack.remove(popped_node)
211*99e0aae7SDavid Rees        strongly_connected_component.append(popped_node)
212*99e0aae7SDavid Rees        if popped_node == node:
213*99e0aae7SDavid Rees          break
214*99e0aae7SDavid Rees      if (len(strongly_connected_component) > 1 or
215*99e0aae7SDavid Rees          strongly_connected_component[0] in
216*99e0aae7SDavid Rees          graph[strongly_connected_component[0]]):
217*99e0aae7SDavid Rees        nontrivial_components.add(frozenset(strongly_connected_component))
218*99e0aae7SDavid Rees
219*99e0aae7SDavid Rees  for node in graph:
220*99e0aae7SDavid Rees    if node not in node_indices:
221*99e0aae7SDavid Rees      strong_connect(node)
222*99e0aae7SDavid Rees  return nontrivial_components
223*99e0aae7SDavid Rees
224*99e0aae7SDavid Rees
225*99e0aae7SDavid Reesdef _find_object_dependency_cycles(ir):
226*99e0aae7SDavid Rees  """Finds dependency cycles in types in the ir."""
227*99e0aae7SDavid Rees  dependencies, find_dependency_errors = _find_dependencies(ir)
228*99e0aae7SDavid Rees  if find_dependency_errors:
229*99e0aae7SDavid Rees    return find_dependency_errors
230*99e0aae7SDavid Rees  errors = []
231*99e0aae7SDavid Rees  cycles = _find_cycles(dict(dependencies))
232*99e0aae7SDavid Rees  for cycle in cycles:
233*99e0aae7SDavid Rees    # TODO(bolms): This lists the entire strongly-connected component in a
234*99e0aae7SDavid Rees    # fairly arbitrary order.  This is simple, and handles components that
235*99e0aae7SDavid Rees    # aren't simple cycles, but may not be the most user-friendly way to
236*99e0aae7SDavid Rees    # present this information.
237*99e0aae7SDavid Rees    cycle_list = sorted(list(cycle))
238*99e0aae7SDavid Rees    node_object = ir_util.find_object(cycle_list[0], ir)
239*99e0aae7SDavid Rees    error_group = [
240*99e0aae7SDavid Rees        error.error(cycle_list[0][0], node_object.source_location,
241*99e0aae7SDavid Rees                    "Dependency cycle\n" + node_object.name.name.text)
242*99e0aae7SDavid Rees    ]
243*99e0aae7SDavid Rees    for node in cycle_list[1:]:
244*99e0aae7SDavid Rees      node_object = ir_util.find_object(node, ir)
245*99e0aae7SDavid Rees      error_group.append(error.note(node[0], node_object.source_location,
246*99e0aae7SDavid Rees                                    node_object.name.name.text))
247*99e0aae7SDavid Rees    errors.append(error_group)
248*99e0aae7SDavid Rees  return errors
249*99e0aae7SDavid Rees
250*99e0aae7SDavid Rees
251*99e0aae7SDavid Reesdef _find_module_dependency_cycles(ir):
252*99e0aae7SDavid Rees  """Finds dependency cycles in modules in the ir."""
253*99e0aae7SDavid Rees  dependencies = _find_module_import_dependencies(ir)
254*99e0aae7SDavid Rees  cycles = _find_cycles(dict(dependencies))
255*99e0aae7SDavid Rees  errors = []
256*99e0aae7SDavid Rees  for cycle in cycles:
257*99e0aae7SDavid Rees    cycle_list = sorted(list(cycle))
258*99e0aae7SDavid Rees    module = ir_util.find_object(cycle_list[0], ir)
259*99e0aae7SDavid Rees    error_group = [
260*99e0aae7SDavid Rees        error.error(cycle_list[0][0], module.source_location,
261*99e0aae7SDavid Rees                    "Import dependency cycle\n" + module.source_file_name)
262*99e0aae7SDavid Rees    ]
263*99e0aae7SDavid Rees    for module_name in cycle_list[1:]:
264*99e0aae7SDavid Rees      module = ir_util.find_object(module_name, ir)
265*99e0aae7SDavid Rees      error_group.append(error.note(module_name[0], module.source_location,
266*99e0aae7SDavid Rees                                    module.source_file_name))
267*99e0aae7SDavid Rees    errors.append(error_group)
268*99e0aae7SDavid Rees  return errors
269*99e0aae7SDavid Rees
270*99e0aae7SDavid Rees
271*99e0aae7SDavid Reesdef find_dependency_cycles(ir):
272*99e0aae7SDavid Rees  """Finds any dependency cycles in the ir."""
273*99e0aae7SDavid Rees  errors = _find_module_dependency_cycles(ir)
274*99e0aae7SDavid Rees  return errors + _find_object_dependency_cycles(ir)
275*99e0aae7SDavid Rees
276*99e0aae7SDavid Rees
277*99e0aae7SDavid Reesdef set_dependency_order(ir):
278*99e0aae7SDavid Rees  """Sets the fields_in_dependency_order member of Structures."""
279*99e0aae7SDavid Rees  _find_dependency_ordering_for_fields(ir)
280*99e0aae7SDavid Rees  return []
281