diff --git a/compiler/src/org.graalvm.compiler.nodes/src/org/graalvm/compiler/nodes/SimplifyingGraphDecoder.java b/compiler/src/org.graalvm.compiler.nodes/src/org/graalvm/compiler/nodes/SimplifyingGraphDecoder.java index 5acd528a5396..de03880c5bd6 100644 --- a/compiler/src/org.graalvm.compiler.nodes/src/org/graalvm/compiler/nodes/SimplifyingGraphDecoder.java +++ b/compiler/src/org.graalvm.compiler.nodes/src/org/graalvm/compiler/nodes/SimplifyingGraphDecoder.java @@ -38,8 +38,6 @@ import org.graalvm.compiler.graph.Edges; import org.graalvm.compiler.graph.Node; import org.graalvm.compiler.graph.NodeClass; -import org.graalvm.compiler.nodes.spi.Canonicalizable; -import org.graalvm.compiler.nodes.spi.CanonicalizerTool; import org.graalvm.compiler.nodeinfo.InputType; import org.graalvm.compiler.nodeinfo.NodeInfo; import org.graalvm.compiler.nodes.calc.FloatingNode; @@ -49,6 +47,8 @@ import org.graalvm.compiler.nodes.java.ArrayLengthNode; import org.graalvm.compiler.nodes.java.LoadFieldNode; import org.graalvm.compiler.nodes.java.LoadIndexedNode; +import org.graalvm.compiler.nodes.spi.Canonicalizable; +import org.graalvm.compiler.nodes.spi.CanonicalizerTool; import org.graalvm.compiler.nodes.spi.CoreProviders; import org.graalvm.compiler.nodes.spi.CoreProvidersDelegate; import org.graalvm.compiler.nodes.util.GraphUtil; @@ -333,7 +333,11 @@ private void handleCanonicalization(LoopScope loopScope, int nodeOrderId, FixedN } } if (!node.isDeleted()) { - GraphUtil.unlinkFixedNode((FixedWithNextNode) node); + if (node instanceof WithExceptionNode) { + GraphUtil.unlinkAndKillExceptionEdge((WithExceptionNode) node); + } else { + GraphUtil.unlinkFixedNode((FixedWithNextNode) node); + } node.replaceAtUsagesAndDelete(canonical); } assert lookupNode(loopScope, nodeOrderId) == node; diff --git a/compiler/src/org.graalvm.compiler.phases.common/src/org/graalvm/compiler/phases/common/CanonicalizerPhase.java b/compiler/src/org.graalvm.compiler.phases.common/src/org/graalvm/compiler/phases/common/CanonicalizerPhase.java index 962e86c0a0b7..557468bcf3be 100644 --- a/compiler/src/org.graalvm.compiler.phases.common/src/org/graalvm/compiler/phases/common/CanonicalizerPhase.java +++ b/compiler/src/org.graalvm.compiler.phases.common/src/org/graalvm/compiler/phases/common/CanonicalizerPhase.java @@ -57,6 +57,7 @@ import org.graalvm.compiler.nodes.StructuredGraph; import org.graalvm.compiler.nodes.StructuredGraph.StageFlag; import org.graalvm.compiler.nodes.ValueNode; +import org.graalvm.compiler.nodes.WithExceptionNode; import org.graalvm.compiler.nodes.calc.FloatingNode; import org.graalvm.compiler.nodes.spi.Canonicalizable; import org.graalvm.compiler.nodes.spi.Canonicalizable.BinaryCommutative; @@ -490,6 +491,49 @@ private boolean performReplacement(final Node node, Node newCanonical) { fixed.replaceAtPredecessor(canonical); GraphUtil.killCFG(fixed); return true; + + } else if (fixed instanceof WithExceptionNode) { + /* + * A fixed node with an exception edge is handled similarly to a + * FixedWithNextNode. The only difference is that the exception edge needs + * to be killed as part of canonicalization (unless the canonical node is a + * new WithExceptionNode too). + */ + WithExceptionNode withException = (WithExceptionNode) fixed; + + // When removing a fixed node, new canonicalization + // opportunities for its successor may arise + assert withException.next() != null; + tool.addToWorkList(withException.next()); + if (canonical == null) { + // case 3 + node.replaceAtUsages(null); + GraphUtil.unlinkAndKillExceptionEdge(withException); + GraphUtil.killWithUnusedFloatingInputs(withException); + } else if (canonical instanceof FloatingNode) { + // case 4 + withException.killExceptionEdge(); + graph.replaceSplitWithFloating(withException, (FloatingNode) canonical, withException.next()); + } else { + assert canonical instanceof FixedNode; + if (canonical.predecessor() == null) { + assert !canonical.cfgSuccessors().iterator().hasNext() : "replacement " + canonical + " shouldn't have successors"; + // case 5 + if (canonical instanceof WithExceptionNode) { + graph.replaceWithExceptionSplit(withException, (WithExceptionNode) canonical); + } else { + withException.killExceptionEdge(); + graph.replaceSplitWithFixed(withException, (FixedWithNextNode) canonical, withException.next()); + } + } else { + assert canonical.cfgSuccessors().iterator().hasNext() : "replacement " + canonical + " should have successors"; + // case 6 + node.replaceAtUsages(canonical); + GraphUtil.unlinkAndKillExceptionEdge(withException); + GraphUtil.killWithUnusedFloatingInputs(withException); + } + } + } else { assert fixed instanceof FixedWithNextNode; FixedWithNextNode fixedWithNext = (FixedWithNextNode) fixed; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/classinitialization/EnsureClassInitializedNode.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/classinitialization/EnsureClassInitializedNode.java index fb13149b2713..70ff9bddcc05 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/classinitialization/EnsureClassInitializedNode.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/classinitialization/EnsureClassInitializedNode.java @@ -25,6 +25,7 @@ package com.oracle.svm.core.classinitialization; import org.graalvm.compiler.core.common.type.StampFactory; +import org.graalvm.compiler.graph.Node; import org.graalvm.compiler.graph.Node.NodeIntrinsicFactory; import org.graalvm.compiler.graph.NodeClass; import org.graalvm.compiler.nodeinfo.InputType; @@ -37,9 +38,10 @@ import org.graalvm.compiler.nodes.WithExceptionNode; import org.graalvm.compiler.nodes.graphbuilderconf.GraphBuilderContext; import org.graalvm.compiler.nodes.memory.SingleMemoryKill; +import org.graalvm.compiler.nodes.spi.Canonicalizable; +import org.graalvm.compiler.nodes.spi.CanonicalizerTool; +import org.graalvm.compiler.nodes.spi.CoreProviders; import org.graalvm.compiler.nodes.spi.Lowerable; -import org.graalvm.compiler.nodes.spi.Simplifiable; -import org.graalvm.compiler.nodes.spi.SimplifierTool; import org.graalvm.compiler.nodes.type.StampTool; import org.graalvm.word.LocationIdentity; @@ -47,7 +49,7 @@ @NodeInfo(size = NodeSize.SIZE_16, cycles = NodeCycles.CYCLES_2, cyclesRationale = "Class initialization only runs at most once at run time, so the amortized cost is only the is-initialized check") @NodeIntrinsicFactory -public class EnsureClassInitializedNode extends WithExceptionNode implements Simplifiable, StateSplit, SingleMemoryKill, Lowerable { +public class EnsureClassInitializedNode extends WithExceptionNode implements Canonicalizable, StateSplit, SingleMemoryKill, Lowerable { public static final NodeClass TYPE = NodeClass.create(EnsureClassInitializedNode.class); @@ -95,15 +97,61 @@ public boolean hasSideEffect() { return true; } - @Override - public void simplify(SimplifierTool tool) { + public ResolvedJavaType constantTypeOrNull(CoreProviders providers) { if (hub.isConstant()) { - ResolvedJavaType type = tool.getConstantReflection().asJavaType(hub.asConstant()); - if (type != null && type.isInitialized()) { - killExceptionEdge(); - graph().removeSplit(this, next()); - return; + return providers.getConstantReflection().asJavaType(hub.asConstant()); + } else { + return null; + } + } + + @Override + public Node canonical(CanonicalizerTool tool) { + ResolvedJavaType type = constantTypeOrNull(tool); + if (type != null) { + for (FrameState cur = stateAfter; cur != null; cur = cur.outerFrameState()) { + if (!needsRuntimeInitialization(cur.getMethod().getDeclaringClass(), type)) { + return null; + } + } + } + return this; + } + + /** + * Return true if the type needs to be initialized at run time, i.e., it has not been already + * initialized during image generation or initialization is implied by the declaringClass. + */ + public static boolean needsRuntimeInitialization(ResolvedJavaType declaringClass, ResolvedJavaType type) { + if (type.isInitialized() || type.isArray() || type.equals(declaringClass)) { + /* + * The simple cases: the type is already initialized, or the type is an exact match with + * the declaring class. + */ + return false; + + } else if (declaringClass.isInterface()) { + /* + * Initialization of an interface does not trigger initialization of superinterfaces. + * Regardless whether any of the involved interfaces declares default methods. + */ + return true; + + } else if (type.isAssignableFrom(declaringClass)) { + if (type.isInterface()) { + /* + * Initialization of a class only triggers initialization of an implemented + * interface if that interface declares default methods. + */ + return !type.declaresDefaultMethods(); + } else { + /* Initialization of a class triggers initialization of all superclasses. */ + return false; } + + } else { + /* No relationship between the two types. */ + return true; } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SVMHost.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SVMHost.java index b3b25d09da09..f1730aaebdd4 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SVMHost.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SVMHost.java @@ -109,7 +109,6 @@ import com.oracle.svm.hosted.substitute.UnsafeAutomaticSubstitutionProcessor; import com.oracle.svm.util.ReflectionUtil; -import jdk.vm.ci.meta.Constant; import jdk.vm.ci.meta.DeoptimizationReason; import jdk.vm.ci.meta.ResolvedJavaField; import jdk.vm.ci.meta.ResolvedJavaMethod; @@ -652,10 +651,9 @@ private void checkClassInitializerSideEffect(BigBang bb, AnalysisMethod method, */ classInitializerSideEffect.put(method, true); } else if (n instanceof EnsureClassInitializedNode) { - Constant constantHub = ((EnsureClassInitializedNode) n).getHub().asConstant(); - if (constantHub != null) { - AnalysisType type = (AnalysisType) bb.getProviders().getConstantReflection().asJavaType(constantHub); - initializedClasses.computeIfAbsent(method, k -> new HashSet<>()).add(type); + ResolvedJavaType type = ((EnsureClassInitializedNode) n).constantTypeOrNull(bb.getProviders()); + if (type != null) { + initializedClasses.computeIfAbsent(method, k -> new HashSet<>()).add((AnalysisType) type); } else { classInitializerSideEffect.put(method, true); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/EarlyClassInitializerAnalysis.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/EarlyClassInitializerAnalysis.java index fb3399a9b234..1aa15f0fc94b 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/EarlyClassInitializerAnalysis.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/EarlyClassInitializerAnalysis.java @@ -55,6 +55,7 @@ import org.graalvm.compiler.phases.util.Providers; import com.oracle.svm.core.ParsingReason; +import com.oracle.svm.core.classinitialization.EnsureClassInitializedNode; import com.oracle.svm.core.graal.thread.VMThreadLocalAccess; import com.oracle.svm.core.option.HostedOptionValues; import com.oracle.svm.core.util.VMError; @@ -197,7 +198,7 @@ final class AbortOnUnitializedClassPlugin extends NoClassInitializationPlugin { @Override public boolean apply(GraphBuilderContext b, ResolvedJavaType type, Supplier frameState, ValueNode[] classInit) { ResolvedJavaMethod clinitMethod = b.getGraph().method(); - if (type.isInitialized() || type.isArray() || type.equals(clinitMethod.getDeclaringClass())) { + if (!EnsureClassInitializedNode.needsRuntimeInitialization(clinitMethod.getDeclaringClass(), type)) { return false; } if (classInitializationSupport.computeInitKindAndMaybeInitializeClass(ConfigurableClassInitialization.getJavaClass(type), true, analyzedClasses) != InitKind.RUN_TIME) { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/HostedGraphKit.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/HostedGraphKit.java index 1d5405250356..512d21d91466 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/HostedGraphKit.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/HostedGraphKit.java @@ -101,7 +101,7 @@ protected Instance createGraphBuilderInstance(GraphBuilderConfiguration graphBui } public void emitEnsureInitializedCall(ResolvedJavaType type) { - if (SubstrateClassInitializationPlugin.needsRuntimeInitialization(graph.method().getDeclaringClass(), type)) { + if (EnsureClassInitializedNode.needsRuntimeInitialization(graph.method().getDeclaringClass(), type)) { ValueNode hub = createConstant(getConstantReflection().asJavaClass(type), JavaKind.Object); appendWithUnwind(new EnsureClassInitializedNode(hub)); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/SubstrateClassInitializationPlugin.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/SubstrateClassInitializationPlugin.java index 60a0359d0340..056a7da1c649 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/SubstrateClassInitializationPlugin.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/SubstrateClassInitializationPlugin.java @@ -60,7 +60,7 @@ public void loadReferencedType(GraphBuilderContext builder, ConstantPool constan @Override public boolean apply(GraphBuilderContext builder, ResolvedJavaType type, Supplier frameState, ValueNode[] classInit) { - if (needsRuntimeInitialization(builder.getMethod().getDeclaringClass(), type)) { + if (EnsureClassInitializedNode.needsRuntimeInitialization(builder.getMethod().getDeclaringClass(), type)) { emitEnsureClassInitialized(builder, SubstrateObjectConstant.forObject(host.dynamicHub(type)), frameState.get()); /* * The classInit value is only registered with Invoke nodes. Since we do not need that, @@ -79,12 +79,4 @@ private static void emitEnsureClassInitialized(GraphBuilderContext builder, Java EnsureClassInitializedNode node = new EnsureClassInitializedNode(hub, frameState); builder.add(node); } - - /** - * Return true if the type needs to be initialized at run time, i.e., it has not been already - * initialized during image generation. - */ - static boolean needsRuntimeInitialization(ResolvedJavaType declaringClass, ResolvedJavaType type) { - return !declaringClass.equals(type) && !type.isInitialized() && !type.isArray(); - } }