Skip to content

Commit 977e596

Browse files
committed
[GR-61493] Add RootNode#prepareForCall and use it in Bytecode DSL interpreters.
PullRequest: graal/19953
2 parents 352fe5a + bcff906 commit 977e596

File tree

9 files changed

+188
-13
lines changed

9 files changed

+188
-13
lines changed

truffle/CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ This changelog summarizes major changes between Truffle versions relevant to lan
44

55
## Version 25.0
66
* 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.
7-
7+
* GR-61493 Added `RootNode.prepareForCall` which allows root nodes to prepare themselves for use as a call target (or to validate whether they can be used as a call target).
88

99
## Version 24.2.0
1010
* GR-60636 Truffle now stops compiling when the code cache fills up on HotSpot. A warning is printed when that happens.

truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/basic_interpreter/BasicInterpreterTest.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3280,6 +3280,26 @@ public void testTags() {
32803280
assertEquals(3L, root.call());
32813281
}
32823282

3283+
@Test
3284+
public void testPrepareForCall() {
3285+
assertThrows(IllegalStateException.class, () -> parse("getCallTargetDuringParse", b -> {
3286+
b.beginRoot();
3287+
b.beginReturn();
3288+
b.emitLoadConstant(42L);
3289+
b.endReturn();
3290+
BasicInterpreter root = b.endRoot();
3291+
root.getCallTarget();
3292+
}));
3293+
3294+
assertTrue(parse("getCallTargetAfterParse", b -> {
3295+
b.beginRoot();
3296+
b.beginReturn();
3297+
b.emitLoadConstant(42L);
3298+
b.endReturn();
3299+
b.endRoot();
3300+
}) != null);
3301+
}
3302+
32833303
@Test
32843304
public void testCloneUninitializedAdd() {
32853305
// return arg0 + arg1;

truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/error_tests/ErrorTests.java

Lines changed: 59 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -361,37 +361,86 @@ protected BadOverrides(ErrorLanguage language, FrameDescriptor frameDescriptor)
361361
super(language, frameDescriptor);
362362
}
363363

364-
@ExpectError("This method is overridden by the generated Bytecode DSL class, so it cannot be declared final. Since it is overridden, the definition is unreachable and can be removed.")
364+
private static final String ERROR_MESSAGE = "This method is overridden by the generated Bytecode DSL class, so it cannot be declared final. " +
365+
"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.";
366+
367+
@ExpectError(ERROR_MESSAGE)
365368
@Override
366369
public final Object execute(VirtualFrame frame) {
367370
return null;
368371
}
369372

370-
@ExpectError("This method is overridden by the generated Bytecode DSL class, so it cannot be declared final. Since it is overridden, the definition is unreachable and can be removed.")
373+
@ExpectError(ERROR_MESSAGE)
374+
@Override
375+
public final int computeSize() {
376+
return 0;
377+
}
378+
379+
@ExpectError(ERROR_MESSAGE)
380+
@Override
381+
public final int findBytecodeIndex(Node node, Frame frame) {
382+
return 0;
383+
}
384+
385+
@ExpectError(ERROR_MESSAGE)
386+
@Override
387+
public final Node findInstrumentableCallNode(Node callNode, Frame frame, int bytecodeIndex) {
388+
return null;
389+
}
390+
391+
@ExpectError(ERROR_MESSAGE)
392+
@Override
393+
public final boolean isInstrumentable() {
394+
return false;
395+
}
396+
397+
@ExpectError(ERROR_MESSAGE)
398+
@Override
399+
public final boolean isCaptureFramesForTrace(boolean compiledFrame) {
400+
return false;
401+
}
402+
403+
@ExpectError(ERROR_MESSAGE)
404+
@Override
405+
public final void prepareForCall() {
406+
}
407+
408+
@ExpectError(ERROR_MESSAGE)
409+
@Override
410+
public final boolean prepareForCompilation(boolean rootCompilation, int compilationTier, boolean lastTier) {
411+
return false;
412+
}
413+
414+
@ExpectError(ERROR_MESSAGE)
415+
@Override
416+
public final void prepareForInstrumentation(Set<Class<?>> tags) {
417+
}
418+
419+
@ExpectError(ERROR_MESSAGE)
371420
public final Object getOSRMetadata() {
372421
return null;
373422
}
374423

375-
@ExpectError("This method is overridden by the generated Bytecode DSL class, so it cannot be declared final. Since it is overridden, the definition is unreachable and can be removed.")
424+
@ExpectError(ERROR_MESSAGE)
376425
public final void setOSRMetadata(Object osrMetadata) {
377426
}
378427

