xref: /aosp_15_r20/external/emboss/compiler/util/traverse_ir.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"""Routines for fully traversing an IR."""
16*99e0aae7SDavid Rees
17*99e0aae7SDavid Reesimport inspect
18*99e0aae7SDavid Rees
19*99e0aae7SDavid Reesfrom compiler.util import ir_data
20*99e0aae7SDavid Reesfrom compiler.util import ir_data_fields
21*99e0aae7SDavid Reesfrom compiler.util import ir_data_utils
22*99e0aae7SDavid Reesfrom compiler.util import simple_memoizer
23*99e0aae7SDavid Rees
24*99e0aae7SDavid Rees
25*99e0aae7SDavid Reesclass _FunctionCaller:
26*99e0aae7SDavid Rees  """Provides a template for setting up a generic call to a function.
27*99e0aae7SDavid Rees
28*99e0aae7SDavid Rees  The function parameters are inspected at run-time to build up a set of valid
29*99e0aae7SDavid Rees  and required arguments. When invoking the function unneccessary parameters
30*99e0aae7SDavid Rees  will be trimmed out. If arguments are missing an assertion will be triggered.
31*99e0aae7SDavid Rees
32*99e0aae7SDavid Rees  This is currently limited to functions that have at least one positional
33*99e0aae7SDavid Rees  parameter.
34*99e0aae7SDavid Rees
35*99e0aae7SDavid Rees  Example usage:
36*99e0aae7SDavid Rees  ```
37*99e0aae7SDavid Rees  def func_1(a, b, c=2): pass
38*99e0aae7SDavid Rees  def func_2(a, d): pass
39*99e0aae7SDavid Rees  caller_1 = _FunctionCaller(func_1)
40*99e0aae7SDavid Rees  caller_2 = _FunctionCaller(func_2)
41*99e0aae7SDavid Rees  generic_params = {"b": 2, "c": 3, "d": 4}
42*99e0aae7SDavid Rees
43*99e0aae7SDavid Rees  # Equivalent of: func_1(a, b=2, c=3)
44*99e0aae7SDavid Rees  caller_1.invoke(a, generic_params)
45*99e0aae7SDavid Rees
46*99e0aae7SDavid Rees  # Equivalent of: func_2(a, d=4)
47*99e0aae7SDavid Rees  caller_2.invoke(a, generic_params)
48*99e0aae7SDavid Rees  """
49*99e0aae7SDavid Rees
50*99e0aae7SDavid Rees  def __init__(self, function):
51*99e0aae7SDavid Rees    self.function = function
52*99e0aae7SDavid Rees    self.needs_filtering = True
53*99e0aae7SDavid Rees    self.valid_arg_names = set()
54*99e0aae7SDavid Rees    self.required_arg_names = set()
55*99e0aae7SDavid Rees
56*99e0aae7SDavid Rees    argspec = inspect.getfullargspec(function)
57*99e0aae7SDavid Rees    if argspec.varkw:
58*99e0aae7SDavid Rees      # If the function accepts a kwargs parameter, then it will accept all
59*99e0aae7SDavid Rees      # arguments.
60*99e0aae7SDavid Rees      # Note: this isn't technically true if one of the keyword arguments has the
61*99e0aae7SDavid Rees      # same name as one of the positional arguments.
62*99e0aae7SDavid Rees      self.needs_filtering = False
63*99e0aae7SDavid Rees    else:
64*99e0aae7SDavid Rees      # argspec.args is a list of all parameter names excluding keyword only
65*99e0aae7SDavid Rees      # args. The first element is our required positional_arg and should be
66*99e0aae7SDavid Rees      # ignored.
67*99e0aae7SDavid Rees      args = argspec.args[1:]
68*99e0aae7SDavid Rees      self.valid_arg_names.update(args)
69*99e0aae7SDavid Rees
70*99e0aae7SDavid Rees      # args.kwonlyargs gives us the list of keyword only args which are
71*99e0aae7SDavid Rees      # also valid.
72*99e0aae7SDavid Rees      self.valid_arg_names.update(argspec.kwonlyargs)
73*99e0aae7SDavid Rees
74*99e0aae7SDavid Rees      # Required args are positional arguments that don't have defaults.
75*99e0aae7SDavid Rees      # Keyword only args are always optional and can be ignored. Args with
76*99e0aae7SDavid Rees      # defaults are the last elements of the argsepec.args list and should
77*99e0aae7SDavid Rees      # be ignored.
78*99e0aae7SDavid Rees      if argspec.defaults:
79*99e0aae7SDavid Rees        # Trim the arguments with defaults.
80*99e0aae7SDavid Rees        args = args[: -len(argspec.defaults)]
81*99e0aae7SDavid Rees      self.required_arg_names.update(args)
82*99e0aae7SDavid Rees
83*99e0aae7SDavid Rees  def invoke(self, positional_arg, keyword_args):
84*99e0aae7SDavid Rees    """Invokes the function with the given args."""
85*99e0aae7SDavid Rees    if self.needs_filtering:
86*99e0aae7SDavid Rees      # Trim to just recognized args.
87*99e0aae7SDavid Rees      matched_args = {
88*99e0aae7SDavid Rees          k: v for k, v in keyword_args.items() if k in self.valid_arg_names
89*99e0aae7SDavid Rees      }
90*99e0aae7SDavid Rees      # Check if any required args are missing.
91*99e0aae7SDavid Rees      missing_args = self.required_arg_names.difference(matched_args.keys())
92*99e0aae7SDavid Rees      assert not missing_args, (
93*99e0aae7SDavid Rees          f"Attempting to call '{self.function.__name__}'; "
94*99e0aae7SDavid Rees          f"missing {missing_args} (have {set(keyword_args.keys())})"
95*99e0aae7SDavid Rees      )
96*99e0aae7SDavid Rees      keyword_args = matched_args
97*99e0aae7SDavid Rees
98*99e0aae7SDavid Rees    return self.function(positional_arg, **keyword_args)
99*99e0aae7SDavid Rees
100*99e0aae7SDavid Rees
101*99e0aae7SDavid Rees@simple_memoizer.memoize
102*99e0aae7SDavid Reesdef _memoized_caller(function):
103*99e0aae7SDavid Rees  default_lambda_name = (lambda: None).__name__
104*99e0aae7SDavid Rees  assert (
105*99e0aae7SDavid Rees      callable(function) and not function.__name__ == default_lambda_name
106*99e0aae7SDavid Rees  ), "For performance reasons actions must be defined as static functions"
107*99e0aae7SDavid Rees  return _FunctionCaller(function)
108*99e0aae7SDavid Rees
109*99e0aae7SDavid Rees
110*99e0aae7SDavid Reesdef _call_with_optional_args(function, positional_arg, keyword_args):
111*99e0aae7SDavid Rees  """Calls function with whatever keyword_args it will accept."""
112*99e0aae7SDavid Rees  caller = _memoized_caller(function)
113*99e0aae7SDavid Rees  return caller.invoke(positional_arg, keyword_args)
114*99e0aae7SDavid Rees
115*99e0aae7SDavid Rees
116*99e0aae7SDavid Reesdef _fast_traverse_proto_top_down(proto, incidental_actions, pattern,
117*99e0aae7SDavid Rees                                  skip_descendants_of, action, parameters):
118*99e0aae7SDavid Rees  """Traverses an IR, calling `action` on some nodes."""
119*99e0aae7SDavid Rees
120*99e0aae7SDavid Rees  # Parameters are scoped to the branch of the tree, so make a copy here, before
121*99e0aae7SDavid Rees  # any action or incidental_action can update them.
122*99e0aae7SDavid Rees  parameters = parameters.copy()
123*99e0aae7SDavid Rees
124*99e0aae7SDavid Rees  # If there is an incidental action for this node type, run it.
125*99e0aae7SDavid Rees  if type(proto) in incidental_actions:  # pylint: disable=unidiomatic-typecheck
126*99e0aae7SDavid Rees    for incidental_action in incidental_actions[type(proto)]:
127*99e0aae7SDavid Rees      parameters.update(_call_with_optional_args(
128*99e0aae7SDavid Rees          incidental_action, proto, parameters) or {})
129*99e0aae7SDavid Rees
130*99e0aae7SDavid Rees  # If we are at the end of pattern, check to see if we should call action.
131*99e0aae7SDavid Rees  if len(pattern) == 1:
132*99e0aae7SDavid Rees    new_pattern = pattern
133*99e0aae7SDavid Rees    if pattern[0] == type(proto):
134*99e0aae7SDavid Rees      parameters.update(
135*99e0aae7SDavid Rees          _call_with_optional_args(action, proto, parameters) or {})
136*99e0aae7SDavid Rees  else:
137*99e0aae7SDavid Rees    # Otherwise, if this node's type matches the head of pattern, recurse with
138*99e0aae7SDavid Rees    # the tail of the pattern.
139*99e0aae7SDavid Rees    if pattern[0] == type(proto):
140*99e0aae7SDavid Rees      new_pattern = pattern[1:]
141*99e0aae7SDavid Rees    else:
142*99e0aae7SDavid Rees      new_pattern = pattern
143*99e0aae7SDavid Rees
144*99e0aae7SDavid Rees  # If the current node's type is one of the types whose branch should be
145*99e0aae7SDavid Rees  # skipped, then bail.  This has to happen after `action` is called, because
146*99e0aae7SDavid Rees  # clients rely on being able to, e.g., get a callback for the "root"
147*99e0aae7SDavid Rees  # Expression without getting callbacks for every sub-Expression.
148*99e0aae7SDavid Rees  # pylint: disable=unidiomatic-typecheck
149*99e0aae7SDavid Rees  if type(proto) in skip_descendants_of:
150*99e0aae7SDavid Rees    return
151*99e0aae7SDavid Rees
152*99e0aae7SDavid Rees  # Otherwise, recurse.  _FIELDS_TO_SCAN_BY_CURRENT_AND_TARGET tells us, given
153*99e0aae7SDavid Rees  # the current node's type and the current target type, which fields to check.
154*99e0aae7SDavid Rees  singular_fields, repeated_fields = _FIELDS_TO_SCAN_BY_CURRENT_AND_TARGET[
155*99e0aae7SDavid Rees      type(proto), new_pattern[0]]
156*99e0aae7SDavid Rees  for member_name in singular_fields:
157*99e0aae7SDavid Rees    if proto.HasField(member_name):
158*99e0aae7SDavid Rees      _fast_traverse_proto_top_down(getattr(proto, member_name),
159*99e0aae7SDavid Rees                                    incidental_actions, new_pattern,
160*99e0aae7SDavid Rees                                    skip_descendants_of, action, parameters)
161*99e0aae7SDavid Rees  for member_name in repeated_fields:
162*99e0aae7SDavid Rees    for array_element in getattr(proto, member_name) or []:
163*99e0aae7SDavid Rees      _fast_traverse_proto_top_down(array_element, incidental_actions,
164*99e0aae7SDavid Rees                                    new_pattern, skip_descendants_of, action,
165*99e0aae7SDavid Rees                                    parameters)
166*99e0aae7SDavid Rees
167*99e0aae7SDavid Rees
168*99e0aae7SDavid Reesdef _fields_to_scan_by_current_and_target():
169*99e0aae7SDavid Rees  """Generates _FIELDS_TO_SCAN_BY_CURRENT_AND_TARGET."""
170*99e0aae7SDavid Rees  # In order to avoid spending a *lot* of time just walking the IR, this
171*99e0aae7SDavid Rees  # function sets up a dict that allows `_fast_traverse_proto_top_down()` to
172*99e0aae7SDavid Rees  # skip traversing large portions of the IR, depending on what node types it is
173*99e0aae7SDavid Rees  # targeting.
174*99e0aae7SDavid Rees  #
175*99e0aae7SDavid Rees  # Without this branch culling scheme, the Emboss front end (at time of
176*99e0aae7SDavid Rees  # writing) spends roughly 70% (19s out of 31s) of its time just walking the
177*99e0aae7SDavid Rees  # IR.  With branch culling, that goes down to 6% (0.7s out of 12.2s).
178*99e0aae7SDavid Rees
179*99e0aae7SDavid Rees  # type_to_fields is a map of types to maps of field names to field types.
180*99e0aae7SDavid Rees  # That is, type_to_fields[ir_data.Module]["type"] == ir_data.AddressableUnit.
181*99e0aae7SDavid Rees  type_to_fields = {}
182*99e0aae7SDavid Rees
183*99e0aae7SDavid Rees  # Later, we need to know which fields are singular and which are repeated,
184*99e0aae7SDavid Rees  # because the access methods are not uniform.  This maps (type, field_name)
185*99e0aae7SDavid Rees  # tuples to descriptor labels: type_fields_to_cardinality[ir_data.Module,
186*99e0aae7SDavid Rees  # "type"] == ir_data.Repeated.
187*99e0aae7SDavid Rees  type_fields_to_cardinality = {}
188*99e0aae7SDavid Rees
189*99e0aae7SDavid Rees  # Fill out the above maps by recursively walking the IR type tree, starting
190*99e0aae7SDavid Rees  # from the root.
191*99e0aae7SDavid Rees  types_to_check = [ir_data.EmbossIr]
192*99e0aae7SDavid Rees  while types_to_check:
193*99e0aae7SDavid Rees    type_to_check = types_to_check.pop()
194*99e0aae7SDavid Rees    if type_to_check in type_to_fields:
195*99e0aae7SDavid Rees      continue
196*99e0aae7SDavid Rees    fields = {}
197*99e0aae7SDavid Rees    for field_name, field_type in ir_data_utils.field_specs(type_to_check).items():
198*99e0aae7SDavid Rees      if field_type.is_dataclass:
199*99e0aae7SDavid Rees        fields[field_name] = field_type.data_type
200*99e0aae7SDavid Rees        types_to_check.append(field_type.data_type)
201*99e0aae7SDavid Rees        type_fields_to_cardinality[type_to_check, field_name] = (
202*99e0aae7SDavid Rees            field_type.container)
203*99e0aae7SDavid Rees    type_to_fields[type_to_check] = fields
204*99e0aae7SDavid Rees
205*99e0aae7SDavid Rees  # type_to_descendant_types is a map of all types that can be reached from a
206*99e0aae7SDavid Rees  # particular type.  After the setup, type_to_descendant_types[ir_data.EmbossIr]
207*99e0aae7SDavid Rees  # == set(<all types>) and type_to_descendant_types[ir_data.Reference] ==
208*99e0aae7SDavid Rees  # {ir_data.CanonicalName, ir_data.Word, ir_data.Location} and
209*99e0aae7SDavid Rees  # type_to_descendant_types[ir_data.Word] == set().
210*99e0aae7SDavid Rees  #
211*99e0aae7SDavid Rees  # The while loop basically ors in the known descendants of each known
212*99e0aae7SDavid Rees  # descendant of each type until the dict stops changing, which is a bit
213*99e0aae7SDavid Rees  # brute-force, but in practice only iterates a few times.
214*99e0aae7SDavid Rees  type_to_descendant_types = {}
215*99e0aae7SDavid Rees  for parent_type, field_map in type_to_fields.items():
216*99e0aae7SDavid Rees    type_to_descendant_types[parent_type] = set(field_map.values())
217*99e0aae7SDavid Rees  previous_map = {}
218*99e0aae7SDavid Rees  while type_to_descendant_types != previous_map:
219*99e0aae7SDavid Rees    # In order to check the previous iteration against the current iteration, it
220*99e0aae7SDavid Rees    # is necessary to make a two-level copy.  Otherwise, the updates to the
221*99e0aae7SDavid Rees    # values will also update previous_map's values, which causes the loop to
222*99e0aae7SDavid Rees    # exit prematurely.
223*99e0aae7SDavid Rees    previous_map = {k: set(v) for k, v in type_to_descendant_types.items()}
224*99e0aae7SDavid Rees    for ancestor_type, descendents in previous_map.items():
225*99e0aae7SDavid Rees      for descendent in descendents:
226*99e0aae7SDavid Rees        type_to_descendant_types[ancestor_type] |= previous_map[descendent]
227*99e0aae7SDavid Rees
228*99e0aae7SDavid Rees  # Finally, we have all of the information we need to make the map we really
229*99e0aae7SDavid Rees  # want: given a current node type and a target node type, which fields should
230*99e0aae7SDavid Rees  # be checked?  (This implicitly skips fields that *can't* contain the target
231*99e0aae7SDavid Rees  # type.)
232*99e0aae7SDavid Rees  fields_to_scan_by_current_and_target = {}
233*99e0aae7SDavid Rees  for current_node_type in type_to_fields:
234*99e0aae7SDavid Rees    for target_node_type in type_to_fields:
235*99e0aae7SDavid Rees      singular_fields_to_scan = []
236*99e0aae7SDavid Rees      repeated_fields_to_scan = []
237*99e0aae7SDavid Rees      for field_name, field_type in type_to_fields[current_node_type].items():
238*99e0aae7SDavid Rees        # If the target node type cannot contain another instance of itself, it
239*99e0aae7SDavid Rees        # is still necessary to scan fields that have the actual target type.
240*99e0aae7SDavid Rees        if (target_node_type == field_type or
241*99e0aae7SDavid Rees            target_node_type in type_to_descendant_types[field_type]):
242*99e0aae7SDavid Rees          # Singular and repeated fields go to different lists, so that they can
243*99e0aae7SDavid Rees          # be handled separately.
244*99e0aae7SDavid Rees          if (type_fields_to_cardinality[current_node_type, field_name] is not
245*99e0aae7SDavid Rees              ir_data_fields.FieldContainer.LIST):
246*99e0aae7SDavid Rees            singular_fields_to_scan.append(field_name)
247*99e0aae7SDavid Rees          else:
248*99e0aae7SDavid Rees            repeated_fields_to_scan.append(field_name)
249*99e0aae7SDavid Rees      fields_to_scan_by_current_and_target[
250*99e0aae7SDavid Rees          current_node_type, target_node_type] = (
251*99e0aae7SDavid Rees              singular_fields_to_scan, repeated_fields_to_scan)
252*99e0aae7SDavid Rees  return fields_to_scan_by_current_and_target
253*99e0aae7SDavid Rees
254*99e0aae7SDavid Rees
255*99e0aae7SDavid Rees_FIELDS_TO_SCAN_BY_CURRENT_AND_TARGET = _fields_to_scan_by_current_and_target()
256*99e0aae7SDavid Rees
257*99e0aae7SDavid Reesdef _emboss_ir_action(ir):
258*99e0aae7SDavid Rees  return {"ir": ir}
259*99e0aae7SDavid Rees
260*99e0aae7SDavid Reesdef _module_action(m):
261*99e0aae7SDavid Rees  return {"source_file_name": m.source_file_name}
262*99e0aae7SDavid Rees
263*99e0aae7SDavid Reesdef _type_definition_action(t):
264*99e0aae7SDavid Rees  return {"type_definition": t}
265*99e0aae7SDavid Rees
266*99e0aae7SDavid Reesdef _field_action(f):
267*99e0aae7SDavid Rees  return {"field": f}
268*99e0aae7SDavid Rees
269*99e0aae7SDavid Reesdef fast_traverse_ir_top_down(ir, pattern, action, incidental_actions=None,
270*99e0aae7SDavid Rees                              skip_descendants_of=(), parameters=None):
271*99e0aae7SDavid Rees  """Traverses an IR from the top down, executing the given actions.
272*99e0aae7SDavid Rees
273*99e0aae7SDavid Rees  `fast_traverse_ir_top_down` walks the given IR in preorder traversal,
274*99e0aae7SDavid Rees  specifically looking for nodes whose path from the root of the tree matches
275*99e0aae7SDavid Rees  `pattern`.  For every node which matches `pattern`, `action` will be called.
276*99e0aae7SDavid Rees
277*99e0aae7SDavid Rees  `pattern` is just a list of node types.  For example, to execute `print` on
278*99e0aae7SDavid Rees  every `ir_data.Word` in the IR:
279*99e0aae7SDavid Rees
280*99e0aae7SDavid Rees      fast_traverse_ir_top_down(ir, [ir_data.Word], print)
281*99e0aae7SDavid Rees
282*99e0aae7SDavid Rees  If more than one type is specified, then each one must be found inside the
283*99e0aae7SDavid Rees  previous.  For example, to print only the Words inside of import statements:
284*99e0aae7SDavid Rees
285*99e0aae7SDavid Rees      fast_traverse_ir_top_down(ir, [ir_data.Import, ir_data.Word], print)
286*99e0aae7SDavid Rees
287*99e0aae7SDavid Rees  The optional arguments provide additional control.
288*99e0aae7SDavid Rees
289*99e0aae7SDavid Rees  `skip_descendants_of` is a list of types that should be treated as if they are
290*99e0aae7SDavid Rees  leaf nodes when they are encountered.  That is, traversal will skip any
291*99e0aae7SDavid Rees  nodes with any ancestor node whose type is in `skip_descendants_of`.  For
292*99e0aae7SDavid Rees  example, to `do_something` only on outermost `Expression`s:
293*99e0aae7SDavid Rees
294*99e0aae7SDavid Rees      fast_traverse_ir_top_down(ir, [ir_data.Expression], do_something,
295*99e0aae7SDavid Rees                                skip_descendants_of={ir_data.Expression})
296*99e0aae7SDavid Rees
297*99e0aae7SDavid Rees  `parameters` specifies a dictionary of initial parameters which can be passed
298*99e0aae7SDavid Rees  as arguments to `action` and `incidental_actions`.  Note that the parameters
299*99e0aae7SDavid Rees  can be overridden for parts of the tree by `action` and `incidental_actions`.
300*99e0aae7SDavid Rees  Parameters can be used to set an object which may be updated by `action`, such
301*99e0aae7SDavid Rees  as a list of errors generated by some check in `action`:
302*99e0aae7SDavid Rees
303*99e0aae7SDavid Rees      def check_structure(structure, errors):
304*99e0aae7SDavid Rees        if structure_is_bad(structure):
305*99e0aae7SDavid Rees          errors.append(error_for_structure(structure))
306*99e0aae7SDavid Rees
307*99e0aae7SDavid Rees      errors = []
308*99e0aae7SDavid Rees      fast_traverse_ir_top_down(ir, [ir_data.Structure], check_structure,
309*99e0aae7SDavid Rees                                parameters={"errors": errors})
310*99e0aae7SDavid Rees      if errors:
311*99e0aae7SDavid Rees        print("Errors: {}".format(errors))
312*99e0aae7SDavid Rees        sys.exit(1)
313*99e0aae7SDavid Rees
314*99e0aae7SDavid Rees  `incidental_actions` is a map from node types to functions (or tuples of
315*99e0aae7SDavid Rees  functions or lists of functions) which should be called on those nodes.
316*99e0aae7SDavid Rees  Because `fast_traverse_ir_top_down` may skip branches that can't contain
317*99e0aae7SDavid Rees  `pattern`, functions in `incidental_actions` should generally not have any
318*99e0aae7SDavid Rees  side effects: instead, they may return a dictionary, which will be used to
319*99e0aae7SDavid Rees  override `parameters` for any children of the node they were called on.  For
320*99e0aae7SDavid Rees  example:
321*99e0aae7SDavid Rees
322*99e0aae7SDavid Rees      def do_something(expression, field_name=None):
323*99e0aae7SDavid Rees        if field_name:
324*99e0aae7SDavid Rees          print("Found {} inside {}".format(expression, field_name))
325*99e0aae7SDavid Rees        else:
326*99e0aae7SDavid Rees          print("Found {} not in any field".format(expression))
327*99e0aae7SDavid Rees
328*99e0aae7SDavid Rees      fast_traverse_ir_top_down(
329*99e0aae7SDavid Rees          ir, [ir_data.Expression], do_something,
330*99e0aae7SDavid Rees          incidental_actions={ir_data.Field: lambda f: {"field_name": f.name}})
331*99e0aae7SDavid Rees
332*99e0aae7SDavid Rees  (The `action` may also return a dict in the same way.)
333*99e0aae7SDavid Rees
334*99e0aae7SDavid Rees  A few `incidental_actions` are built into `fast_traverse_ir_top_down`, so
335*99e0aae7SDavid Rees  that certain parameters are contextually available with well-known names:
336*99e0aae7SDavid Rees
337*99e0aae7SDavid Rees      ir: The complete IR (the root ir_data.EmbossIr node).
338*99e0aae7SDavid Rees      source_file_name: The file name from which the current node was sourced.
339*99e0aae7SDavid Rees      type_definition: The most-immediate ancestor type definition.
340*99e0aae7SDavid Rees      field: The field containing the current node, if any.
341*99e0aae7SDavid Rees
342*99e0aae7SDavid Rees  Arguments:
343*99e0aae7SDavid Rees    ir: An ir_data.Ir object to walk.
344*99e0aae7SDavid Rees    pattern: A list of node types to match.
345*99e0aae7SDavid Rees    action: A callable, which will be called on nodes matching `pattern`.
346*99e0aae7SDavid Rees    incidental_actions: A dict of node types to callables, which can be used to
347*99e0aae7SDavid Rees        set new parameters for `action` for part of the IR tree.
348*99e0aae7SDavid Rees    skip_descendants_of: A list of types whose children should be skipped when
349*99e0aae7SDavid Rees        traversing `ir`.
350*99e0aae7SDavid Rees    parameters: A list of top-level parameters.
351*99e0aae7SDavid Rees
352*99e0aae7SDavid Rees  Returns:
353*99e0aae7SDavid Rees    None
354*99e0aae7SDavid Rees  """
355*99e0aae7SDavid Rees  all_incidental_actions = {
356*99e0aae7SDavid Rees      ir_data.EmbossIr: [_emboss_ir_action],
357*99e0aae7SDavid Rees      ir_data.Module: [_module_action],
358*99e0aae7SDavid Rees      ir_data.TypeDefinition: [_type_definition_action],
359*99e0aae7SDavid Rees      ir_data.Field: [_field_action],
360*99e0aae7SDavid Rees  }
361*99e0aae7SDavid Rees  if incidental_actions:
362*99e0aae7SDavid Rees    for key, incidental_action in incidental_actions.items():
363*99e0aae7SDavid Rees      if not isinstance(incidental_action, (list, tuple)):
364*99e0aae7SDavid Rees        incidental_action = [incidental_action]
365*99e0aae7SDavid Rees      all_incidental_actions.setdefault(key, []).extend(incidental_action)
366*99e0aae7SDavid Rees  _fast_traverse_proto_top_down(ir, all_incidental_actions, pattern,
367*99e0aae7SDavid Rees                                skip_descendants_of, action, parameters or {})
368*99e0aae7SDavid Rees
369*99e0aae7SDavid Rees
370*99e0aae7SDavid Reesdef fast_traverse_node_top_down(node, pattern, action, incidental_actions=None,
371*99e0aae7SDavid Rees                                skip_descendants_of=(), parameters=None):
372*99e0aae7SDavid Rees  """Traverse a subtree of an IR, executing the given actions.
373*99e0aae7SDavid Rees
374*99e0aae7SDavid Rees  fast_traverse_node_top_down is like fast_traverse_ir_top_down, except that:
375*99e0aae7SDavid Rees
376*99e0aae7SDavid Rees  It may be called on a subtree, instead of the top of the IR.
377*99e0aae7SDavid Rees
378*99e0aae7SDavid Rees  It does not have any built-in incidental actions.
379*99e0aae7SDavid Rees
380*99e0aae7SDavid Rees  Arguments:
381*99e0aae7SDavid Rees    node: An ir_data.Ir object to walk.
382*99e0aae7SDavid Rees    pattern: A list of node types to match.
383*99e0aae7SDavid Rees    action: A callable, which will be called on nodes matching `pattern`.
384*99e0aae7SDavid Rees    incidental_actions: A dict of node types to callables, which can be used to
385*99e0aae7SDavid Rees        set new parameters for `action` for part of the IR tree.
386*99e0aae7SDavid Rees    skip_descendants_of: A list of types whose children should be skipped when
387*99e0aae7SDavid Rees        traversing `node`.
388*99e0aae7SDavid Rees    parameters: A list of top-level parameters.
389*99e0aae7SDavid Rees
390*99e0aae7SDavid Rees  Returns:
391*99e0aae7SDavid Rees    None
392*99e0aae7SDavid Rees  """
393*99e0aae7SDavid Rees  _fast_traverse_proto_top_down(node, incidental_actions or {}, pattern,
394*99e0aae7SDavid Rees                                skip_descendants_of or {}, action,
395*99e0aae7SDavid Rees                                parameters or {})
396