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