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