Skip to content

Commit d77c5e2

Browse files
committed
Add BytecodeFrame abstraction and GenerateBytecode#captureFramesForTrace configuration option
Adds a new BytecodeFrame abstraction that captures the frame and location information. This abstraction should be preferred over BytecodeNode helpers since it ensures the correct location is used. Also adds a new GenerateBytecode#captureFramesForTrace configuration option that directs the interpreter to capture frames for interpreter use. Previously, the frame would sometimes be available depending on internal interpreter correctness requirements. Now, the frame is reliably available depending on the configuration option. Lanugages should use BytecodeFrame.get(element) to obtain the frame data from a TruffleStackTraceElement (element.getFrame() may still return a non-null value, but its use by languages is not supported).
1 parent f932a26 commit d77c5e2

File tree

13 files changed

+1097
-142
lines changed

13 files changed

+1097
-142
lines changed

compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/truffle/test/BytecodeDSLCompilationTest.java

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import static com.oracle.truffle.api.bytecode.test.basic_interpreter.AbstractBasicInterpreterTest.createNodes;
2828
import static com.oracle.truffle.api.bytecode.test.basic_interpreter.AbstractBasicInterpreterTest.parseNode;
2929
import static org.junit.Assert.assertEquals;
30+
import static org.junit.Assert.assertNotNull;
3031
import static org.junit.Assert.assertNull;
3132
import static org.junit.Assert.fail;
3233
import static org.junit.Assume.assumeTrue;
@@ -44,18 +45,21 @@
4445
import org.junit.runners.Parameterized.Parameters;
4546

4647
import com.oracle.truffle.api.bytecode.BytecodeConfig;
48+
import com.oracle.truffle.api.bytecode.BytecodeFrame;
4749
import com.oracle.truffle.api.bytecode.BytecodeLocal;
4850
import com.oracle.truffle.api.bytecode.BytecodeLocation;
4951
import com.oracle.truffle.api.bytecode.BytecodeNode;
5052
import com.oracle.truffle.api.bytecode.BytecodeParser;
5153
import com.oracle.truffle.api.bytecode.BytecodeRootNodes;
54+
import com.oracle.truffle.api.bytecode.BytecodeTier;
5255
import com.oracle.truffle.api.bytecode.ContinuationResult;
5356
import com.oracle.truffle.api.bytecode.test.BytecodeDSLTestLanguage;
5457
import com.oracle.truffle.api.bytecode.test.basic_interpreter.AbstractBasicInterpreterTest;
5558
import com.oracle.truffle.api.bytecode.test.basic_interpreter.AbstractBasicInterpreterTest.TestRun;
5659
import com.oracle.truffle.api.bytecode.test.basic_interpreter.BasicInterpreter;
5760
import com.oracle.truffle.api.bytecode.test.basic_interpreter.BasicInterpreterBuilder;
5861
import com.oracle.truffle.api.bytecode.test.basic_interpreter.BasicInterpreterBuilder.BytecodeVariant;
62+
import com.oracle.truffle.api.frame.FrameInstance;
5963
import com.oracle.truffle.api.frame.FrameSlotKind;
6064
import com.oracle.truffle.api.frame.VirtualFrame;
6165
import com.oracle.truffle.api.instrumentation.ExecutionEventNode;
@@ -957,6 +961,176 @@ public void testTagInstrumentation() {
957961
assertCompiled(target);
958962
}
959963

