| OLD | NEW |
| (Empty) |
| 1 library dart2js.cps_ir_integrity; | |
| 2 | |
| 3 import '../tracer.dart' as tracer; | |
| 4 import 'cps_ir_nodes.dart'; | |
| 5 import 'cps_ir_nodes_sexpr.dart'; | |
| 6 | |
| 7 /// Dump S-expressions on error if the tracer is enabled. | |
| 8 /// | |
| 9 /// Technically this has nothing to do with the tracer, but if you want one | |
| 10 /// enabled, you typically want the other as well, so we use the same flag. | |
| 11 const bool ENABLE_DUMP = tracer.TRACE_FILTER_PATTERN != null; | |
| 12 | |
| 13 enum ScopeType { InScope, InDefinition, NotInScope } | |
| 14 | |
| 15 /// Performs integrity checks on the CPS IR. | |
| 16 /// | |
| 17 /// To be run for debugging purposes, not for use in production. | |
| 18 /// | |
| 19 /// The following integrity checks are performed: | |
| 20 /// | |
| 21 /// - References are in scope of their definitions. | |
| 22 /// - Recursive Continuations and InvokeContinuations are marked as recursive. | |
| 23 /// - InvokeContinuations have the same arity as their target. | |
| 24 /// - Reference chains are valid doubly-linked lists. | |
| 25 /// - Reference chains contain exactly the references that are in the IR. | |
| 26 /// - Each definition object occurs only once in the IR (no redeclaring). | |
| 27 /// - Each reference object occurs only once in the IR (no sharing). | |
| 28 /// | |
| 29 class CheckCpsIntegrity extends TrampolineRecursiveVisitor { | |
| 30 FunctionDefinition topLevelNode; | |
| 31 final Map<Definition, ScopeType> inScope = <Definition, ScopeType>{}; | |
| 32 final List<Definition> definitions = []; | |
| 33 String previousPass; | |
| 34 | |
| 35 void handleDeclaration(Definition def) { | |
| 36 definitions.add(def); | |
| 37 // Check the reference chain for cycles broken links. | |
| 38 Reference anchor = null; | |
| 39 int i = 0; | |
| 40 for (Reference ref = def.firstRef; ref != null; ref = ref.next) { | |
| 41 if (ref.definition != def) { | |
| 42 error( | |
| 43 'Reference to ${ref.definition} found in ' | |
| 44 'reference chain for $def', | |
| 45 def); | |
| 46 } | |
| 47 if (ref == anchor) { | |
| 48 error('Cyclic reference chain for $def', def); | |
| 49 } | |
| 50 if (i & ++i == 0) { | |
| 51 // Move the anchor every 2^Nth step. | |
| 52 anchor = ref; | |
| 53 } | |
| 54 } | |
| 55 } | |
| 56 | |
| 57 void enterScope(Iterable<Definition> definitions) { | |
| 58 for (Definition def in definitions) { | |
| 59 inScope[def] = ScopeType.InScope; | |
| 60 } | |
| 61 pushAction(() { | |
| 62 for (Definition def in definitions) { | |
| 63 inScope[def] = ScopeType.NotInScope; | |
| 64 } | |
| 65 }); | |
| 66 } | |
| 67 | |
| 68 void enterContinuation(Continuation cont) { | |
| 69 inScope[cont] = ScopeType.InDefinition; | |
| 70 pushAction(() { | |
| 71 inScope[cont] = ScopeType.NotInScope; | |
| 72 }); | |
| 73 } | |
| 74 | |
| 75 void check(FunctionDefinition node, String previousPass) { | |
| 76 // [check] will be called multiple times per instance to avoid reallocating | |
| 77 // the large [inScope] map. Reset the other fields. | |
| 78 this.topLevelNode = node; | |
| 79 this.previousPass = previousPass; | |
| 80 this.definitions.clear(); | |
| 81 ParentChecker.checkParents(node, this); | |
| 82 visit(node); | |
| 83 // Check for broken reference chains. We check this last, so out-of-scope | |
| 84 // references are not classified as a broken reference chain. | |
| 85 definitions.forEach(checkReferenceChain); | |
| 86 } | |
| 87 | |
| 88 @override | |
| 89 Expression traverseLetCont(LetCont node) { | |
| 90 node.continuations.forEach(handleDeclaration); | |
| 91 node.continuations.forEach(push); | |
| 92 | |
| 93 // Put all continuations in scope when visiting the body. | |
| 94 enterScope(node.continuations); | |
| 95 | |
| 96 return node.body; | |
| 97 } | |
| 98 | |
| 99 @override | |
| 100 Expression traverseLetPrim(LetPrim node) { | |
| 101 handleDeclaration(node.primitive); | |
| 102 | |
| 103 // Process references in the primitive. | |
| 104 visit(node.primitive); | |
| 105 | |
| 106 // Put the primitive in scope when visiting the body. | |
| 107 enterScope([node.primitive]); | |
| 108 | |
| 109 return node.body; | |
| 110 } | |
| 111 | |
| 112 @override | |
| 113 Expression traverseLetMutable(LetMutable node) { | |
| 114 handleDeclaration(node.variable); | |
| 115 processReference(node.valueRef); | |
| 116 | |
| 117 // Put the primitive in scope when visiting the body. | |
| 118 enterScope([node.variable]); | |
| 119 | |
| 120 return node.body; | |
| 121 } | |
| 122 | |
| 123 @override | |
| 124 Expression traverseContinuation(Continuation cont) { | |
| 125 if (cont.isReturnContinuation) { | |
| 126 error('Non-return continuation missing body', cont); | |
| 127 } | |
| 128 cont.parameters.forEach(handleDeclaration); | |
| 129 enterScope(cont.parameters); | |
| 130 // Put every continuation in scope at its own body. The isRecursive | |
| 131 // flag is checked explicitly using [insideContinuations]. | |
| 132 enterScope([cont]); | |
| 133 enterContinuation(cont); | |
| 134 return cont.body; | |
| 135 } | |
| 136 | |
| 137 @override | |
| 138 visitFunctionDefinition(FunctionDefinition node) { | |
| 139 if (node.interceptorParameter != null) { | |
| 140 handleDeclaration(node.interceptorParameter); | |
| 141 enterScope([node.interceptorParameter]); | |
| 142 } | |
| 143 if (node.receiverParameter != null) { | |
| 144 handleDeclaration(node.receiverParameter); | |
| 145 enterScope([node.receiverParameter]); | |
| 146 } | |
| 147 node.parameters.forEach(handleDeclaration); | |
| 148 enterScope(node.parameters); | |
| 149 handleDeclaration(node.returnContinuation); | |
| 150 enterScope([node.returnContinuation]); | |
| 151 if (!node.returnContinuation.isReturnContinuation) { | |
| 152 error('Return continuation with a body', node); | |
| 153 } | |
| 154 visit(node.body); | |
| 155 } | |
| 156 | |
| 157 @override | |
| 158 processReference(Reference ref) { | |
| 159 Definition def = ref.definition; | |
| 160 if (inScope[def] == ScopeType.NotInScope) { | |
| 161 error('Referenced out of scope: $def', ref); | |
| 162 } | |
| 163 if (ref.previous == ref) { | |
| 164 error('Shared Reference object to $def', ref); | |
| 165 } | |
| 166 if (ref.previous == null && def.firstRef != ref || | |
| 167 ref.previous != null && ref.previous.next != ref) { | |
| 168 error('Broken .previous link in reference to $def', def); | |
| 169 } | |
| 170 ref.previous = ref; // Mark reference as "seen". We will repair it later. | |
| 171 } | |
| 172 | |
| 173 @override | |
| 174 processInvokeContinuation(InvokeContinuation node) { | |
| 175 Continuation target = node.continuation; | |
| 176 if (node.isRecursive && inScope[target] == ScopeType.InScope) { | |
| 177 error('Non-recursive InvokeContinuation marked as recursive', node); | |
| 178 } | |
| 179 if (!node.isRecursive && inScope[target] == ScopeType.InDefinition) { | |
| 180 error('Recursive InvokeContinuation marked as non-recursive', node); | |
| 181 } | |
| 182 if (node.isRecursive && !target.isRecursive) { | |
| 183 error('Recursive Continuation was not marked as recursive', node); | |
| 184 } | |
| 185 if (node.argumentRefs.length != target.parameters.length) { | |
| 186 error('Arity mismatch in InvokeContinuation', node); | |
| 187 } | |
| 188 } | |
| 189 | |
| 190 @override | |
| 191 processInvokeMethod(InvokeMethod node) { | |
| 192 if (node.callingConvention == CallingConvention.Intercepted) { | |
| 193 if (node.interceptorRef == null) { | |
| 194 error('No interceptor on intercepted call', node); | |
| 195 } | |
| 196 } else { | |
| 197 if (node.interceptorRef != null) { | |
| 198 error('Interceptor on call with ${node.callingConvention}', node); | |
| 199 } | |
| 200 } | |
| 201 } | |
| 202 | |
| 203 void checkReferenceChain(Definition def) { | |
| 204 Reference previous = null; | |
| 205 for (Reference ref = def.firstRef; ref != null; ref = ref.next) { | |
| 206 if (ref.previous != ref) { | |
| 207 // Reference was not seen during IR traversal, so it is orphaned. | |
| 208 error('Orphaned reference in reference chain for $def', def); | |
| 209 } | |
| 210 // Repair the .previous link that was used for marking. | |
| 211 ref.previous = previous; | |
| 212 previous = ref; | |
| 213 } | |
| 214 } | |
| 215 | |
| 216 error(String message, node) { | |
| 217 String sexpr; | |
| 218 if (ENABLE_DUMP) { | |
| 219 try { | |
| 220 Decorator decorator = (n, String s) => n == node ? '**$s**' : s; | |
| 221 sexpr = new SExpressionStringifier(decorator).visit(topLevelNode); | |
| 222 sexpr = 'SExpr dump (offending node marked with **):\n\n$sexpr'; | |
| 223 } catch (e) { | |
| 224 sexpr = '(Exception thrown by SExpressionStringifier: $e)'; | |
| 225 } | |
| 226 } else { | |
| 227 sexpr = '(Set DUMP_IR flag to enable SExpr dump)'; | |
| 228 } | |
| 229 throw 'CPS integrity violation\n' | |
| 230 'After \'$previousPass\' on ${topLevelNode.element}\n' | |
| 231 '$message\n\n' | |
| 232 '$sexpr\n'; | |
| 233 } | |
| 234 } | |
| 235 | |
| 236 /// Traverses the CPS term and checks that node.parent is correctly set | |
| 237 /// for each visited node. | |
| 238 class ParentChecker extends DeepRecursiveVisitor { | |
| 239 static void checkParents(Node node, CheckCpsIntegrity main) { | |
| 240 ParentChecker visitor = new ParentChecker._make(main); | |
| 241 visitor._worklist.add(node); | |
| 242 visitor.trampoline(); | |
| 243 } | |
| 244 | |
| 245 ParentChecker._make(this.main); | |
| 246 | |
| 247 Node _parent; | |
| 248 final List<Node> _worklist = <Node>[]; | |
| 249 final CheckCpsIntegrity main; | |
| 250 | |
| 251 void trampoline() { | |
| 252 while (_worklist.isNotEmpty) { | |
| 253 _parent = _worklist.removeLast(); | |
| 254 _parent.accept(this); | |
| 255 } | |
| 256 } | |
| 257 | |
| 258 error(String message, node) => main.error(message, node); | |
| 259 | |
| 260 @override | |
| 261 visit(Node node) { | |
| 262 _worklist.add(node); | |
| 263 if (node.parent != _parent) { | |
| 264 error('Parent pointer on $node is ${node.parent} but should be $_parent', | |
| 265 node); | |
| 266 } | |
| 267 } | |
| 268 | |
| 269 @override | |
| 270 processReference(Reference node) { | |
| 271 if (node.parent != _parent) { | |
| 272 error('Parent pointer on $node is ${node.parent} but should be $_parent', | |
| 273 node); | |
| 274 } | |
| 275 } | |
| 276 } | |
| OLD | NEW |