379-
@ExpectError("This method is overridden by the generated Bytecode DSL class, so it cannot be declared final. Since it is overridden, the definition is unreachable and can be removed.")
428+
@ExpectError(ERROR_MESSAGE)
380429
public final Object[] storeParentFrameInArguments(VirtualFrame parentFrame) {
381430
return null;
382431
}
383432

384-
@ExpectError("This method is overridden by the generated Bytecode DSL class, so it cannot be declared final. Since it is overridden, the definition is unreachable and can be removed.")
433+
@ExpectError(ERROR_MESSAGE)
385434
public final Frame restoreParentFrameFromArguments(Object[] arguments) {
386435
return null;
387436
}
388437

389-
@ExpectError("This method is overridden by the generated Bytecode DSL class, so it cannot be declared final. Since it is overridden, the definition is unreachable and can be removed.")
438+
@ExpectError(ERROR_MESSAGE)
390439
public final BytecodeNode getBytecodeNode() {
391440
return null;
392441
}
393442

394-
@ExpectError("This method is overridden by the generated Bytecode DSL class, so it cannot be declared final. Since it is overridden, the definition is unreachable and can be removed.")
443+
@ExpectError(ERROR_MESSAGE)
395444
public final BytecodeRootNodes<?> getRootNodes() {
396445
return null;
397446
}
@@ -417,11 +466,13 @@ static int add(int x, int y) {
417466
}
418467
}
419468

469+
@ExpectWarning("This method is overridden by the generated Bytecode DSL class, so this definition is unreachable and can be removed.")
420470
@Override
421471
public int findBytecodeIndex(Node node, Frame frame) {
422472
return super.findBytecodeIndex(node, frame);
423473
}
424474

