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