Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 library dart2js.cps_ir.gvn; | |
| 2 | |
| 3 import 'cps_ir_nodes.dart'; | |
| 4 import '../universe/side_effects.dart'; | |
| 5 import '../elements/elements.dart'; | |
| 6 import 'optimizers.dart' show Pass; | |
| 7 import 'loop_hierarchy.dart'; | |
| 8 import 'loop_effects.dart'; | |
| 9 import '../world.dart'; | |
| 10 import '../compiler.dart' show Compiler; | |
| 11 import '../js_backend/js_backend.dart' show JavaScriptBackend; | |
| 12 import '../constants/values.dart'; | |
| 13 | |
| 14 /// Eliminates redundant primitives by reusing the value of another primitive | |
| 15 /// that is known to have the same result. Primitives are also hoisted out of | |
| 16 /// loops when possible. | |
| 17 /// | |
| 18 /// Reusing values can introduce new temporaries, which in some cases is more | |
| 19 /// expensive than recomputing the value on-demand. For example, pulling an | |
| 20 /// expression such as "n+1" out of a loop is generally not worth it. | |
| 21 /// Such primitives are said to be "trivial". | |
| 22 /// | |
| 23 /// Trivial primitives are shared on-demand, i.e. they are only shared if | |
| 24 /// this enables a non-trivial primitive to be hoisted out of a loop. | |
| 25 // | |
| 26 // TODO(asgerf): Enable hoisting across refinement guards when this is safe: | |
| 27 // - Determine the type required for a given primitive to be "safe" | |
| 28 // - Recompute the type of a primitive after hoisting. | |
| 29 // E.g. GetIndex on a String can become a GetIndex on an arbitrary | |
| 30 // indexable, which is still safe but the type may change | |
| 31 // - Since the new type may be worse, insert a refinement at the old | |
| 32 // definition site, so we do not degrade existing type information. | |
| 33 // | |
| 34 // TODO(asgerf): Put this pass at a better place in the pipeline. We currently | |
| 35 // cannot put it anywhere we want, because this pass relies on refinement | |
| 36 // nodes being present (for safety), whereas other passes rely on refinement | |
| 37 // nodes being absent (for simplicity & precision). | |
| 38 // | |
| 39 class GVN extends TrampolineRecursiveVisitor implements Pass { | |
| 40 String get passName => 'GVN'; | |
| 41 | |
| 42 final Compiler compiler; | |
| 43 JavaScriptBackend get backend => compiler.backend; | |
| 44 World get world => compiler.world; | |
| 45 | |
| 46 final GvnTable gvnTable = new GvnTable(); | |
| 47 GvnVectorBuilder gvnVectorBuilder; | |
| 48 LoopHierarchy loopHierarchy; | |
| 49 LoopSideEffects loopEffects; | |
| 50 | |
| 51 /// Effect numbers at the given join point. | |
| 52 Map<Continuation, EffectNumbers> effectsAt = <Continuation, EffectNumbers>{}; | |
| 53 | |
| 54 /// The effect numbers at the current position (during traversal). | |
| 55 EffectNumbers effectNumbers = new EffectNumbers(); | |
| 56 | |
| 57 /// The loop currently enclosing the binding of a given primitive. | |
| 58 final Map<Primitive, Continuation> loopHeaderFor = | |
| 59 <Primitive, Continuation>{}; | |
| 60 | |
| 61 /// The loop to which a given trivial primitive can be hoisted. | |
| 62 final Map<Primitive, Continuation> potentialLoopHeaderFor = | |
| 63 <Primitive, Continuation>{}; | |
| 64 | |
| 65 /// The GVNs for primitives that have been hoisted outside the given loop. | |
| 66 /// | |
| 67 /// These should be removed from the environment when exiting the loop. | |
| 68 final Map<Continuation, List<int>> loopHoistedBindings = | |
| 69 <Continuation, List<int>>{}; | |
| 70 | |
| 71 /// Maps GVNs to a currently-in-scope binding for that value. | |
| 72 final Map<int, Primitive> environment = <int, Primitive>{}; | |
| 73 | |
| 74 /// Maps GVN'able primitives to their global value number. | |
| 75 final Map<Primitive, int> gvnFor = <Primitive, int>{}; | |
| 76 | |
| 77 Continuation currentLoopHeader; | |
| 78 | |
| 79 GVN(this.compiler); | |
| 80 | |
| 81 int _usedEffectNumbers = 0; | |
| 82 int makeNewEffect() => ++_usedEffectNumbers; | |
| 83 | |
| 84 void rewrite(FunctionDefinition node) { | |
| 85 gvnVectorBuilder = new GvnVectorBuilder(gvnFor, backend); | |
| 86 loopHierarchy = new LoopHierarchy(node); | |
| 87 loopEffects = | |
| 88 new LoopSideEffects(node, world, loopHierarchy: loopHierarchy); | |
| 89 visit(node); | |
| 90 } | |
| 91 | |
| 92 // ------------------ GLOBAL VALUE NUMBERING --------------------- | |
| 93 | |
| 94 @override | |
| 95 Expression traverseLetPrim(LetPrim node) { | |
| 96 Expression next = node.body; | |
| 97 Primitive prim = node.primitive; | |
| 98 | |
| 99 loopHeaderFor[prim] = currentLoopHeader; | |
| 100 | |
| 101 if (prim is Refinement) { | |
| 102 // Do not share refinements (they have no runtime or code size cost), and | |
| 103 // do not put them in the GVN table because GvnVectorBuilder unfolds | |
| 104 // refinements by itself. | |
| 105 return next; | |
| 106 } | |
| 107 | |
| 108 // Compute the GVN vector for this computation. | |
| 109 List vector = gvnVectorBuilder.make(prim, effectNumbers); | |
| 110 | |
| 111 // Update effect numbers due to side effects. | |
| 112 // Do this after computing the GVN vector so the primitive's GVN is not | |
| 113 // influenced by its own side effects. | |
| 114 visit(prim); | |
| 115 | |
| 116 if (vector == null) { | |
| 117 // The primitive is not GVN'able. Move on. | |
| 118 return next; | |
| 119 } | |
| 120 | |
| 121 // Compute the GVN for this primitive. | |
| 122 int gvn = gvnTable.insert(vector); | |
| 123 gvnFor[prim] = gvn; | |
| 124 | |
| 125 // Try to reuse a previously computed value with the same GVN. | |
| 126 Primitive existing = environment[gvn]; | |
| 127 if (existing != null && | |
| 128 prim.isSafeForElimination && | |
| 129 !isTrivialPrimitive(prim)) { | |
| 130 if (prim is Interceptor) { | |
| 131 Interceptor interceptor = existing; | |
| 132 interceptor.interceptedClasses.addAll(prim.interceptedClasses); | |
| 133 interceptor.flags |= prim.flags; | |
| 134 } | |
| 135 existing.substituteFor(prim); | |
| 136 prim.destroy(); | |
| 137 node.remove(); | |
| 138 return next; | |
| 139 } | |
| 140 | |
| 141 // If the primitive has no side effects, try to hoist it out of a loop. | |
| 142 if (prim.isSafeForElimination && currentLoopHeader != null) { | |
|
sra1
2015/11/17 05:41:13
Is there some way to move these 80 lines into thei
asgerf
2015/11/17 12:43:42
Done. I'm glad to be rid of the pyramid of doom co
| |
| 143 // Find the depth of the outermost scope where we can bind the primitive | |
| 144 // without bringing a reference out of scope. -1 is the depth of the | |
| 145 // top-level scope. | |
| 146 int hoistDepth = -1; | |
| 147 List<Primitive> inputsHoistedOnDemand = <Primitive>[]; | |
| 148 ReferenceVisitor.forEachReference(prim, (Reference ref) { | |
|
sra1
2015/11/17 05:41:14
We should tweak the name a little.
On first readin
asgerf
2015/11/17 12:43:42
Changed to InputVisitor.forEach.
| |
| 149 Primitive input = ref.definition; | |
| 150 if (canIgnoreRefinementGuards(prim)) { | |
| 151 input = input.effectiveDefinition; | |
| 152 } | |
| 153 Continuation loopHeader; | |
| 154 if (potentialLoopHeaderFor.containsKey(input)) { | |
| 155 // This is a reference to a value that can be hoisted further out than | |
| 156 // it currently is. If we decide to hoist [prim], we must also hoist | |
| 157 // such dependent values. | |
| 158 loopHeader = potentialLoopHeaderFor[input]; | |
| 159 inputsHoistedOnDemand.add(input); | |
| 160 } else { | |
| 161 loopHeader = loopHeaderFor[input]; | |
| 162 } | |
| 163 Continuation referencedLoop = | |
| 164 loopHierarchy.lowestCommonAncestor(loopHeader, currentLoopHeader); | |
| 165 int depth = loopHierarchy.getDepth(referencedLoop); | |
| 166 if (depth > hoistDepth) { | |
| 167 hoistDepth = depth; | |
| 168 } | |
| 169 }); | |
| 170 if (hoistDepth != loopHierarchy.getDepth(currentLoopHeader)) { | |
| 171 // Walk up the loop hierarchy and check at every step that any heap | |
| 172 // dependencies can safely be hoisted out of the loop. | |
| 173 Continuation enclosingLoop = currentLoopHeader; | |
| 174 Continuation hoistTarget = null; | |
| 175 while (loopHierarchy.getDepth(enclosingLoop) > hoistDepth && | |
| 176 canHoistHeapDependencyOutOfLoop(prim, enclosingLoop)) { | |
| 177 hoistTarget = enclosingLoop; | |
| 178 enclosingLoop = loopHierarchy.getEnclosingLoop(enclosingLoop); | |
| 179 } | |
| 180 if (hoistTarget != null) { | |
| 181 if (isTrivialPrimitive(prim)) { | |
| 182 // The overhead from introducting a temporary might be greater than | |
| 183 // the overhead of evaluating this primitive at every iteration. | |
| 184 // Only hoist if this enables hoisting of a non-trivial primitive. | |
| 185 potentialLoopHeaderFor[prim] = enclosingLoop; | |
| 186 return next; | |
| 187 } else { | |
| 188 LetCont loopBinding = hoistTarget.parent; | |
| 189 | |
| 190 // The primitive may depend on values that have not yet been | |
| 191 // hoisted as far as they can. Hoist those now. | |
| 192 for (Primitive input in inputsHoistedOnDemand) { | |
| 193 hoistTrivialPrimitive(input, loopBinding, enclosingLoop); | |
| 194 } | |
| 195 | |
| 196 // Hoist the primitive. | |
| 197 node.remove(); | |
| 198 node.insertAbove(loopBinding); | |
| 199 loopHeaderFor[prim] = enclosingLoop; | |
| 200 | |
| 201 // If a refinement guard was bypassed, use the best refinement | |
| 202 // currently in scope. | |
| 203 if (canIgnoreRefinementGuards(prim)) { | |
| 204 int target = loopHierarchy.getDepth(enclosingLoop); | |
| 205 ReferenceVisitor.forEachReference(prim, (Reference ref) { | |
| 206 Primitive input = ref.definition; | |
| 207 while (input is Refinement) { | |
| 208 Continuation loop = loopHeaderFor[input]; | |
| 209 loop = loopHierarchy.lowestCommonAncestor(loop, enclosingLoop) ; | |
|
sra1
2015/11/17 05:41:14
line length
asgerf
2015/11/17 12:43:42
Done by extract method
| |
| 210 if (loopHierarchy.getDepth(loop) <= target) break; | |
| 211 Refinement refinement = input; | |
| 212 input = refinement.value.definition; | |
| 213 } | |
| 214 ref.changeTo(input); | |
| 215 }); | |
| 216 } | |
| 217 | |
| 218 // Put the primitive in the environment while processing the loop. | |
| 219 environment[gvn] = prim; | |
| 220 loopHoistedBindings | |
| 221 .putIfAbsent(hoistTarget, () => <int>[]) | |
| 222 .add(gvn); | |
| 223 return next; | |
| 224 } | |
| 225 } | |
| 226 } | |
| 227 } | |
| 228 | |
| 229 // The primitive could not be hoisted. Put the primitive in the | |
| 230 // environment while processing the body of the LetPrim. | |
| 231 environment[gvn] = prim; | |
| 232 pushAction(() { | |
| 233 assert(environment[gvn] == prim); | |
| 234 environment[gvn] = existing; | |
| 235 }); | |
| 236 | |
| 237 return next; | |
| 238 } | |
| 239 | |
| 240 /// If the given primitive is a trivial primitive that should be hoisted | |
| 241 /// on-demand, hoist it and its dependent values above [loopBinding]. | |
| 242 void hoistTrivialPrimitive(Primitive prim, | |
| 243 LetCont loopBinding, | |
| 244 Continuation enclosingLoop) { | |
| 245 if (!potentialLoopHeaderFor.containsKey(prim)) return; | |
| 246 assert(isTrivialPrimitive(prim)); | |
| 247 | |
| 248 // The primitive might already be bound in an outer scope. Do not reloate | |
|
sra1
2015/11/17 05:41:14
reloate
asgerf
2015/11/17 12:43:42
Done.
| |
| 249 // the primitive unless we are lifting it. | |
|
sra1
2015/11/17 05:41:14
Would this happen if the primitive is referenced a
asgerf
2015/11/17 12:43:42
Yes, this is exactly how it happens. I put your ex
| |
| 250 Continuation currentLoop = loopHeaderFor[prim]; | |
| 251 int currentDepth = loopHierarchy.getDepth(currentLoop); | |
| 252 int targetDepth = loopHierarchy.getDepth(enclosingLoop); | |
| 253 if (currentDepth <= targetDepth) return; | |
| 254 | |
| 255 // Hoist the trivial primitives being depended on so they remain in scope. | |
| 256 ReferenceVisitor.forEachReference(prim, (Reference ref) { | |
| 257 hoistTrivialPrimitive(ref.definition, loopBinding, enclosingLoop); | |
| 258 }); | |
| 259 | |
| 260 // Move the primitive. | |
| 261 LetPrim binding = prim.parent; | |
| 262 binding.remove(); | |
| 263 binding.insertAbove(loopBinding); | |
| 264 loopHeaderFor[prim] = enclosingLoop; | |
| 265 | |
| 266 if (potentialLoopHeaderFor[prim] == enclosingLoop) { | |
| 267 potentialLoopHeaderFor.remove(prim); | |
| 268 } | |
| 269 } | |
| 270 | |
| 271 bool canIgnoreRefinementGuards(Primitive primitive) { | |
| 272 return primitive is Interceptor; | |
| 273 } | |
| 274 | |
| 275 /// Returns true if the given primitive is so cheap at runtime that it is | |
| 276 /// better to (redundantly) recompute it rather than introduce a temporary. | |
| 277 bool isTrivialPrimitive(Primitive primitive) { | |
| 278 return primitive is ApplyBuiltinOperator || | |
| 279 primitive is Constant && isTrivialConstant(primitive.value); | |
| 280 } | |
| 281 | |
| 282 /// Returns true if the given constant has almost no runtime cost. | |
| 283 bool isTrivialConstant(ConstantValue value) { | |
| 284 return value.isPrimitive || value.isDummy; | |
| 285 } | |
| 286 | |
| 287 /// True if [element] is a final or constant field or a function. | |
| 288 bool isImmutable(Element element) { | |
| 289 if (element.isField && backend.isNative(element)) return false; | |
| 290 return element.isField && (element.isFinal || element.isConst) || | |
| 291 element.isFunction; | |
| 292 } | |
| 293 | |
| 294 /// Assuming [prim] has no side effects, returns true if it can safely | |
| 295 /// be hoisted out of [loop] without changing its value. | |
| 296 bool canHoistHeapDependencyOutOfLoop(Primitive prim, Continuation loop) { | |
| 297 assert(prim.isSafeForElimination); | |
| 298 if (prim is GetLength) { | |
| 299 return !loopEffects.loopChangesLength(loop); | |
| 300 } else if (prim is GetField && !isImmutable(prim.field)) { | |
| 301 return !loopEffects.getSideEffectsInLoop(loop).changesInstanceProperty(); | |
| 302 } else if (prim is GetStatic && !isImmutable(prim.element)) { | |
| 303 return !loopEffects.getSideEffectsInLoop(loop).changesStaticProperty(); | |
| 304 } else if (prim is GetIndex) { | |
| 305 return !loopEffects.getSideEffectsInLoop(loop).changesIndex(); | |
| 306 } else { | |
| 307 return true; | |
| 308 } | |
| 309 } | |
| 310 | |
| 311 | |
| 312 // ------------------ TRAVERSAL AND EFFECT NUMBERING --------------------- | |
| 313 // | |
| 314 // These methods traverse the IR while updating the current effect numbers. | |
| 315 // They are not specific to GVN. | |
| 316 // | |
| 317 // TODO(asgerf): Avoid duplicated code for side effect analysis. | |
| 318 // Should be easier to fix once primitives and call expressions are the same. | |
| 319 | |
| 320 void addSideEffects(SideEffects fx, {bool length: true}) { | |
| 321 if (fx.changesInstanceProperty()) { | |
| 322 effectNumbers.instanceField = makeNewEffect(); | |
| 323 } | |
| 324 if (fx.changesStaticProperty()) { | |
| 325 effectNumbers.staticField = makeNewEffect(); | |
| 326 } | |
| 327 if (fx.changesIndex()) { | |
| 328 effectNumbers.indexableContent = makeNewEffect(); | |
| 329 } | |
| 330 if (length && fx.changesIndex()) { | |
| 331 effectNumbers.indexableLength = makeNewEffect(); | |
| 332 } | |
| 333 } | |
| 334 | |
| 335 void addAllSideEffects() { | |
| 336 effectNumbers.instanceField = makeNewEffect(); | |
| 337 effectNumbers.staticField = makeNewEffect(); | |
| 338 effectNumbers.indexableContent = makeNewEffect(); | |
| 339 effectNumbers.indexableLength = makeNewEffect(); | |
| 340 } | |
| 341 | |
| 342 Expression traverseLetHandler(LetHandler node) { | |
| 343 // Assume any kind of side effects may occur in the try block. | |
| 344 effectsAt[node.handler] = new EffectNumbers() | |
| 345 ..instanceField = makeNewEffect() | |
| 346 ..staticField = makeNewEffect() | |
| 347 ..indexableContent = makeNewEffect() | |
| 348 ..indexableLength = makeNewEffect(); | |
| 349 push(node.handler); | |
| 350 return node.body; | |
| 351 } | |
| 352 | |
| 353 Expression traverseContinuation(Continuation cont) { | |
| 354 Continuation oldLoopHeader = currentLoopHeader; | |
| 355 currentLoopHeader = loopHierarchy.getLoopHeader(cont); | |
| 356 pushAction(() { | |
| 357 currentLoopHeader = oldLoopHeader; | |
| 358 }); | |
| 359 for (Parameter param in cont.parameters) { | |
| 360 loopHeaderFor[param] = currentLoopHeader; | |
| 361 } | |
| 362 if (cont.isRecursive) { | |
| 363 addSideEffects(loopEffects.getSideEffectsInLoop(cont), length: false); | |
| 364 if (loopEffects.loopChangesLength(cont)) { | |
| 365 effectNumbers.indexableLength = makeNewEffect(); | |
| 366 } | |
| 367 pushAction(() { | |
| 368 List<int> hoistedBindings = loopHoistedBindings[cont]; | |
| 369 if (hoistedBindings != null) { | |
| 370 hoistedBindings.forEach(environment.remove); | |
| 371 } | |
| 372 }); | |
| 373 } else { | |
| 374 EffectNumbers join = effectsAt[cont]; | |
| 375 if (join != null) { | |
| 376 effectNumbers = join; | |
| 377 } else { | |
| 378 // This is a call continuation seen immediately after its use. | |
| 379 // Reuse the current effect numbers. | |
| 380 } | |
| 381 } | |
| 382 | |
| 383 return cont.body; | |
| 384 } | |
| 385 | |
| 386 void visitInvokeContinuation(InvokeContinuation node) { | |
| 387 Continuation cont = node.continuation.definition; | |
| 388 if (cont.isRecursive) return; | |
| 389 EffectNumbers join = effectsAt[cont]; | |
| 390 if (join == null) { | |
| 391 effectsAt[cont] = effectNumbers.copy(); | |
| 392 } else { | |
| 393 if (effectNumbers.instanceField != join.instanceField) { | |
| 394 join.instanceField = makeNewEffect(); | |
| 395 } | |
| 396 if (effectNumbers.staticField != join.staticField) { | |
| 397 join.staticField = makeNewEffect(); | |
| 398 } | |
| 399 if (effectNumbers.indexableContent != join.indexableContent) { | |
| 400 join.indexableContent = makeNewEffect(); | |
| 401 } | |
| 402 if (effectNumbers.indexableLength != join.indexableLength) { | |
| 403 join.indexableLength = makeNewEffect(); | |
| 404 } | |
| 405 } | |
| 406 } | |
| 407 | |
| 408 void visitBranch(Branch node) { | |
| 409 Continuation trueCont = node.trueContinuation.definition; | |
| 410 Continuation falseCont = node.falseContinuation.definition; | |
| 411 // Copy the effect number vector once, so the analysis of one branch does | |
| 412 // not influence the other. | |
| 413 effectsAt[trueCont] = effectNumbers; | |
| 414 effectsAt[falseCont] = effectNumbers.copy(); | |
| 415 } | |
| 416 | |
| 417 void visitInvokeMethod(InvokeMethod node) { | |
| 418 addSideEffects(world.getSideEffectsOfSelector(node.selector, node.mask)); | |
| 419 } | |
| 420 | |
| 421 void visitInvokeStatic(InvokeStatic node) { | |
| 422 addSideEffects(world.getSideEffectsOfElement(node.target)); | |
| 423 } | |
| 424 | |
| 425 void visitInvokeMethodDirectly(InvokeMethodDirectly node) { | |
| 426 FunctionElement target = node.target; | |
| 427 if (target is ConstructorBodyElement) { | |
| 428 ConstructorBodyElement body = target; | |
| 429 target = body.constructor; | |
|
sra1
2015/11/17 05:41:14
should be able to just do
target = target.cons
asgerf
2015/11/17 12:43:42
Type promotion bails out if the block contains an
| |
| 430 } | |
| 431 addSideEffects(world.getSideEffectsOfElement(target)); | |
| 432 } | |
| 433 | |
| 434 void visitInvokeConstructor(InvokeConstructor node) { | |
| 435 addSideEffects(world.getSideEffectsOfElement(node.target)); | |
| 436 } | |
| 437 | |
| 438 void visitSetStatic(SetStatic node) { | |
| 439 effectNumbers.staticField = makeNewEffect(); | |
| 440 } | |
| 441 | |
| 442 void visitSetField(SetField node) { | |
| 443 effectNumbers.instanceField = makeNewEffect(); | |
| 444 } | |
| 445 | |
| 446 void visitSetIndex(SetIndex node) { | |
| 447 effectNumbers.indexableContent = makeNewEffect(); | |
| 448 } | |
| 449 | |
| 450 void visitForeignCode(ForeignCode node) { | |
| 451 addSideEffects(node.nativeBehavior.sideEffects); | |
| 452 } | |
| 453 | |
| 454 void visitGetLazyStatic(GetLazyStatic node) { | |
| 455 // TODO(asgerf): How do we get the side effects of a lazy field initializer? | |
| 456 addAllSideEffects(); | |
| 457 } | |
| 458 | |
| 459 void visitAwait(Await node) { | |
| 460 addAllSideEffects(); | |
| 461 } | |
| 462 | |
| 463 void visitYield(Yield node) { | |
| 464 addAllSideEffects(); | |
| 465 } | |
| 466 | |
| 467 void visitApplyBuiltinMethod(ApplyBuiltinMethod node) { | |
| 468 // Push and pop. | |
| 469 effectNumbers.indexableContent = makeNewEffect(); | |
| 470 effectNumbers.indexableLength = makeNewEffect(); | |
| 471 } | |
| 472 } | |
| 473 | |
| 474 /// For each of the four categories of heap locations, the IR is divided into | |
| 475 /// regions wherein the given heap locations are known not to be modified. | |
| 476 /// | |
| 477 /// Each region is identified by its "effect number". Effect numbers from | |
| 478 /// different categories have no relationship to each other. | |
| 479 class EffectNumbers { | |
| 480 int indexableLength = 0; | |
| 481 int indexableContent = 0; | |
| 482 int staticField = 0; | |
| 483 int instanceField = 0; | |
| 484 | |
| 485 EffectNumbers copy() { | |
| 486 return new EffectNumbers() | |
| 487 ..indexableLength = indexableLength | |
| 488 ..indexableContent = indexableContent | |
| 489 ..staticField = staticField | |
| 490 ..instanceField = instanceField; | |
| 491 } | |
| 492 } | |
| 493 | |
| 494 /// Maps vectors to numbers, such that two vectors with the same contents | |
| 495 /// map to the same number. | |
| 496 class GvnTable { | |
| 497 Map<GvnEntry, int> _table = <GvnEntry, int>{}; | |
| 498 int _usedGvns = 0; | |
| 499 int _makeNewGvn() => ++_usedGvns; | |
| 500 | |
| 501 int insert(List vector) { | |
| 502 return _table.putIfAbsent(new GvnEntry(vector), _makeNewGvn); | |
| 503 } | |
| 504 } | |
| 505 | |
| 506 /// Wrapper around a [List] that compares for equality based on contents | |
| 507 /// instead of object identity. | |
| 508 class GvnEntry { | |
| 509 final List vector; | |
| 510 final int hashCode; | |
| 511 | |
| 512 GvnEntry(List vector) : vector = vector, hashCode = computeHashCode(vector); | |
| 513 | |
| 514 bool operator==(other) { | |
| 515 if (other is! GvnEntry) return false; | |
| 516 GvnEntry entry = other; | |
| 517 List otherVector = entry.vector; | |
| 518 if (vector.length != otherVector.length) return false; | |
| 519 for (int i = 0; i < vector.length; ++i) { | |
| 520 if (vector[i] != otherVector[i]) return false; | |
| 521 } | |
| 522 return true; | |
| 523 } | |
| 524 | |
| 525 /// Combines the hash codes of [vector] using Jenkin's hash function. | |
|
sra1
2015/11/17 05:41:14
At least say 'modified' Jenkin's, since the Smi ma
asgerf
2015/11/17 12:43:42
Done.
| |
| 526 static int computeHashCode(List vector) { | |
| 527 int hash = 0; | |
| 528 for (int i = 0; i < vector.length; ++i) { | |
| 529 hash = 0x1fffffff & (hash + vector[i].hashCode); | |
|
sra1
2015/11/17 05:41:14
Do all the things we put in the vector have fast h
asgerf
2015/11/17 12:43:42
There are: Element, DartType, Primitive, BuiltinOp
| |
| 530 hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); | |
| 531 hash = hash ^ (hash >> 6); | |
| 532 } | |
| 533 hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); | |
| 534 hash = hash ^ (hash >> 11); | |
| 535 return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); | |
| 536 } | |
| 537 } | |
| 538 | |
| 539 /// Converts GVN'able primitives to a vector containing all the values | |
| 540 /// to be considered when computing a GVN for it. | |
| 541 /// | |
| 542 /// This includes the instruction type, inputs, effect numbers for any part | |
| 543 /// of the heap being depended on, as well as any instruction-specific payload | |
| 544 /// such as any DartTypes, Elements, and operator kinds. | |
| 545 /// | |
| 546 /// Each `visit` or `process` method for a primitive must initialize [vector] | |
| 547 /// if the primitive is GVN'able and fill in any components except the inputs. | |
| 548 /// The inputs will be filled in by [processReference]. | |
| 549 class GvnVectorBuilder extends DeepRecursiveVisitor { | |
| 550 List vector; | |
| 551 final Map<Primitive, int> gvnFor; | |
| 552 final JavaScriptBackend backend; | |
| 553 EffectNumbers effectNumbers; | |
| 554 | |
| 555 GvnVectorBuilder(this.gvnFor, this.backend); | |
| 556 | |
| 557 List make(Primitive prim, EffectNumbers effectNumbers) { | |
| 558 this.effectNumbers = effectNumbers; | |
| 559 vector = null; | |
| 560 visit(prim); | |
| 561 return vector; | |
| 562 } | |
| 563 | |
| 564 /// The `process` methods below do not insert the referenced arguments into | |
| 565 /// the vector, but instead rely on them being inserted here. | |
| 566 processReference(Reference ref) { | |
| 567 if (vector == null) return; | |
| 568 Primitive prim = ref.definition.effectiveDefinition; | |
| 569 vector.add(gvnFor[prim] ?? prim); | |
| 570 } | |
| 571 | |
| 572 visitTypeTest(TypeTest node) { | |
| 573 vector = [GvnCode.TYPE_TEST, node.dartType]; | |
| 574 processReference(node.value); | |
| 575 node.typeArguments.forEach(processReference); | |
| 576 // Suppress processing of the interceptor argument. | |
| 577 } | |
| 578 | |
| 579 processTypeTestViaFlag(TypeTestViaFlag node) { | |
| 580 vector = [GvnCode.TYPE_TEST_VIA_FLAG, node.dartType]; | |
| 581 } | |
| 582 | |
| 583 processApplyBuiltinOperator(ApplyBuiltinOperator node) { | |
| 584 vector = [GvnCode.BUILTIN_OPERATOR, node.operator]; | |
| 585 } | |
| 586 | |
| 587 processGetLength(GetLength node) { | |
| 588 // TODO(asgerf): Take fixed lengths into account? | |
|
sra1
2015/11/17 05:41:14
Yes. For typed arrays V8 should know how to connec
asgerf
2015/11/17 12:43:42
Acknowledged.
| |
| 589 vector = [GvnCode.GET_LENGTH, effectNumbers.indexableLength]; | |
| 590 } | |
| 591 | |
| 592 bool isImmutable(Element element) { | |
| 593 return element.isFunction || | |
| 594 element.isField && (element.isFinal || element.isConst); | |
| 595 } | |
| 596 | |
| 597 bool isNativeField(FieldElement field) { | |
| 598 // TODO(asgerf): We should add a GetNativeField instruction. | |
|
sra1
2015/11/17 05:41:14
Maybe called GetProperty
asgerf
2015/11/17 12:43:42
Acknowledged.
| |
| 599 return backend.isNative(field); | |
| 600 } | |
| 601 | |
| 602 processGetField(GetField node) { | |
| 603 if (isNativeField(node.field)) { | |
| 604 vector = null; // Native field access cannot be GVN'ed. | |
| 605 } else if (isImmutable(node.field)) { | |
| 606 vector = [GvnCode.GET_FIELD, node.field]; | |
| 607 } else { | |
| 608 vector = [GvnCode.GET_FIELD, node.field, effectNumbers.instanceField]; | |
| 609 } | |
| 610 } | |
| 611 | |
| 612 processGetIndex(GetIndex node) { | |
| 613 vector = [GvnCode.GET_INDEX, effectNumbers.indexableContent]; | |
| 614 } | |
| 615 | |
| 616 processGetStatic(GetStatic node) { | |
| 617 if (isImmutable(node.element)) { | |
| 618 vector = [GvnCode.GET_STATIC, node.element]; | |
| 619 } else { | |
| 620 vector = [GvnCode.GET_STATIC, node.element, effectNumbers.staticField]; | |
| 621 } | |
| 622 } | |
| 623 | |
| 624 processConstant(Constant node) { | |
| 625 vector = [GvnCode.CONSTANT, node.value]; | |
| 626 } | |
| 627 | |
| 628 processReifyRuntimeType(ReifyRuntimeType node) { | |
| 629 vector = [GvnCode.REIFY_RUNTIME_TYPE]; | |
| 630 } | |
| 631 | |
| 632 processReadTypeVariable(ReadTypeVariable node) { | |
| 633 vector = [GvnCode.READ_TYPE_VARIABLE, node.variable]; | |
| 634 } | |
| 635 | |
| 636 processTypeExpression(TypeExpression node) { | |
| 637 vector = [GvnCode.TYPE_EXPRESSION, node.dartType]; | |
| 638 } | |
| 639 | |
| 640 processInterceptor(Interceptor node) { | |
| 641 vector = [GvnCode.INTERCEPTOR]; | |
| 642 } | |
| 643 } | |
| 644 | |
| 645 class GvnCode { | |
| 646 static const int TYPE_TEST = 1; | |
| 647 static const int TYPE_TEST_VIA_FLAG = 2; | |
| 648 static const int BUILTIN_OPERATOR = 3; | |
| 649 static const int GET_LENGTH = 4; | |
| 650 static const int GET_FIELD = 5; | |
| 651 static const int GET_INDEX = 6; | |
| 652 static const int GET_STATIC = 7; | |
| 653 static const int CONSTANT = 8; | |
| 654 static const int REIFY_RUNTIME_TYPE = 9; | |
| 655 static const int READ_TYPE_VARIABLE = 10; | |
| 656 static const int TYPE_EXPRESSION = 11; | |
| 657 static const int INTERCEPTOR = 12; | |
| 658 } | |
| 659 | |
| 660 typedef ReferenceCallback(Reference ref); | |
| 661 class ReferenceVisitor extends DeepRecursiveVisitor { | |
|
sra1
2015/11/17 05:41:13
Maybe we should split DeepRecursiveVisitor into th
asgerf
2015/11/17 12:43:42
The visitors need an overhaul, but I'd rather wait
| |
| 662 ReferenceCallback callback; | |
| 663 | |
| 664 ReferenceVisitor(this.callback); | |
| 665 | |
| 666 @override | |
| 667 processReference(Reference ref) { | |
| 668 callback(ref); | |
| 669 } | |
| 670 | |
| 671 static void forEachReference(Node node, ReferenceCallback callback) { | |
|
sra1
2015/11/17 05:41:13
This is a pretty wild visitor pattern if `node` is
asgerf
2015/11/17 12:43:42
Done.
| |
| 672 new ReferenceVisitor(callback).visit(node); | |
| 673 } | |
| 674 } | |
| OLD | NEW |