Skip to content

Commit da57f0f

Browse files
committed
[GR-69861] [GR-69760] Add BytecodeFrame and GenerateBytecode#captureFramesForTrace, fix/improve continuation frame support.
PullRequest: graal/22225
2 parents e4425a4 + 616f133 commit da57f0f

File tree

20 files changed

+1758
-282
lines changed

20 files changed

+1758
-282
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.

truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/BytecodeDSLTestLanguage.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,15 @@
4040
*/
4141
package com.oracle.truffle.api.bytecode.test;
4242

43+
import org.graalvm.polyglot.Context;
44+
4345
import com.oracle.truffle.api.TruffleLanguage;
4446
import com.oracle.truffle.api.instrumentation.ProvidedTags;
4547
import com.oracle.truffle.api.instrumentation.StandardTags.ExpressionTag;
4648
import com.oracle.truffle.api.instrumentation.StandardTags.RootBodyTag;
4749
import com.oracle.truffle.api.instrumentation.StandardTags.RootTag;
4850
import com.oracle.truffle.api.instrumentation.StandardTags.StatementTag;
51+
import com.oracle.truffle.tck.tests.TruffleTestAssumptions;
4952

5053
/**
5154
* Placeholder language for Bytecode DSL test interpreters.
@@ -60,5 +63,19 @@ protected Object createContext(Env env) {
6063
return new Object();
6164
}
6265

66+
/**
67+
* Ensures compilation is disabled (when supported). This allows tests to validate the behaviour
68+
* of assertions, which are optimized away in compiled code.
69+
*/
70+
public static Context createPolyglotContextWithCompilationDisabled() {
71+
var builder = Context.newBuilder(ID);
72+
if (TruffleTestAssumptions.isOptimizingRuntime()) {
73+
builder.option("engine.Compilation", "false");
74+
}
75+
Context result = builder.build();
76+
result.enter();
77+
return result;
78+
}
79+
6380
public static final LanguageReference<BytecodeDSLTestLanguage> REF = LanguageReference.create(BytecodeDSLTestLanguage.class);
6481
}

0 commit comments

Comments
 (0)