475+
@ExpectWarning("This method is overridden by the generated Bytecode DSL class, so this definition is unreachable and can be removed.")
425476
@Override
426477
public boolean isCaptureFramesForTrace(boolean compiledFrame) {
427478
return super.isCaptureFramesForTrace(compiledFrame);
@@ -777,7 +828,7 @@ public Object fallback(Object a, Object b) {
777828

778829
/**
779830
* These specializations should not be a problem. See
780-
* {@link OperationErrorTests.PackagePrivateSpecializationOperation}
831+
* {@link ErrorTests.BadSpecializationTests.PackagePrivateSpecializationOperation}
781832
*/
782833
@OperationProxy.Proxyable
783834
public abstract static class PackagePrivateSpecializationOperationProxy extends Node {

truffle/src/com.oracle.truffle.api.bytecode/snapshot.sigtest

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -807,6 +807,7 @@ meth protected int computeSize()
807807
meth protected int findBytecodeIndex(com.oracle.truffle.api.nodes.Node,com.oracle.truffle.api.frame.Frame)
808808
meth protected java.lang.Object translateStackTraceElement(com.oracle.truffle.api.TruffleStackTraceElement)
809809
meth protected java.util.List<com.oracle.truffle.api.TruffleStackTraceElement> findAsynchronousFrames(com.oracle.truffle.api.frame.Frame)
810+
meth protected void prepareForCall()
810811
meth protected void prepareForInstrumentation(java.util.Set<java.lang.Class<?>>)
811812
meth public abstract java.lang.Object execute(com.oracle.truffle.api.frame.VirtualFrame)
812813
meth public boolean isCaptureFramesForTrace()

truffle/src/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/RootNodeTest.java

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151

5252
import java.util.List;
5353

54+
import com.oracle.truffle.api.RootCallTarget;
5455
import org.graalvm.polyglot.Context;
5556
import org.junit.Assert;
5657
import org.junit.BeforeClass;
@@ -683,4 +684,65 @@ protected int findBytecodeIndex(Node node, Frame frame) {
683684

684685
}
685686

687+
@Test
688+
public void testPrepareForCall() {
689+
LazyInitializedRootNode root = new LazyInitializedRootNode();
690+
assertEquals(false, root.execute(null));
691+
RootCallTarget rootCallTarget = root.getCallTarget();
692+
assertEquals(true, rootCallTarget.call());
693+
}
694+
695+
static final class LazyInitializedRootNode extends RootNode {
696+
private boolean isInitialized = false;
697+
698+
LazyInitializedRootNode() {
699+
super(null);
700+
}
701+
702+
@Override
703+
public Object execute(VirtualFrame frame) {
704+
return isInitialized;
705+
}
706+
707+
@Override
708+
protected void prepareForCall() {
709+
isInitialized = true;
710+
}
711+
}
712+
713+
@Test
714+
public void testPrepareForCallPreventCall() {
715+
PreventCallWhenUninitializedRootNode root = new PreventCallWhenUninitializedRootNode();
716+
assertEquals(false, root.execute(null));
717+
root.initialize();
718+
assertEquals(true, root.getCallTarget().call());
719+
720+
PreventCallWhenUninitializedRootNode root2 = new PreventCallWhenUninitializedRootNode();
721+
assertEquals(false, root2.execute(null));
722+
assertThrows(IllegalStateException.class, root2::getCallTarget);
723+
}
724+
725+
static final class PreventCallWhenUninitializedRootNode extends RootNode {
726+
private boolean isInitialized = false;
727+
728+
PreventCallWhenUninitializedRootNode() {
729+
super(null);
730+
}
731+
732+
@Override
733+
public Object execute(VirtualFrame frame) {
734+
return isInitialized;
735+
}
736+
737+
public void initialize() {
738+
isInitialized = true;
739+
}
740+
741+
@Override
742+
protected void prepareForCall() {
743+
if (!isInitialized) {
744+
throw new IllegalStateException("Root node is not ready to be used as a call target.");
745+
}
746+
}
747+
}
686748
}

truffle/src/com.oracle.truffle.api/snapshot.sigtest

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1436,6 +1436,7 @@ meth protected int computeSize()
14361436
meth protected int findBytecodeIndex(com.oracle.truffle.api.nodes.Node,com.oracle.truffle.api.frame.Frame)
14371437
meth protected java.lang.Object translateStackTraceElement(com.oracle.truffle.api.TruffleStackTraceElement)
14381438
meth protected java.util.List<com.oracle.truffle.api.TruffleStackTraceElement> findAsynchronousFrames(com.oracle.truffle.api.frame.Frame)
1439+
meth protected void prepareForCall()
14391440
meth protected void prepareForInstrumentation(java.util.Set<java.lang.Class<?>>)
14401441
meth public abstract java.lang.Object execute(com.oracle.truffle.api.frame.VirtualFrame)
14411442
meth public boolean isCaptureFramesForTrace()
@@ -1703,7 +1704,7 @@ meth public void printStackTrace(java.io.PrintStream)
17031704
meth public void printStackTrace(java.io.PrintWriter)
17041705
meth public void setStackTrace(java.lang.StackTraceElement[])
17051706
supr java.lang.Object
1706-
hfds CAUSE_CAPTION,EMPTY_THROWABLE_ARRAY,NULL_CAUSE_MESSAGE,SELF_SUPPRESSION_MESSAGE,SUPPRESSED_CAPTION,SUPPRESSED_SENTINEL,UNASSIGNED_STACK,backtrace,cause,depth,detailMessage,jfrTracing,serialVersionUID,stackTrace,suppressedExceptions
1707+
hfds CAUSE_CAPTION,EMPTY_THROWABLE_ARRAY,NULL_CAUSE_MESSAGE,SELF_SUPPRESSION_MESSAGE,SUPPRESSED_CAPTION,SUPPRESSED_SENTINEL,UNASSIGNED_STACK,backtrace,cause,depth,detailMessage,serialVersionUID,stackTrace,suppressedExceptions
17071708
hcls PrintStreamOrWriter,SentinelHolder,WrappedPrintStream,WrappedPrintWriter
17081709

17091710
CLSS public abstract interface java.lang.annotation.Annotation

truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/nodes/RootNode.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,7 @@ private void setupCallTarget(RootCallTarget callTarget, String message) {
475475
if (this.callTarget != null) {
476476
throw CompilerDirectives.shouldNotReachHere(message);
477477
}
478+
prepareForCall();
478479
this.callTarget = callTarget;
479480

480481
// Call notifyOnLoad() after the callTarget field is set, so the invariant that if a
@@ -503,6 +504,18 @@ protected boolean isInstrumentable() {
503504
return true;
504505
}
505506

507+
/**
508+
* Prepares this {@link RootNode} to be called with a {@link CallTarget}. This method will be
509+
* called exactly once when a {@link #getCallTarget() call target is requested} for the first
510+
* time. This method can be used to lazily initialize parts of a root node, or to validate that
511+
* a root node can be used as a call target.
512+
*
513+
* @since 25.0
514+
*/
515+
protected void prepareForCall() {
516+
// no default implementation
517+
}
518+
506519
/**
507520
* Prepares this {@link RootNode} for compilation. This method is guaranteed to be called at
508521
* least once before any code paths are executed as compiled code (see

truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/bytecode/generator/BytecodeRootNodeElement.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,7 @@ final class BytecodeRootNodeElement extends CodeTypeElement {
385385

386386
// Other root node overrides.
387387
this.add(createIsInstrumentable());
388+
this.add(createPrepareForCall());
388389
this.addOptional(createPrepareForInstrumentation());
389390
this.addOptional(createPrepareForCompilation());
390391

@@ -1622,6 +1623,15 @@ private CodeExecutableElement createIsInstrumentable() {
16221623
return ex;
16231624
}
16241625

1626+
private CodeExecutableElement createPrepareForCall() {
1627+
CodeExecutableElement ex = overrideImplementRootNodeMethod(model, "prepareForCall");
1628+
CodeTreeBuilder b = ex.createBuilder();
1629+
b.startIf().string("!this.nodes.isParsed()").end().startBlock();
1630+
emitThrowIllegalStateException(ex, b, "A call target cannot be created until bytecode parsing completes. Request a call target after the parse is complete instead.");
1631+
b.end();
1632+
return ex;
1633+
}
1634+
16251635
private CodeExecutableElement createPrepareForInstrumentation() {
16261636
if (!model.enableTagInstrumentation) {
16271637
return null;
@@ -8393,6 +8403,7 @@ void lazyInit() {
83938403
this.add(createGetParserImpl());
83948404
this.add(createValidate());
83958405
this.add(createGetLanguage());
8406+
this.add(createIsParsed());
83968407

83978408
if (model.enableSerialization) {
83988409
this.add(createSerialize());
@@ -8618,6 +8629,13 @@ private CodeExecutableElement createGetLanguage() {
86188629
return ex;
86198630
}
86208631

8632+
public CodeExecutableElement createIsParsed() {
8633+
CodeExecutableElement ex = new CodeExecutableElement(Set.of(PRIVATE), type(boolean.class), "isParsed");
8634+
CodeTreeBuilder b = ex.createBuilder();
8635+
b.startReturn().string("nodes != null").end();
8636+
return ex;
8637+
}
8638+
86218639
private CodeExecutableElement createSerialize() {
86228640
CodeExecutableElement ex = GeneratorUtils.override(types.BytecodeRootNodes, "serialize", new String[]{"buffer", "callback"});
86238641
mergeSuppressWarnings(ex, "cast");

truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/bytecode/parser/BytecodeDSLParser.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -376,15 +376,23 @@ private void parseBytecodeDSLModel(TypeElement typeElement, BytecodeDSLModel mod
376376
model.interceptTruffleException = ElementUtils.findMethod(typeElement, "interceptTruffleException");
377377

378378
// Detect method implementations that will be overridden by the generated class.
379-
List<ExecutableElement> overrides = List.of(
379+
List<ExecutableElement> overrides = new ArrayList<>(List.of(
380380
ElementUtils.findMethod(types.RootNode, "execute"),
381+
ElementUtils.findMethod(types.RootNode, "computeSize"),
382+
ElementUtils.findMethod(types.RootNode, "findBytecodeIndex"),
383+
ElementUtils.findMethod(types.RootNode, "findInstrumentableCallNode"),
384+
ElementUtils.findMethod(types.RootNode, "isInstrumentable"),
385+
ElementUtils.findMethod(types.RootNode, "isCaptureFramesForTrace"),
386+
ElementUtils.findMethod(types.RootNode, "prepareForCall"),
387+
ElementUtils.findMethod(types.RootNode, "prepareForCompilation"),
388+
ElementUtils.findMethod(types.RootNode, "prepareForInstrumentation"),
381389
ElementUtils.findMethod(types.BytecodeRootNode, "getBytecodeNode"),
382390
ElementUtils.findMethod(types.BytecodeRootNode, "getRootNodes"),
383391
ElementUtils.findMethod(types.BytecodeOSRNode, "executeOSR"),
384392
ElementUtils.findMethod(types.BytecodeOSRNode, "getOSRMetadata"),
385393
ElementUtils.findMethod(types.BytecodeOSRNode, "setOSRMetadata"),
386394
ElementUtils.findMethod(types.BytecodeOSRNode, "storeParentFrameInArguments"),
387-
ElementUtils.findMethod(types.BytecodeOSRNode, "restoreParentFrameFromArguments"));
395+
ElementUtils.findMethod(types.BytecodeOSRNode, "restoreParentFrameFromArguments")));
388396

389397
for (ExecutableElement override : overrides) {
390398
ExecutableElement declared = ElementUtils.findMethod(typeElement, override.getSimpleName().toString());
@@ -394,7 +402,8 @@ private void parseBytecodeDSLModel(TypeElement typeElement, BytecodeDSLModel mod
394402

395403
if (declared.getModifiers().contains(Modifier.FINAL)) {
396404
model.addError(declared,
397-
"This method is overridden by the generated Bytecode DSL class, so it cannot be declared final. Since it is overridden, the definition is unreachable and can be removed.");
405+
"This method is overridden by the generated Bytecode DSL class, so it cannot be declared final. " +
406+
"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.");
398407
} else {
399408
model.addWarning(declared, "This method is overridden by the generated Bytecode DSL class, so this definition is unreachable and can be removed.");
400409
}

0 commit comments

Comments
 (0)