diff --git a/compiler/src/org.graalvm.compiler.replacements.test/src/org/graalvm/compiler/replacements/test/PEGraphDecoderTest.java b/compiler/src/org.graalvm.compiler.replacements.test/src/org/graalvm/compiler/replacements/test/PEGraphDecoderTest.java index e17b2bc42b6b..8ab6ae8b15e6 100644 --- a/compiler/src/org.graalvm.compiler.replacements.test/src/org/graalvm/compiler/replacements/test/PEGraphDecoderTest.java +++ b/compiler/src/org.graalvm.compiler.replacements.test/src/org/graalvm/compiler/replacements/test/PEGraphDecoderTest.java @@ -138,7 +138,7 @@ public void test() { registerPlugins(graphBuilderConfig.getPlugins().getInvocationPlugins()); targetGraph = new StructuredGraph.Builder(getInitialOptions(), debug, AllowAssumptions.YES).method(testMethod).build(); CachingPEGraphDecoder decoder = new CachingPEGraphDecoder(getTarget().arch, targetGraph, getProviders(), graphBuilderConfig, OptimisticOptimizations.NONE, AllowAssumptions.YES, - null, null, new InlineInvokePlugin[]{new InlineAll()}, null, null, null, null); + null, null, new InlineInvokePlugin[]{new InlineAll()}, null, null, null, null, null); decoder.decode(testMethod, false, false); debug.dump(DebugContext.BASIC_LEVEL, targetGraph, "Target Graph"); diff --git a/compiler/src/org.graalvm.compiler.replacements/src/org/graalvm/compiler/replacements/CachingPEGraphDecoder.java b/compiler/src/org.graalvm.compiler.replacements/src/org/graalvm/compiler/replacements/CachingPEGraphDecoder.java index edefeab8fee2..70ded5d154c6 100644 --- a/compiler/src/org.graalvm.compiler.replacements/src/org/graalvm/compiler/replacements/CachingPEGraphDecoder.java +++ b/compiler/src/org.graalvm.compiler.replacements/src/org/graalvm/compiler/replacements/CachingPEGraphDecoder.java @@ -46,6 +46,8 @@ import org.graalvm.compiler.nodes.graphbuilderconf.MethodSubstitutionPlugin; import org.graalvm.compiler.nodes.graphbuilderconf.NodePlugin; import org.graalvm.compiler.nodes.graphbuilderconf.ParameterPlugin; +import org.graalvm.compiler.nodes.spi.CoreProviders; +import org.graalvm.compiler.phases.BasePhase; import org.graalvm.compiler.phases.OptimisticOptimizations; import org.graalvm.compiler.phases.common.CanonicalizerPhase; import org.graalvm.compiler.phases.util.Providers; @@ -64,11 +66,13 @@ public class CachingPEGraphDecoder extends PEGraphDecoder { protected final OptimisticOptimizations optimisticOpts; private final AllowAssumptions allowAssumptions; private final EconomicMap graphCache; + private final BasePhase postParsingPhase; public CachingPEGraphDecoder(Architecture architecture, StructuredGraph graph, Providers providers, GraphBuilderConfiguration graphBuilderConfig, OptimisticOptimizations optimisticOpts, AllowAssumptions allowAssumptions, LoopExplosionPlugin loopExplosionPlugin, InvocationPlugins invocationPlugins, InlineInvokePlugin[] inlineInvokePlugins, ParameterPlugin parameterPlugin, - NodePlugin[] nodePlugins, ResolvedJavaMethod callInlinedMethod, SourceLanguagePositionProvider sourceLanguagePositionProvider) { + NodePlugin[] nodePlugins, ResolvedJavaMethod callInlinedMethod, SourceLanguagePositionProvider sourceLanguagePositionProvider, + BasePhase postParsingPhase) { super(architecture, graph, providers, loopExplosionPlugin, invocationPlugins, inlineInvokePlugins, parameterPlugin, nodePlugins, callInlinedMethod, sourceLanguagePositionProvider); @@ -76,6 +80,7 @@ public CachingPEGraphDecoder(Architecture architecture, StructuredGraph graph, P this.graphBuilderConfig = graphBuilderConfig; this.optimisticOpts = optimisticOpts; this.allowAssumptions = allowAssumptions; + this.postParsingPhase = postParsingPhase; this.graphCache = EconomicMap.create(); } @@ -128,6 +133,9 @@ private StructuredGraph buildGraph(ResolvedJavaMethod method, MethodSubstitution GraphBuilderPhase.Instance graphBuilderPhaseInstance = createGraphBuilderPhaseInstance(initialIntrinsicContext); graphBuilderPhaseInstance.apply(graphToEncode); new CanonicalizerPhase().apply(graphToEncode, providers); + if (postParsingPhase != null) { + postParsingPhase.apply(graphToEncode, providers); + } } catch (Throwable ex) { throw debug.handle(ex); } diff --git a/compiler/src/org.graalvm.compiler.truffle.common/src/org/graalvm/compiler/truffle/common/TruffleCompilerRuntime.java b/compiler/src/org.graalvm.compiler.truffle.common/src/org/graalvm/compiler/truffle/common/TruffleCompilerRuntime.java index 0498d27799cf..1bf11cd95177 100644 --- a/compiler/src/org.graalvm.compiler.truffle.common/src/org/graalvm/compiler/truffle/common/TruffleCompilerRuntime.java +++ b/compiler/src/org.graalvm.compiler.truffle.common/src/org/graalvm/compiler/truffle/common/TruffleCompilerRuntime.java @@ -194,7 +194,13 @@ enum InlineKind { * Denotes a call site must not be inlined and the execution should be transferred to * interpreter in case of an exception. */ - DO_NOT_INLINE_DEOPTIMIZE_ON_EXCEPTION(false); + DO_NOT_INLINE_DEOPTIMIZE_ON_EXCEPTION(false), + + /** + * Denotes a call site must not be inlined and the execution should be speculatively + * transferred to interpreter in case of an exception, unless the speculation has failed. + */ + DO_NOT_INLINE_WITH_SPECULATIVE_EXCEPTION(false); private final boolean allowsInlining; diff --git a/compiler/src/org.graalvm.compiler.truffle.compiler/src/org/graalvm/compiler/truffle/compiler/PartialEvaluator.java b/compiler/src/org.graalvm.compiler.truffle.compiler/src/org/graalvm/compiler/truffle/compiler/PartialEvaluator.java index 09cc0b6e796d..a0796d22edbf 100644 --- a/compiler/src/org.graalvm.compiler.truffle.compiler/src/org/graalvm/compiler/truffle/compiler/PartialEvaluator.java +++ b/compiler/src/org.graalvm.compiler.truffle.compiler/src/org/graalvm/compiler/truffle/compiler/PartialEvaluator.java @@ -94,8 +94,10 @@ import org.graalvm.compiler.replacements.PEGraphDecoder; import org.graalvm.compiler.replacements.ReplacementsImpl; import org.graalvm.compiler.serviceprovider.GraalServices; +import org.graalvm.compiler.serviceprovider.SpeculationReasonGroup; import org.graalvm.compiler.truffle.common.CompilableTruffleAST; import org.graalvm.compiler.truffle.common.TruffleCompilerRuntime; +import org.graalvm.compiler.truffle.common.TruffleCompilerRuntime.InlineKind; import org.graalvm.compiler.truffle.common.TruffleInliningPlan; import org.graalvm.compiler.truffle.common.TruffleSourceLanguagePosition; import org.graalvm.compiler.truffle.compiler.debug.HistogramInlineInvokePlugin; @@ -105,6 +107,7 @@ import org.graalvm.compiler.truffle.compiler.phases.InstrumentBranchesPhase; import org.graalvm.compiler.truffle.compiler.phases.InstrumentPhase; import org.graalvm.compiler.truffle.compiler.phases.InstrumentTruffleBoundariesPhase; +import org.graalvm.compiler.truffle.compiler.phases.DeoptimizeOnExceptionPhase; import org.graalvm.compiler.truffle.compiler.phases.VerifyFrameDoesNotEscapePhase; import org.graalvm.compiler.truffle.compiler.substitutions.KnownTruffleTypes; import org.graalvm.compiler.truffle.compiler.substitutions.TruffleGraphBuilderPlugins; @@ -119,6 +122,7 @@ import jdk.vm.ci.meta.ResolvedJavaMethod; import jdk.vm.ci.meta.ResolvedJavaType; import jdk.vm.ci.meta.SpeculationLog; +import jdk.vm.ci.meta.SpeculationLog.SpeculationReason; /** * Class performing the partial evaluation starting from the root node of an AST. @@ -445,10 +449,12 @@ protected PEGraphDecoder createGraphDecoder(StructuredGraph graph, final HighTie plugins.appendInlineInvokePlugin(new InlineDuringParsingPlugin()); } + DeoptimizeOnExceptionPhase postParsingPhase = new DeoptimizeOnExceptionPhase( + method -> TruffleCompilerRuntime.getRuntime().getInlineKind(method, true) == InlineKind.DO_NOT_INLINE_WITH_SPECULATIVE_EXCEPTION); + Providers compilationUnitProviders = providers.copyWith(new TruffleConstantFieldProvider(providers.getConstantFieldProvider(), providers.getMetaAccess())); - return new CachingPEGraphDecoder(architecture, graph, compilationUnitProviders, newConfig, TruffleCompilerImpl.Optimizations, - AllowAssumptions.ifNonNull(graph.getAssumptions()), - loopExplosionPlugin, decodingInvocationPlugins, inlineInvokePlugins, parameterPlugin, nodePluginList, callInlined, sourceLanguagePositionProvider); + return new CachingPEGraphDecoder(architecture, graph, compilationUnitProviders, newConfig, TruffleCompilerImpl.Optimizations, AllowAssumptions.ifNonNull(graph.getAssumptions()), + loopExplosionPlugin, decodingInvocationPlugins, inlineInvokePlugins, parameterPlugin, nodePluginList, callInlined, sourceLanguagePositionProvider, postParsingPhase); } protected void doGraphPE(CompilableTruffleAST compilable, StructuredGraph graph, HighTierContext tierContext, TruffleInliningPlan inliningDecision) { @@ -741,6 +747,7 @@ private static InlineInfo asInlineInfo(final TruffleCompilerRuntime.InlineKind i case DO_NOT_INLINE_NO_EXCEPTION: return InlineInfo.DO_NOT_INLINE_NO_EXCEPTION; case DO_NOT_INLINE_WITH_EXCEPTION: + case DO_NOT_INLINE_WITH_SPECULATIVE_EXCEPTION: return InlineInfo.DO_NOT_INLINE_WITH_EXCEPTION; case INLINE: return InlineInfo.createStandardInlineInfo(method); @@ -749,6 +756,12 @@ private static InlineInfo asInlineInfo(final TruffleCompilerRuntime.InlineKind i } } + private static final SpeculationReasonGroup TRUFFLE_BOUNDARY_EXCEPTION_SPECULATIONS = new SpeculationReasonGroup("TruffleBoundaryWithoutException", ResolvedJavaMethod.class); + + public static SpeculationReason createTruffleBoundaryExceptionSpeculation(ResolvedJavaMethod targetMethod) { + return TRUFFLE_BOUNDARY_EXCEPTION_SPECULATIONS.createSpeculationReason(targetMethod); + } + private static final class SourceLanguagePositionImpl implements SourceLanguagePosition { private final TruffleSourceLanguagePosition delegate; diff --git a/compiler/src/org.graalvm.compiler.truffle.compiler/src/org/graalvm/compiler/truffle/compiler/nodes/SpeculativeExceptionAnchorNode.java b/compiler/src/org.graalvm.compiler.truffle.compiler/src/org/graalvm/compiler/truffle/compiler/nodes/SpeculativeExceptionAnchorNode.java new file mode 100644 index 000000000000..09f45507bf83 --- /dev/null +++ b/compiler/src/org.graalvm.compiler.truffle.compiler/src/org/graalvm/compiler/truffle/compiler/nodes/SpeculativeExceptionAnchorNode.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.graalvm.compiler.truffle.compiler.nodes; + +import static org.graalvm.compiler.nodeinfo.NodeCycles.CYCLES_0; +import static org.graalvm.compiler.nodeinfo.NodeSize.SIZE_0; + +import org.graalvm.compiler.core.common.type.StampFactory; +import org.graalvm.compiler.graph.Node; +import org.graalvm.compiler.graph.NodeClass; +import org.graalvm.compiler.graph.spi.Canonicalizable; +import org.graalvm.compiler.graph.spi.CanonicalizerTool; +import org.graalvm.compiler.nodeinfo.NodeInfo; +import org.graalvm.compiler.nodes.DeoptimizeNode; +import org.graalvm.compiler.nodes.FixedWithNextNode; +import org.graalvm.compiler.truffle.compiler.PartialEvaluator; + +import jdk.vm.ci.meta.DeoptimizationAction; +import jdk.vm.ci.meta.DeoptimizationReason; +import jdk.vm.ci.meta.ResolvedJavaMethod; +import jdk.vm.ci.meta.SpeculationLog; +import jdk.vm.ci.meta.SpeculationLog.Speculation; +import jdk.vm.ci.meta.SpeculationLog.SpeculationReason; + +/** + * A speculation-less node that is inserted into the exception branch of TruffleBoundary calls + * during parsing (graph encoding). During partial evaluation (graph decoding) when a speculation + * log is available, it will speculate that TruffleBoundary method will not throw and either becomes + * a control-flow sink {@link DeoptimizeNode} with the {@link Speculation} in order to off the + * branch, or if the speculation has already failed for this compilation root, disappears. + */ +@NodeInfo(cycles = CYCLES_0, size = SIZE_0) +public final class SpeculativeExceptionAnchorNode extends FixedWithNextNode implements Canonicalizable { + + public static final NodeClass TYPE = NodeClass.create(SpeculativeExceptionAnchorNode.class); + + private final DeoptimizationReason reason; + private final DeoptimizationAction action; + private final ResolvedJavaMethod targetMethod; + + public SpeculativeExceptionAnchorNode(DeoptimizationReason reason, DeoptimizationAction action, ResolvedJavaMethod targetMethod) { + super(TYPE, StampFactory.forVoid()); + this.reason = reason; + this.action = action; + this.targetMethod = targetMethod; + } + + @Override + public Node canonical(CanonicalizerTool tool) { + SpeculationLog speculationLog = graph().getSpeculationLog(); + if (speculationLog != null) { + SpeculationReason speculationReason = PartialEvaluator.createTruffleBoundaryExceptionSpeculation(targetMethod); + if (speculationLog.maySpeculate(speculationReason)) { + Speculation exceptionSpeculation = speculationLog.speculate(speculationReason); + return new DeoptimizeNode(action, reason, exceptionSpeculation); + } + return null; + } + return this; + } +} diff --git a/compiler/src/org.graalvm.compiler.truffle.compiler/src/org/graalvm/compiler/truffle/compiler/phases/DeoptimizeOnExceptionPhase.java b/compiler/src/org.graalvm.compiler.truffle.compiler/src/org/graalvm/compiler/truffle/compiler/phases/DeoptimizeOnExceptionPhase.java new file mode 100644 index 000000000000..6867365929bf --- /dev/null +++ b/compiler/src/org.graalvm.compiler.truffle.compiler/src/org/graalvm/compiler/truffle/compiler/phases/DeoptimizeOnExceptionPhase.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.graalvm.compiler.truffle.compiler.phases; + +import java.util.function.Predicate; + +import org.graalvm.compiler.nodes.AbstractBeginNode; +import org.graalvm.compiler.nodes.FixedWithNextNode; +import org.graalvm.compiler.nodes.Invoke; +import org.graalvm.compiler.nodes.InvokeWithExceptionNode; +import org.graalvm.compiler.nodes.StructuredGraph; +import org.graalvm.compiler.phases.Phase; +import org.graalvm.compiler.truffle.compiler.nodes.SpeculativeExceptionAnchorNode; + +import jdk.vm.ci.meta.DeoptimizationAction; +import jdk.vm.ci.meta.DeoptimizationReason; +import jdk.vm.ci.meta.ResolvedJavaMethod; + +/** + * Instruments the exception edge of TruffleBoundary method calls with a speculative transfer to + * interpreter. + */ +public class DeoptimizeOnExceptionPhase extends Phase { + private final Predicate deoptimizeOnExceptionPredicate; + + public DeoptimizeOnExceptionPhase(Predicate deoptimizeOnExceptionPredicate) { + this.deoptimizeOnExceptionPredicate = deoptimizeOnExceptionPredicate; + } + + @Override + protected void run(StructuredGraph graph) { + for (Invoke invoke : graph.getInvokes()) { + if (invoke instanceof InvokeWithExceptionNode) { + InvokeWithExceptionNode invokeWithException = (InvokeWithExceptionNode) invoke; + ResolvedJavaMethod targetMethod = invokeWithException.callTarget().targetMethod(); + if (deoptimizeOnExceptionPredicate.test(targetMethod)) { + // Method has @TruffleBoundary(transferToInterpreterOnException=true) + // Note: Speculation is inserted during PE. + AbstractBeginNode exceptionEdge = invokeWithException.exceptionEdge(); + FixedWithNextNode newNode = graph.add(new SpeculativeExceptionAnchorNode(DeoptimizationReason.TransferToInterpreter, DeoptimizationAction.InvalidateRecompile, targetMethod)); + graph.addAfterFixed(exceptionEdge, newNode); + } + } + } + } +} diff --git a/compiler/src/org.graalvm.compiler.truffle.runtime/src/org/graalvm/compiler/truffle/runtime/GraalTruffleRuntime.java b/compiler/src/org.graalvm.compiler.truffle.runtime/src/org/graalvm/compiler/truffle/runtime/GraalTruffleRuntime.java index 8bf6671df1a0..c29960a735f3 100644 --- a/compiler/src/org.graalvm.compiler.truffle.runtime/src/org/graalvm/compiler/truffle/runtime/GraalTruffleRuntime.java +++ b/compiler/src/org.graalvm.compiler.truffle.runtime/src/org/graalvm/compiler/truffle/runtime/GraalTruffleRuntime.java @@ -998,16 +998,18 @@ public int getFrameSlotKindTagsCount() { public InlineKind getInlineKind(ResolvedJavaMethod original, boolean duringPartialEvaluation) { TruffleBoundary truffleBoundary = getAnnotation(TruffleBoundary.class, original); if (truffleBoundary != null) { - if (duringPartialEvaluation || !truffleBoundary.allowInlining()) { + if (duringPartialEvaluation) { // Since this method is invoked by the bytecode parser plugins, which can be invoked // by the partial evaluator, we want to prevent inlining across the boundary during // partial evaluation, // even if the TruffleBoundary allows inlining after partial evaluation. if (truffleBoundary.transferToInterpreterOnException()) { - return InlineKind.DO_NOT_INLINE_DEOPTIMIZE_ON_EXCEPTION; + return InlineKind.DO_NOT_INLINE_WITH_SPECULATIVE_EXCEPTION; } else { return InlineKind.DO_NOT_INLINE_WITH_EXCEPTION; } + } else if (!truffleBoundary.allowInlining()) { + return InlineKind.DO_NOT_INLINE_WITH_EXCEPTION; } } else if (getAnnotation(TruffleCallBoundary.class, original) != null) { return InlineKind.DO_NOT_INLINE_WITH_EXCEPTION; diff --git a/compiler/src/org.graalvm.compiler.truffle.test/src/org/graalvm/compiler/truffle/test/TruffleBoundaryExceptionsTest.java b/compiler/src/org.graalvm.compiler.truffle.test/src/org/graalvm/compiler/truffle/test/TruffleBoundaryExceptionsTest.java index 54a4f40ebd88..d4e1748e2412 100644 --- a/compiler/src/org.graalvm.compiler.truffle.test/src/org/graalvm/compiler/truffle/test/TruffleBoundaryExceptionsTest.java +++ b/compiler/src/org.graalvm.compiler.truffle.test/src/org/graalvm/compiler/truffle/test/TruffleBoundaryExceptionsTest.java @@ -24,12 +24,14 @@ */ package org.graalvm.compiler.truffle.test; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + import org.graalvm.compiler.truffle.runtime.GraalTruffleRuntime; import org.graalvm.compiler.truffle.runtime.GraalTruffleRuntimeListener; import org.graalvm.compiler.truffle.runtime.OptimizedCallTarget; -import org.graalvm.compiler.truffle.runtime.TruffleRuntimeOptions; import org.graalvm.compiler.truffle.runtime.SharedTruffleRuntimeOptions; -import org.junit.Assert; +import org.graalvm.compiler.truffle.runtime.TruffleRuntimeOptions; import org.junit.Test; import com.oracle.truffle.api.CompilerDirectives; @@ -42,7 +44,7 @@ public class TruffleBoundaryExceptionsTest extends TestWithSynchronousCompiling private static final GraalTruffleRuntime runtime = (GraalTruffleRuntime) Truffle.getRuntime(); @Test - public void testExceptionOnTruffleBoundaryDoesNotDeop() { + public void testExceptionOnTruffleBoundaryDeoptsOnce() { final int compilationThreshold = TruffleRuntimeOptions.getValue(SharedTruffleRuntimeOptions.TruffleCompilationThreshold); class DeoptCountingExceptionOverBoundaryRootNode extends RootNode { @@ -50,7 +52,7 @@ protected DeoptCountingExceptionOverBoundaryRootNode() { super(null); } - int deopCounter = 0; + int deoptCounter = 0; int catchCounter = 0; int interpretCount = 0; @@ -66,7 +68,7 @@ public Object execute(VirtualFrame frame) { catchCounter++; } if (startedCompiled && CompilerDirectives.inInterpreter()) { - deopCounter++; + deoptCounter++; } return null; } @@ -83,29 +85,36 @@ public void onCompilationStarted(OptimizedCallTarget target) { compilationCount[0]++; } }; - final OptimizedCallTarget outerTarget = (OptimizedCallTarget) runtime.createCallTarget(new DeoptCountingExceptionOverBoundaryRootNode()); + DeoptCountingExceptionOverBoundaryRootNode rootNode = new DeoptCountingExceptionOverBoundaryRootNode(); + final OptimizedCallTarget outerTarget = (OptimizedCallTarget) runtime.createCallTarget(rootNode); for (int i = 0; i < compilationThreshold; i++) { outerTarget.call(); } + // deoptimizes immediately due to the exception + assertEquals("Incorrect number of deopts detected!", 1, rootNode.deoptCounter); + assertNotCompiled(outerTarget); + // recompile with exception branch + outerTarget.call(); assertCompiled(outerTarget); runtime.addListener(listener); - final int execCount = 10; - for (int i = 0; i < execCount; i++) { - outerTarget.call(); - } + try { + final int execCount = 10; + for (int i = 0; i < execCount; i++) { + outerTarget.call(); + } - final int totalExecutions = compilationThreshold + execCount; - int catchCount = ((DeoptCountingExceptionOverBoundaryRootNode) outerTarget.getRootNode()).catchCounter; - Assert.assertEquals("Incorrect number of catch block executions", totalExecutions, catchCount); + final int totalExecutions = compilationThreshold + 1 + execCount; + assertEquals("Incorrect number of catch block executions", totalExecutions, rootNode.catchCounter); - int interpretCount = ((DeoptCountingExceptionOverBoundaryRootNode) outerTarget.getRootNode()).interpretCount; - int deopCount = ((DeoptCountingExceptionOverBoundaryRootNode) outerTarget.getRootNode()).deopCounter; - Assert.assertEquals("Incorrect number of deops detected!", totalExecutions - interpretCount, deopCount); + assertEquals("Incorrect number of interpreted executions", compilationThreshold - 1, rootNode.interpretCount); + assertEquals("Incorrect number of deopts detected!", 1, rootNode.deoptCounter); - Assert.assertEquals("Compilation happened!", 0, compilationCount[0]); - runtime.removeListener(listener); + assertEquals("Compilation happened!", 0, compilationCount[0]); + } finally { + runtime.removeListener(listener); + } } @Test @@ -117,7 +126,7 @@ protected DeoptCountingExceptionOverBoundaryRootNode() { super(null); } - int deopCounter = 0; + int deoptCounter = 0; int catchCounter = 0; @Override @@ -129,7 +138,7 @@ public Object execute(VirtualFrame frame) { catchCounter++; } if (startedCompiled && CompilerDirectives.inInterpreter()) { - deopCounter++; + deoptCounter++; } return null; } @@ -140,7 +149,8 @@ public void throwExceptionBoundary() { } } - final OptimizedCallTarget outerTarget = (OptimizedCallTarget) runtime.createCallTarget(new DeoptCountingExceptionOverBoundaryRootNode()); + DeoptCountingExceptionOverBoundaryRootNode rootNode = new DeoptCountingExceptionOverBoundaryRootNode(); + final OptimizedCallTarget outerTarget = (OptimizedCallTarget) runtime.createCallTarget(rootNode); for (int i = 0; i < compilationThreshold; i++) { outerTarget.call(); @@ -153,12 +163,9 @@ public void throwExceptionBoundary() { } final int totalExecutions = compilationThreshold + execCount; - int catchCount = ((DeoptCountingExceptionOverBoundaryRootNode) outerTarget.getRootNode()).catchCounter; - Assert.assertEquals("Incorrect number of catch block executions", totalExecutions, catchCount); - - int deopCount = ((DeoptCountingExceptionOverBoundaryRootNode) outerTarget.getRootNode()).deopCounter; - Assert.assertEquals("Incorrect number of deops detected!", 0, deopCount); + assertEquals("Incorrect number of catch block executions", totalExecutions, rootNode.catchCounter); + assertEquals("Incorrect number of deopts detected!", 0, rootNode.deoptCounter); } @Test @@ -170,14 +177,18 @@ protected DeoptCountingExceptionOverBoundaryRootNode() { super(null); } - int deopCounter = 0; + int deoptCounter = 0; @Override public Object execute(VirtualFrame frame) { boolean startedCompiled = CompilerDirectives.inCompiledCode(); - throwExceptionBoundary(); - if (startedCompiled && CompilerDirectives.inInterpreter()) { - deopCounter++; + try { + throwExceptionBoundary(); + } catch (Exception e) { + if (startedCompiled && CompilerDirectives.inInterpreter()) { + deoptCounter++; + } + throw e; } return null; } @@ -188,29 +199,40 @@ public void throwExceptionBoundary() { } } - final OptimizedCallTarget outerTarget = (OptimizedCallTarget) runtime.createCallTarget(new DeoptCountingExceptionOverBoundaryRootNode()); + DeoptCountingExceptionOverBoundaryRootNode rootNode = new DeoptCountingExceptionOverBoundaryRootNode(); + final OptimizedCallTarget outerTarget = (OptimizedCallTarget) runtime.createCallTarget(rootNode); for (int i = 0; i < compilationThreshold; i++) { try { outerTarget.call(); + fail(); } catch (RuntimeException e) { // do nothing } } + // deoptimizes immediately due to the exception + assertNotCompiled(outerTarget); + assertEquals("Incorrect number of deopts detected!", 1, rootNode.deoptCounter); + // recompile with exception branch + try { + outerTarget.call(); + fail(); + } catch (RuntimeException e) { + // do nothing + } assertCompiled(outerTarget); final int execCount = 10; for (int i = 0; i < execCount; i++) { try { outerTarget.call(); + fail(); } catch (RuntimeException e) { // do nothing } } - int deopCount = ((DeoptCountingExceptionOverBoundaryRootNode) outerTarget.getRootNode()).deopCounter; - Assert.assertEquals("Incorrect number of deops detected!", 0, deopCount); - + assertEquals("Incorrect number of deopts detected!", 1, rootNode.deoptCounter); } @Test @@ -222,14 +244,18 @@ protected DeoptCountingExceptionOverBoundaryRootNode() { super(null); } - int deopCounter = 0; + int deoptCounter = 0; @Override public Object execute(VirtualFrame frame) { boolean startedCompiled = CompilerDirectives.inCompiledCode(); - throwExceptionBoundary(); - if (startedCompiled && CompilerDirectives.inInterpreter()) { - deopCounter++; + try { + throwExceptionBoundary(); + } catch (Exception e) { + if (startedCompiled && CompilerDirectives.inInterpreter()) { + deoptCounter++; + } + throw e; } return null; } @@ -240,11 +266,13 @@ public void throwExceptionBoundary() { } } - final OptimizedCallTarget outerTarget = (OptimizedCallTarget) runtime.createCallTarget(new DeoptCountingExceptionOverBoundaryRootNode()); + DeoptCountingExceptionOverBoundaryRootNode rootNode = new DeoptCountingExceptionOverBoundaryRootNode(); + final OptimizedCallTarget outerTarget = (OptimizedCallTarget) runtime.createCallTarget(rootNode); for (int i = 0; i < compilationThreshold; i++) { try { outerTarget.call(); + fail(); } catch (RuntimeException e) { // do nothing } @@ -255,13 +283,12 @@ public void throwExceptionBoundary() { for (int i = 0; i < execCount; i++) { try { outerTarget.call(); + fail(); } catch (RuntimeException e) { // do nothing } } - int deopCount = ((DeoptCountingExceptionOverBoundaryRootNode) outerTarget.getRootNode()).deopCounter; - Assert.assertEquals("Incorrect number of deops detected!", 0, deopCount); - + assertEquals("Incorrect number of deopts detected!", 0, rootNode.deoptCounter); } } diff --git a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/GraalFeature.java b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/GraalFeature.java index d5db92bf6a05..027cc2a84564 100644 --- a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/GraalFeature.java +++ b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/GraalFeature.java @@ -71,6 +71,7 @@ import org.graalvm.compiler.phases.common.inlining.InliningUtil; import org.graalvm.compiler.phases.tiers.Suites; import org.graalvm.compiler.phases.util.Providers; +import org.graalvm.compiler.truffle.compiler.phases.DeoptimizeOnExceptionPhase; import org.graalvm.compiler.word.WordTypes; import org.graalvm.nativeimage.hosted.Feature; import org.graalvm.nativeimage.ImageSingletons; @@ -238,8 +239,8 @@ public static class RuntimeGraphBuilderPhase extends SubstrateGraphBuilderPhase RuntimeGraphBuilderPhase(Providers providers, GraphBuilderConfiguration graphBuilderConfig, OptimisticOptimizations optimisticOpts, IntrinsicContext initialIntrinsicContext, WordTypes wordTypes, - Predicate deoptimizeOnExceptionPredicate, CallTreeNode node) { - super(providers, graphBuilderConfig, optimisticOpts, initialIntrinsicContext, wordTypes, deoptimizeOnExceptionPredicate); + CallTreeNode node) { + super(providers, graphBuilderConfig, optimisticOpts, initialIntrinsicContext, wordTypes); this.node = node; } @@ -501,8 +502,7 @@ private void processMethod(CallTreeNode node, Deque worklist, BigB try (DebugContext.Scope scope = debug.scope("RuntimeCompile", graph)) { if (parse) { - RuntimeGraphBuilderPhase builderPhase = new RuntimeGraphBuilderPhase(hostedProviders, graphBuilderConfig, optimisticOpts, null, hostedProviders.getWordTypes(), - deoptimizeOnExceptionPredicate, node); + RuntimeGraphBuilderPhase builderPhase = new RuntimeGraphBuilderPhase(hostedProviders, graphBuilderConfig, optimisticOpts, null, hostedProviders.getWordTypes(), node); builderPhase.apply(graph); } @@ -520,6 +520,9 @@ private void processMethod(CallTreeNode node, Deque worklist, BigB } new CanonicalizerPhase().apply(graph, hostedProviders); + if (deoptimizeOnExceptionPredicate != null) { + new DeoptimizeOnExceptionPhase(deoptimizeOnExceptionPredicate).apply(graph); + } new ConvertDeoptimizeToGuardPhase().apply(graph, hostedProviders); graphEncoder.prepare(graph); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/SubstrateGraphMaker.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/SubstrateGraphMaker.java index f63e2313c01d..a938db5e47bd 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/SubstrateGraphMaker.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/SubstrateGraphMaker.java @@ -53,7 +53,7 @@ protected SubstrateGraphMaker(ReplacementsImpl replacements, ResolvedJavaMethod @Override protected Instance createGraphBuilder(Providers providers, GraphBuilderConfiguration graphBuilderConfig, OptimisticOptimizations optimisticOpts, IntrinsicContext initialIntrinsicContext) { - return new SubstrateGraphBuilderPhase(providers, graphBuilderConfig, optimisticOpts, initialIntrinsicContext, wordTypes, null); + return new SubstrateGraphBuilderPhase(providers, graphBuilderConfig, optimisticOpts, initialIntrinsicContext, wordTypes); } @Override diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/HostedGraphBuilderPhase.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/HostedGraphBuilderPhase.java index dcc349b8d97d..e17eabf8720d 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/HostedGraphBuilderPhase.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/HostedGraphBuilderPhase.java @@ -67,7 +67,7 @@ public class HostedGraphBuilderPhase extends SubstrateGraphBuilderPhase { public HostedGraphBuilderPhase(Providers providers, GraphBuilderConfiguration graphBuilderConfig, OptimisticOptimizations optimisticOpts, IntrinsicContext initialIntrinsicContext, WordTypes wordTypes) { - super(providers, graphBuilderConfig, optimisticOpts, initialIntrinsicContext, wordTypes, null); + super(providers, graphBuilderConfig, optimisticOpts, initialIntrinsicContext, wordTypes); } @Override diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/SubstrateGraphBuilderPhase.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/SubstrateGraphBuilderPhase.java index e4491e3867ac..c1ed0a2f0974 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/SubstrateGraphBuilderPhase.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/SubstrateGraphBuilderPhase.java @@ -24,18 +24,13 @@ */ package com.oracle.svm.hosted.phases; -import java.util.function.Predicate; - import org.graalvm.compiler.api.replacements.Fold; import org.graalvm.compiler.graph.Node; import org.graalvm.compiler.graph.Node.NodeIntrinsic; import org.graalvm.compiler.java.BytecodeParser; -import org.graalvm.compiler.java.FrameStateBuilder; import org.graalvm.compiler.java.GraphBuilderPhase; import org.graalvm.compiler.nodes.AbstractBeginNode; import org.graalvm.compiler.nodes.CallTargetNode; -import org.graalvm.compiler.nodes.DeoptimizeNode; -import org.graalvm.compiler.nodes.FixedWithNextNode; import org.graalvm.compiler.nodes.InvokeWithExceptionNode; import org.graalvm.compiler.nodes.KillingBeginNode; import org.graalvm.compiler.nodes.StructuredGraph; @@ -54,23 +49,16 @@ import com.oracle.svm.core.annotate.Uninterruptible; import com.oracle.svm.core.graal.nodes.SubstrateNewArrayNode; import com.oracle.svm.core.graal.nodes.SubstrateNewInstanceNode; -import com.oracle.svm.core.util.VMError; -import jdk.vm.ci.meta.DeoptimizationAction; -import jdk.vm.ci.meta.DeoptimizationReason; import jdk.vm.ci.meta.JavaKind; import jdk.vm.ci.meta.ResolvedJavaMethod; import jdk.vm.ci.meta.ResolvedJavaType; public class SubstrateGraphBuilderPhase extends SharedGraphBuilderPhase { - private final Predicate deoptimizeOnExceptionPredicate; - public SubstrateGraphBuilderPhase(Providers providers, - GraphBuilderConfiguration graphBuilderConfig, OptimisticOptimizations optimisticOpts, IntrinsicContext initialIntrinsicContext, WordTypes wordTypes, - Predicate deoptimizeOnExceptionPredicate) { + GraphBuilderConfiguration graphBuilderConfig, OptimisticOptimizations optimisticOpts, IntrinsicContext initialIntrinsicContext, WordTypes wordTypes) { super(providers, graphBuilderConfig, optimisticOpts, initialIntrinsicContext, wordTypes); - this.deoptimizeOnExceptionPredicate = deoptimizeOnExceptionPredicate != null ? deoptimizeOnExceptionPredicate : (method -> false); } @Override @@ -104,36 +92,6 @@ protected NewArrayNode createNewArray(ResolvedJavaType elementType, ValueNode le return new SubstrateNewArrayNode(elementType, length, fillContents, null); } - /** - * We do not have access to the inovked method i {@link #createHandleExceptionTarget}. - * Therefore, we need to make the decision whether to deoptimize in - * {@link #createInvokeWithException} and propagate the result via this field. - */ - private boolean curDeoptimizeOnException; - - @Override - protected void createHandleExceptionTarget(FixedWithNextNode afterExceptionLoaded, int bci, FrameStateBuilder dispatchState) { - if (curDeoptimizeOnException) { - DeoptimizeNode deoptimize = graph.add(new DeoptimizeNode(DeoptimizationAction.None, DeoptimizationReason.NotCompiledExceptionHandler)); - VMError.guarantee(afterExceptionLoaded.next() == null); - afterExceptionLoaded.setNext(deoptimize); - - } else { - super.createHandleExceptionTarget(afterExceptionLoaded, bci, dispatchState); - } - } - - @Override - protected InvokeWithExceptionNode createInvokeWithException(int invokeBci, CallTargetNode callTarget, JavaKind resultType, ExceptionEdgeAction exceptionEdgeAction) { - try { - assert curDeoptimizeOnException == false; - curDeoptimizeOnException = getGraphBuilderInstance().deoptimizeOnExceptionPredicate.test(callTarget.targetMethod()); - return super.createInvokeWithException(invokeBci, callTarget, resultType, exceptionEdgeAction); - } finally { - curDeoptimizeOnException = false; - } - } - /** * {@link Fold} and {@link NodeIntrinsic} can be deferred during parsing/decoding. Only by * the end of {@linkplain SnippetTemplate#instantiate Snippet instantiation} do they need to diff --git a/substratevm/src/com.oracle.svm.truffle/src/com/oracle/svm/truffle/api/TruffleBoundaryPhase.java b/substratevm/src/com.oracle.svm.truffle/src/com/oracle/svm/truffle/api/TruffleBoundaryPhase.java index 7062f5a3525a..4564f2d88633 100644 --- a/substratevm/src/com.oracle.svm.truffle/src/com/oracle/svm/truffle/api/TruffleBoundaryPhase.java +++ b/substratevm/src/com.oracle.svm.truffle/src/com/oracle/svm/truffle/api/TruffleBoundaryPhase.java @@ -24,20 +24,25 @@ */ package com.oracle.svm.truffle.api; -import org.graalvm.compiler.graph.Node; import org.graalvm.compiler.nodes.DeoptimizeNode; import org.graalvm.compiler.nodes.FixedNode; +import org.graalvm.compiler.nodes.Invoke; import org.graalvm.compiler.nodes.InvokeWithExceptionNode; import org.graalvm.compiler.nodes.StructuredGraph; import org.graalvm.compiler.nodes.java.ExceptionObjectNode; import org.graalvm.compiler.nodes.util.GraphUtil; import org.graalvm.compiler.phases.Phase; +import org.graalvm.compiler.truffle.compiler.PartialEvaluator; import com.oracle.svm.hosted.phases.SubstrateGraphBuilderPhase.SubstrateBytecodeParser; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import jdk.vm.ci.meta.DeoptimizationAction; import jdk.vm.ci.meta.DeoptimizationReason; +import jdk.vm.ci.meta.ResolvedJavaMethod; +import jdk.vm.ci.meta.SpeculationLog; +import jdk.vm.ci.meta.SpeculationLog.Speculation; +import jdk.vm.ci.meta.SpeculationLog.SpeculationReason; /** * Deoptimize for {@link TruffleBoundary} calls when {@link TruffleBoundary#transferToInterpreter()} @@ -51,17 +56,18 @@ public class TruffleBoundaryPhase extends Phase { @Override @SuppressWarnings("deprecation") protected void run(StructuredGraph graph) { - for (Node n : graph.getNodes()) { + for (Invoke n : graph.getInvokes()) { if (n instanceof InvokeWithExceptionNode) { InvokeWithExceptionNode invoke = (InvokeWithExceptionNode) n; ExceptionObjectNode exceptionObject = (ExceptionObjectNode) invoke.exceptionEdge(); FixedNode originalNext = exceptionObject.next(); if (!(originalNext instanceof DeoptimizeNode) && invoke.callTarget().targetMethod() != null) { - TruffleBoundary truffleBoundary = invoke.callTarget().targetMethod().getAnnotation(TruffleBoundary.class); + ResolvedJavaMethod targetMethod = invoke.callTarget().targetMethod(); + TruffleBoundary truffleBoundary = targetMethod.getAnnotation(TruffleBoundary.class); if (truffleBoundary != null) { if (truffleBoundary.transferToInterpreterOnException()) { - addDeoptimizeNode(graph, originalNext); + addDeoptimizeNode(graph, originalNext, targetMethod); } } } @@ -69,9 +75,16 @@ protected void run(StructuredGraph graph) { } } - private static void addDeoptimizeNode(StructuredGraph graph, FixedNode originalNext) { - DeoptimizeNode deoptimize = graph.add(new DeoptimizeNode(DeoptimizationAction.None, DeoptimizationReason.NotCompiledExceptionHandler)); - originalNext.replaceAtPredecessor(deoptimize); - GraphUtil.killCFG(originalNext); + private static void addDeoptimizeNode(StructuredGraph graph, FixedNode originalNext, ResolvedJavaMethod targetMethod) { + SpeculationLog speculationLog = graph.getSpeculationLog(); + if (speculationLog != null) { + SpeculationReason speculationReason = PartialEvaluator.createTruffleBoundaryExceptionSpeculation(targetMethod); + if (speculationLog.maySpeculate(speculationReason)) { + Speculation exceptionSpeculation = speculationLog.speculate(speculationReason); + DeoptimizeNode deoptimize = graph.add(new DeoptimizeNode(DeoptimizationAction.InvalidateRecompile, DeoptimizationReason.TransferToInterpreter, exceptionSpeculation)); + originalNext.replaceAtPredecessor(deoptimize); + GraphUtil.killCFG(originalNext); + } + } } } diff --git a/truffle/CHANGELOG.md b/truffle/CHANGELOG.md index 7b90913884d8..a449a0421011 100644 --- a/truffle/CHANGELOG.md +++ b/truffle/CHANGELOG.md @@ -8,6 +8,7 @@ This changelog summarizes major changes between Truffle versions relevant to lan * Removed deprecated methods`TruffleStackTraceElement#getStackTrace` and `TruffleStackTraceElement#fillIn` (use methods of `TruffleStackTrace` instead). * `SlowPathException#fillInStackTrace` is now `final`. * Added an ability to read a [path separator](https://www.graalvm.org/truffle/javadoc/com/oracle/truffle/api/TruffleLanguage.Env.html#getPathSeparator--) used to separate filenames in a path list. +* `@TruffleBoundary` methods that throw but are not annotated with `@TruffleBoundary(transferToInterpreterOnException=false)` will now transfer to the interpreter only once per `CallTarget` (compilation root). ## Version 19.0.0 * Renamed version 1.0.0 to 19.0.0 diff --git a/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/CompilerDirectives.java b/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/CompilerDirectives.java index 11b0d7fd97de..9fe6926950f5 100644 --- a/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/CompilerDirectives.java +++ b/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/CompilerDirectives.java @@ -265,8 +265,9 @@ public static void bailout(String reason) { public @interface TruffleBoundary { /** - * Determines whether execution should be transferred to the interpreter in the case that an - * exception is thrown across this boundary. + * Determines whether execution should be transferred to the interpreter if an exception is + * thrown across this boundary, in which case the caller's compiled code is invalidated and + * will not transfer to the interpreter on exceptions for this method again. * * @since 0.28 */ diff --git a/vm/mx.vm/suite.py b/vm/mx.vm/suite.py index 3ad4865706fc..d9862163c167 100644 --- a/vm/mx.vm/suite.py +++ b/vm/mx.vm/suite.py @@ -61,7 +61,7 @@ }, { "name": "fastr", - "version": "b6a319e06982464d5280b8d0691dc861b9e7192b", + "version": "0d5c6ae9127282147e401c8b3698787aedd55e41", "dynamic": True, "urls": [ {"url": "https://github.com/oracle/fastr.git", "kind": "git"},