diff --git a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/truffle/test/BytecodeDSLCompilationTest.java b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/truffle/test/BytecodeDSLCompilationTest.java index ce6aa48a12a7..e052ee46ee67 100644 --- a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/truffle/test/BytecodeDSLCompilationTest.java +++ b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/truffle/test/BytecodeDSLCompilationTest.java @@ -27,6 +27,7 @@ import static com.oracle.truffle.api.bytecode.test.basic_interpreter.AbstractBasicInterpreterTest.createNodes; import static com.oracle.truffle.api.bytecode.test.basic_interpreter.AbstractBasicInterpreterTest.parseNode; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.fail; import static org.junit.Assume.assumeTrue; @@ -44,11 +45,13 @@ import org.junit.runners.Parameterized.Parameters; import com.oracle.truffle.api.bytecode.BytecodeConfig; +import com.oracle.truffle.api.bytecode.BytecodeFrame; import com.oracle.truffle.api.bytecode.BytecodeLocal; import com.oracle.truffle.api.bytecode.BytecodeLocation; import com.oracle.truffle.api.bytecode.BytecodeNode; import com.oracle.truffle.api.bytecode.BytecodeParser; import com.oracle.truffle.api.bytecode.BytecodeRootNodes; +import com.oracle.truffle.api.bytecode.BytecodeTier; import com.oracle.truffle.api.bytecode.ContinuationResult; import com.oracle.truffle.api.bytecode.test.BytecodeDSLTestLanguage; import com.oracle.truffle.api.bytecode.test.basic_interpreter.AbstractBasicInterpreterTest; @@ -56,6 +59,7 @@ import com.oracle.truffle.api.bytecode.test.basic_interpreter.BasicInterpreter; import com.oracle.truffle.api.bytecode.test.basic_interpreter.BasicInterpreterBuilder; import com.oracle.truffle.api.bytecode.test.basic_interpreter.BasicInterpreterBuilder.BytecodeVariant; +import com.oracle.truffle.api.frame.FrameInstance; import com.oracle.truffle.api.frame.FrameSlotKind; import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.instrumentation.ExecutionEventNode; @@ -957,6 +961,176 @@ public void testTagInstrumentation() { assertCompiled(target); } + @Test + public void testCaptureFrame() { + BytecodeRootNodes rootNodes = createNodes(run, BytecodeDSLTestLanguage.REF.get(null), BytecodeConfig.DEFAULT, b -> { + b.beginRoot(); + b.beginReturn(); + b.beginCaptureFrame(); + b.emitLoadArgument(0); + b.emitLoadArgument(1); + b.endCaptureFrame(); + b.endReturn(); + BasicInterpreter callee = b.endRoot(); + callee.setName("callee"); + + b.beginRoot(); + BytecodeLocal x = b.createLocal(); + b.beginStoreLocal(x); + b.emitLoadConstant(123); + b.endStoreLocal(); + b.beginInvoke(); + b.emitLoadConstant(callee); + b.emitLoadArgument(0); + b.emitLoadArgument(1); + b.endInvoke(); + b.endRoot().setName("caller"); + }); + BasicInterpreter caller = rootNodes.getNode(1); + + OptimizedCallTarget target = (OptimizedCallTarget) caller.getCallTarget(); + + // The callee frame (the top of the stack) should never be accessible. + assertNull(target.call(0, FrameInstance.FrameAccess.READ_ONLY)); + + // In the interpreter the caller frame should always be accessible. + assertNotCompiled(target); + checkCallerBytecodeFrame((BytecodeFrame) target.call(1, FrameInstance.FrameAccess.READ_ONLY), false); + assertNotCompiled(target); + checkCallerBytecodeFrame((BytecodeFrame) target.call(1, FrameInstance.FrameAccess.READ_WRITE), false); + assertNotCompiled(target); + checkCallerBytecodeFrame((BytecodeFrame) target.call(1, FrameInstance.FrameAccess.MATERIALIZE), false); + + // Force transition to cached. + caller.getBytecodeNode().setUncachedThreshold(0); + target.call(0, FrameInstance.FrameAccess.READ_ONLY); + assertEquals(BytecodeTier.CACHED, caller.getBytecodeNode().getTier()); + + // In compiled code the caller frame should always be accessible, but may be a copy. + // Requesting the frame should not invalidate compiled code. + target.compile(true); + assertCompiled(target); + checkCallerBytecodeFrame((BytecodeFrame) target.call(1, FrameInstance.FrameAccess.READ_ONLY), true); + assertCompiled(target); + checkCallerBytecodeFrame((BytecodeFrame) target.call(1, FrameInstance.FrameAccess.READ_WRITE), false); + assertCompiled(target); + checkCallerBytecodeFrame((BytecodeFrame) target.call(1, FrameInstance.FrameAccess.MATERIALIZE), false); + assertCompiled(target); + } + + @Test + public void testCaptureNonVirtualFrame() { + BytecodeRootNodes rootNodes = createNodes(run, BytecodeDSLTestLanguage.REF.get(null), BytecodeConfig.DEFAULT, b -> { + b.beginRoot(); + b.beginReturn(); + b.beginCaptureNonVirtualFrame(); + b.emitLoadArgument(0); + b.endCaptureNonVirtualFrame(); + b.endReturn(); + BasicInterpreter callee = b.endRoot(); + callee.setName("callee"); + + b.beginRoot(); + BytecodeLocal x = b.createLocal(); + b.beginStoreLocal(x); + b.emitLoadConstant(123); + b.endStoreLocal(); + b.beginInvoke(); + b.emitLoadConstant(callee); + b.emitLoadArgument(0); + b.endInvoke(); + b.endRoot().setName("caller"); + }); + BasicInterpreter caller = rootNodes.getNode(1); + + OptimizedCallTarget target = (OptimizedCallTarget) caller.getCallTarget(); + + // The callee frame (the top of the stack) should never be accessible. + assertNull(target.call(0)); + + // In the interpreter the non-virtual caller frame should be accessible. + assertNotCompiled(target); + BytecodeFrame nonVirtualFrame = (BytecodeFrame) target.call(1); + assertNotCompiled(target); + checkCallerBytecodeFrame(nonVirtualFrame, false); + + // Force transition to cached. + caller.getBytecodeNode().setUncachedThreshold(0); + target.call(0); + assertEquals(BytecodeTier.CACHED, caller.getBytecodeNode().getTier()); + + // In compiled code the non-virtual caller frame should be inaccessible. + target.compile(true); + assertCompiled(target); + assertNull(target.call(1)); + assertCompiled(target); + } + + @Test + public void testCaptureNonVirtualFrameAfterMaterialization() { + BytecodeRootNodes rootNodes = createNodes(run, BytecodeDSLTestLanguage.REF.get(null), BytecodeConfig.DEFAULT, b -> { + b.beginRoot(); + b.beginReturn(); + b.beginCaptureNonVirtualFrame(); + b.emitLoadArgument(0); + b.endCaptureNonVirtualFrame(); + b.endReturn(); + BasicInterpreter callee = b.endRoot(); + callee.setName("callee"); + + b.beginRoot(); + BytecodeLocal x = b.createLocal(); + b.beginStoreLocal(x); + b.emitLoadConstant(123); + b.endStoreLocal(); + + b.beginBlackhole(); + b.emitMaterializeFrame(); // force materialize frame. + b.endBlackhole(); + + b.beginInvoke(); + b.emitLoadConstant(callee); + b.emitLoadArgument(0); + b.endInvoke(); + b.endRoot().setName("caller"); + }); + BasicInterpreter caller = rootNodes.getNode(1); + + OptimizedCallTarget target = (OptimizedCallTarget) caller.getCallTarget(); + + // The callee frame (the top of the stack) should never be accessible. + assertNull(target.call(0)); + + // In the interpreter the non-virtual caller frame should be accessible. + assertNotCompiled(target); + BytecodeFrame nonVirtualFrame = (BytecodeFrame) target.call(1); + assertNotCompiled(target); + checkCallerBytecodeFrame(nonVirtualFrame, false); + + // Force transition to cached. + caller.getBytecodeNode().setUncachedThreshold(0); + target.call(0); + assertEquals(BytecodeTier.CACHED, caller.getBytecodeNode().getTier()); + + // In compiled code the frame should be accessible because it was materialized already. + target.compile(true); + assertCompiled(target); + nonVirtualFrame = (BytecodeFrame) target.call(1); + checkCallerBytecodeFrame(nonVirtualFrame, false); + assertCompiled(target); + } + + private void checkCallerBytecodeFrame(BytecodeFrame bytecodeFrame, boolean isCopy) { + assertNotNull(bytecodeFrame); + assertEquals(1, bytecodeFrame.getLocalCount()); + if (isCopy || AbstractBasicInterpreterTest.hasRootScoping(run.interpreterClass())) { + assertEquals(123, bytecodeFrame.getLocalValue(0)); + } else { + // the local gets cleared on exit. + assertEquals(AbstractBasicInterpreterTest.getDefaultLocalValue(run.interpreterClass()), bytecodeFrame.getLocalValue(0)); + } + } + @TruffleInstrument.Registration(id = BytecodeDSLCompilationTestInstrumentation.ID, services = Instrumenter.class) public static class BytecodeDSLCompilationTestInstrumentation extends TruffleInstrument { diff --git a/truffle/CHANGELOG.md b/truffle/CHANGELOG.md index 1bab0733a35b..7a5f2b4a35d2 100644 --- a/truffle/CHANGELOG.md +++ b/truffle/CHANGELOG.md @@ -38,6 +38,8 @@ This changelog summarizes major changes between Truffle versions relevant to lan * GR-70086: Added `replacementOf` and `replacementMethod` attributes to `GenerateLibrary.Abstract` annotation. They enable automatic generation of legacy delegators during message library evolution, while allowing custom conversions when needed. * GR-70086 Deprecated `Message.resolve(Class, String)`. Use `Message.resolveExact(Class, String, Class...)` with argument types instead. This deprecation was necessary as library messages are no longer unique by message name, if the previous message was deprecated. +* GR-69861: Bytecode DSL: Added a `BytecodeFrame` abstraction for capturing frame state and accessing frame data. This abstraction should be preferred over `BytecodeNode` access methods because it captures the correct interpreter location data. +* GR-69861: Bytecode DSL: Added a `captureFramesForTrace` parameter to `@GenerateBytecode` that enables capturing of frames in `TruffleStackTraceElement`s. Previously, frame data was unreliably available in stack traces; now, it is guaranteed to be available if requested. Languages must use the `BytecodeFrame` abstraction to access frame data from `TruffleStackTraceElement`s rather than access the frame directly. ## Version 25.0 * GR-31495 Added ability to specify language and instrument specific options using `Source.Builder.option(String, String)`. Languages may describe available source options by implementing `TruffleLanguage.getSourceOptionDescriptors()` and `TruffleInstrument.getSourceOptionDescriptors()` respectively. diff --git a/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/BytecodeDSLTestLanguage.java b/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/BytecodeDSLTestLanguage.java index 1e686b040d7b..0d79831b5237 100644 --- a/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/BytecodeDSLTestLanguage.java +++ b/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/BytecodeDSLTestLanguage.java @@ -40,12 +40,15 @@ */ package com.oracle.truffle.api.bytecode.test; +import org.graalvm.polyglot.Context; + import com.oracle.truffle.api.TruffleLanguage; import com.oracle.truffle.api.instrumentation.ProvidedTags; import com.oracle.truffle.api.instrumentation.StandardTags.ExpressionTag; import com.oracle.truffle.api.instrumentation.StandardTags.RootBodyTag; import com.oracle.truffle.api.instrumentation.StandardTags.RootTag; import com.oracle.truffle.api.instrumentation.StandardTags.StatementTag; +import com.oracle.truffle.tck.tests.TruffleTestAssumptions; /** * Placeholder language for Bytecode DSL test interpreters. @@ -60,5 +63,19 @@ protected Object createContext(Env env) { return new Object(); } + /** + * Ensures compilation is disabled (when supported). This allows tests to validate the behaviour + * of assertions, which are optimized away in compiled code. + */ + public static Context createPolyglotContextWithCompilationDisabled() { + var builder = Context.newBuilder(ID); + if (TruffleTestAssumptions.isOptimizingRuntime()) { + builder.option("engine.Compilation", "false"); + } + Context result = builder.build(); + result.enter(); + return result; + } + public static final LanguageReference REF = LanguageReference.create(BytecodeDSLTestLanguage.class); } diff --git a/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/LocalHelpersTest.java b/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/LocalHelpersTest.java index 3594e3979313..eb4545a7a5c9 100644 --- a/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/LocalHelpersTest.java +++ b/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/LocalHelpersTest.java @@ -51,7 +51,9 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.Function; +import org.graalvm.polyglot.Context; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -65,7 +67,10 @@ import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.RootCallTarget; import com.oracle.truffle.api.Truffle; +import com.oracle.truffle.api.TruffleStackTrace; + import com.oracle.truffle.api.bytecode.BytecodeConfig; +import com.oracle.truffle.api.bytecode.BytecodeFrame; import com.oracle.truffle.api.bytecode.BytecodeLocal; import com.oracle.truffle.api.bytecode.BytecodeNode; import com.oracle.truffle.api.bytecode.BytecodeParser; @@ -85,6 +90,7 @@ import com.oracle.truffle.api.dsl.Bind; import com.oracle.truffle.api.dsl.Cached; import com.oracle.truffle.api.dsl.Cached.Shared; +import com.oracle.truffle.api.exception.AbstractTruffleException; import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.frame.FrameDescriptor; import com.oracle.truffle.api.frame.FrameInstance; @@ -94,6 +100,7 @@ import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.nodes.DirectCallNode; import com.oracle.truffle.api.nodes.IndirectCallNode; +import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.nodes.RootNode; import com.oracle.truffle.api.nodes.UnexpectedResultException; @@ -133,6 +140,11 @@ private boolean hasBoxingElimination() { interpreterClass == BytecodeNodeWithLocalIntrospectionWithBEIllegal.class; } + private boolean capturesFrameForTrace() { + Class interpreterClass = bytecode.getGeneratedClass(); + return interpreterClass != BytecodeNodeWithLocalIntrospectionBaseNoCapturedFrames.class; + } + public BytecodeRootNodes parseNodes(BytecodeParser builder) { return bytecode.create(null, BytecodeConfig.DEFAULT, builder); } @@ -882,36 +894,140 @@ public void testSetLocalUsingBytecodeLocalIndex() { } @Test - public void testGetLocalsSimpleStacktrace() { + public void testCreateMaterializedBytecodeFrame() { /* @formatter:off - * - * def bar() { - * y = 42 - * z = "hello" - * - * } - * - * def foo() { - * x = 123 - * } - * - * @formatter:on - */ - CallTarget collectFrames = new RootNode(null) { - @Override - public Object execute(VirtualFrame frame) { - List frames = new ArrayList<>(); - Truffle.getRuntime().iterateFrames(f -> { - frames.add(f); - return null; - }); - return frames; - } - }.getCallTarget(); + * + * foo = 42 + * bar = 123 + * yield createMaterializedFrame() + * return (arg0, foo) + * + * @formatter:on + */ + BytecodeNodeWithLocalIntrospection root = parseNode(b -> { + b.beginRoot(); + b.beginBlock(); + BytecodeLocal foo = makeLocal(b, "foo"); + BytecodeLocal bar = makeLocal(b, "bar"); - BytecodeNodeWithLocalIntrospection bar = parseNode(b -> { + b.beginStoreLocal(foo); + b.emitLoadConstant(42); + b.endStoreLocal(); + + b.beginStoreLocal(bar); + b.emitLoadConstant(123); + b.endStoreLocal(); + + b.beginYield(); + b.emitCreateMaterializedBytecodeFrame(); + b.endYield(); + + b.beginReturn(); + b.beginMakePair(); + b.emitLoadArgument(0); + b.emitLoadLocal(foo); + b.endMakePair(); + b.endReturn(); + + b.endBlock(); + b.endRoot(); + }); + + ContinuationResult cont = (ContinuationResult) root.getCallTarget().call(444); + BytecodeFrame bytecodeFrame = (BytecodeFrame) cont.getResult(); + assertEquals(2, bytecodeFrame.getLocalCount()); + assertArrayEquals(new Object[]{"foo", "bar"}, bytecodeFrame.getLocalNames()); + assertEquals(42, bytecodeFrame.getLocalValue(0)); + assertEquals(123, bytecodeFrame.getLocalValue(1)); + assertEquals(1, bytecodeFrame.getArgumentCount()); + assertEquals(444, bytecodeFrame.getArgument(0)); + // Updates to a materialized frame should be visible. + bytecodeFrame.setArgument(0, -444); + assertEquals(-444, bytecodeFrame.getArgument(0)); + bytecodeFrame.setLocalValue(0, -42); + assertEquals(-42, bytecodeFrame.getLocalValue(0)); + assertEquals(new Pair(-444, -42), cont.continueWith(null)); + } + + @Test + public void testCreateCopiedBytecodeFrame() { + /* @formatter:off + * + * foo = 42 + * bar = 123 + * yield createCopiedFrame() + * return (arg0, foo) + * + * @formatter:on + */ + BytecodeNodeWithLocalIntrospection root = parseNode(b -> { b.beginRoot(); + b.beginBlock(); + BytecodeLocal foo = makeLocal(b, "foo"); + BytecodeLocal bar = makeLocal(b, "bar"); + + b.beginStoreLocal(foo); + b.emitLoadConstant(42); + b.endStoreLocal(); + + b.beginStoreLocal(bar); + b.emitLoadConstant(123); + b.endStoreLocal(); + b.beginYield(); + b.emitCreateCopiedBytecodeFrame(); + b.endYield(); + + b.beginReturn(); + b.beginMakePair(); + b.emitLoadArgument(0); + b.emitLoadLocal(foo); + b.endMakePair(); + b.endReturn(); + + b.endBlock(); + b.endRoot(); + }); + + ContinuationResult cont = (ContinuationResult) root.getCallTarget().call(444); + BytecodeFrame bytecodeFrame = (BytecodeFrame) cont.getResult(); + assertEquals(2, bytecodeFrame.getLocalCount()); + assertArrayEquals(new Object[]{"foo", "bar"}, bytecodeFrame.getLocalNames()); + assertEquals(42, bytecodeFrame.getLocalValue(0)); + assertEquals(123, bytecodeFrame.getLocalValue(1)); + assertEquals(1, bytecodeFrame.getArgumentCount()); + assertEquals(444, bytecodeFrame.getArgument(0)); + // Updates to a copied frame should not be visible. + bytecodeFrame.setArgument(0, -444); + assertEquals(-444, bytecodeFrame.getArgument(0)); + bytecodeFrame.setLocalValue(0, -42); + assertEquals(-42, bytecodeFrame.getLocalValue(0)); + assertEquals(new Pair(444, 42), cont.continueWith(null)); + } + + private void doTestBytecodeFrameGet(boolean yield, Function frameCaptured, boolean frameWritable, RootNode collectBytecodeFrames) { + /* @formatter:off + * + * def bar(arg0) { + * y = 42 + * z = "hello" + * if (yield) y = yield 0 + * return collectBytecodeFrames() + * } + * + * def foo(arg0) { + * x = 123 + * if (yield) return continue(bar(444), 43) + * else bar(444) + * } + * + * foo(222) + * + * @formatter:on + */ + + BytecodeNodeWithLocalIntrospection bar = parseNode(b -> { + b.beginRoot(); b.beginBlock(); BytecodeLocal y = makeLocal(b, "y"); @@ -924,14 +1040,21 @@ public Object execute(VirtualFrame frame) { b.emitLoadConstant("hello"); b.endStoreLocal(); + if (yield) { + b.beginStoreLocal(y); + b.beginYield(); + b.emitLoadConstant(0); + b.endYield(); + b.endStoreLocal(); + } + b.beginReturn(); b.beginInvoke(); - b.emitLoadConstant(collectFrames); + b.emitLoadConstant(collectBytecodeFrames.getCallTarget()); b.endInvoke(); b.endReturn(); b.endBlock(); - b.endRoot(); }); @@ -946,9 +1069,17 @@ public Object execute(VirtualFrame frame) { b.endStoreLocal(); b.beginReturn(); + if (yield) { + b.beginContinue(); + } b.beginInvoke(); b.emitLoadConstant(bar); + b.emitLoadConstant(444); b.endInvoke(); + if (yield) { + b.emitLoadConstant(43); + b.endContinue(); + } b.endReturn(); b.endBlock(); @@ -956,121 +1087,185 @@ public Object execute(VirtualFrame frame) { b.endRoot(); }); - Object result = foo.getCallTarget().call(); + Object result = foo.getCallTarget().call(222); assertTrue(result instanceof List); @SuppressWarnings("unchecked") - List frames = (List) result; + List frames = (List) result; assertEquals(3, frames.size()); // - assertNull(BytecodeNode.getLocalValues(frames.get(0))); + assertNull(frames.get(0)); // bar - Object[] barLocals = BytecodeNode.getLocalValues(frames.get(1)); - assertArrayEquals(new Object[]{42, "hello"}, barLocals); - Object[] barLocalNames = BytecodeNode.getLocalNames(frames.get(1)); - assertArrayEquals(new Object[]{"y", "z"}, barLocalNames); - BytecodeNode.setLocalValues(frames.get(1), new Object[]{-42, "goodbye"}); - assertArrayEquals(new Object[]{-42, "goodbye"}, BytecodeNode.getLocalValues(frames.get(1))); + BytecodeFrame barFrame = frames.get(1); + if (frameCaptured.apply(1)) { + assertEquals(2, barFrame.getLocalCount()); + if (yield) { + assertEquals(43, barFrame.getLocalValue(0)); + } else { + assertEquals(42, barFrame.getLocalValue(0)); + } + assertEquals("hello", barFrame.getLocalValue(1)); + if (frameWritable) { + barFrame.setLocalValue(0, -42); + assertEquals(-42, barFrame.getLocalValue(0)); + } + assertArrayEquals(new Object[]{"y", "z"}, barFrame.getLocalNames()); + assertEquals(1, barFrame.getArgumentCount()); + assertEquals(444, barFrame.getArgument(0)); + assertEquals(BytecodeNodeWithLocalIntrospection.FRAME_DESCRIPTOR_INFO, barFrame.getFrameDescriptorInfo()); + } else { + assertNull(barFrame); + } // foo - Object[] fooLocals = BytecodeNode.getLocalValues(frames.get(2)); - assertArrayEquals(new Object[]{123}, fooLocals); - Object[] fooLocalNames = BytecodeNode.getLocalNames(frames.get(2)); - assertArrayEquals(new Object[]{"x"}, fooLocalNames); - BytecodeNode.setLocalValues(frames.get(2), new Object[]{456}); - assertArrayEquals(new Object[]{456}, BytecodeNode.getLocalValues(frames.get(2))); + BytecodeFrame fooFrame = frames.get(2); + if (frameCaptured.apply(2)) { + assertEquals(1, fooFrame.getLocalCount()); + assertEquals(123, fooFrame.getLocalValue(0)); + if (frameWritable) { + barFrame.setLocalValue(0, 456); + assertEquals(456, barFrame.getLocalValue(0)); + } + assertArrayEquals(new Object[]{"x"}, fooFrame.getLocalNames()); + assertEquals(1, fooFrame.getArgumentCount()); + assertEquals(222, fooFrame.getArgument(0)); + assertEquals(BytecodeNodeWithLocalIntrospection.FRAME_DESCRIPTOR_INFO, fooFrame.getFrameDescriptorInfo()); + } else { + assertNull(fooFrame); + } } @Test - public void testGetLocalsContinuationStacktrace() { - /* @formatter:off - * - * def bar() { - * y = yield 0 - * - * } - * - * def foo() { - * x = 123 - * continue(bar(), 42) - * } - * - * @formatter:on - */ - CallTarget collectFrames = new RootNode(null) { + public void testBytecodeFrameGetFrameInstances() { + doTestBytecodeFrameGet(false, LocalHelpersTest::alwaysCaptured, true, new RootNode(null) { @Override public Object execute(VirtualFrame frame) { - List frames = new ArrayList<>(); + List frames = new ArrayList<>(); Truffle.getRuntime().iterateFrames(f -> { - frames.add(BytecodeNode.getLocalValues(f)); + frames.add(BytecodeFrame.get(f, FrameInstance.FrameAccess.READ_WRITE)); return null; }); return frames; } - }.getCallTarget(); - - BytecodeNodeWithLocalIntrospection bar = parseNode(b -> { - b.beginRoot(); - - BytecodeLocal y = makeLocal(b, "y"); - - b.beginStoreLocal(y); - b.beginYield(); - b.emitLoadConstant(0); - b.endYield(); - b.endStoreLocal(); - - b.beginReturn(); - b.beginInvoke(); - b.emitLoadConstant(collectFrames); - b.endInvoke(); - b.endReturn(); - - b.endRoot(); }); + } - BytecodeNodeWithLocalIntrospection foo = parseNode(b -> { - b.beginRoot(); - BytecodeLocal x = makeLocal(b, "x"); - - b.beginStoreLocal(x); - b.emitLoadConstant(123); - b.endStoreLocal(); - - b.beginReturn(); - b.beginContinue(); + @Test + public void testBytecodeFrameGetFrameInstancesContinuation() { + doTestBytecodeFrameGet(true, LocalHelpersTest::alwaysCaptured, true, new RootNode(null) { + @Override + public Object execute(VirtualFrame frame) { + List frames = new ArrayList<>(); + Truffle.getRuntime().iterateFrames(f -> { + frames.add(BytecodeFrame.get(f, FrameInstance.FrameAccess.READ_WRITE)); + return null; + }); + return frames; + } + }); + } - b.beginInvoke(); - b.emitLoadConstant(bar); - b.endInvoke(); + @Test + public void testBytecodeFrameGetNonVirtualFrameInstances() { + try (Context c = BytecodeDSLTestLanguage.createPolyglotContextWithCompilationDisabled()) { + doTestBytecodeFrameGet(false, LocalHelpersTest::alwaysCaptured, true, new RootNode(null) { + @Override + public Object execute(VirtualFrame frame) { + List frames = new ArrayList<>(); + Truffle.getRuntime().iterateFrames(f -> { + frames.add(BytecodeFrame.getNonVirtual(f)); + return null; + }); + return frames; + } + }); + } + } - b.emitLoadConstant(42); + @Test + public void testBytecodeFrameGetNonVirtualFrameInstancesContinuation() { + try (Context c = BytecodeDSLTestLanguage.createPolyglotContextWithCompilationDisabled()) { + doTestBytecodeFrameGet(true, LocalHelpersTest::alwaysCaptured, true, new RootNode(null) { + @Override + public Object execute(VirtualFrame frame) { + List frames = new ArrayList<>(); + Truffle.getRuntime().iterateFrames(f -> { + frames.add(BytecodeFrame.getNonVirtual(f)); + return null; + }); + return frames; + } + }); + } + } - b.endContinue(); - b.endReturn(); + @Test + public void testBytecodeFrameGetTruffleStackTraceElement() { + doTestBytecodeFrameGet(false, unused -> capturesFrameForTrace(), false, new RootNode(null) { + @Override + public Object execute(VirtualFrame frame) { + return getBytecodeFrames(new TestException(this)); + } + }); + } - b.endRoot(); + @Test + public void testBytecodeFrameGetTruffleStackTraceElementContinuation() { + doTestBytecodeFrameGet(true, unused -> capturesFrameForTrace(), false, new RootNode(null) { + @Override + public Object execute(VirtualFrame frame) { + return getBytecodeFrames(new TestException(this)); + } }); + } - Object result = foo.getCallTarget().call(); - assertTrue(result instanceof List); + @Test + public void testBytecodeFrameGetNonVirtualTruffleStackTraceElement() { + try (Context c = BytecodeDSLTestLanguage.createPolyglotContextWithCompilationDisabled()) { + // stack trace elements capture read-only copies, so getNonVirtual is always null. + doTestBytecodeFrameGet(false, LocalHelpersTest::neverCaptured, true, new RootNode(null) { + @Override + public Object execute(VirtualFrame frame) { + return getNonVirtualBytecodeFrames(new TestException(this)); + } + }); + } + } - @SuppressWarnings("unchecked") - List frames = (List) result; - assertEquals(3, frames.size()); + @Test + public void testBytecodeFrameGetNonVirtualTruffleStackTraceElementContinuation() { + try (Context c = BytecodeDSLTestLanguage.createPolyglotContextWithCompilationDisabled()) { + // getNonVirtual returns a result for continuation frames because they're materialized. + Function frameCaptured = index -> index == 1 && capturesFrameForTrace(); + doTestBytecodeFrameGet(true, frameCaptured, true, new RootNode(null) { + @Override + public Object execute(VirtualFrame frame) { + return getNonVirtualBytecodeFrames(new TestException(this)); + } + }); + } + } - // - assertNull(frames.get(0)); + @SuppressWarnings("unused") + private static boolean alwaysCaptured(int unused) { + return true; + } - // bar - Object[] barLocals = frames.get(1); - assertArrayEquals(new Object[]{42}, barLocals); + @SuppressWarnings("unused") + private static boolean neverCaptured(int unused) { + return false; + } - // foo - Object[] fooLocals = frames.get(2); - assertArrayEquals(new Object[]{123}, fooLocals); + @TruffleBoundary + private static List getBytecodeFrames(AbstractTruffleException ex) { + return TruffleStackTrace.getStackTrace(ex).stream().map(BytecodeFrame::get).toList(); + } + + @TruffleBoundary + private static List getNonVirtualBytecodeFrames(AbstractTruffleException ex) { + return TruffleStackTrace.getStackTrace(ex).stream().map(BytecodeFrame::getNonVirtual).toList(); } @Test @@ -1632,51 +1827,62 @@ public void testGetLocalMetadataMaterializedAccessor() { @GenerateBytecodeTestVariants({ @Variant(suffix = "Base", configuration = @GenerateBytecode(languageClass = BytecodeDSLTestLanguage.class, // enableYield = true, // - enableMaterializedLocalAccesses = true)), + enableMaterializedLocalAccesses = true, // + captureFramesForTrace = true)), @Variant(suffix = "BaseDefault", configuration = @GenerateBytecode(languageClass = BytecodeDSLTestLanguage.class, // defaultLocalValue = "DEFAULT", // enableYield = true, // - enableMaterializedLocalAccesses = true)), + enableMaterializedLocalAccesses = true, // + captureFramesForTrace = true)), + @Variant(suffix = "BaseNoCapturedFrames", configuration = @GenerateBytecode(languageClass = BytecodeDSLTestLanguage.class, // + enableYield = true, // + enableMaterializedLocalAccesses = true, // + captureFramesForTrace = false)), @Variant(suffix = "WithBEIllegal", configuration = @GenerateBytecode(languageClass = BytecodeDSLTestLanguage.class, // enableQuickening = true, // enableUncachedInterpreter = true, // boxingEliminationTypes = {boolean.class, long.class}, // enableYield = true, // - enableMaterializedLocalAccesses = true)), + enableMaterializedLocalAccesses = true, // + captureFramesForTrace = true)), @Variant(suffix = "WithBEIllegalRootScoped", configuration = @GenerateBytecode(languageClass = BytecodeDSLTestLanguage.class, // enableQuickening = true, // enableUncachedInterpreter = true, // boxingEliminationTypes = {boolean.class, long.class}, // enableBlockScoping = false, // enableYield = true, // - enableMaterializedLocalAccesses = true)), + enableMaterializedLocalAccesses = true, // + captureFramesForTrace = true)), @Variant(suffix = "WithBEObjectDefault", configuration = @GenerateBytecode(languageClass = BytecodeDSLTestLanguage.class, // enableQuickening = true, // boxingEliminationTypes = {boolean.class, long.class}, // enableUncachedInterpreter = true, // defaultLocalValue = "resolveDefault()", // enableYield = true, // - enableMaterializedLocalAccesses = true)), + enableMaterializedLocalAccesses = true, // + captureFramesForTrace = true)), @Variant(suffix = "WithBENullDefault", configuration = @GenerateBytecode(languageClass = BytecodeDSLTestLanguage.class, // enableQuickening = true, // boxingEliminationTypes = {boolean.class, long.class}, // enableUncachedInterpreter = true, // defaultLocalValue = "null", // enableYield = true, // - enableMaterializedLocalAccesses = true)) + enableMaterializedLocalAccesses = true, // + captureFramesForTrace = true)) }) abstract class BytecodeNodeWithLocalIntrospection extends DebugBytecodeRootNode implements BytecodeRootNode { @CompilationFinal public int reservedLocalIndex = -1; static final Object DEFAULT = new Object(); + static final Object FRAME_DESCRIPTOR_INFO = new Object(); static Object resolveDefault() { CompilerAsserts.neverPartOfCompilation("Must be cached and not triggered during compilation."); return DEFAULT; } - protected BytecodeNodeWithLocalIntrospection(BytecodeDSLTestLanguage language, FrameDescriptor frameDescriptor) { - super(language, frameDescriptor); + protected BytecodeNodeWithLocalIntrospection(BytecodeDSLTestLanguage language, FrameDescriptor.Builder frameDescriptorBuilder) { + super(language, frameDescriptorBuilder.info(FRAME_DESCRIPTOR_INFO).build()); } @Operation @@ -2044,6 +2250,22 @@ public static void perform(VirtualFrame frame, Object value, } } + @Operation + public static final class CreateMaterializedBytecodeFrame { + @Specialization + public static BytecodeFrame perform(VirtualFrame frame, @Bind BytecodeNode node, @Bind("$bytecodeIndex") int bci) { + return node.createMaterializedFrame(bci, frame.materialize()); + } + } + + @Operation + public static final class CreateCopiedBytecodeFrame { + @Specialization + public static BytecodeFrame perform(VirtualFrame frame, @Bind BytecodeNode node, @Bind("$bytecodeIndex") int bci) { + return node.createCopiedFrame(bci, frame); + } + } + @Operation public static final class Same { @Specialization @@ -2110,3 +2332,12 @@ public static Pair doMakePair(Object left, Object right) { record Pair(Object left, Object right) { } + +@SuppressWarnings("serial") +class TestException extends AbstractTruffleException { + + TestException(Node location) { + super(location); + } + +} diff --git a/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/StackTraceTest.java b/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/StackTraceTest.java index 808df07448db..1314346c55b1 100644 --- a/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/StackTraceTest.java +++ b/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/StackTraceTest.java @@ -45,10 +45,13 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.junit.Assume.assumeTrue; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import org.graalvm.polyglot.Context; @@ -63,22 +66,29 @@ import com.oracle.truffle.api.CallTarget; import com.oracle.truffle.api.CompilerDirectives; +import com.oracle.truffle.api.Truffle; import com.oracle.truffle.api.TruffleStackTrace; import com.oracle.truffle.api.TruffleStackTraceElement; import com.oracle.truffle.api.bytecode.BytecodeConfig; +import com.oracle.truffle.api.bytecode.BytecodeLocal; import com.oracle.truffle.api.bytecode.BytecodeNode; import com.oracle.truffle.api.bytecode.BytecodeParser; import com.oracle.truffle.api.bytecode.BytecodeRootNode; import com.oracle.truffle.api.bytecode.BytecodeRootNodes; import com.oracle.truffle.api.bytecode.ConstantOperand; +import com.oracle.truffle.api.bytecode.ContinuationResult; import com.oracle.truffle.api.bytecode.GenerateBytecode; import com.oracle.truffle.api.bytecode.GenerateBytecodeTestVariants; import com.oracle.truffle.api.bytecode.GenerateBytecodeTestVariants.Variant; +import com.oracle.truffle.api.bytecode.Instruction; import com.oracle.truffle.api.bytecode.Operation; import com.oracle.truffle.api.dsl.Bind; import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.exception.AbstractTruffleException; import com.oracle.truffle.api.frame.FrameDescriptor; +import com.oracle.truffle.api.frame.FrameInstance; +import com.oracle.truffle.api.frame.FrameInstanceVisitor; +import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.interop.ArityException; import com.oracle.truffle.api.interop.InteropLibrary; import com.oracle.truffle.api.interop.TruffleObject; @@ -89,6 +99,7 @@ import com.oracle.truffle.api.library.ExportMessage; import com.oracle.truffle.api.nodes.EncapsulatingNodeReference; import com.oracle.truffle.api.nodes.Node; +import com.oracle.truffle.api.nodes.RootNode; import com.oracle.truffle.api.source.Source; @RunWith(Parameterized.class) @@ -166,10 +177,7 @@ public void testThrow() { Assert.fail(); } catch (TestException e) { List elements = TruffleStackTrace.getStackTrace(e); - assertEquals(nodes.length, elements.size()); - for (int i = 0; i < nodes.length; i++) { - assertStackElement(elements.get(i), nodes[i], false); - } + assertStackElements(nodes, elements, "c.ThrowError", "c.Call", false); } } } @@ -192,10 +200,7 @@ public void testThrowBehindInterop() { Assert.fail(); } catch (TestException e) { List elements = TruffleStackTrace.getStackTrace(e); - assertEquals(nodes.length, elements.size()); - for (int i = 0; i < nodes.length; i++) { - assertStackElement(elements.get(i), nodes[i], false); - } + assertStackElements(nodes, elements, "c.ThrowErrorBehindInterop", "c.Call", false); } } } @@ -216,10 +221,7 @@ public void testCapture() { for (int repeat = 0; repeat < REPEATS; repeat++) { List elements = (List) outer.getCallTarget().call(); - assertEquals(nodes.length, elements.size()); - for (int i = 0; i < nodes.length; i++) { - assertStackElement(elements.get(i), nodes[i], false); - } + assertStackElements(nodes, elements, "c.CaptureStack", "c.Call", false); } } @@ -244,15 +246,146 @@ public void testCaptureWithSources() { for (int repeat = 0; repeat < REPEATS; repeat++) { List elements = (List) outer.getCallTarget().call(); - assertEquals(nodes.length, elements.size()); - for (int i = 0; i < nodes.length; i++) { - assertStackElement(elements.get(i), nodes[i], true); - } + assertStackElements(nodes, elements, "c.CaptureStack", "c.Call", true); } } - private void assertStackElement(TruffleStackTraceElement element, StackTraceTestRootNode target, boolean checkSources) { - assertSame(target.getCallTarget(), element.getTarget()); + @Test + @SuppressWarnings("unchecked") + public void testValidateFrameInstances() { + int depth = run.depth; + StackTraceTestRootNode[] nodesConstant = new StackTraceTestRootNode[run.depth]; + StackTraceTestRootNode[] nodes = chainCalls(depth, b -> { + b.beginRoot(); + b.emitDummy(); + b.emitValidateFrameInstances(run, nodesConstant, "c.Call"); + b.endRoot(); + }, true, false); + StackTraceTestRootNode outer = nodes[nodes.length - 1]; + System.arraycopy(nodes, 0, nodesConstant, 0, depth); + + for (int repeat = 0; repeat < REPEATS; repeat++) { + outer.getCallTarget().call(); + } + } + + @Test + @SuppressWarnings("unchecked") + public void testCaptureResumed() { + int dept = run.depth; + StackTraceTestRootNode[] nodes = chainResumes(dept, b -> { + b.beginRoot(); + b.beginYield(); + b.emitLoadNull(); + b.endYield(); + b.beginReturn(); + b.emitCaptureStack(); + b.endReturn(); + b.endRoot(); + }, false); + StackTraceTestRootNode outer = nodes[nodes.length - 1]; + + for (int repeat = 0; repeat < REPEATS; repeat++) { + ContinuationResult outerCont = (ContinuationResult) outer.getCallTarget().call(); + List elements = (List) outerCont.continueWith(null); + assertStackElements(nodes, elements, "c.CaptureStack", "c.Resume", false); + } + } + + @Test + @SuppressWarnings("unchecked") + public void testCaptureResumedWithSources() { + int dept = run.depth; + Source s = Source.newBuilder(BytecodeDSLTestLanguage.ID, "root0", "root0.txt").build(); + StackTraceTestRootNode[] nodes = chainResumes(dept, b -> { + b.beginSource(s); + b.beginSourceSection(0, "root0".length()); + b.beginRoot(); + b.beginYield(); + b.emitLoadNull(); + b.endYield(); + b.beginReturn(); + b.emitCaptureStack(); + b.endReturn(); + b.endRoot(); + b.endSourceSection(); + b.endSource(); + }, true); + StackTraceTestRootNode outer = nodes[nodes.length - 1]; + + for (int repeat = 0; repeat < REPEATS; repeat++) { + ContinuationResult outerCont = (ContinuationResult) outer.getCallTarget().call(); + List elements = (List) outerCont.continueWith(null); + assertStackElements(nodes, elements, "c.CaptureStack", "c.Resume", true); + } + } + + @Test + @SuppressWarnings("unchecked") + public void testValidateResumedFrameInstances() { + int depth = run.depth; + StackTraceTestRootNode[] nodesConstant = new StackTraceTestRootNode[run.depth]; + StackTraceTestRootNode[] nodes = chainResumes(depth, b -> { + b.beginRoot(); + b.beginYield(); + b.emitLoadNull(); + b.endYield(); + b.emitValidateFrameInstances(run, nodesConstant, "c.Resume"); + b.endRoot(); + }, false); + StackTraceTestRootNode outer = nodes[nodes.length - 1]; + System.arraycopy(nodes, 0, nodesConstant, 0, depth); + + for (int repeat = 0; repeat < REPEATS; repeat++) { + ContinuationResult outerCont = (ContinuationResult) outer.getCallTarget().call(); + outerCont.continueWith(null); + } + } + + @Test + @SuppressWarnings("unchecked") + public void testCaptureWithLimit() { + int depth = run.depth; + assumeTrue("Cannot test stack trace limits with less than two frames in the trace.", depth >= 2); + int stackTraceLimit = depth / 2; + StackTraceTestRootNode[] nodes = chainCalls(depth, b -> { + b.beginRoot(); + b.emitDummy(); + b.beginReturn(); + b.emitCaptureStackWithLimit(stackTraceLimit); + b.endReturn(); + b.endRoot(); + }, true, false); + StackTraceTestRootNode outer = nodes[nodes.length - 1]; + + // First, invoke as usual. The stack trace should consist of the top stackTraceLimit frames. + StackTraceTestRootNode[] topFrames = Arrays.copyOfRange(nodes, 0, stackTraceLimit); + for (int repeat = 0; repeat < REPEATS; repeat++) { + List elements = (List) outer.getCallTarget().call(); + assertStackElements(topFrames, elements, "c.CaptureStackWithLimit", "c.Call", false); + } + + // Next, mark the top (depth - stackTraceLimit) roots as internal so they do not "count". + for (int i = 0; i < depth - stackTraceLimit; i++) { + nodes[i].internal = true; + } + // Since they do not "count", we should capture every frame. + for (int repeat = 0; repeat < REPEATS; repeat++) { + List elements = (List) outer.getCallTarget().call(); + assertStackElements(nodes, elements, "c.CaptureStackWithLimit", "c.Call", false); + } + } + + private void assertStackElements(StackTraceTestRootNode[] nodes, List elements, String firstElementInstruction, String chainedElementInstruction, boolean checkSources) { + assertEquals(nodes.length, elements.size()); + assertStackElement(elements.get(0), nodes[0], firstElementInstruction, checkSources); + for (int i = 1; i < nodes.length; i++) { + assertStackElement(elements.get(i), nodes[i], chainedElementInstruction, checkSources); + } + } + + private void assertStackElement(TruffleStackTraceElement element, StackTraceTestRootNode target, String instruction, boolean checkSources) { + assertEquals(target, run.interpreter.variant.cast(element.getTarget())); assertNotNull(element.getLocation()); BytecodeNode bytecode = target.getBytecodeNode(); if (run.interpreter.cached) { @@ -261,7 +394,7 @@ private void assertStackElement(TruffleStackTraceElement element, StackTraceTest } else { assertSame(bytecode, element.getLocation()); } - assertEquals(bytecode.getInstructionsAsList().get(1).getBytecodeIndex(), element.getBytecodeIndex()); + assertEquals(getBytecodeIndexOfFirstInstruction(bytecode, instruction), element.getBytecodeIndex()); Object interopObject = element.getGuestObject(); InteropLibrary lib = InteropLibrary.getFactory().create(interopObject); @@ -276,7 +409,16 @@ private void assertStackElement(TruffleStackTraceElement element, StackTraceTest } catch (UnsupportedMessageException ex) { fail("Interop object could not receive message: " + ex); } + } + private static int getBytecodeIndexOfFirstInstruction(BytecodeNode bytecode, String instructionName) { + for (Instruction i : bytecode.getInstructions()) { + if (instructionName.equals(i.getName())) { + return i.getBytecodeIndex(); + } + } + fail("No instruction found matching instruction name " + instructionName); + return -1; } @Test @@ -311,6 +453,52 @@ private static void assertStackElementNoLocation(TruffleStackTraceElement elemen assertNull(BytecodeNode.get(element)); } + @Test + public void testBadLocationFrameInstance() { + try (Context c = BytecodeDSLTestLanguage.createPolyglotContextWithCompilationDisabled()) { + CallTarget collectBytecodeNodes = new RootNode(null) { + @Override + public Object execute(VirtualFrame frame) { + List bytecodeNodes = new ArrayList<>(); + Truffle.getRuntime().iterateFrames(f -> { + bytecodeNodes.add(BytecodeNode.get(f)); + return null; + }); + return bytecodeNodes; + } + }.getCallTarget(); + + StackTraceTestRootNode caller = parse(b -> { + b.beginRoot(); + b.emitCallWithBadLocation(collectBytecodeNodes); + b.endRoot(); + }); + + assertThrows(AssertionError.class, () -> caller.getCallTarget().call()); + } + } + + @Test + public void testBadLocationTruffleStackTraceElement() { + try (Context c = BytecodeDSLTestLanguage.createPolyglotContextWithCompilationDisabled()) { + StackTraceTestRootNode capturesStack = parse(b -> { + b.beginRoot(); + b.beginReturn(); + b.emitCaptureStack(); + b.endReturn(); + b.endRoot(); + }); + + StackTraceTestRootNode caller = parse(b -> { + b.beginRoot(); + b.emitCallWithBadLocation(capturesStack.getCallTarget()); + b.endRoot(); + }); + + assertThrows(AssertionError.class, () -> caller.getCallTarget().call()); + } + } + private StackTraceTestRootNode[] chainCalls(int depth, BytecodeParser innerParser, boolean includeLocation, boolean includeSources) { StackTraceTestRootNode[] nodes = new StackTraceTestRootNode[depth]; nodes[0] = parse(innerParser); @@ -345,15 +533,64 @@ private StackTraceTestRootNode[] chainCalls(int depth, BytecodeParser innerParser, boolean includeSources) { + // Constructs a chain of methods that call the previous method, then yield, and then resume + // the previous method. + StackTraceTestRootNode[] nodes = new StackTraceTestRootNode[depth]; + nodes[0] = parse(innerParser); + nodes[0].setName("root0"); + for (int i = 1; i < depth; i++) { + int index = i; + String name = "root" + i; + Source s = includeSources ? Source.newBuilder(BytecodeDSLTestLanguage.ID, name, name + ".txt").build() : null; + nodes[i] = parse(b -> { + // x = prev_root() + // yield + // resume(x) + if (includeSources) { + b.beginSource(s); + b.beginSourceSection(0, name.length()); + } + b.beginRoot(); + BytecodeLocal calleeContinuation = b.createLocal(); + b.beginStoreLocal(calleeContinuation); + b.emitCall(nodes[index - 1].getCallTarget()); + b.endStoreLocal(); + + b.beginYield(); + b.emitLoadConstant(index); + b.endYield(); + + b.beginReturn(); + b.beginResume(); + b.emitLoadLocal(calleeContinuation); + b.emitLoadNull(); + b.endResume(); + b.endReturn(); + b.endRoot().depth = index; + if (includeSources) { + b.endSourceSection(); + b.endSource(); + } + }); + nodes[i].setName(name); + } + return nodes; + } + @GenerateBytecodeTestVariants({ @Variant(suffix = "CachedDefault", configuration = // - @GenerateBytecode(languageClass = BytecodeDSLTestLanguage.class, storeBytecodeIndexInFrame = false, enableUncachedInterpreter = false, boxingEliminationTypes = {int.class})), + @GenerateBytecode(languageClass = BytecodeDSLTestLanguage.class, storeBytecodeIndexInFrame = false, enableUncachedInterpreter = false, enableYield = true, boxingEliminationTypes = { + int.class})), @Variant(suffix = "UncachedDefault", configuration = // - @GenerateBytecode(languageClass = BytecodeDSLTestLanguage.class, storeBytecodeIndexInFrame = false, enableUncachedInterpreter = true, boxingEliminationTypes = {int.class})), + @GenerateBytecode(languageClass = BytecodeDSLTestLanguage.class, storeBytecodeIndexInFrame = false, enableUncachedInterpreter = true, enableYield = true, boxingEliminationTypes = { + int.class})), @Variant(suffix = "CachedBciInFrame", configuration = // - @GenerateBytecode(languageClass = BytecodeDSLTestLanguage.class, storeBytecodeIndexInFrame = true, enableUncachedInterpreter = false, boxingEliminationTypes = {int.class})), + @GenerateBytecode(languageClass = BytecodeDSLTestLanguage.class, storeBytecodeIndexInFrame = true, enableUncachedInterpreter = false, enableYield = true, boxingEliminationTypes = { + int.class})), @Variant(suffix = "UncachedBciInFrame", configuration = // - @GenerateBytecode(languageClass = BytecodeDSLTestLanguage.class, storeBytecodeIndexInFrame = true, enableUncachedInterpreter = true, boxingEliminationTypes = {int.class})) + @GenerateBytecode(languageClass = BytecodeDSLTestLanguage.class, storeBytecodeIndexInFrame = true, enableUncachedInterpreter = true, enableYield = true, boxingEliminationTypes = { + int.class})) }) public abstract static class StackTraceTestRootNode extends DebugBytecodeRootNode implements BytecodeRootNode { @@ -363,6 +600,7 @@ protected StackTraceTestRootNode(BytecodeDSLTestLanguage language, FrameDescript public String name; public int depth; + public boolean internal = false; public void setName(String name) { this.name = name; @@ -373,6 +611,11 @@ public String getName() { return name; } + @Override + public boolean isInternal() { + return internal; + } + @Override public String toString() { return "StackTest[name=" + name + ", depth=" + depth + "]"; @@ -405,6 +648,25 @@ static Object doDefault(CallTarget target) { } } + @Operation(storeBytecodeIndex = true) + @ConstantOperand(type = CallTarget.class) + static final class CallWithBadLocation { + @Specialization + static Object doDefault(CallTarget target, @Bind StackTraceTestRootNode root) { + // This is incorrect. The tests using this operation exert paths that should trigger + // assertions. + return target.call(root); + } + } + + @Operation(storeBytecodeIndex = true) + static final class Resume { + @Specialization + static Object doDefault(ContinuationResult cont, Object resumeValue, @Bind Node node) { + return cont.getContinuationCallTarget().call(node, cont.getFrame(), resumeValue); + } + } + @Operation(storeBytecodeIndex = true) static final class ThrowErrorBehindInterop { @@ -445,6 +707,75 @@ static Object doDefault(@Bind Node node) { return TruffleStackTrace.getStackTrace(ex); } } + + @Operation(storeBytecodeIndex = true) + @ConstantOperand(type = int.class, name = "stackTraceLimit") + static final class CaptureStackWithLimit { + + @Specialization + static Object doDefault(int stackTraceLimit, @Bind Node node) { + TestException ex = new TestException(node, stackTraceLimit); + return TruffleStackTrace.getStackTrace(ex); + } + } + + @SuppressWarnings("truffle-interpreted-performance") + @Operation(storeBytecodeIndex = true) + // The parser should populate this array after parsing. This is a hack to expose the roots. + @ConstantOperand(type = Run.class, name = "run") + @ConstantOperand(type = StackTraceTestRootNode[].class, name = "rootNodes") + @ConstantOperand(type = String.class, name = "chainedInstruction") + static final class ValidateFrameInstances { + + @Specialization + static void doDefault(Run run, StackTraceTestRootNode[] rootNodes, String chainedInstruction) { + FrameInstanceValidator visitor = new FrameInstanceValidator(run, rootNodes, chainedInstruction); + Truffle.getRuntime().iterateFrames(visitor); + visitor.checkFinished(); + } + } + + static final class FrameInstanceValidator implements FrameInstanceVisitor { + public final Run run; + public final StackTraceTestRootNode[] targets; + public final String chainedFrameInstruction; + public int index = 0; + + FrameInstanceValidator(Run run, StackTraceTestRootNode[] targets, String chainedFrameInstruction) { + this.run = run; + this.targets = targets; + this.chainedFrameInstruction = chainedFrameInstruction; + } + + public Void visitFrame(FrameInstance frameInstance) { + if (index >= targets.length) { + fail("Visited too many frames."); + } + StackTraceTestRootNode target = targets[index]; + + assertEquals(target, run.interpreter.variant.cast(frameInstance.getCallTarget())); + BytecodeNode bytecode = target.getBytecodeNode(); + if (index != 0) { + assertNotNull(frameInstance.getCallNode()); + if (run.interpreter.cached) { + assertSame(bytecode, BytecodeNode.get(frameInstance.getCallNode())); + assertSame(bytecode, BytecodeNode.get(frameInstance)); + } else { + assertSame(bytecode, frameInstance.getCallNode()); + } + assertEquals(getBytecodeIndexOfFirstInstruction(bytecode, chainedFrameInstruction), frameInstance.getBytecodeIndex()); + } + index++; + return null; + } + + public void checkFinished() { + if (index != targets.length) { + fail("Did not visit every frame. Index is " + index + " and there are " + targets.length + " expected frames."); + } + } + + } } @ExportLibrary(InteropLibrary.class) @@ -471,6 +802,10 @@ static class TestException extends AbstractTruffleException { super(resolveLocation(location)); } + TestException(Node location, int stackTraceElementLimit) { + super(null, null, stackTraceElementLimit, resolveLocation(location)); + } + private static Node resolveLocation(Node location) { if (location == null) { return null; diff --git a/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/basic_interpreter/AbstractBasicInterpreterTest.java b/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/basic_interpreter/AbstractBasicInterpreterTest.java index 8430c1153e81..f090ee6fb891 100644 --- a/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/basic_interpreter/AbstractBasicInterpreterTest.java +++ b/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/basic_interpreter/AbstractBasicInterpreterTest.java @@ -694,6 +694,18 @@ public static List allVariants() { return BasicInterpreterBuilder.variants(); } + public static boolean hasRootScoping(Class interpreterClass) { + return interpreterClass == BasicInterpreterWithRootScoping.class || + interpreterClass == BasicInterpreterProductionRootScoping.class; + } + + public static Object getDefaultLocalValue(Class interpreterClass) { + if (interpreterClass == BasicInterpreterWithOptimizations.class || interpreterClass == BasicInterpreterWithRootScoping.class) { + return BasicInterpreter.LOCAL_DEFAULT_VALUE; + } + return null; + } + /// Code gen helpers protected static void emitReturn(BasicInterpreterBuilder b, long value) { diff --git a/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/basic_interpreter/BasicInterpreter.java b/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/basic_interpreter/BasicInterpreter.java index c9b42dea7847..188e488814fa 100644 --- a/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/basic_interpreter/BasicInterpreter.java +++ b/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/basic_interpreter/BasicInterpreter.java @@ -54,6 +54,7 @@ import com.oracle.truffle.api.RootCallTarget; import com.oracle.truffle.api.Truffle; import com.oracle.truffle.api.bytecode.BytecodeConfig; +import com.oracle.truffle.api.bytecode.BytecodeFrame; import com.oracle.truffle.api.bytecode.BytecodeLocation; import com.oracle.truffle.api.bytecode.BytecodeNode; import com.oracle.truffle.api.bytecode.BytecodeRootNode; @@ -82,6 +83,7 @@ import com.oracle.truffle.api.exception.AbstractTruffleException; import com.oracle.truffle.api.frame.Frame; import com.oracle.truffle.api.frame.FrameDescriptor; +import com.oracle.truffle.api.frame.FrameInstance; import com.oracle.truffle.api.frame.MaterializedFrame; import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.interop.InteropLibrary; @@ -778,6 +780,56 @@ public static BytecodeLocation perform(@Bind BytecodeLocation location) { } } + @Operation(storeBytecodeIndex = true) + @SuppressWarnings("truffle-interpreted-performance") + public static final class CaptureFrame { + private static final Object FRAME_UNAVAILABLE = new Object(); + + @Specialization + public static BytecodeFrame perform(int skipFrames, FrameInstance.FrameAccess access) { + Object frameWalkResult = Truffle.getRuntime().iterateFrames(frameInstance -> { + BytecodeFrame result = BytecodeFrame.get(frameInstance, access); + if (result == null) { + // Return a sentinel value so that frame walking doesn't continue. + return FRAME_UNAVAILABLE; + } + return result; + }, skipFrames); + + return frameWalkResult == FRAME_UNAVAILABLE ? null : (BytecodeFrame) frameWalkResult; + } + } + + @Operation(storeBytecodeIndex = true) + @SuppressWarnings("truffle-interpreted-performance") + public static final class CaptureNonVirtualFrame { + private static final Object FRAME_UNAVAILABLE = new Object(); + + @Specialization + public static BytecodeFrame perform(int skipFrames) { + Object frameWalkResult = Truffle.getRuntime().iterateFrames(frameInstance -> { + BytecodeFrame result = BytecodeFrame.getNonVirtual(frameInstance); + if (result == null) { + // Return a sentinel value so that frame walking doesn't continue. + return FRAME_UNAVAILABLE; + } + return result; + }, skipFrames); + + return frameWalkResult == FRAME_UNAVAILABLE ? null : (BytecodeFrame) frameWalkResult; + } + } + + // Special operation that forces its operand to escape. + @Operation + public static final class Blackhole { + @Specialization + @TruffleBoundary + public static void perform(@SuppressWarnings("unused") Object value) { + // do nothing + } + } + @Instrumentation public static final class PrintHere { @Specialization diff --git a/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/basic_interpreter/BasicInterpreterTest.java b/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/basic_interpreter/BasicInterpreterTest.java index dd5e4b3ee91d..99e543202744 100644 --- a/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/basic_interpreter/BasicInterpreterTest.java +++ b/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/basic_interpreter/BasicInterpreterTest.java @@ -1406,7 +1406,7 @@ public void testMaterializedFrameAccessesDeadVariable() { assumeTrue(run.storesBciInFrame()); // This test relies on an assertion. Explicitly open a context with compilation disabled. - try (Context c = createContextWithCompilationDisabled()) { + try (Context c = BytecodeDSLTestLanguage.createPolyglotContextWithCompilationDisabled()) { BytecodeRootNodes nodes = createNodes(BytecodeConfig.DEFAULT, b -> { b.beginRoot(); @@ -1480,16 +1480,6 @@ public void testMaterializedFrameAccessesDeadVariable() { } - private static Context createContextWithCompilationDisabled() { - var builder = Context.newBuilder(BytecodeDSLTestLanguage.ID); - if (TruffleTestAssumptions.isOptimizingRuntime()) { - builder.option("engine.Compilation", "false"); - } - Context result = builder.build(); - result.enter(); - return result; - } - /* * In this test we check that accessing a local from the wrong frame throws an assertion. */ diff --git a/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/error_tests/ErrorTests.java b/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/error_tests/ErrorTests.java index 7bd0d2d0c4fd..03fc4e88aeb9 100644 --- a/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/error_tests/ErrorTests.java +++ b/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/error_tests/ErrorTests.java @@ -367,7 +367,7 @@ protected BadOverrides(ErrorLanguage language, FrameDescriptor frameDescriptor) super(language, frameDescriptor); } - private static final String ERROR_MESSAGE_DELEGATED = "This method is overridden by the generated Bytecode DSL class, so it cannot be declared final."; + private static final String ERROR_MESSAGE_DELEGATED = "This method is overridden by the generated Bytecode DSL class. It cannot be declared final."; private static final String ERROR_MESSAGE = ERROR_MESSAGE_DELEGATED + " You can remove the final modifier to resolve this issue, but since the override will make this method unreachable, it is recommended to simply remove it."; @@ -389,7 +389,7 @@ public final int findBytecodeIndex(Node node, Frame frame) { return 0; } - @ExpectError(ERROR_MESSAGE) + @ExpectError(ERROR_MESSAGE_DELEGATED) @Override public final Node findInstrumentableCallNode(Node callNode, Frame frame, int bytecodeIndex) { return null; @@ -401,7 +401,7 @@ public final boolean isInstrumentable() { return false; } - @ExpectError(ERROR_MESSAGE) + @ExpectError(ERROR_MESSAGE + " Bytecode DSL interpreters should use GenerateBytecode#captureFramesForTrace instead.") @Override public final boolean isCaptureFramesForTrace(boolean compiledFrame) { return false; @@ -459,6 +459,11 @@ public final BytecodeRootNodes getRootNodes() { * methods. The generated code should respect the wider visibility (otherwise, a compiler error * will occur). */ + @ExpectWarning({ + "Method isInstrumentable() in supertype com.oracle.truffle.api.bytecode.test.error_tests.ErrorTests.RootNodeWithOverrides is overridden by the generated Bytecode DSL class." + + " You can suppress this warning by re-declaring the method as abstract in this class.", + "Method prepareForInstrumentation(Set>) in supertype com.oracle.truffle.api.bytecode.test.error_tests.ErrorTests.RootNodeWithMoreOverrides is overridden by the generated Bytecode DSL class." + + " You can suppress this warning by re-declaring the method as abstract in this class."}) @GenerateBytecode(languageClass = ErrorLanguage.class, enableTagInstrumentation = true) public abstract static class AcceptableOverrides extends RootNodeWithMoreOverrides implements BytecodeRootNode { protected AcceptableOverrides(ErrorLanguage language, FrameDescriptor frameDescriptor) { @@ -473,19 +478,47 @@ static int add(int x, int y) { } } - @ExpectWarning("This method is overridden by the generated Bytecode DSL class, so this definition is unreachable and can be removed.") + @ExpectWarning("This method is overridden by the generated Bytecode DSL class. It is unreachable and can be removed.") @Override public int findBytecodeIndex(Node node, Frame frame) { return super.findBytecodeIndex(node, frame); } - @ExpectWarning("This method is overridden by the generated Bytecode DSL class, so this definition is unreachable and can be removed.") + @ExpectWarning("This method is overridden by the generated Bytecode DSL class. It is unreachable and can be removed. Bytecode DSL interpreters should use GenerateBytecode#captureFramesForTrace instead.") @Override public boolean isCaptureFramesForTrace(boolean compiledFrame) { return super.isCaptureFramesForTrace(compiledFrame); } } + // Unlike AcceptableOverrides, this class suppresses warnings about inherited overrides by + // redeclaring them as abstract. + @GenerateBytecode(languageClass = ErrorLanguage.class, enableTagInstrumentation = true) + public abstract static class SuppressedOverrideWarnings extends RootNodeWithMoreOverrides implements BytecodeRootNode { + + protected SuppressedOverrideWarnings(ErrorLanguage language, FrameDescriptor frameDescriptor) { + super(language, frameDescriptor); + } + + @Operation + public static final class Add { + @Specialization + static int add(int x, int y) { + return x + y; + } + } + + @Override + protected abstract Node findInstrumentableCallNode(Node callNode, Frame frame, int bytecodeIndex); + + @Override + public abstract boolean isInstrumentable(); + + @Override + public abstract void prepareForInstrumentation(Set> tags); + + } + private abstract static class RootNodeWithOverrides extends RootNode { protected RootNodeWithOverrides(ErrorLanguage language, FrameDescriptor frameDescriptor) { super(language, frameDescriptor); @@ -517,6 +550,63 @@ public void prepareForInstrumentation(Set> tags) { super.prepareForInstrumentation(tags); } + @Override + protected boolean prepareForCompilation(boolean rootCompilation, int compilationTier, boolean lastTier) { + return super.prepareForCompilation(rootCompilation, compilationTier, lastTier); + } + + } + + @GenerateBytecode(languageClass = ErrorLanguage.class, enableTagInstrumentation = true) + @ExpectError({ + "Method findInstrumentableCallNode(Node, Frame, int) in supertype com.oracle.truffle.api.bytecode.test.error_tests.ErrorTests.RootNodeWithFinalOverrides is overridden by the generated Bytecode DSL class." + + " It cannot be declared final.", + "Method prepareForInstrumentation(Set>) in supertype com.oracle.truffle.api.bytecode.test.error_tests.ErrorTests.RootNodeWithFinalOverrides is overridden by the generated Bytecode DSL class." + + " It cannot be declared final.", + "Method isCaptureFramesForTrace(boolean) in supertype com.oracle.truffle.api.bytecode.test.error_tests.ErrorTests.RootNodeWithFinalOverrides is overridden by the generated Bytecode DSL class." + + " It cannot be declared final. Bytecode DSL interpreters should use GenerateBytecode#captureFramesForTrace instead." + }) + public abstract static class InheritsFinalOverrides extends RootNodeWithFinalOverrides implements BytecodeRootNode { + + protected InheritsFinalOverrides(ErrorLanguage language, FrameDescriptor frameDescriptor) { + super(language, frameDescriptor); + } + + @Operation + public static final class Add { + @Specialization + static int add(int x, int y) { + return x + y; + } + } + + } + + private abstract static class RootNodeWithFinalOverrides extends RootNode { + protected RootNodeWithFinalOverrides(ErrorLanguage language, FrameDescriptor frameDescriptor) { + super(language, frameDescriptor); + } + + @Override + protected final Node findInstrumentableCallNode(Node callNode, Frame frame, int bytecodeIndex) { + return super.findInstrumentableCallNode(callNode, frame, bytecodeIndex); + } + + @Override + protected final boolean isCaptureFramesForTrace(boolean compiledFrame) { + return super.isCaptureFramesForTrace(compiledFrame); + } + + @Override + public final void prepareForInstrumentation(Set> tags) { + super.prepareForInstrumentation(tags); + } + + // This one is OK, we only override if not final + @Override + protected final Object translateStackTraceElement(TruffleStackTraceElement element) { + return super.translateStackTraceElement(element); + } } @ExpectError("The used type system is invalid. Fix errors in the type system first.") diff --git a/truffle/src/com.oracle.truffle.api.bytecode/snapshot.sigtest b/truffle/src/com.oracle.truffle.api.bytecode/snapshot.sigtest index e182a77bc20f..353dd6c75247 100644 --- a/truffle/src/com.oracle.truffle.api.bytecode/snapshot.sigtest +++ b/truffle/src/com.oracle.truffle.api.bytecode/snapshot.sigtest @@ -71,6 +71,26 @@ meth public static com.oracle.truffle.api.bytecode.BytecodeEncodingException cre supr java.lang.RuntimeException hfds serialVersionUID +CLSS public final com.oracle.truffle.api.bytecode.BytecodeFrame +meth public com.oracle.truffle.api.bytecode.BytecodeFrame copy() +meth public com.oracle.truffle.api.bytecode.BytecodeLocation getLocation() +meth public com.oracle.truffle.api.bytecode.BytecodeNode getBytecodeNode() +meth public int getArgumentCount() +meth public int getBytecodeIndex() +meth public int getLocalCount() +meth public java.lang.Object getArgument(int) +meth public java.lang.Object getFrameDescriptorInfo() +meth public java.lang.Object getLocalValue(int) +meth public java.lang.Object[] getLocalNames() +meth public static com.oracle.truffle.api.bytecode.BytecodeFrame get(com.oracle.truffle.api.TruffleStackTraceElement) +meth public static com.oracle.truffle.api.bytecode.BytecodeFrame get(com.oracle.truffle.api.frame.FrameInstance,com.oracle.truffle.api.frame.FrameInstance$FrameAccess) +meth public static com.oracle.truffle.api.bytecode.BytecodeFrame getNonVirtual(com.oracle.truffle.api.TruffleStackTraceElement) +meth public static com.oracle.truffle.api.bytecode.BytecodeFrame getNonVirtual(com.oracle.truffle.api.frame.FrameInstance) +meth public void setArgument(int,java.lang.Object) +meth public void setLocalValue(int,java.lang.Object) +supr java.lang.Object +hfds bytecode,bytecodeIndex,frame + CLSS public abstract com.oracle.truffle.api.bytecode.BytecodeLabel cons public init(java.lang.Object) supr java.lang.Object @@ -106,6 +126,7 @@ cons protected init(java.lang.Object) meth protected abstract boolean isLocalClearedInternal(com.oracle.truffle.api.frame.Frame,int,int) meth protected abstract boolean validateBytecodeIndex(int) meth protected abstract com.oracle.truffle.api.bytecode.Instruction findInstruction(int) +meth protected abstract com.oracle.truffle.api.frame.Frame resolveFrameImpl(com.oracle.truffle.api.frame.FrameInstance,com.oracle.truffle.api.frame.FrameInstance$FrameAccess) meth protected abstract int findBytecodeIndex(com.oracle.truffle.api.frame.Frame,com.oracle.truffle.api.nodes.Node) meth protected abstract int findBytecodeIndex(com.oracle.truffle.api.frame.FrameInstance) meth protected abstract int translateBytecodeIndex(com.oracle.truffle.api.bytecode.BytecodeNode,int) @@ -116,6 +137,8 @@ meth protected abstract void clearLocalValueInternal(com.oracle.truffle.api.fram meth protected abstract void setLocalValueInternal(com.oracle.truffle.api.frame.Frame,int,int,java.lang.Object) meth protected boolean getLocalValueInternalBoolean(com.oracle.truffle.api.frame.Frame,int,int) throws com.oracle.truffle.api.nodes.UnexpectedResultException meth protected byte getLocalValueInternalByte(com.oracle.truffle.api.frame.Frame,int,int) throws com.oracle.truffle.api.nodes.UnexpectedResultException +meth protected com.oracle.truffle.api.frame.Frame resolveFrameImpl(com.oracle.truffle.api.TruffleStackTraceElement) +meth protected com.oracle.truffle.api.frame.Frame resolveNonVirtualFrameImpl(com.oracle.truffle.api.TruffleStackTraceElement) meth protected double getLocalValueInternalDouble(com.oracle.truffle.api.frame.Frame,int,int) throws com.oracle.truffle.api.nodes.UnexpectedResultException meth protected final com.oracle.truffle.api.bytecode.BytecodeLocation findLocation(int) meth protected final static java.lang.Object createDefaultStackTraceElement(com.oracle.truffle.api.TruffleStackTraceElement) @@ -143,6 +166,8 @@ meth public abstract java.util.List getSourceInformation() meth public abstract void setLocalValue(int,com.oracle.truffle.api.frame.Frame,int,java.lang.Object) meth public abstract void setUncachedThreshold(int) +meth public final com.oracle.truffle.api.bytecode.BytecodeFrame createCopiedFrame(int,com.oracle.truffle.api.frame.Frame) +meth public final com.oracle.truffle.api.bytecode.BytecodeFrame createMaterializedFrame(int,com.oracle.truffle.api.frame.MaterializedFrame) meth public final com.oracle.truffle.api.bytecode.BytecodeLocation getBytecodeLocation(com.oracle.truffle.api.frame.Frame,com.oracle.truffle.api.nodes.Node) meth public final com.oracle.truffle.api.bytecode.BytecodeLocation getBytecodeLocation(com.oracle.truffle.api.frame.FrameInstance) meth public final com.oracle.truffle.api.bytecode.BytecodeLocation getBytecodeLocation(int) @@ -332,6 +357,7 @@ CLSS public abstract interface !annotation com.oracle.truffle.api.bytecode.Gener intf java.lang.annotation.Annotation meth public abstract !hasdefault boolean additionalAssertions() meth public abstract !hasdefault boolean allowUnsafe() +meth public abstract !hasdefault boolean captureFramesForTrace() meth public abstract !hasdefault boolean enableBlockScoping() meth public abstract !hasdefault boolean enableBytecodeDebugListener() meth public abstract !hasdefault boolean enableInstructionTracing() @@ -758,7 +784,7 @@ meth public void printHistogram(java.io.PrintStream) meth public void reset() supr java.lang.Object hfds cache,counters,descriptor,filterClause,groupClauses,rootCounters -hcls Counters,LRUCache +hcls Counters,LastTraceCache CLSS public final static com.oracle.truffle.api.bytecode.debug.HistogramInstructionTracer$Builder outer com.oracle.truffle.api.bytecode.debug.HistogramInstructionTracer @@ -793,7 +819,7 @@ meth public void onInstructionEnter(com.oracle.truffle.api.bytecode.InstructionT meth public void reset() supr java.lang.Object hfds cache,executedInstructions,filter,out -hcls LRUCache +hcls LastTraceCache CLSS public final static com.oracle.truffle.api.bytecode.debug.PrintInstructionTracer$Builder outer com.oracle.truffle.api.bytecode.debug.PrintInstructionTracer diff --git a/truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/BytecodeFrame.java b/truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/BytecodeFrame.java new file mode 100644 index 000000000000..99bd98440dde --- /dev/null +++ b/truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/BytecodeFrame.java @@ -0,0 +1,331 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.oracle.truffle.api.bytecode; + +import java.util.Arrays; +import java.util.Objects; + +import com.oracle.truffle.api.Truffle; +import com.oracle.truffle.api.TruffleStackTraceElement; +import com.oracle.truffle.api.frame.Frame; +import com.oracle.truffle.api.frame.FrameDescriptor; +import com.oracle.truffle.api.frame.FrameInstance; +import com.oracle.truffle.api.frame.FrameInstance.FrameAccess; + +/** + * Represents a captured Bytecode DSL frame, including the location metadata needed to access the + * data in the frame. + *

+ * {@link BytecodeFrame} is intended for use cases where the frame escapes or outlives the root node + * invocation. Prefer using a built-in operation or {@link LocalAccessor} to access the frame + * whenever possible. + *

+ * There are a few ways to capture the frame: + *

    + *
  • {@link BytecodeNode#createCopiedFrame} captures a copy.
  • + *
  • {@link BytecodeNode#createMaterializedFrame} captures the original frame.
  • + *
  • {@link BytecodeFrame#get(FrameInstance, FrameAccess)} captures a frame from a + * {@link FrameInstance}. It captures the original frame if {@link FrameAccess#READ_WRITE} or + * {@link FrameAccess#MATERIALIZE} is requested. Otherwise, it captures either the original frame or + * a copy.
  • + *
  • {@link BytecodeFrame#get(TruffleStackTraceElement)} captures a frame from a + * {@link TruffleStackTraceElement}. It captures either the original frame or a copy.
  • + *
  • {@link BytecodeFrame#getNonVirtual(FrameInstance)} captures the original frame from a + * {@link FrameInstance}, if it is non-virtual.
  • + *
  • {@link BytecodeFrame#getNonVirtual(TruffleStackTraceElement)} captures the original frame + * from a {@link TruffleStackTraceElement}, if it is non-virtual and available in the stack + * trace.
  • + *
+ * Copied frames do not observe updates made to the original frame. + *

+ * Note: if the interpreter uses {@link GenerateBytecode#enableBlockScoping block scoping}, any + * non-copied {@link BytecodeFrame} is only valid until the interpreter continues execution. The + * frame must not be used after this point; doing so can cause undefined behaviour. + * If you need to access the frame after execution continues, you should capture a copy or + * explicitly {@link #copy()} the captured bytecode frame. This restriction also applies to frames + * created by methods like {@link BytecodeFrame#get(TruffleStackTraceElement)}, which do not specify + * whether they capture the original frame or a copy. + * + * @since 25.1 + */ +public final class BytecodeFrame { + private final Frame frame; + private final BytecodeNode bytecode; + private final int bytecodeIndex; + + BytecodeFrame(Frame frame, BytecodeNode bytecode, int bytecodeIndex) { + assert frame.getFrameDescriptor() == bytecode.getRootNode().getFrameDescriptor(); + this.frame = Objects.requireNonNull(frame); + this.bytecode = bytecode; + this.bytecodeIndex = bytecodeIndex; + } + + /** + * Returns a copy of this frame. This method can be used to snapshot the current state of a + * bytecode frame, in case it may be modified or become invalid in the future. + * + * @return a copy of this frame that is always valid and will not observe updates + * @since 25.1 + */ + public BytecodeFrame copy() { + return new BytecodeFrame(copyFrame(frame), bytecode, bytecodeIndex); + } + + /** + * Returns the bytecode location associated with the captured frame. This location is only valid + * until the bytecode interpreter resumes execution. + * + * @since 25.1 + */ + public BytecodeLocation getLocation() { + return new BytecodeLocation(bytecode, bytecodeIndex); + } + + /** + * Returns the bytecode node associated with the captured frame. + * + * @since 25.1 + */ + public BytecodeNode getBytecodeNode() { + return bytecode; + } + + /** + * Returns the bytecode index associated with the captured frame. + * + * @since 25.1 + */ + public int getBytecodeIndex() { + return bytecodeIndex; + } + + /** + * Returns the number of live locals in the captured frame. + * + * @since 25.1 + */ + public int getLocalCount() { + return bytecode.getLocalCount(bytecodeIndex); + } + + /** + * Returns the value of the local at the given offset. The offset should be between 0 and + * {@link #getLocalCount()}. + * + * @since 25.1 + */ + public Object getLocalValue(int localOffset) { + return bytecode.getLocalValue(bytecodeIndex, frame, localOffset); + } + + /** + * Updates the value of the local at the given offset. The offset should be between 0 and + * {@link #getLocalCount()}. + *

+ * This method will throw an {@link AssertionError} if the captured frame does not support + * writes. + * + * @since 25.1 + */ + public void setLocalValue(int localOffset, Object value) { + bytecode.setLocalValue(bytecodeIndex, frame, localOffset, value); + } + + /** + * Returns the names associated with the live locals, if provided. + * + * @since 25.1 + */ + public Object[] getLocalNames() { + return bytecode.getLocalNames(bytecodeIndex); + } + + /** + * Returns the number of arguments in the captured frame. + * + * @since 25.1 + */ + public int getArgumentCount() { + return frame.getArguments().length; + } + + /** + * Returns the value of the argument at the given index. The offset should be between 0 and + * {@link #getArgumentCount()}. + * + * @since 25.1 + */ + public Object getArgument(int argumentIndex) { + return frame.getArguments()[argumentIndex]; + } + + /** + * Updates the value of the local at the given offset. The offset should be between 0 and + * {@link #getArgumentCount()}. + * + * @since 25.1 + */ + public void setArgument(int argumentIndex, Object value) { + frame.getArguments()[argumentIndex] = value; + } + + /** + * Returns the {@link FrameDescriptor#getInfo() info} object associated with the frame's + * descriptor. + * + * @since 25.1 + */ + public Object getFrameDescriptorInfo() { + return frame.getFrameDescriptor().getInfo(); + } + + /** + * Creates a copy of the given frame. + */ + static Frame copyFrame(Frame frame) { + FrameDescriptor fd = frame.getFrameDescriptor(); + Object[] args = frame.getArguments(); + Frame copiedFrame = Truffle.getRuntime().createMaterializedFrame(Arrays.copyOf(args, args.length), fd); + frame.copyTo(0, copiedFrame, 0, fd.getNumberOfSlots()); + return copiedFrame; + } + + /** + * Creates a bytecode frame from the given frame instance. + * + * @param frameInstance the frame instance + * @param access the access mode to use when capturing the frame + * @return a bytecode frame, or null if the frame instance is missing location info. + * @since 25.1 + */ + public static BytecodeFrame get(FrameInstance frameInstance, FrameInstance.FrameAccess access) { + BytecodeNode bytecode = BytecodeNode.get(frameInstance); + if (bytecode == null) { + return null; + } + Frame frame = bytecode.resolveFrameImpl(frameInstance, access); + int bytecodeIndex = bytecode.findBytecodeIndex(frameInstance); + return new BytecodeFrame(frame, bytecode, bytecodeIndex); + } + + /** + * Attempts to create a bytecode frame from the given frame instance. Returns null if the + * corresponding frame is virtual. The frame can be read from, written to, and escaped. + *

+ * This method can be used to probe for a frame that can safely escape without forcing + * materialization. For example, if a language needs to capture local variables from a stack + * frame, it's often more efficient to use an existing non-virtual frame rather than create a + * copy of all variables. + * + * @param frameInstance the frame instance + * @return a bytecode frame or null if the frame is virtual or if the frame instance is missing + * location info. + * + * @since 25.1 + */ + public static BytecodeFrame getNonVirtual(FrameInstance frameInstance) { + if (frameInstance.isVirtualFrame()) { + return null; + } + BytecodeNode bytecode = BytecodeNode.get(frameInstance); + if (bytecode == null) { + return null; + } + /* + * READ_WRITE returns the original frame. Since it's not virtual it is safe to escape it + * (either we are in the interpreter, or it is already materialized). + */ + Frame frame = bytecode.resolveFrameImpl(frameInstance, FrameAccess.READ_WRITE); + int bytecodeIndex = bytecode.findBytecodeIndex(frameInstance); + return new BytecodeFrame(frame, bytecode, bytecodeIndex); + } + + /** + * Creates a bytecode frame from the given stack trace element. + *

+ * This method will return null unless the interpreter specifies + * {@link GenerateBytecode#captureFramesForTrace}, which indicates whether frames should be + * captured. + * + * @param element the stack trace element + * @return a bytecode frame, or null if the frame was not captured or the stack trace element is + * missing location information. + * @since 25.1 + */ + public static BytecodeFrame get(TruffleStackTraceElement element) { + BytecodeNode bytecode = BytecodeNode.get(element); + if (bytecode == null) { + return null; + } + Frame frame = bytecode.resolveFrameImpl(element); + if (frame == null) { + return null; + } + return new BytecodeFrame(frame, bytecode, element.getBytecodeIndex()); + } + + /** + * Attempts to create a bytecode frame from the given stack trace element. Returns null if the + * corresponding frame is virtual. The frame can be read from, written to, and escaped. + *

+ * This method can be used to probe for a frame that can safely escape without forcing + * materialization. For example, if a language needs to capture local variables from a stack + * frame, it's often more efficient to use an existing non-virtual frame rather than create a + * copy of all variables. + * + * @param element the stack trace element + * @return a bytecode frame or null if the frame is virtual/unavailable or if the frame instance + * is missing location info. + * + * @since 25.1 + */ + public static BytecodeFrame getNonVirtual(TruffleStackTraceElement element) { + BytecodeNode bytecode = BytecodeNode.get(element); + if (bytecode == null) { + return null; + } + Frame frame = bytecode.resolveNonVirtualFrameImpl(element); + if (frame == null) { + return null; + } + return new BytecodeFrame(frame, bytecode, element.getBytecodeIndex()); + } +} diff --git a/truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/BytecodeLocation.java b/truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/BytecodeLocation.java index 4472985a735e..f858f5bbf20d 100644 --- a/truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/BytecodeLocation.java +++ b/truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/BytecodeLocation.java @@ -278,14 +278,7 @@ public static BytecodeLocation get(FrameInstance frameInstance) { * into the frame before any operation that might call another node. This incurs a bit of * overhead during regular execution (but just for the uncached interpreter). */ - Node location = frameInstance.getCallNode(); - BytecodeNode foundBytecodeNode = null; - for (Node current = location; current != null; current = current.getParent()) { - if (current instanceof BytecodeNode bytecodeNode) { - foundBytecodeNode = bytecodeNode; - break; - } - } + BytecodeNode foundBytecodeNode = BytecodeNode.get(frameInstance); if (foundBytecodeNode == null) { return null; } diff --git a/truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/BytecodeNode.java b/truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/BytecodeNode.java index 25a91851ccb3..7e8a7f2f1f52 100644 --- a/truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/BytecodeNode.java +++ b/truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/BytecodeNode.java @@ -49,7 +49,6 @@ import com.oracle.truffle.api.CompilerAsserts; import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; -import com.oracle.truffle.api.RootCallTarget; import com.oracle.truffle.api.TruffleStackTraceElement; import com.oracle.truffle.api.bytecode.Instruction.InstructionIterable; import com.oracle.truffle.api.dsl.Bind; @@ -57,6 +56,7 @@ import com.oracle.truffle.api.frame.Frame; import com.oracle.truffle.api.frame.FrameInstance; import com.oracle.truffle.api.frame.FrameInstance.FrameAccess; +import com.oracle.truffle.api.frame.MaterializedFrame; import com.oracle.truffle.api.nodes.ExplodeLoop; import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.nodes.RootNode; @@ -446,6 +446,41 @@ public final BytecodeNode ensureSourceInformation() { */ public abstract TagTree getTagTree(); + /** + * Returns a {@link BytecodeFrame} capturing the materialized interpreter state. Note that + * materialized frames may become invalid once the interpreter resumes; see the + * {@link BytecodeFrame} javadoc for more info. + *

+ * Prefer to capture the frame using this method (rather than {@link #createCopiedFrame}) when + * capturing the frame is a frequent operation or when future updates to the frame should be + * observable. + * + * @param bytecodeIndex the current bytecode index + * @param frame the current materialized frame + * @return the captured bytecode frame + * @since 25.1 + */ + public final BytecodeFrame createMaterializedFrame(int bytecodeIndex, MaterializedFrame frame) { + return new BytecodeFrame(frame, this, bytecodeIndex); + } + + /** + * Returns a {@link BytecodeFrame} capturing a copy of the interpreter state. The copy is always + * valid, but will not observe subsequent changes to the frame. + *

+ * Prefer to capture the frame using this method (rather than {@link #createMaterializedFrame}) + * when capturing the frame is an infrequent operation or when the frame does not need to + * observe future updates. + * + * @param bytecodeIndex the current bytecode index + * @param frame the current frame + * @return the captured bytecode frame + * @since 25.1 + */ + public final BytecodeFrame createCopiedFrame(int bytecodeIndex, Frame frame) { + return new BytecodeFrame(BytecodeFrame.copyFrame(frame), this, bytecodeIndex); + } + /** * Returns a new array containing the current value of each local in the frame. This method * should only be used for slow-path use cases (like frame introspection). Prefer reading locals @@ -1216,8 +1251,9 @@ protected static final Object createDefaultStackTraceElement(TruffleStackTraceEl * {@link com.oracle.truffle.api.frame.FrameInstance frameInstance}. * * @see #getLocalValues(int, Frame) + * @see BytecodeFrame#get(FrameInstance, FrameInstance.FrameAccess) * @param frameInstance the frame instance - * @return a new array of local values, or null if the frame instance does not correspond to an + * @return a new array of local values, or null if the frame instance does not correspond to a * {@link BytecodeRootNode} * @since 24.2 */ @@ -1226,7 +1262,7 @@ public static Object[] getLocalValues(FrameInstance frameInstance) { if (bytecode == null) { return null; } - Frame frame = resolveFrame(frameInstance); + Frame frame = bytecode.resolveFrameImpl(frameInstance, FrameAccess.READ_ONLY); int bci = bytecode.findBytecodeIndexImpl(frame, frameInstance.getCallNode()); return bytecode.getLocalValues(bci, frame); } @@ -1236,8 +1272,9 @@ public static Object[] getLocalValues(FrameInstance frameInstance) { * {@link com.oracle.truffle.api.frame.FrameInstance frameInstance}. * * @see #getLocalNames(int) + * @see BytecodeFrame#get(FrameInstance, FrameInstance.FrameAccess) * @param frameInstance the frame instance - * @return a new array of names, or null if the frame instance does not correspond to an + * @return a new array of names, or null if the frame instance does not correspond to a * {@link BytecodeRootNode} * @since 24.2 */ @@ -1255,6 +1292,7 @@ public static Object[] getLocalNames(FrameInstance frameInstance) { * {@link com.oracle.truffle.api.frame.FrameInstance frameInstance}. * * @see #setLocalValues(int, Frame, Object[]) + * @see BytecodeFrame#get(FrameInstance, FrameInstance.FrameAccess) * @param frameInstance the frame instance * @return whether the locals could be set with the information available in the frame instance * @since 24.2 @@ -1265,18 +1303,41 @@ public static boolean setLocalValues(FrameInstance frameInstance, Object[] value return false; } int bci = bytecode.findBytecodeIndex(frameInstance); - bytecode.setLocalValues(bci, frameInstance.getFrame(FrameAccess.READ_WRITE), values); + bytecode.setLocalValues(bci, bytecode.resolveFrameImpl(frameInstance, FrameAccess.READ_WRITE), values); return true; } - private static Frame resolveFrame(FrameInstance frameInstance) { - Frame frame = frameInstance.getFrame(FrameAccess.READ_ONLY); - if (frameInstance.getCallTarget() instanceof RootCallTarget root) { - if (root.getRootNode() instanceof ContinuationRootNode continuation) { - frame = continuation.findFrame(frame); - } - } - return frame; + /** + * Internal method to be overridden by generated code. + * + * @since 25.1 + */ + protected abstract Frame resolveFrameImpl(FrameInstance frameInstance, FrameInstance.FrameAccess access); + + /** + * Internal method to be overridden by generated code. + *

+ * By default, frames are unavailable in stack trace elements unless + * {@link GenerateBytecode#captureFramesForTrace()} is set. + * + * @since 25.1 + */ + @SuppressWarnings("unused") + protected Frame resolveFrameImpl(TruffleStackTraceElement element) { + return null; + } + + /** + * Internal method to be overridden by generated code. + *

+ * By default, frames are unavailable in stack trace elements unless + * {@link GenerateBytecode#captureFramesForTrace()} is set. + * + * @since 25.1 + */ + @SuppressWarnings("unused") + protected Frame resolveNonVirtualFrameImpl(TruffleStackTraceElement element) { + return null; } /** @@ -1291,6 +1352,7 @@ private static Frame resolveFrame(FrameInstance frameInstance) { */ @TruffleBoundary public static BytecodeNode get(FrameInstance frameInstance) { + assert !(frameInstance.getCallNode() instanceof BytecodeRootNode) : "A BytecodeRootNode should not be used as a call location."; return get(frameInstance.getCallNode()); } @@ -1303,8 +1365,7 @@ public static BytecodeNode get(FrameInstance frameInstance) { */ @ExplodeLoop public static BytecodeNode get(Node node) { - Node location = node; - for (Node currentNode = location; currentNode != null; currentNode = currentNode.getParent()) { + for (Node currentNode = node; currentNode != null; currentNode = currentNode.getParent()) { if (currentNode instanceof BytecodeNode bytecodeNode) { return bytecodeNode; } diff --git a/truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/GenerateBytecode.java b/truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/GenerateBytecode.java index 0cf8b9ffed01..211cb8e56781 100644 --- a/truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/GenerateBytecode.java +++ b/truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/GenerateBytecode.java @@ -48,6 +48,7 @@ import com.oracle.truffle.api.CompilerAsserts; import com.oracle.truffle.api.HostCompilerDirectives; import com.oracle.truffle.api.TruffleLanguage; +import com.oracle.truffle.api.TruffleStackTraceElement; import com.oracle.truffle.api.bytecode.debug.BytecodeDebugListener; import com.oracle.truffle.api.frame.Frame; import com.oracle.truffle.api.frame.FrameSlotTypeException; @@ -57,6 +58,7 @@ import com.oracle.truffle.api.instrumentation.StandardTags.RootTag; import com.oracle.truffle.api.interop.NodeLibrary; import com.oracle.truffle.api.nodes.Node; +import com.oracle.truffle.api.nodes.RootNode; /** * Generates a bytecode interpreter using the Bytecode DSL. The Bytecode DSL automatically produces @@ -420,6 +422,22 @@ */ boolean storeBytecodeIndexInFrame() default false; + /** + * Whether {@link TruffleStackTraceElement stack trace elements} of the annotated root node + * should capture frames. This flag should be used instead of + * {@link RootNode#isCaptureFramesForTrace(boolean)}, which the Bytecode DSL prevents you from + * overriding. + *

+ * When this flag is non-null, you can use {@link BytecodeFrame#get(TruffleStackTraceElement)} + * to access frame data from a stack trace element. The frame only supports read-only access. + *

+ * Bytecode DSL interpreters must not access frames directly using + * {@link TruffleStackTraceElement#getFrame}. + * + * @since 25.1 + */ + boolean captureFramesForTrace() default false; + /** * Path to a file containing optimization decisions. This file is generated using tracing on a * representative corpus of code. diff --git a/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/TruffleStackTraceElement.java b/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/TruffleStackTraceElement.java index afb835c353fe..49df48f884e4 100644 --- a/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/TruffleStackTraceElement.java +++ b/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/TruffleStackTraceElement.java @@ -146,6 +146,11 @@ public RootCallTarget getTarget() { * Returns the read-only frame. Returns null if the initial {@link RootNode} that * filled in the stack trace did not request frames to be captured by overriding * {@link RootNode#isCaptureFramesForTrace(Node)}. + *

+ * Bytecode DSL note: This method should not be used with Bytecode DSL + * interpreters. See + * {@link com.oracle.truffle.api.bytecode.GenerateBytecode#captureFramesForTrace} for more + * information. * * @since 0.31 */ diff --git a/truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/TruffleTypes.java b/truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/TruffleTypes.java index 154460dc2da7..e3293b85da59 100644 --- a/truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/TruffleTypes.java +++ b/truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/TruffleTypes.java @@ -153,6 +153,7 @@ public class TruffleTypes { public static final String Option_Group_Name = "com.oracle.truffle.api.Option.Group"; public static final String Option_Name = "com.oracle.truffle.api.Option"; public static final String Profile_Name = "com.oracle.truffle.api.profiles.Profile"; + public static final String RootCallTarget_Name = "com.oracle.truffle.api.RootCallTarget"; public static final String RootNode_Name = "com.oracle.truffle.api.nodes.RootNode"; public static final String IndirectCallNode_Name = "com.oracle.truffle.api.nodes.IndirectCallNode"; public static final String InlinedProfile_Name = "com.oracle.truffle.api.profiles.InlinedProfile"; @@ -215,6 +216,7 @@ public class TruffleTypes { public final DeclaredType NodeInterface = c.getDeclaredType(NodeInterface_Name); public final DeclaredType NodeUtil = c.getDeclaredType(NodeUtil_Name); public final DeclaredType Profile = c.getDeclaredTypeOptional(Profile_Name); + public final DeclaredType RootCallTarget = c.getDeclaredType(RootCallTarget_Name); public final DeclaredType RootNode = c.getDeclaredType(RootNode_Name); public final DeclaredType IndirectCallNode = c.getDeclaredType(IndirectCallNode_Name); public final DeclaredType InlinedProfile = c.getDeclaredTypeOptional(InlinedProfile_Name); diff --git a/truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/bytecode/generator/BytecodeRootNodeElement.java b/truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/bytecode/generator/BytecodeRootNodeElement.java index d17570b1496c..98640f7bd6f7 100644 --- a/truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/bytecode/generator/BytecodeRootNodeElement.java +++ b/truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/bytecode/generator/BytecodeRootNodeElement.java @@ -500,7 +500,6 @@ final class BytecodeRootNodeElement extends CodeTypeElement { this.add(createInvalidate()); this.add(createGetRootNodes()); - this.addOptional(createCountTowardsStackTraceLimit()); this.add(createGetSourceSection()); CodeExecutableElement translateStackTraceElement = this.addOptional(createTranslateStackTraceElement()); if (translateStackTraceElement != null) { @@ -541,7 +540,13 @@ final class BytecodeRootNodeElement extends CodeTypeElement { abstractBytecodeNode.add(new CodeVariableElement(Set.of(VOLATILE), arrayOf(type(byte.class)), "oldBytecodes")); } - // this should be at the end after all methods have been added. + /* + * These calls should occur after all methods have been added. They use the generated root + * node's method set to determine what method delegate/stub methods to generate. + */ + if (model.hasYieldOperation()) { + continuationRootNodeImpl.addRootNodeDelegateMethods(); + } if (model.enableSerialization) { addMethodStubsToSerializationRootNode(); } @@ -848,10 +853,19 @@ private CodeExecutableElement createContinueAt() { private Element createIsCaptureFramesForTrace() { CodeExecutableElement ex = overrideImplementRootNodeMethod(model, "isCaptureFramesForTrace", new String[]{"compiled"}, new TypeMirror[]{type(boolean.class)}); CodeTreeBuilder b = ex.createBuilder(); - if (model.storeBciInFrame) { + if (model.captureFramesForTrace) { + b.lineComment("GenerateBytecode#captureFramesForTrace is true."); b.statement("return true"); - } else { + } else if (model.storeBciInFrame) { + b.lineComment("GenerateBytecode#storeBytecodeIndexInFrame is true, so the frame is needed for location computations."); + b.statement("return true"); + } else if (model.enableUncachedInterpreter) { + b.lineComment("The uncached interpreter (which is never compiled) needs the frame for location computations."); + b.lineComment("This may capture the frame in more situations than strictly necessary, but doing so in the interpreter is inexpensive."); b.statement("return !compiled"); + } else { + b.lineComment("GenerateBytecode#captureFramesForTrace is not true, and the interpreter does not need the frame for location lookups."); + b.statement("return false"); } return ex; } @@ -860,6 +874,7 @@ private Element createFindBytecodeIndex() { CodeExecutableElement ex = overrideImplementRootNodeMethod(model, "findBytecodeIndex", new String[]{"node", "frame"}); mergeSuppressWarnings(ex, "hiding"); CodeTreeBuilder b = ex.createBuilder(); + b.startAssert().string("!(node instanceof ").type(types.BytecodeRootNode).string("): ").doubleQuote("A BytecodeRootNode should not be used as a call location.").end(); if (model.storeBciInFrame) { b.startIf().string("node == null").end().startBlock(); b.statement("return -1"); @@ -877,7 +892,7 @@ private Element createFindBytecodeIndex() { b.declaration(types.Node, "prev", "node"); b.declaration(types.Node, "current", "node"); b.startWhile().string("current != null").end().startBlock(); - b.startIf().string("current ").instanceOf(abstractBytecodeNode.asType()).string(" b").end().startBlock(); + b.startIf().string("current").instanceOf(abstractBytecodeNode.asType()).string(" b").end().startBlock(); b.statement("bytecode = b"); b.statement("break"); b.end(); @@ -899,7 +914,13 @@ private CodeExecutableElement createFindInstrumentableCallNode() { CodeTreeBuilder b = ex.createBuilder(); b.startDeclaration(types.BytecodeNode, "bc").startStaticCall(types.BytecodeNode, "get").string("callNode").end().end(); b.startIf().string("bc == null || !(bc instanceof AbstractBytecodeNode bytecodeNode)").end().startBlock(); - b.startReturn().string("super.findInstrumentableCallNode(callNode, frame, bytecodeIndex)").end(); + ExecutableElement superImpl = ElementUtils.findMethodInClassHierarchy(ElementUtils.findMethod(types.RootNode, "findInstrumentableCallNode"), model.templateType); + if (superImpl.getModifiers().contains(ABSTRACT)) { + // edge case: root node could redeclare findInstrumentableCallNode as abstract. + b.startReturn().string("null").end(); + } else { + b.startReturn().string("super.findInstrumentableCallNode(callNode, frame, bytecodeIndex)").end(); + } b.end(); b.statement("return bytecodeNode.findInstrumentableCallNode(bytecodeIndex)"); return ex; @@ -1131,28 +1152,6 @@ private CodeExecutableElement createCreateStackTraceElement() { return ex; } - private CodeExecutableElement createCountTowardsStackTraceLimit() { - ExecutableElement executable = ElementUtils.findOverride(ElementUtils.findMethod(types.RootNode, "countsTowardsStackTraceLimit"), model.templateType); - if (executable != null) { - return null; - } - CodeExecutableElement ex = overrideImplementRootNodeMethod(model, "countsTowardsStackTraceLimit"); - if (ex.getModifiers().contains(Modifier.FINAL)) { - // already overridden by the root node. - return null; - } - - ex.getModifiers().remove(Modifier.ABSTRACT); - ex.getModifiers().add(Modifier.FINAL); - CodeTreeBuilder b = ex.createBuilder(); - /* - * We do override with false by default to avoid materialization of sources during stack - * walking. - */ - b.returnTrue(); - return ex; - } - private CodeExecutableElement createGetSourceSection() { CodeExecutableElement ex = GeneratorUtils.override(types.Node, "getSourceSection"); CodeTreeBuilder b = ex.createBuilder(); @@ -1722,8 +1721,8 @@ private CodeExecutableElement createPrepareForCompilation() { // Disable compilation for the uncached interpreter. b.string("bytecode.getTier() != ").staticReference(types.BytecodeTier, "UNCACHED"); - ExecutableElement parentImpl = ElementUtils.findOverride(ElementUtils.findMethod(types.RootNode, "prepareForCompilation", 3), model.templateType); - if (parentImpl != null) { + ExecutableElement parentImpl = ElementUtils.findMethodInClassHierarchy(ElementUtils.findMethod(types.RootNode, "prepareForCompilation", 3), model.templateType); + if (parentImpl != null && !parentImpl.getModifiers().contains(ABSTRACT)) { // Delegate to the parent impl. b.string(" && ").startCall("super.prepareForCompilation").variables(ex.getParameters()).end(); } @@ -12554,6 +12553,12 @@ final class AbstractBytecodeNodeElement extends CodeTypeElement { this.add(createGetLocalInfo()); this.add(createGetLocals()); + this.add(createResolveFrameImplFrameInstance()); + if (model.captureFramesForTrace) { + this.add(createResolveFrameImplTruffleStackTraceElement()); + this.add(createResolveNonVirtualFrameImpl()); + } + if (model.enableTagInstrumentation) { this.add(createGetTagNodes()); } @@ -12965,6 +12970,63 @@ private CodeExecutableElement createGetLocals() { return ex; } + private CodeExecutableElement createResolveFrameImplFrameInstance() { + CodeExecutableElement ex = GeneratorUtils.override(types.BytecodeNode, "resolveFrameImpl", new String[]{"frameInstance", "access"}); + CodeTreeBuilder b = ex.createBuilder(); + + if (model.hasYieldOperation()) { + b.startIf().string("frameInstance.getCallTarget() instanceof ").type(types.RootCallTarget).string(" root && root.getRootNode() instanceof ").type( + continuationRootNodeImpl.asType()).string(" continuation").end().startBlock(); + b.lineComment("Continuations use materialized frames, which support all access modes."); + b.startReturn().startCall("continuation.findFrame").startCall("frameInstance.getFrame"); + b.staticReference(types.FrameInstance_FrameAccess, "READ_ONLY"); + b.end(3); + b.end(); // if + } + b.startReturn().string("frameInstance.getFrame(access)").end(); + return ex; + } + + private CodeExecutableElement createResolveFrameImplTruffleStackTraceElement() { + if (!model.captureFramesForTrace) { + throw new AssertionError("should not generate resolveFrameImpl(TruffleStackTraceElement) if frames are not captured."); + } + CodeExecutableElement ex = GeneratorUtils.override(types.BytecodeNode, "resolveFrameImpl", new String[]{"element"}); + CodeTreeBuilder b = ex.createBuilder(); + + if (model.hasYieldOperation()) { + b.declaration(types.Frame, "frame", "element.getFrame()"); + b.startIf().string("frame != null && element.getTarget().getRootNode() instanceof ").type(continuationRootNodeImpl.asType()).string( + " continuation").end().startBlock(); + b.statement("frame = continuation.findFrame(frame)"); + b.end(); + b.startReturn().string("frame").end(); + } else { + b.startReturn().string("element.getFrame()").end(); + } + return ex; + } + + private CodeExecutableElement createResolveNonVirtualFrameImpl() { + if (!model.captureFramesForTrace) { + throw new AssertionError("should not generate resolveNonVirtualFrameImpl(TruffleStackTraceElement) if frames are not captured."); + } + CodeExecutableElement ex = GeneratorUtils.override(types.BytecodeNode, "resolveNonVirtualFrameImpl", new String[]{"element"}); + CodeTreeBuilder b = ex.createBuilder(); + + if (model.hasYieldOperation()) { + b.declaration(types.Frame, "frame", "element.getFrame()"); + b.startIf().string("frame != null && element.getTarget().getRootNode() instanceof ").type(continuationRootNodeImpl.asType()).string( + " continuation").end().startBlock(); + b.lineComment("Continuation frames are always materialized."); + b.startReturn().string("continuation.findFrame(frame)").end(); + b.end(); + } + b.lineComment("Frames obtained in stack walks are always read-only."); + b.startReturn().string("null").end(); + return ex; + } + record InstructionValidationGroup(List immediates, int instructionLength, boolean allowNegativeChildBci, boolean localVar, boolean localVarMat) { InstructionValidationGroup(BytecodeDSLModel model, InstructionModel instruction) { @@ -18975,10 +19037,8 @@ void lazyInit() { // RootNode overrides. this.add(createIsCloningAllowed()); this.add(createIsCloneUninitializedSupported()); + this.add(createFindBytecodeIndex()); this.addOptional(createPrepareForCompilation()); - // Should appear last. Uses current method set to determine which methods need to be - // implemented. - this.addAll(createRootNodeProxyMethods()); } private CodeExecutableElement createExecute() { @@ -19127,6 +19187,19 @@ private CodeExecutableElement createIsCloneUninitializedSupported() { return ex; } + private CodeExecutableElement createFindBytecodeIndex() { + CodeExecutableElement ex = GeneratorUtils.override(types.RootNode, "findBytecodeIndex", new String[]{"node", "frame"}); + CodeTreeBuilder b = ex.createBuilder(); + b.startReturn().startCall("root", "findBytecodeIndex"); + b.string("node"); + // unwrap the frame from the continuation frame + b.startGroup().string("frame == null ? null : "); + b.startCall("findFrame").string("frame").end(); + b.end(); + b.end(2); + return ex; + } + private CodeExecutableElement createPrepareForCompilation() { if (!model.enableUncachedInterpreter) { return null; @@ -19143,14 +19216,20 @@ private CodeExecutableElement createPrepareForCompilation() { return ex; } - private List createRootNodeProxyMethods() { - List result = new ArrayList<>(); - - List existing = ElementFilter.methodsIn(continuationRootNodeImpl.getEnclosedElements()); + private void addRootNodeDelegateMethods() { + List existing = ElementFilter.methodsIn(this.getEnclosedElements()); List excludes = List.of( + // Not supported (see isCloningAllowed, isCloneUninitializedSupported). ElementUtils.findMethod(types.RootNode, "copy"), - ElementUtils.findMethod(types.RootNode, "cloneUninitialized")); + ElementUtils.findMethod(types.RootNode, "cloneUninitialized"), + // User code can only obtain a continuation root by executing a yield. + // Parsing is done at this point, so the root is already prepared. + ElementUtils.findMethod(types.RootNode, "prepareForCall"), + // The instrumenter should already know about/have instrumented the root + // node by the time we try to instrument a continuation root. + ElementUtils.findMethod(types.RootNode, "isInstrumentable"), + ElementUtils.findMethod(types.RootNode, "prepareForInstrumentation")); outer: for (ExecutableElement rootNodeMethod : ElementUtils.getOverridableMethods((TypeElement) types.RootNode.asElement())) { // Exclude methods we have already implemented. @@ -19165,33 +19244,31 @@ private List createRootNodeProxyMethods() { continue outer; } } - // Only proxy methods overridden by the template class or its parents. - ExecutableElement templateMethod = ElementUtils.findOverride(rootNodeMethod, model.templateType); + // Only delegate to methods overridden by the generated class or its parents. + ExecutableElement templateMethod = ElementUtils.findOverride(rootNodeMethod, BytecodeRootNodeElement.this); if (templateMethod == null) { continue outer; } - CodeExecutableElement proxyMethod = GeneratorUtils.override(templateMethod); - CodeTreeBuilder b = proxyMethod.createBuilder(); + CodeExecutableElement delegateMethod = GeneratorUtils.override(templateMethod); + CodeTreeBuilder b = delegateMethod.createBuilder(); - boolean isVoid = ElementUtils.isVoid(proxyMethod.getReturnType()); + boolean isVoid = ElementUtils.isVoid(delegateMethod.getReturnType()); if (isVoid) { b.startStatement(); } else { b.startReturn(); } - b.startCall("root", rootNodeMethod.getSimpleName().toString()); - for (VariableElement param : rootNodeMethod.getParameters()) { + b.startCall("root", templateMethod.getSimpleName().toString()); + for (VariableElement param : templateMethod.getParameters()) { b.variable(param); } b.end(); // call b.end(); // statement / return - result.add(proxyMethod); + this.add(delegateMethod); } - - return result; } } diff --git a/truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/bytecode/model/BytecodeDSLModel.java b/truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/bytecode/model/BytecodeDSLModel.java index 4e721c130d27..89c38a322677 100644 --- a/truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/bytecode/model/BytecodeDSLModel.java +++ b/truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/bytecode/model/BytecodeDSLModel.java @@ -118,6 +118,7 @@ public BytecodeDSLModel(ProcessorContext context, TypeElement templateType, Anno public boolean enableYield; public boolean enableMaterializedLocalAccesses; public boolean storeBciInFrame; + public boolean captureFramesForTrace; public boolean bytecodeDebugListener; public boolean additionalAssertions; public boolean inlinePrimitiveConstants; diff --git a/truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/bytecode/parser/BytecodeDSLParser.java b/truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/bytecode/parser/BytecodeDSLParser.java index f98d3f006fb9..16de9b31a4b7 100644 --- a/truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/bytecode/parser/BytecodeDSLParser.java +++ b/truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/bytecode/parser/BytecodeDSLParser.java @@ -238,6 +238,7 @@ private void parseBytecodeDSLModel(TypeElement typeElement, BytecodeDSLModel mod model.enableMaterializedLocalAccesses = ElementUtils.getAnnotationValue(Boolean.class, generateBytecodeMirror, "enableMaterializedLocalAccesses"); model.enableYield = ElementUtils.getAnnotationValue(Boolean.class, generateBytecodeMirror, "enableYield"); model.storeBciInFrame = ElementUtils.getAnnotationValue(Boolean.class, generateBytecodeMirror, "storeBytecodeIndexInFrame"); + model.captureFramesForTrace = ElementUtils.getAnnotationValue(Boolean.class, generateBytecodeMirror, "captureFramesForTrace"); model.enableQuickening = ElementUtils.getAnnotationValue(Boolean.class, generateBytecodeMirror, "enableQuickening"); model.enableTagInstrumentation = ElementUtils.getAnnotationValue(Boolean.class, generateBytecodeMirror, "enableTagInstrumentation"); model.enableRootTagging = model.enableTagInstrumentation && ElementUtils.getAnnotationValue(Boolean.class, generateBytecodeMirror, "enableRootTagging"); @@ -387,53 +388,7 @@ private void parseBytecodeDSLModel(TypeElement typeElement, BytecodeDSLModel mod model.interceptInternalException = ElementUtils.findMethod(typeElement, "interceptInternalException"); model.interceptTruffleException = ElementUtils.findMethod(typeElement, "interceptTruffleException"); - // Detect method implementations that will be overridden by the generated class. - List overrides = new ArrayList<>(List.of( - ElementUtils.findMethod(types.RootNode, "execute"), - ElementUtils.findMethod(types.RootNode, "computeSize"), - ElementUtils.findMethod(types.RootNode, "findBytecodeIndex"), - ElementUtils.findMethod(types.RootNode, "findInstrumentableCallNode"), - ElementUtils.findMethod(types.RootNode, "isInstrumentable"), - ElementUtils.findMethod(types.RootNode, "isCaptureFramesForTrace"), - ElementUtils.findMethod(types.RootNode, "prepareForCall"), - ElementUtils.findMethod(types.RootNode, "prepareForInstrumentation"), - ElementUtils.findMethod(types.BytecodeRootNode, "getBytecodeNode"), - ElementUtils.findMethod(types.BytecodeRootNode, "getRootNodes"), - ElementUtils.findMethod(types.BytecodeOSRNode, "executeOSR"), - ElementUtils.findMethod(types.BytecodeOSRNode, "getOSRMetadata"), - ElementUtils.findMethod(types.BytecodeOSRNode, "setOSRMetadata"), - ElementUtils.findMethod(types.BytecodeOSRNode, "storeParentFrameInArguments"), - ElementUtils.findMethod(types.BytecodeOSRNode, "restoreParentFrameFromArguments"))); - - for (ExecutableElement override : overrides) { - ExecutableElement declared = ElementUtils.findMethod(typeElement, override.getSimpleName().toString()); - if (declared == null) { - continue; - } - - if (declared.getModifiers().contains(Modifier.FINAL)) { - model.addError(declared, - "This method is overridden by the generated Bytecode DSL class, so it cannot be declared final. " + - "You can remove the final modifier to resolve this issue, but since the override will make this method unreachable, it is recommended to simply remove it."); - } else { - model.addWarning(declared, "This method is overridden by the generated Bytecode DSL class, so this definition is unreachable and can be removed."); - } - } - - List overridesWithDelegation = new ArrayList<>(List.of( - ElementUtils.findMethod(types.RootNode, "prepareForCompilation"))); - - for (ExecutableElement override : overridesWithDelegation) { - ExecutableElement declared = ElementUtils.findMethod(typeElement, override.getSimpleName().toString()); - if (declared == null) { - continue; - } - - if (declared.getModifiers().contains(Modifier.FINAL)) { - model.addError(declared, "This method is overridden by the generated Bytecode DSL class, so it cannot be declared final."); - } - } - + checkRootNodeOverrides(typeElement, model); if (model.hasErrors()) { return; } @@ -710,6 +665,92 @@ private void parseBytecodeDSLModel(TypeElement typeElement, BytecodeDSLModel mod return; } + /** + * Detect method implementations that will be overridden by the generated class and report an + * appropriate message. + */ + private void checkRootNodeOverrides(TypeElement typeElement, BytecodeDSLModel model) { + List overrides = new ArrayList<>(List.of( + ElementUtils.findMethod(types.RootNode, "execute"), + ElementUtils.findMethod(types.RootNode, "computeSize"), + ElementUtils.findMethod(types.RootNode, "findBytecodeIndex"), + ElementUtils.findMethod(types.RootNode, "isInstrumentable"), + ElementUtils.findMethod(types.RootNode, "prepareForCall"), + ElementUtils.findMethod(types.RootNode, "prepareForInstrumentation"), + ElementUtils.findMethod(types.BytecodeRootNode, "getBytecodeNode"), + ElementUtils.findMethod(types.BytecodeRootNode, "getRootNodes"), + ElementUtils.findMethod(types.BytecodeOSRNode, "executeOSR"), + ElementUtils.findMethod(types.BytecodeOSRNode, "getOSRMetadata"), + ElementUtils.findMethod(types.BytecodeOSRNode, "setOSRMetadata"), + ElementUtils.findMethod(types.BytecodeOSRNode, "storeParentFrameInArguments"), + ElementUtils.findMethod(types.BytecodeOSRNode, "restoreParentFrameFromArguments"))); + + for (ExecutableElement override : overrides) { + checkRootNodeOverride(typeElement, override, model, false, null); + } + + String captureFramesForTraceMessage = String.format("Bytecode DSL interpreters should use %s#captureFramesForTrace instead.", getSimpleName(types.GenerateBytecode)); + checkRootNodeOverride(typeElement, ElementUtils.findInstanceMethod(context.getTypeElement(types.RootNode), "isCaptureFramesForTrace", new TypeMirror[]{context.getType(boolean.class)}), model, + false, captureFramesForTraceMessage); + + List overridesWithDelegation = new ArrayList<>(List.of( + ElementUtils.findMethod(types.RootNode, "findInstrumentableCallNode"), + ElementUtils.findMethod(types.RootNode, "prepareForCompilation"))); + + for (ExecutableElement override : overridesWithDelegation) { + checkRootNodeOverride(typeElement, override, model, true, null); + } + } + + private void checkRootNodeOverride(TypeElement typeElement, ExecutableElement rootNodeMethod, BytecodeDSLModel model, boolean delegated, String customMessage) { + ExecutableElement override = ElementUtils.findOverride(rootNodeMethod, typeElement); + if (override == null || ElementUtils.typeEquals(override.getEnclosingElement().asType(), types.RootNode) || override.getModifiers().contains(Modifier.ABSTRACT)) { + return; + } + boolean inherited = !override.getEnclosingElement().equals(typeElement); + + Element messageElement; + String message; + if (inherited) { + messageElement = typeElement; + message = String.format("Method %s in supertype %s", ElementUtils.getReadableSignature(override), override.getEnclosingElement()); + } else { + messageElement = override; + message = "This method"; + } + message += " is overridden by the generated Bytecode DSL class."; + + if (override.getModifiers().contains(Modifier.FINAL)) { + model.addError(messageElement, message + " " + badFinalOverrideMessage(delegated, inherited, customMessage)); + } else if (!delegated) { + model.addWarning(messageElement, message + " " + badOverrideMessage(inherited, customMessage)); + } + } + + private static String badFinalOverrideMessage(boolean delegated, boolean inherited, String customMessage) { + String message = "It cannot be declared final."; + if (!delegated && !inherited) { + message += " You can remove the final modifier to resolve this issue, but since the override will make this method unreachable, it is recommended to simply remove it."; + } + if (customMessage != null) { + message += " " + customMessage; + } + return message; + } + + private static String badOverrideMessage(boolean inherited, String customMessage) { + String message; + if (inherited) { + message = "You can suppress this warning by re-declaring the method as abstract in this class."; + } else { + message = "It is unreachable and can be removed."; + } + if (customMessage != null) { + message += " " + customMessage; + } + return message; + } + private void resolveBoxingElimination(BytecodeDSLModel model, List manualQuickenings) { /* * If boxing elimination is enabled and the language uses operations with statically known diff --git a/truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/java/ElementUtils.java b/truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/java/ElementUtils.java index 7fc3dcf07aaf..4b59cef70a91 100644 --- a/truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/java/ElementUtils.java +++ b/truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/java/ElementUtils.java @@ -2249,12 +2249,30 @@ public static List newElementList(List src) { return workaround; } + /** + * Searches the superclass hierarchy of {@code type} for an override of {@code method}, which + * belongs to the base class. Returns {@code null} if the resolved override is the original + * method. + */ public static ExecutableElement findOverride(ExecutableElement method, TypeElement type) { + ExecutableElement override = findMethodInClassHierarchy(method, type); + if (override != null && !elementEquals(method, override)) { + return override; + } + return null; + } + + /** + * Searches the superclass hierarchy of {@code type} for the most concrete implementation of + * {@code method}. + */ + public static ExecutableElement findMethodInClassHierarchy(ExecutableElement method, TypeElement type) { TypeElement searchType = type; - while (searchType != null && !elementEquals(method.getEnclosingElement(), searchType)) { - ExecutableElement override = findInstanceMethod(searchType, method.getSimpleName().toString(), method.getParameters().stream().map(VariableElement::asType).toArray(TypeMirror[]::new)); - if (override != null) { - return override; + while (searchType != null) { + ExecutableElement instanceMethod = findInstanceMethod(searchType, method.getSimpleName().toString(), + method.getParameters().stream().map(VariableElement::asType).toArray(TypeMirror[]::new)); + if (instanceMethod != null) { + return instanceMethod; } searchType = castTypeElement(searchType.getSuperclass()); }