Linter Demo Errors: 0Warnings: 6File: /home/fstrocco/Dart/dart/benchmark/compiler/lib/src/ssa/interceptor_simplifier.dart // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. part of ssa; /** * This phase simplifies interceptors in multiple ways: * * 1) If the interceptor is for an object whose type is known, it * tries to use a constant interceptor instead. * * 2) Interceptors are specialized based on the selector it is used with. * * 3) If we know the object is not intercepted, we just use the object * instead. * * 4) Single use interceptors at dynamic invoke sites are replaced with 'one * shot interceptors' which are synthesized static helper functions that fetch * the interceptor and then call the method. This saves code size and makes the * receiver of an intercepted call a candidate for being generated at use site. * * 5) Some HIs operations on an interceptor are replaced with a HIs version that * uses 'instanceof' rather than testing a type flag. * */ class SsaSimplifyInterceptors extends HBaseVisitor implements OptimizationPhase { final String name = "SsaSimplifyInterceptors"; final ConstantSystem constantSystem; final Compiler compiler; final CodegenWorkItem work; HGraph graph; SsaSimplifyInterceptors(this.compiler, this.constantSystem, this.work); void visitGraph(HGraph graph) { this.graph = graph; visitDominatorTree(graph); } void visitBasicBlock(HBasicBlock node) { currentBlock = node; HInstruction instruction = node.first; while (instruction != null) { bool shouldRemove = instruction.accept(this); HInstruction next = instruction.next; if (shouldRemove) { instruction.block.remove(instruction); } instruction = next; } } bool visitInstruction(HInstruction instruction) => false; bool visitInvoke(HInvoke invoke) { if (!invoke.isInterceptedCall) return false; var interceptor = invoke.inputs[0]; if (interceptor is! HInterceptor) return false; // TODO(sra): Move this per-call code to visitInterceptor. // // The interceptor is visited first, so we get here only when the // interceptor was not rewritten to a single shared replacement. I'm not // sure we should substitute a constant interceptor on a per-call basis if // the interceptor is already available in a local variable, but it is // possible that all uses can be rewritten to use different constants. // TODO(sra): Also do self-interceptor rewrites on a per-use basis. HInstruction constant = tryComputeConstantInterceptor( invoke.inputs[1], interceptor.interceptedClasses); if (constant != null) { invoke.changeUse(interceptor, constant); } return false; } bool canUseSelfForInterceptor(HInstruction receiver, Set interceptedClasses) { JavaScriptBackend backend = compiler.backend; ClassWorld classWorld = compiler.world; if (receiver.canBePrimitive(compiler)) { // Primitives always need interceptors. return false; } if (receiver.canBeNull() && interceptedClasses.contains(backend.jsNullClass)) { // Need the JSNull interceptor. return false; } // All intercepted classes extend `Interceptor`, so if the receiver can't be // a class extending `Interceptor` then it can be called directly. return new TypeMask.nonNullSubclass(backend.jsInterceptorClass, classWorld) .intersection(receiver.instructionType, classWorld) .isEmpty; } HInstruction tryComputeConstantInterceptor( HInstruction input, Set interceptedClasses) { if (input == graph.explicitReceiverParameter) { // If `explicitReceiverParameter` is set it means the current method is an // interceptor method, and `this` is the interceptor. The caller just did // `getInterceptor(foo).currentMethod(foo)` to enter the current method. return graph.thisInstruction; } ClassElement constantInterceptor = tryComputeConstantInterceptorFromType( input.instructionType, interceptedClasses); if (constantInterceptor == null) return null; // If we just happen to be in an instance method of the constant // interceptor, `this` is a shorter alias. if (constantInterceptor == work.element.enclosingClass && graph.thisInstruction != null) { return graph.thisInstruction; } ConstantValue constant = new InterceptorConstantValue(constantInterceptor.thisType); return graph.addConstant(constant, compiler); } ClassElement tryComputeConstantInterceptorFromType( TypeMask type, Set interceptedClasses) { ClassWorld classWorld = compiler.world; JavaScriptBackend backend = compiler.backend; if (type.isNullable) { if (type.isEmpty) { return backend.jsNullClass; } } else if (type.containsOnlyInt(classWorld)) { return backend.jsIntClass; } else if (type.containsOnlyDouble(classWorld)) { return backend.jsDoubleClass; } else if (type.containsOnlyBool(classWorld)) { return backend.jsBoolClass; } else if (type.containsOnlyString(classWorld)) { return backend.jsStringClass; } else if (type.satisfies(backend.jsArrayClass, classWorld)) { return backend.jsArrayClass; } else if (type.containsOnlyNum(classWorld) && !interceptedClasses.contains(backend.jsIntClass) && !interceptedClasses.contains(backend.jsDoubleClass)) { // If the method being intercepted is not defined in [int] or [double] we // can safely use the number interceptor. This is because none of the // [int] or [double] methods are called from a method defined on [num]. return backend.jsNumberClass; } else { // Try to find constant interceptor for a native class. If the receiver // is constrained to a leaf native class, we can use the class's // interceptor directly. // TODO(sra): Key DOM classes like Node, Element and Event are not leaf // classes. When the receiver type is not a leaf class, we might still be // able to use the receiver class as a constant interceptor. It is // usually the case that methods defined on a non-leaf class don't test // for a subclass or call methods defined on a subclass. Provided the // code is completely insensitive to the specific instance subclasses, we // can use the non-leaf class directly. ClassElement element = type.singleClass(classWorld); if (element != null && element.isNative) { return element; } } return null; } HInstruction findDominator(Iterable instructions) { HInstruction result; L1: for (HInstruction candidate in instructions) { for (HInstruction current in instructions) { if (current != candidate && !candidate.dominates(current)) continue L1; } result = candidate; break; } return result; } bool visitInterceptor(HInterceptor node) { if (node.isConstant()) return false; // Specialize the interceptor with set of classes it intercepts, considering // all uses. (The specialized interceptor has a shorter dispatch chain). // This operation applies only where the interceptor is used to dispatch a // method. Other uses, e.g. as an ordinary argument or a HIs check use the // most general interceptor. // // TODO(sra): Take into account the receiver type at each call. e.g: // // (a) => a.length + a.hashCode // // Currently we use the most general interceptor since all intercepted types // implement `hashCode`. But in this example, `a.hashCode` is only reached // if `a.length` succeeds, which is indicated by the hashCode receiver being // a HTypeKnown instruction. int useCount(HInstruction user, HInstruction used) => user.inputs.where((input) => input == used).length; Set interceptedClasses; JavaScriptBackend backend = compiler.backend; HInstruction dominator = findDominator(node.usedBy); // If there is a call that dominates all other uses, we can use just the // selector of that instruction. if (dominator is HInvokeDynamic && dominator.isCallOnInterceptor(compiler) && node == dominator.receiver && useCount(dominator, node) == 1) { interceptedClasses = backend.getInterceptedClassesOn(dominator.selector.name); // If we found that we need number, we must still go through all // uses to check if they require int, or double. if (interceptedClasses.contains(backend.jsNumberClass) && !(interceptedClasses.contains(backend.jsDoubleClass) || interceptedClasses.contains(backend.jsIntClass))) { for (HInstruction user in node.usedBy) { if (user is! HInvoke) continue; Set intercepted = backend.getInterceptedClassesOn(user.selector.name); if (intercepted.contains(backend.jsIntClass)) { interceptedClasses.add(backend.jsIntClass); } if (intercepted.contains(backend.jsDoubleClass)) { interceptedClasses.add(backend.jsDoubleClass); } } } } else { interceptedClasses = new Set(); for (HInstruction user in node.usedBy) { if (user is HInvokeDynamic && user.isCallOnInterceptor(compiler) && node == user.receiver && useCount(user, node) == 1) { interceptedClasses.addAll( backend.getInterceptedClassesOn(user.selector.name)); } else if (user is HInvokeSuper && user.isCallOnInterceptor(compiler) && node == user.receiver && useCount(user, node) == 1) { interceptedClasses.addAll( backend.getInterceptedClassesOn(user.selector.name)); } else { // Use a most general interceptor for other instructions, example, // is-checks and escaping interceptors. interceptedClasses.addAll(backend.interceptedClasses); break; } } } node.interceptedClasses = interceptedClasses; HInstruction receiver = node.receiver; // TODO(sra): We should consider each use individually and then all uses // together. Each use might permit a different rewrite due to a refined // receiver type. Self-interceptor rewrites are always beneficial since the // receiver is live at a invocation. Constant-interceptor rewrites are only // guaranteed to be beneficial if they can eliminate the need for the // interceptor or reduce the uses to one that can be simplified with a // one-shot interceptor or optimized is-check. if (canUseSelfForInterceptor(receiver, interceptedClasses)) { return rewriteToUseSelfAsInterceptor(node, receiver); } // Try computing a constant interceptor. HInstruction constantInterceptor = tryComputeConstantInterceptor(receiver, interceptedClasses); if (constantInterceptor != null) { node.block.rewrite(node, constantInterceptor); return false; } // Do we have an 'almost constant' interceptor? The receiver could be // `null` but not any other JavaScript falsy value, `null` values cause // `NoSuchMethodError`s, and if the receiver was not null we would have a // constant interceptor `C`. Then we can use `(receiver && C)` for the // interceptor. if (receiver.canBeNull() && !node.isConditionalConstantInterceptor) { if (!interceptedClasses.contains(backend.jsNullClass)) { // Can use `(receiver && C)` only if receiver is either null or truthy. if (!(receiver.canBePrimitiveNumber(compiler) || receiver.canBePrimitiveBoolean(compiler) || receiver.canBePrimitiveString(compiler))) { ClassElement interceptorClass = tryComputeConstantInterceptorFromType( receiver.instructionType.nonNullable(), interceptedClasses); if (interceptorClass != null) { HInstruction constantInstruction = graph.addConstant( new InterceptorConstantValue(interceptorClass.thisType), compiler); node.conditionalConstantInterceptor = constantInstruction; constantInstruction.usedBy.add(node); return false; } } } } // Try creating a one-shot interceptor or optimized is-check if (compiler.hasIncrementalSupport) return false; if (node.usedBy.length != 1) return false; HInstruction user = node.usedBy.single; // If the interceptor [node] was loop hoisted, we keep the interceptor. if (!user.hasSameLoopHeaderAs(node)) return false; bool replaceUserWith(HInstruction replacement) { HBasicBlock block = user.block; block.addAfter(user, replacement); block.rewrite(user, replacement); block.remove(user); return false; } if (user is HIs) { // See if we can rewrite the is-check to use 'instanceof', i.e. rewrite // "getInterceptor(x).$isT" to "x instanceof T". if (node == user.interceptor) { JavaScriptBackend backend = compiler.backend; if (backend.mayGenerateInstanceofCheck(user.typeExpression)) { HInstruction instanceofCheck = new HIs.instanceOf( user.typeExpression, user.expression, user.instructionType); instanceofCheck.sourceInformation = user.sourceInformation; instanceofCheck.sourceElement = user.sourceElement; return replaceUserWith(instanceofCheck); } } } else if (user is HInvokeDynamic) { if (node == user.inputs[0]) { // Replace the user with a [HOneShotInterceptor]. HConstant nullConstant = graph.addConstantNull(compiler); List inputs = new List.from(user.inputs); inputs[0] = nullConstant; HOneShotInterceptor oneShotInterceptor = new HOneShotInterceptor( user.selector, inputs, user.instructionType, interceptedClasses); oneShotInterceptor.sourceInformation = user.sourceInformation; oneShotInterceptor.sourceElement = user.sourceElement; return replaceUserWith(oneShotInterceptor); } } return false; } bool rewriteToUseSelfAsInterceptor(HInterceptor node, HInstruction receiver) { for (HInstruction user in node.usedBy.toList()) { if (user is HIs) { user.changeUse(node, receiver); } else { // Use the potentially self-argument as new receiver. Note that the // self-argument could potentially have a tighter type than the // receiver which was the input to the interceptor. assert(user.inputs[0] == node); assert(receiver.nonCheck() == user.inputs[1].nonCheck()); user.changeUse(node, user.inputs[1]); } } return false; } bool visitOneShotInterceptor(HOneShotInterceptor node) { HInstruction constant = tryComputeConstantInterceptor( node.inputs[1], node.interceptedClasses); if (constant == null) return false; Selector selector = node.selector; HInstruction instruction; if (selector.isGetter) { instruction = new HInvokeDynamicGetter( selector, node.element, [constant, node.inputs[1]], node.instructionType); } else if (selector.isSetter) { instruction = new HInvokeDynamicSetter( selector, node.element, [constant, node.inputs[1], node.inputs[2]], node.instructionType); } else { List inputs = new List.from(node.inputs); inputs[0] = constant; instruction = new HInvokeDynamicMethod( selector, inputs, node.instructionType, true); } HBasicBlock block = node.block; block.addAfter(node, instruction); block.rewrite(node, instruction); return true; } }