964+
@Test
965+
public void testCaptureFrame() {
966+
BytecodeRootNodes<BasicInterpreter> rootNodes = createNodes(run, BytecodeDSLTestLanguage.REF.get(null), BytecodeConfig.DEFAULT, b -> {
967+
b.beginRoot();
968+
b.beginReturn();
969+
b.beginCaptureFrame();
970+
b.emitLoadArgument(0);
971+
b.emitLoadArgument(1);
972+
b.endCaptureFrame();
973+
b.endReturn();
974+
BasicInterpreter callee = b.endRoot();
975+
callee.setName("callee");
976+
977+
b.beginRoot();
978+
BytecodeLocal x = b.createLocal();
979+
b.beginStoreLocal(x);
980+
b.emitLoadConstant(123);
981+
b.endStoreLocal();
982+
b.beginInvoke();
983+
b.emitLoadConstant(callee);
984+
b.emitLoadArgument(0);
985+
b.emitLoadArgument(1);
986+
b.endInvoke();
987+
b.endRoot().setName("caller");
988+
});
989+
BasicInterpreter caller = rootNodes.getNode(1);
990+
991+
OptimizedCallTarget target = (OptimizedCallTarget) caller.getCallTarget();
992+
993+
// The callee frame (the top of the stack) should never be accessible.
994+
assertNull(target.call(0, FrameInstance.FrameAccess.READ_ONLY));
995+
996+
// In the interpreter the caller frame should always be accessible.
997+
assertNotCompiled(target);
998+
checkCallerBytecodeFrame((BytecodeFrame) target.call(1, FrameInstance.FrameAccess.READ_ONLY), false);
999+
assertNotCompiled(target);
1000+
checkCallerBytecodeFrame((BytecodeFrame) target.call(1, FrameInstance.FrameAccess.READ_WRITE), false);
1001+
assertNotCompiled(target);
1002+
checkCallerBytecodeFrame((BytecodeFrame) target.call(1, FrameInstance.FrameAccess.MATERIALIZE), false);
1003+
1004+
// Force transition to cached.
1005+
caller.getBytecodeNode().setUncachedThreshold(0);
1006+
target.call(0, FrameInstance.FrameAccess.READ_ONLY);
1007+
assertEquals(BytecodeTier.CACHED, caller.getBytecodeNode().getTier());
1008+
1009+
// In compiled code the caller frame should always be accessible, but may be a copy.
1010+
// Requesting the frame should not invalidate compiled code.
1011+
target.compile(true);
1012+
assertCompiled(target);
1013+
checkCallerBytecodeFrame((BytecodeFrame) target.call(1, FrameInstance.FrameAccess.READ_ONLY), true);
1014+
assertCompiled(target);
1015+
checkCallerBytecodeFrame((BytecodeFrame) target.call(1, FrameInstance.FrameAccess.READ_WRITE), false);
1016+
assertCompiled(target);
1017+
checkCallerBytecodeFrame((BytecodeFrame) target.call(1, FrameInstance.FrameAccess.MATERIALIZE), false);
1018+
assertCompiled(target);
1019+
}
1020+
1021+
@Test
1022+
public void testCaptureNonVirtualFrame() {
1023+
BytecodeRootNodes<BasicInterpreter> rootNodes = createNodes(run, BytecodeDSLTestLanguage.REF.get(null), BytecodeConfig.DEFAULT, b -> {
1024+
b.beginRoot();
1025+
b.beginReturn();
1026+
b.beginCaptureNonVirtualFrame();
1027+
b.emitLoadArgument(0);
1028+
b.endCaptureNonVirtualFrame();
1029+
b.endReturn();
1030+
BasicInterpreter callee = b.endRoot();
1031+
callee.setName("callee");
1032+
1033+
b.beginRoot();
1034+
BytecodeLocal x = b.createLocal();
1035+
b.beginStoreLocal(x);
1036+
b.emitLoadConstant(123);
1037+
b.endStoreLocal();
1038+
b.beginInvoke();
1039+
b.emitLoadConstant(callee);
1040+
b.emitLoadArgument(0);
1041+
b.endInvoke();
1042+
b.endRoot().setName("caller");
1043+
});
1044+
BasicInterpreter caller = rootNodes.getNode(1);
1045+
1046+
OptimizedCallTarget target = (OptimizedCallTarget) caller.getCallTarget();
1047+
1048+
// The callee frame (the top of the stack) should never be accessible.
1049+
assertNull(target.call(0));
1050+
1051+
// In the interpreter the non-virtual caller frame should be accessible.
1052+
assertNotCompiled(target);
1053+
BytecodeFrame nonVirtualFrame = (BytecodeFrame) target.call(1);
1054+
assertNotCompiled(target);
1055+
checkCallerBytecodeFrame(nonVirtualFrame, false);
1056+
1057+
// Force transition to cached.
1058+
caller.getBytecodeNode().setUncachedThreshold(0);
1059+
target.call(0);
1060+
assertEquals(BytecodeTier.CACHED, caller.getBytecodeNode().getTier());
1061+
1062+
// In compiled code the non-virtual caller frame should be inaccessible.
1063+
target.compile(true);
1064+
assertCompiled(target);
1065+
assertNull(target.call(1));
1066+
assertCompiled(target);
1067+
}
1068+
1069+
@Test
1070+
public void testCaptureNonVirtualFrameAfterMaterialization() {
1071+
BytecodeRootNodes<BasicInterpreter> rootNodes = createNodes(run, BytecodeDSLTestLanguage.REF.get(null), BytecodeConfig.DEFAULT, b -> {
1072+
b.beginRoot();
1073+
b.beginReturn();
1074+
b.beginCaptureNonVirtualFrame();
1075+
b.emitLoadArgument(0);
1076+
b.endCaptureNonVirtualFrame();
1077+
b.endReturn();
1078+
BasicInterpreter callee = b.endRoot();
1079+
callee.setName("callee");
1080+
1081+
b.beginRoot();
1082+
BytecodeLocal x = b.createLocal();
1083+
b.beginStoreLocal(x);
1084+
b.emitLoadConstant(123);
1085+
b.endStoreLocal();
1086+
1087+
b.beginBlackhole();
1088+
b.emitMaterializeFrame(); // force materialize frame.
1089+
b.endBlackhole();
1090+
1091+
b.beginInvoke();
1092+
b.emitLoadConstant(callee);
1093+
b.emitLoadArgument(0);
1094+
b.endInvoke();
1095+
b.endRoot().setName("caller");
1096+
});
1097+
BasicInterpreter caller = rootNodes.getNode(1);
1098+
1099+
OptimizedCallTarget target = (OptimizedCallTarget) caller.getCallTarget();
1100+
1101+
// The callee frame (the top of the stack) should never be accessible.
1102+
assertNull(target.call(0));
1103+
1104+
// In the interpreter the non-virtual caller frame should be accessible.
1105+
assertNotCompiled(target);
1106+
BytecodeFrame nonVirtualFrame = (BytecodeFrame) target.call(1);
1107+
assertNotCompiled(target);
1108+
checkCallerBytecodeFrame(nonVirtualFrame, false);
1109+
1110+
// Force transition to cached.
1111+
caller.getBytecodeNode().setUncachedThreshold(0);
1112+
target.call(0);
1113+
assertEquals(BytecodeTier.CACHED, caller.getBytecodeNode().getTier());
1114+
1115+
// In compiled code the frame should be accessible because it was materialized already.
1116+
target.compile(true);
1117+
assertCompiled(target);
1118+
nonVirtualFrame = (BytecodeFrame) target.call(1);
1119+
checkCallerBytecodeFrame(nonVirtualFrame, false);
1120+
assertCompiled(target);
1121+
}
1122+
1123+
private void checkCallerBytecodeFrame(BytecodeFrame bytecodeFrame, boolean isCopy) {
1124+
assertNotNull(bytecodeFrame);
1125+
assertEquals(1, bytecodeFrame.getLocalCount());
1126+
if (isCopy || AbstractBasicInterpreterTest.hasRootScoping(run.interpreterClass())) {
1127+
assertEquals(123, bytecodeFrame.getLocalValue(0));
1128+
} else {
1129+
// the local gets cleared on exit.
1130+
assertEquals(AbstractBasicInterpreterTest.getDefaultLocalValue(run.interpreterClass()), bytecodeFrame.getLocalValue(0));
1131+
}
1132+
}
1133+
9601134
@TruffleInstrument.Registration(id = BytecodeDSLCompilationTestInstrumentation.ID, services = Instrumenter.class)
9611135
public static class BytecodeDSLCompilationTestInstrumentation extends TruffleInstrument {
9621136

truffle/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ This changelog summarizes major changes between Truffle versions relevant to lan
3838
* 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.
3939
* 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.
4040

41+
* 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.
42+
* 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.
4143

4244
## Version 25.0
4345
* 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.

0 commit comments

Comments
 (0)