/** * Similar to {@link #deverbosifyInputsInPlace(com.oracle.graal.nodes.ValueNode)}, except that not * the parent but a fresh clone is updated upon any of its children changing. * * @return the original parent if no updated took place, a copy-on-write version of it otherwise. */ private MethodCallTargetNode deverbosifyInputsCopyOnWrite(MethodCallTargetNode parent) { final CallTargetNode.InvokeKind ik = parent.invokeKind(); final boolean shouldTryDevirt = (ik == CallTargetNode.InvokeKind.Interface || ik == CallTargetNode.InvokeKind.Virtual); boolean shouldDowncastReceiver = shouldTryDevirt; MethodCallTargetNode changed = null; for (ValueNode i : FlowUtil.distinctValueAndConditionInputs(parent)) { ValueNode j = (ValueNode) reasoner.deverbosify(i); if (shouldDowncastReceiver) { shouldDowncastReceiver = false; j = reasoner.downcast(j); } if (i != j) { assert j != parent; if (changed == null) { changed = (MethodCallTargetNode) parent.copyWithInputs(); reasoner.added.add(changed); // copyWithInputs() implies graph.unique(changed) assert changed.isAlive(); assert FlowUtil.lacksUsages(changed); } FlowUtil.replaceInPlace(changed, i, j); } } if (changed == null) { return parent; } FlowUtil.inferStampAndCheck(changed); /* * No need to rememberSubstitution() because not called from deverbosify(). In detail, it's * only deverbosify() that skips visited nodes (thus we'd better have recorded any * substitutions we want for them). Not this case. */ return changed; }
/** * Reduce input nodes based on the state at the program point for the argument (ie, based on * "valid facts" only, without relying on any floating-guard-assumption). * * <p>For each (direct or indirect) child, a copy-on-write version is made in case any of its * children changed, with the copy accommodating the updated children. If the parent was shared, * copy-on-write prevents the updates from becoming visible to anyone but the invoker of this * method. * * <p><b> Please note the parent node is mutated upon any descendant changing. No copy-on-write is * performed for the parent node itself. </b> * * <p>In more detail, for each direct {@link com.oracle.graal.nodes.ValueNode} input of the node * at hand, * * <ol> * <li>Obtain a lazy-copied version (via spanning tree) of the DAG rooted at the input-usage in * question. Lazy-copying is done by walking a spanning tree of the original DAG, stopping * at non-FloatingNodes but transitively walking FloatingNodes and their inputs. Upon * arriving at a (floating) node N, the state's facts are checked to determine whether a * constant C can be used instead in the resulting lazy-copied DAG. A NodeBitMap is used to * realize the spanning tree. * <li>Provided one or more N-to-C node replacements took place, the resulting lazy-copied DAG * has a parent different from the original (ie different object identity) which indicates * the (copied, updated) DAG should replace the original via replaceFirstInput(), and * inferStamp() should be invoked to reflect the updated inputs. * </ol> * * @return whether any reduction was performed on the inputs of the arguments. */ public boolean deverbosifyInputsInPlace(ValueNode parent) { boolean changed = false; for (ValueNode i : FlowUtil.distinctValueAndConditionInputs(parent)) { assert !(i instanceof GuardNode) : "This phase not intended to run during MidTier"; ValueNode j = (ValueNode) reasoner.deverbosify(i); if (i != j) { changed = true; FlowUtil.replaceInPlace(parent, i, j); } } if (changed) { FlowUtil.inferStampAndCheck(parent); } return changed; }
/** * In case the scrutinee: * * <ul> * <li>is known to be null, an unconditional deopt is added. * <li>is known to be non-null, the NullCheckNode is removed. * <li>otherwise, the NullCheckNode is lowered to a FixedGuardNode which then allows using it as * anchor for state-tracking. * </ul> * * <p>Precondition: the input (ie, object) hasn't been deverbosified yet. */ private void visitNullCheckNode(NullCheckNode ncn) { ValueNode object = ncn.getObject(); if (state.isNull(object)) { postponedDeopts.addDeoptBefore(ncn, NullCheckException); state.impossiblePath(); return; } if (state.isNonNull(object)) { /* * Redundant NullCheckNode. Unlike GuardingPiNode or FixedGuardNode, NullCheckNode-s * aren't used as GuardingNode-s, thus in this case can be removed without further ado. */ assert FlowUtil.lacksUsages(ncn); graph.removeFixed(ncn); return; } /* * Lower the NullCheckNode to a FixedGuardNode which then allows using it as anchor for * state-tracking. TODO the assumption here is that the code emitted for the resulting * FixedGuardNode is as efficient as for NullCheckNode. */ IsNullNode isNN = graph.unique(IsNullNode.create(object)); reasoner.added.add(isNN); FixedGuardNode nullCheck = graph.add(FixedGuardNode.create(isNN, UnreachedCode, InvalidateReprofile, true)); graph.replaceFixedWithFixed(ncn, nullCheck); state.trackNN(object, nullCheck); }
/** * For one or more `invoke` arguments, flow-sensitive information may suggest their narrowing or * simplification. In those cases, a new {@link com.oracle.graal.nodes.java.MethodCallTargetNode * MethodCallTargetNode} is prepared just for this callsite, consuming reduced arguments. * * <p>Specializing the {@link com.oracle.graal.nodes.java.MethodCallTargetNode * MethodCallTargetNode} as described above may enable two optimizations: * * <ul> * <li>devirtualization of an {@link com.oracle.graal.nodes.CallTargetNode.InvokeKind#Interface} * or {@link com.oracle.graal.nodes.CallTargetNode.InvokeKind#Virtual} callsite * (devirtualization made possible after narrowing the type of the receiver) * <li>(future work) actual-argument-aware inlining, ie, to specialize callees on the types of * arguments other than the receiver (examples: multi-methods, the inlining problem, lambdas * as arguments). * </ul> * * <p>Precondition: inputs haven't been deverbosified yet. */ private void visitInvoke(Invoke invoke) { if (invoke.asNode().stamp() instanceof IllegalStamp) { return; // just to be safe } boolean isMethodCallTarget = invoke.callTarget() instanceof MethodCallTargetNode; if (!isMethodCallTarget) { return; } FlowUtil.replaceInPlace( invoke.asNode(), invoke.callTarget(), deverbosifyInputsCopyOnWrite((MethodCallTargetNode) invoke.callTarget())); MethodCallTargetNode callTarget = (MethodCallTargetNode) invoke.callTarget(); if (callTarget.invokeKind() != CallTargetNode.InvokeKind.Interface && callTarget.invokeKind() != CallTargetNode.InvokeKind.Virtual) { return; } ValueNode receiver = callTarget.receiver(); if (receiver == null) { return; } if (!FlowUtil.hasLegalObjectStamp(receiver)) { return; } Witness w = state.typeInfo(receiver); ResolvedJavaType type; ResolvedJavaType stampType = StampTool.typeOrNull(receiver); if (w == null || w.cluelessAboutType()) { // can't improve on stamp but wil try to devirtualize anyway type = stampType; } else { type = FlowUtil.tighten(w.type(), stampType); } if (type == null) { return; } ResolvedJavaMethod method = type.resolveMethod(callTarget.targetMethod(), invoke.getContextType()); if (method == null) { return; } if (method.canBeStaticallyBound() || Modifier.isFinal(type.getModifiers())) { metricMethodResolved.increment(); callTarget.setInvokeKind(CallTargetNode.InvokeKind.Special); callTarget.setTargetMethod(method); } }
/** * TODO When tracking integer-stamps, the state at each successor of a TypeSwitchNode should track * an integer-stamp for the LoadHubNode (meet over the constants leading to that successor). * However, are LoadHubNode-s shared frequently enough? */ private void registerTypeSwitchNode(TypeSwitchNode typeSwitch, BeginNode begin) { if (typeSwitch.value() instanceof LoadHubNode) { LoadHubNode loadHub = (LoadHubNode) typeSwitch.value(); ResolvedJavaType type = null; for (int i = 0; i < typeSwitch.keyCount(); i++) { if (typeSwitch.keySuccessor(i) == begin) { if (type == null) { type = typeSwitch.typeAt(i); } else { type = FlowUtil.widen(type, typeSwitch.typeAt(i)); } } } if (type == null) { // `begin` denotes the default case of the TypeSwitchNode return; } // it's unwarranted to assume loadHub.object() to be non-null state.trackCC(loadHub.getValue(), type, begin); } }
/** * This method performs two kinds of cleanup: * * <ol> * <li>marking as unreachable certain code-paths, as described in {@link * com.oracle.graal.phases.common.cfs.BaseReduction.PostponedDeopt} * <li>Removing nodes not in use that were added during this phase, as described next. * </ol> * * <p>Methods like {@link * com.oracle.graal.phases.common.cfs.FlowUtil#replaceInPlace(com.oracle.graal.graph.Node, * com.oracle.graal.graph.Node, com.oracle.graal.graph.Node)} may result in old inputs becoming * disconnected from the graph. It's not advisable to {@link * com.oracle.graal.nodes.util.GraphUtil#tryKillUnused(com.oracle.graal.graph.Node)} at that * moment, because one of the inputs that might get killed is one of {@link #nullConstant}, {@link * #falseConstant}, or {@link #trueConstant}; which thus could get killed too early, before * another invocation of {@link * com.oracle.graal.phases.common.cfs.EquationalReasoner#deverbosify(com.oracle.graal.graph.Node)} * needs them. To recap, {@link * com.oracle.graal.nodes.util.GraphUtil#tryKillUnused(com.oracle.graal.graph.Node)} also * recursively visits the inputs of the its argument. * * <p>This method goes over all of the nodes that deverbosification might have added, which are * either: * * <ul> * <li>{@link com.oracle.graal.nodes.calc.FloatingNode}, added by {@link * com.oracle.graal.phases.common.cfs.EquationalReasoner#deverbosifyFloatingNode(com.oracle.graal.nodes.calc.FloatingNode)} * ; or * <li>{@link com.oracle.graal.nodes.java.MethodCallTargetNode}, added by {@link * #deverbosifyInputsCopyOnWrite(com.oracle.graal.nodes.java.MethodCallTargetNode)} * </ul> * * Checking if they aren't in use, proceeding to remove them in that case. */ @Override public void finished() { if (!postponedDeopts.isEmpty()) { for (PostponedDeopt postponed : postponedDeopts) { postponed.doRewrite(falseConstant); } new DeadCodeEliminationPhase(Optional).apply(graph); } for (MethodCallTargetNode mcn : graph.getNodes().filter(MethodCallTargetNode.class)) { if (mcn.isAlive() && FlowUtil.lacksUsages(mcn)) { mcn.safeDelete(); } } for (Node n : graph.getNodes().filter(FloatingNode.class)) { GraphUtil.tryKillUnused(n); } assert !isAliveWithoutUsages(trueConstant); assert !isAliveWithoutUsages(falseConstant); assert !isAliveWithoutUsages(nullConstant); super.finished(); }
private static boolean isAliveWithoutUsages(FloatingNode node) { return node.isAlive() && FlowUtil.lacksUsages(node); }