three ideas around inlining: further devirt; callee-specific-return-types; and how TypeSwitchNode deals with "umbrella" final-methods

Garcia Gutierrez Miguel Alfredo miguelalfredo.garcia at epfl.ch
Sun Jan 26 05:09:38 PST 2014


After exploring InliningPhase some ideas come to mind, it would be great if someone in the know could comment on them (those comments would be of help to those becoming familiar with Graal)

Summary:

  (1) some further devirtualization could be done in MethodCallTargetNode::canonical(), as shown below.


  (2) after further exploring, I realized that ConditionalEliminationPhase performs that additional devirtualization (ie it's not necessary for the receiver type to be exact, as long as the resolved target method is final; if the receiver's type had been pinned as final then it also would have been exact to start with). ConditionalEliminationPhase does a great job here, because it tracks types in a flow-sensitive manner.


  (3) however there appears to be one place where the devirtualization in question is being missed, after making parameters more precise (as a result of inlining). The snippet shown below, in getInlineInfo()

      (3.a) does detect target methods for exact-type receivers,
      (3.b) but forgets to check for non-exact-type receivers whether a final final method is resolved;

Anyway, at least for (3.a) an ExactInlineInfo for that callsite is returned, however without actually re-wiring the associated MethodCallTargetNode's targetMethod and invokeKind. It remains to-be-done, but an ExactInlineInfo's tryToDevirtualize() is a no-op (it assumes the InvokeSpecial is already there) thus leaving that job again for ConditionalEliminationPhase. Wouldn't it be simpler to just do it on the spot? The MethodCallTargetNode instance is a copy, mutating it shouldn't mess with anyone.


  (4) unlike the items above, there's no code for this one, because it's just an idea: An ExactInlineInfo may end up not being inlined. Given that its getCachedGraph() was just accessed, would it be possible to find out "quickly", based on that graph, what the actual return type is? (which may well be more specific than the declared return type, as usual for a target method overriding the one the callsite states). With that information, a safe downcast can be inserted for the return value, that hopefully propagates in the caller's graph. This optimization could be added to ConditionalEliminationPhase, but the allure of having the getCachedGraph() already in the cache (ha-ha-ha) makes InliningPhase more appealing. The stamp of such "callee-specific return type" could well narrow things even more (including saying something about primitives being returned)

  (5) Another "brainstorm-quality" idea: Detecting final-targets could prove useful in connection with TypeSwitchNode. Frameworks exist where final methods are sprinkled at some subclasses which themselves are extended by yet more classes. From a TypeSwitchNode perspective, lots of type-hub comparisons are made all mapping to the same final-target. In those cases, one or more instanceof could filter those "umbrella" cases, letting the TypeSwitchNode focus on a smaller set of concrete subtypes.


Agree? Disagree? What do you think?


Miguel



Exhibit for item (1) above: MethodCallTargetNode::canonical(), modified by yours truly
======================================================================================

    @Override
    public Node canonical(CanonicalizerTool tool) {
        if (invokeKind == InvokeKind.Interface || invokeKind == InvokeKind.Virtual) {
            ValueNode receiver = receiver();
            if (receiver != null) {
                ResolvedJavaType receiverType = ObjectStamp.typeOrNull(receiver);
                if (receiverType != null) {
                    ResolvedJavaMethod method = null;
                    boolean isExactType = ObjectStamp.isExactType(receiver);
                    if (isExactType || receiverType.isInstanceClass()) {
                        method = receiverType.resolveMethod(targetMethod);
                        if (method != null && !isExactType && !method.canBeStaticallyBound()) {
                            method = null;
                        }
                    }
                    if (method != null) {
                        invokeKind = InvokeKind.Special;
                        targetMethod = method;
                    }
                }
            }
        }
        return this;
    }

Exhibit for item (2) above: ConditionalEliminationPhase devirt
==============================================================

            } else if (node instanceof Invoke) {
                Invoke invoke = (Invoke) node;
                if (invoke.callTarget() instanceof MethodCallTargetNode) {
                    MethodCallTargetNode callTarget = (MethodCallTargetNode) invoke.callTarget();
                    ValueNode receiver = callTarget.receiver();
                    if (receiver != null && (callTarget.invokeKind() == InvokeKind.Interface || callTarget.invokeKind() == InvokeKind.Virtual)) {
                        ResolvedJavaType type = state.getNodeType(receiver);
                        if (type != ObjectStamp.typeOrNull(receiver)) {
                            ResolvedJavaMethod method = type.resolveMethod(callTarget.targetMethod());
                            if (method != null) {
                                if ((method.getModifiers() & Modifier.FINAL) != 0 || (type.getModifiers() & Modifier.FINAL) != 0) {
                                    callTarget.setInvokeKind(InvokeKind.Special);
                                    callTarget.setTargetMethod(method);
                                }
                            }
                        }
                    }
                }

            }

Exhibit for item (3) above: part of getInlineInfo()
===================================================

        if (receiverStamp.type() != null) {
            // the invoke target might be more specific than the holder (happens after inlining:
            // parameters lose their declared type...)
            ResolvedJavaType receiverType = receiverStamp.type();
            if (receiverType != null && holder.isAssignableFrom(receiverType)) {
                holder = receiverType;
                if (receiverStamp.isExactType()) {
                    assert targetMethod.getDeclaringClass().isAssignableFrom(holder) : holder + " subtype of " + targetMethod.getDeclaringClass() + " for " + targetMethod;
                    ResolvedJavaMethod resolvedMethod = holder.resolveMethod(targetMethod);
                    if (resolvedMethod != null) {
                        return getExactInlineInfo(data, invoke, replacements, optimisticOpts, resolvedMethod);
                    }
                }
            }
        }





--
Miguel Garcia
Swiss Federal Institute of Technology
EPFL - IC - LAMP1 - INR 328 - Station 14
CH-1015 Lausanne - Switzerland
http://lamp.epfl.ch/~magarcia/


More information about the graal-dev mailing list