Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -44,18 +45,21 @@
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;
import com.oracle.truffle.api.bytecode.test.basic_interpreter.AbstractBasicInterpreterTest.TestRun;
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;
Expand Down Expand Up @@ -957,6 +961,176 @@ public void testTagInstrumentation() {
assertCompiled(target);
}

@Test
public void testCaptureFrame() {
BytecodeRootNodes<BasicInterpreter> 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<BasicInterpreter> 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<BasicInterpreter> 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 {

Expand Down
2 changes: 2 additions & 0 deletions truffle/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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<BytecodeDSLTestLanguage> REF = LanguageReference.create(BytecodeDSLTestLanguage.class);
}
Loading