| OLD | NEW |
| 1 library dart2js.cps_ir_integrity; | 1 library dart2js.cps_ir_integrity; |
| 2 | 2 |
| 3 import 'cps_ir_nodes.dart'; | 3 import 'cps_ir_nodes.dart'; |
| 4 import 'cps_ir_nodes_sexpr.dart'; | 4 import 'cps_ir_nodes_sexpr.dart'; |
| 5 import '../tracer.dart' as tracer; | 5 import '../tracer.dart' as tracer; |
| 6 | 6 |
| 7 /// Dump S-expressions on error if the tracer is enabled. | 7 /// Dump S-expressions on error if the tracer is enabled. |
| 8 /// | 8 /// |
| 9 /// Technically this has nothing to do with the tracer, but if you want one | 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. | 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; | 11 const bool ENABLE_DUMP = tracer.TRACE_FILTER_PATTERN != null; |
| 12 | 12 |
| 13 enum ScopeType { InScope, InDefinition, NotInScope } |
| 14 |
| 13 /// Performs integrity checks on the CPS IR. | 15 /// Performs integrity checks on the CPS IR. |
| 14 /// | 16 /// |
| 15 /// To be run for debugging purposes, not for use in production. | 17 /// To be run for debugging purposes, not for use in production. |
| 16 /// | 18 /// |
| 17 /// The following integrity checks are performed: | 19 /// The following integrity checks are performed: |
| 18 /// | 20 /// |
| 19 /// - References are in scope of their definitions. | 21 /// - References are in scope of their definitions. |
| 20 /// - Recursive Continuations and InvokeContinuations are marked as recursive. | 22 /// - Recursive Continuations and InvokeContinuations are marked as recursive. |
| 21 /// - InvokeContinuations have the same arity as their target. | 23 /// - InvokeContinuations have the same arity as their target. |
| 22 /// - Reference chains are valid doubly-linked lists. | 24 /// - Reference chains are valid doubly-linked lists. |
| 23 /// - Reference chains contain exactly the references that are in the IR. | 25 /// - Reference chains contain exactly the references that are in the IR. |
| 24 /// - Each definition object occurs only once in the IR (no redeclaring). | 26 /// - Each definition object occurs only once in the IR (no redeclaring). |
| 25 /// - Each reference object occurs only once in the IR (no sharing). | 27 /// - Each reference object occurs only once in the IR (no sharing). |
| 26 /// | 28 /// |
| 27 class CheckCpsIntegrity extends TrampolineRecursiveVisitor { | 29 class CheckCpsIntegrity extends TrampolineRecursiveVisitor { |
| 28 | 30 |
| 29 FunctionDefinition topLevelNode; | 31 FunctionDefinition topLevelNode; |
| 32 final Map<Definition, ScopeType> inScope = <Definition, ScopeType>{}; |
| 33 final List<Definition> definitions = []; |
| 30 String previousPass; | 34 String previousPass; |
| 31 | 35 |
| 32 Set<Definition> seenDefinitions = new Set<Definition>(); | 36 void setScope(Iterable<Definition> defs, ScopeType scope) { |
| 33 Map<Definition, Set<Reference>> seenReferences = | 37 for (Definition def in defs) { |
| 34 <Definition, Set<Reference>>{}; | 38 inScope[def] = scope; |
| 39 } |
| 40 } |
| 35 | 41 |
| 36 Set<Definition> inScope = new Set<Definition>(); | 42 void handleDeclaration(Definition def) { |
| 37 Set<Continuation> insideContinuations = new Set<Continuation>(); | 43 definitions.add(def); |
| 38 | 44 // Check the reference chain for cycles broken links. |
| 39 void markAsSeen(Definition def) { | 45 Reference anchor = null; |
| 40 if (!seenDefinitions.add(def)) { | 46 int i = 0; |
| 41 error('Redeclared $def', def); | 47 for (Reference ref = def.firstRef; ref != null; ref = ref.next) { |
| 48 if (ref.definition != def) { |
| 49 error('Reference to ${ref.definition} found in ' |
| 50 'reference chain for $def', def); |
| 51 } |
| 52 if (ref == anchor) { |
| 53 error('Cyclic reference chain for $def', def); |
| 54 } |
| 55 if (i & ++i == 0) { // Move the anchor every 2^Nth step. |
| 56 anchor = ref; |
| 57 } |
| 42 } | 58 } |
| 43 seenReferences[def] = new Set<Reference>(); | |
| 44 } | 59 } |
| 45 | 60 |
| 46 void enterScope(Iterable<Definition> definitions) { | 61 void enterScope(Iterable<Definition> definitions) { |
| 47 inScope.addAll(definitions); | 62 for (Definition def in definitions) { |
| 48 pushAction(() => inScope.removeAll(definitions)); | 63 inScope[def] = ScopeType.InScope; |
| 64 } |
| 65 pushAction(() { |
| 66 for (Definition def in definitions) { |
| 67 inScope[def] = ScopeType.NotInScope; |
| 68 } |
| 69 }); |
| 49 } | 70 } |
| 50 | 71 |
| 51 void enterContinuation(Continuation cont) { | 72 void enterContinuation(Continuation cont) { |
| 52 insideContinuations.add(cont); | 73 inScope[cont] = ScopeType.InDefinition; |
| 53 pushAction(() => insideContinuations.remove(cont)); | 74 pushAction(() { |
| 75 inScope[cont] = ScopeType.NotInScope; |
| 76 }); |
| 54 } | 77 } |
| 55 | 78 |
| 56 void check(FunctionDefinition node, String previousPass) { | 79 void check(FunctionDefinition node, String previousPass) { |
| 57 topLevelNode = node; | 80 topLevelNode = node; |
| 58 this.previousPass = previousPass; | 81 this.previousPass = previousPass; |
| 59 ParentChecker.checkParents(node, this); | 82 ParentChecker.checkParents(node, this); |
| 60 visit(node); | 83 visit(node); |
| 61 // Check for broken reference chains. We check this last, so out-of-scope | 84 // Check for broken reference chains. We check this last, so out-of-scope |
| 62 // references are not classified as a broken reference chain. | 85 // references are not classified as a broken reference chain. |
| 63 seenDefinitions.forEach(checkReferenceChain); | 86 definitions.forEach(checkReferenceChain); |
| 64 } | 87 } |
| 65 | 88 |
| 66 @override | 89 @override |
| 67 Expression traverseLetCont(LetCont node) { | 90 Expression traverseLetCont(LetCont node) { |
| 68 node.continuations.forEach(markAsSeen); | 91 node.continuations.forEach(handleDeclaration); |
| 69 node.continuations.forEach(push); | 92 node.continuations.forEach(push); |
| 70 | 93 |
| 71 // Put all continuations in scope when visiting the body. | 94 // Put all continuations in scope when visiting the body. |
| 72 enterScope(node.continuations); | 95 enterScope(node.continuations); |
| 73 | 96 |
| 74 return node.body; | 97 return node.body; |
| 75 } | 98 } |
| 76 | 99 |
| 77 @override | 100 @override |
| 78 Expression traverseLetPrim(LetPrim node) { | 101 Expression traverseLetPrim(LetPrim node) { |
| 79 markAsSeen(node.primitive); | 102 handleDeclaration(node.primitive); |
| 80 | 103 |
| 81 // Process references in the primitive. | 104 // Process references in the primitive. |
| 82 visit(node.primitive); | 105 visit(node.primitive); |
| 83 | 106 |
| 84 // Put the primitive in scope when visiting the body. | 107 // Put the primitive in scope when visiting the body. |
| 85 enterScope([node.primitive]); | 108 enterScope([node.primitive]); |
| 86 | 109 |
| 87 return node.body; | 110 return node.body; |
| 88 } | 111 } |
| 89 | 112 |
| 90 @override | 113 @override |
| 91 Expression traverseLetMutable(LetMutable node) { | 114 Expression traverseLetMutable(LetMutable node) { |
| 92 markAsSeen(node.variable); | 115 handleDeclaration(node.variable); |
| 93 processReference(node.value); | 116 processReference(node.value); |
| 94 | 117 |
| 95 // Put the primitive in scope when visiting the body. | 118 // Put the primitive in scope when visiting the body. |
| 96 enterScope([node.variable]); | 119 enterScope([node.variable]); |
| 97 | 120 |
| 98 return node.body; | 121 return node.body; |
| 99 } | 122 } |
| 100 | 123 |
| 101 @override | 124 @override |
| 102 Expression traverseContinuation(Continuation cont) { | 125 Expression traverseContinuation(Continuation cont) { |
| 103 if (cont.isReturnContinuation) { | 126 if (cont.isReturnContinuation) { |
| 104 error('Non-return continuation missing body', cont); | 127 error('Non-return continuation missing body', cont); |
| 105 } | 128 } |
| 106 cont.parameters.forEach(markAsSeen); | 129 cont.parameters.forEach(handleDeclaration); |
| 107 enterScope(cont.parameters); | 130 enterScope(cont.parameters); |
| 108 // Put every continuation in scope at its own body. The isRecursive | 131 // Put every continuation in scope at its own body. The isRecursive |
| 109 // flag is checked explicitly using [insideContinuations]. | 132 // flag is checked explicitly using [insideContinuations]. |
| 110 enterScope([cont]); | 133 enterScope([cont]); |
| 111 enterContinuation(cont); | 134 enterContinuation(cont); |
| 112 return cont.body; | 135 return cont.body; |
| 113 } | 136 } |
| 114 | 137 |
| 115 @override | 138 @override |
| 116 visitFunctionDefinition(FunctionDefinition node) { | 139 visitFunctionDefinition(FunctionDefinition node) { |
| 117 if (node.thisParameter != null) { | 140 if (node.thisParameter != null) { |
| 118 markAsSeen(node.thisParameter); | 141 handleDeclaration(node.thisParameter); |
| 119 enterScope([node.thisParameter]); | 142 enterScope([node.thisParameter]); |
| 120 } | 143 } |
| 121 node.parameters.forEach(markAsSeen); | 144 node.parameters.forEach(handleDeclaration); |
| 122 enterScope(node.parameters); | 145 enterScope(node.parameters); |
| 123 markAsSeen(node.returnContinuation); | 146 handleDeclaration(node.returnContinuation); |
| 124 enterScope([node.returnContinuation]); | 147 enterScope([node.returnContinuation]); |
| 125 if (!node.returnContinuation.isReturnContinuation) { | 148 if (!node.returnContinuation.isReturnContinuation) { |
| 126 error('Return continuation with a body', node); | 149 error('Return continuation with a body', node); |
| 127 } | 150 } |
| 128 visit(node.body); | 151 visit(node.body); |
| 129 } | 152 } |
| 130 | 153 |
| 131 @override | 154 @override |
| 132 processReference(Reference reference) { | 155 processReference(Reference ref) { |
| 133 if (!inScope.contains(reference.definition)) { | 156 Definition def = ref.definition; |
| 134 error('Referenced out of scope: ${reference.definition}', reference); | 157 if (inScope[def] == ScopeType.NotInScope) { |
| 158 error('Referenced out of scope: $def', ref); |
| 135 } | 159 } |
| 136 if (!seenReferences[reference.definition].add(reference)) { | 160 if (ref.previous == null && def.firstRef != ref || |
| 137 error('Duplicate use of Reference to ${reference.definition}', reference); | 161 ref.previous != null && ref.previous.next != ref) { |
| 162 error('Broken .previous link in reference to $def', def); |
| 138 } | 163 } |
| 164 ref.previous = ref; // Mark reference as "seen". We will repair it later. |
| 139 } | 165 } |
| 140 | 166 |
| 141 @override | 167 @override |
| 142 processInvokeContinuation(InvokeContinuation node) { | 168 processInvokeContinuation(InvokeContinuation node) { |
| 143 Continuation target = node.continuation.definition; | 169 Continuation target = node.continuation.definition; |
| 144 if (node.isRecursive && !insideContinuations.contains(target)) { | 170 if (node.isRecursive && inScope[target] == ScopeType.InScope) { |
| 145 error('Non-recursive InvokeContinuation marked as recursive', node); | 171 error('Non-recursive InvokeContinuation marked as recursive', node); |
| 146 } | 172 } |
| 147 if (!node.isRecursive && insideContinuations.contains(target)) { | 173 if (!node.isRecursive && inScope[target] == ScopeType.InDefinition) { |
| 148 error('Recursive InvokeContinuation marked as non-recursive', node); | 174 error('Recursive InvokeContinuation marked as non-recursive', node); |
| 149 } | 175 } |
| 150 if (node.isRecursive && !target.isRecursive) { | 176 if (node.isRecursive && !target.isRecursive) { |
| 151 error('Recursive Continuation was not marked as recursive', node); | 177 error('Recursive Continuation was not marked as recursive', node); |
| 152 } | 178 } |
| 153 if (node.arguments.length != target.parameters.length) { | 179 if (node.arguments.length != target.parameters.length) { |
| 154 error('Arity mismatch in InvokeContinuation', node); | 180 error('Arity mismatch in InvokeContinuation', node); |
| 155 } | 181 } |
| 156 } | 182 } |
| 157 | 183 |
| 158 void checkReferenceChain(Definition def) { | 184 void checkReferenceChain(Definition def) { |
| 159 Set<Reference> chainedReferences = new Set<Reference>(); | 185 Reference previous = null; |
| 160 Reference prev = null; | |
| 161 for (Reference ref = def.firstRef; ref != null; ref = ref.next) { | 186 for (Reference ref = def.firstRef; ref != null; ref = ref.next) { |
| 162 if (ref.definition != def) { | 187 if (ref.previous != ref) { |
| 163 error('Reference in chain for $def points to ${ref.definition}', def); | 188 // Reference was not seen during IR traversal, so it is orphaned. |
| 189 error('Orphaned reference in reference chain for $def', def); |
| 164 } | 190 } |
| 165 if (ref.previous != prev) { | 191 // Repair the .previous link that was used for marking. |
| 166 error('Broken .previous link in reference to $def', def); | 192 ref.previous = previous; |
| 167 } | 193 previous = ref; |
| 168 prev = ref; | |
| 169 if (!chainedReferences.add(ref)) { | |
| 170 error('Cyclic reference chain for $def', def); | |
| 171 } | |
| 172 } | |
| 173 if (!chainedReferences.containsAll(seenReferences[def])) { | |
| 174 error('Seen reference to $def not in reference chain', def); | |
| 175 } | |
| 176 if (!seenReferences[def].containsAll(chainedReferences)) { | |
| 177 error('Reference chain for $def contains orphaned references', def); | |
| 178 } | 194 } |
| 179 } | 195 } |
| 180 | 196 |
| 181 error(String message, node) { | 197 error(String message, node) { |
| 182 String sexpr; | 198 String sexpr; |
| 183 if (ENABLE_DUMP) { | 199 if (ENABLE_DUMP) { |
| 184 try { | 200 try { |
| 185 Decorator decorator = (n, String s) => n == node ? '**$s**' : s; | 201 Decorator decorator = (n, String s) => n == node ? '**$s**' : s; |
| 186 sexpr = new SExpressionStringifier(decorator).visit(topLevelNode); | 202 sexpr = new SExpressionStringifier(decorator).visit(topLevelNode); |
| 187 sexpr = 'SExpr dump (offending node marked with **):\n\n$sexpr'; | 203 sexpr = 'SExpr dump (offending node marked with **):\n\n$sexpr'; |
| (...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 232 } | 248 } |
| 233 | 249 |
| 234 @override | 250 @override |
| 235 processReference(Reference node) { | 251 processReference(Reference node) { |
| 236 if (node.parent != _parent) { | 252 if (node.parent != _parent) { |
| 237 error('Parent pointer on $node is ${node.parent} but should be $_parent', | 253 error('Parent pointer on $node is ${node.parent} but should be $_parent', |
| 238 node); | 254 node); |
| 239 } | 255 } |
| 240 } | 256 } |
| 241 } | 257 } |
| OLD | NEW |