Skip to content

Commit 9ece18c

Browse files
committed
Add @Bind support for Truffle frames
1 parent de6b1ec commit 9ece18c

File tree

12 files changed

+301
-19
lines changed

12 files changed

+301
-19
lines changed

truffle/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ This changelog summarizes major changes between Truffle versions relevant to lan
1212
* GR-66310: Added support for passing arrays of primitive types to native code through the Truffle NFI Panama backend.
1313
* GR-61292 Specialization DSL: Single specialization nodes no longer specialize on first execution unless they use assumptions, cached state, or multiple instances. This was done to improve the interpreter performance and memory footprint of such nodes. As a result, these nodes no longer invalidate on first execution, which means they can no longer be used as an implicit branch profile. Language implementations are encouraged to check whether they are relying on this behavior and insert explicit branch profiles instead (see `BranchProfile` or `InlinedBranchProfile`).
1414
* GR-64005: Bytecode DSL: `@Operation` annotated classes can now inherit specializations and methods from super classes which are also declared in the same bytecode root node class. Language implementations no longer need to use operation proxies to use specialization inheritance.
15+
* GR-67146: Specialization DSL: Added `@Bind` support for frames (including materialized frames).
1516

1617
## Version 25.0
1718
* 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.dsl.test/src/com/oracle/truffle/api/dsl/test/BindExpressionTest.java

Lines changed: 220 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
import com.oracle.truffle.api.Truffle;
5555
import com.oracle.truffle.api.dsl.Bind;
5656
import com.oracle.truffle.api.dsl.Cached;
57+
import com.oracle.truffle.api.dsl.Fallback;
5758
import com.oracle.truffle.api.dsl.Introspectable;
5859
import com.oracle.truffle.api.dsl.Introspection;
5960
import com.oracle.truffle.api.dsl.Introspection.SpecializationInfo;
@@ -63,6 +64,8 @@
6364
import com.oracle.truffle.api.dsl.test.BindExpressionTestFactory.BindBindsCacheNodeGen;
6465
import com.oracle.truffle.api.dsl.test.BindExpressionTestFactory.BindCachedNodeTestNodeGen;
6566
import com.oracle.truffle.api.dsl.test.BindExpressionTestFactory.BindFieldNodeGen;
67+
import com.oracle.truffle.api.dsl.test.BindExpressionTestFactory.BindFrameInFallbackGuardTestNodeGen;
68+
import com.oracle.truffle.api.dsl.test.BindExpressionTestFactory.BindFrameTestNodeGen;
6669
import com.oracle.truffle.api.dsl.test.BindExpressionTestFactory.BindInLimitNodeGen;
6770
import com.oracle.truffle.api.dsl.test.BindExpressionTestFactory.BindMethodNodeGen;
6871
import com.oracle.truffle.api.dsl.test.BindExpressionTestFactory.BindMethodTwiceNodeGen;
@@ -75,8 +78,13 @@
7578
import com.oracle.truffle.api.dsl.test.BindExpressionTestFactory.BindTransitiveDynamicAndCachedNodeGen;
7679
import com.oracle.truffle.api.dsl.test.BindExpressionTestFactory.BindTransitiveDynamicNodeGen;
7780
import com.oracle.truffle.api.dsl.test.BindExpressionTestFactory.BindTransitiveDynamicWithLibraryNodeGen;
81+
import com.oracle.truffle.api.dsl.test.BindExpressionTestFactory.BindVirtualFrameTestNodeGen;
7882
import com.oracle.truffle.api.dsl.test.BindExpressionTestFactory.DefaultBindingNodeGen;
7983
import com.oracle.truffle.api.dsl.test.BindExpressionTestFactory.IntrospectableNodeGen;
84+
import com.oracle.truffle.api.frame.Frame;
85+
import com.oracle.truffle.api.frame.FrameDescriptor;
86+
import com.oracle.truffle.api.frame.MaterializedFrame;
87+
import com.oracle.truffle.api.frame.VirtualFrame;
8088
import com.oracle.truffle.api.interop.InteropLibrary;
8189
import com.oracle.truffle.api.interop.UnsupportedMessageException;
8290
import com.oracle.truffle.api.library.CachedLibrary;
@@ -519,6 +527,134 @@ void s1(int arg,
519527
}
520528
}
521529

530+
private static VirtualFrame makeFrame(Object... args) {
531+
return Truffle.getRuntime().createVirtualFrame(args, new FrameDescriptor());
532+
}
533+
534+
@Test
535+
public void testBindFrame() {
536+
BindFrameTest node = adoptNode(BindFrameTestNodeGen.create()).get();
537+
assertEquals(42, node.execute(makeFrame(42), false, false));
538+
assertEquals(42, node.execute(makeFrame(42), true, false));
539+
assertEquals(42, node.execute(makeFrame(42), false, true));
540+
}
541+
542+
abstract static class BindFrameTest extends Node {
543+
544+
abstract Object execute(Frame f, boolean virtual, boolean materialized);
545+
546+
@Specialization(guards = {"!virtual", "!materialized"})
547+
Object doFrame(boolean virtual, boolean materialized, @Bind Frame frame) {
548+
return frame.getArguments()[0];
549+
}
550+
551+
@Specialization(guards = {"virtual", "!materialized"})
552+
Object doVirtualFrame(boolean virtual, boolean materialized, @Bind VirtualFrame frame) {
553+
return frame.getArguments()[0];
554+
}
555+
556+
@Specialization
557+
Object doMaterialized(boolean virtual, boolean materialized, @Bind MaterializedFrame frame) {
558+
return frame.getArguments()[0];
559+
}
560+
561+
}
562+
563+
@Test
564+
public void testBindVirtualFrame() {
565+
BindVirtualFrameTest node = adoptNode(BindVirtualFrameTestNodeGen.create()).get();
566+
assertEquals(42, node.execute(makeFrame(42), false, false));
567+
assertEquals(42, node.execute(makeFrame(42), true, false));
568+
assertEquals(42, node.execute(makeFrame(42), false, true));
569+
}
570+
571+
abstract static class BindVirtualFrameTest extends Node {
572+
573+
abstract Object execute(VirtualFrame f, boolean virtual, boolean materialized);
574+
575+
@Specialization(guards = {"!virtual", "!materialized"})
576+
Object doFrame(boolean virtual, boolean materialized, @Bind Frame frame) {
577+
return frame.getArguments()[0];
578+
}
579+
580+
@Specialization(guards = {"virtual", "!materialized"})
581+
Object doVirtualFrame(boolean virtual, boolean materialized, @Bind VirtualFrame frame) {
582+
return frame.getArguments()[0];
583+
}
584+
585+
@Specialization
586+
Object doMaterialized(boolean virtual, boolean materialized, @Bind MaterializedFrame frame) {
587+
return frame.getArguments()[0];
588+
}
589+
590+
}
591+
592+
@Test
593+
public void testBindFrameInFallbackGuard() {
594+
// Regression test where the frame is needed in the generated fallback guard.
595+
BindFrameInFallbackGuardTest node = adoptNode(BindFrameInFallbackGuardTestNodeGen.create()).get();
596+
assertEquals(2, node.execute(makeFrame(42, 123)));
597+
assertEquals(0, node.execute(makeFrame()));
598+
}
599+
600+
abstract static class BindFrameInFallbackGuardTest extends Node {
601+
602+
abstract Object execute(VirtualFrame f);
603+
604+
@Specialization(guards = {"frame.getArguments().length != 0"})
605+
Object doSpecialization(@Bind Frame frame) {
606+
return frame.getArguments().length;
607+
}
608+
609+
@Fallback
610+
Object doFallback() {
611+
return 0;
612+
}
613+
614+
}
615+
616+
/**
617+
* Using the frame in a cached expression is not encouraged, but it is possible. Check that the
618+
* generated code at least compiles.
619+
*/
620+
abstract static class CacheFrameTest extends Node {
621+
abstract Object execute(Frame f);
622+
623+
@Specialization(guards = "true")
624+
Object doFrame(@Cached("$frame") Frame f) {
625+
return null;
626+
}
627+
628+
@Specialization(guards = "false")
629+
Object doVirtualFrame(@Cached("$frame") VirtualFrame f) {
630+
return null;
631+
}
632+
633+
@Specialization
634+
Object doMaterializedFrame(@Cached("$frame.materialize()") MaterializedFrame f) {
635+
return null;
636+
}
637+
}
638+
639+
abstract static class CacheVirtualFrameTest extends Node {
640+
abstract Object execute(VirtualFrame f);
641+
642+
@Specialization(guards = "true")
643+
Object doFrame(@Cached("$frame") Frame f) {
644+
return null;
645+
}
646+
647+
@Specialization(guards = "false")
648+
Object doVirtualFrame(@Cached("$frame") VirtualFrame f) {
649+
return null;
650+
}
651+
652+
@Specialization
653+
Object doMaterializedFrame(@Cached("$frame.materialize()") MaterializedFrame f) {
654+
return null;
655+
}
656+
}
657+
522658
@Test
523659
public void testBindCachedNodeTest() {
524660
BindCachedNodeTest node = adoptNode(BindCachedNodeTestNodeGen.create()).get();
@@ -658,7 +794,7 @@ abstract static class WarningRedundantBindingTest1 extends Node {
658794
@Specialization
659795
Object s0(
660796
@ExpectError("Bind expression '$node' is redundant and can be automatically be resolved from the parameter type.%") //
661-
@Bind Node result) {
797+
@Bind("$node") Node result) {
662798
return null;
663799
}
664800

@@ -677,6 +813,33 @@ Object s0(
677813

678814
}
679815

816+
abstract static class WarningRedundantBindingTest3 extends Node {
817+
818+
abstract Object execute(Frame frame);
819+
820+
@Specialization(guards = "true")
821+
Object s0(
822+
@ExpectError("Bind expression '$frame' is redundant and can be automatically be resolved from the parameter type.%") //
823+
@Bind("$frame") Frame result) {
824+
return result;
825+
}
826+
827+
@Specialization(guards = "false")
828+
Object s1(
829+
@ExpectError("Bind expression '$frame' is redundant and can be automatically be resolved from the parameter type.%") //
830+
@Bind("$frame") VirtualFrame result) {
831+
return result;
832+
}
833+
834+
@Specialization
835+
Object s2(
836+
@ExpectError("Bind expression '$frame.materialize()' is redundant and can be automatically be resolved from the parameter type.%") //
837+
@Bind("$frame.materialize()") MaterializedFrame result) {
838+
return result;
839+
}
840+
841+
}
842+
680843
@Test
681844
public void testDefaultBinding() {
682845
DefaultBindingNode node = adoptNode(DefaultBindingNodeGen.create()).get();
@@ -774,4 +937,60 @@ Object s0(
774937

775938
}
776939

940+
abstract static class BindFrameErrorTestNode extends Node {
941+
942+
abstract Object execute();
943+
944+
@Specialization(guards = "true")
945+
Object s0(
946+
@ExpectError("This expression binds the frame, but the frame is not available for this node. Declare a frame parameter on all execute methods to resolve this error.")//
947+
@Bind Frame frame) {
948+
return null;
949+
}
950+
951+
@Specialization(guards = "false")
952+
Object s1(
953+
@ExpectError("This expression binds the frame, but the frame is not available for this node. Declare a frame parameter on all execute methods to resolve this error.")//
954+
@Bind VirtualFrame frame) {
955+
return null;
956+
}
957+
958+
@Specialization
959+
Object s2(
960+
@ExpectError("This expression binds the frame, but the frame is not available for this node. Declare a frame parameter on all execute methods to resolve this error.")//
961+
@Bind MaterializedFrame frame) {
962+
return null;
963+
}
964+
965+
}
966+
967+
abstract static class BindFrameError2TestNode extends Node {
968+
969+
abstract Object execute(Frame frame);
970+
971+
abstract Object executeWithoutFrame();
972+
973+
@Specialization(guards = "true")
974+
Object s0(
975+
@ExpectError("This expression binds the frame, but the frame is not available for this node. Declare a frame parameter on all execute methods to resolve this error.")//
976+
@Bind Frame frame) {
977+
return null;
978+
}
979+
980+
@Specialization(guards = "false")
981+
Object s1(
982+
@ExpectError("This expression binds the frame, but the frame is not available for this node. Declare a frame parameter on all execute methods to resolve this error.")//
983+
@Bind VirtualFrame frame) {
984+
return null;
985+
}
986+
987+
@Specialization
988+
Object s2(
989+
@ExpectError("This expression binds the frame, but the frame is not available for this node. Declare a frame parameter on all execute methods to resolve this error.")//
990+
@Bind MaterializedFrame frame) {
991+
return null;
992+
}
993+
994+
}
995+
777996
}

truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/frame/Frame.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
*
5050
* @since 0.8 or earlier
5151
*/
52+
// Bind.DefaultExpression("$frame")
5253
public interface Frame {
5354

5455
/**

truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/frame/MaterializedFrame.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,10 @@
4444
* Represents a materialized frame containing values of local variables of the guest language. It
4545
* can be created using the {@link VirtualFrame#materialize()} method. Instances of this type are
4646
* the only frame instances that may be stored in fields or cast to {@link java.lang.Object}.
47-
*
47+
*
4848
* @since 0.8 or earlier
4949
*/
50+
// Bind.DefaultExpression("$frame.materialize()")
5051
public interface MaterializedFrame extends VirtualFrame {
5152

5253
}

truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/frame/VirtualFrame.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,9 @@
4545
* type must not be stored in a field or cast to {@link java.lang.Object}. If this is necessary, the
4646
* frame must be explicitly converted into a materialized frame using the
4747
* {@link VirtualFrame#materialize()} method.
48-
*
48+
*
4949
* @since 0.8 or earlier
5050
*/
51+
// Bind.DefaultExpression("$frame")
5152
public interface VirtualFrame extends Frame {
5253
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@
107107
* @see NodeInterface
108108
* @since 0.8 or earlier
109109
*/
110-
// DefaultSymbol("$node")
110+
// Bind.DefaultExpression("$node")
111111
public abstract class Node implements NodeInterface, Cloneable {
112112

113113
@CompilationFinal private volatile Node parent;

truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/generator/FlatNodeGenFactory.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -867,6 +867,11 @@ private static boolean needsFrameToExecute(List<SpecializationData> specializati
867867
if (specialization.getFrame() != null) {
868868
return true;
869869
}
870+
for (CacheExpression cacheExpression : specialization.getCaches()) {
871+
if (cacheExpression.isRequiresFrame()) {
872+
return true;
873+
}
874+
}
870875
}
871876
return false;
872877
}
@@ -7103,7 +7108,7 @@ private Map<Variable, LocalVariable> bindExpressionValues(FrameState frameState,
71037108
} else {
71047109
CodeTree tree = plugs.bindExpressionValue(frameState, variable);
71057110
if (tree != null) {
7106-
bindings.put(variable, new LocalVariable(variable.getResolvedType(), "$bytecode", tree));
7111+
bindings.put(variable, new LocalVariable(variable.getResolvedType(), variable.getName(), tree));
71077112
} else {
71087113
if (specialization.isNodeReceiverVariable(variable.getResolvedVariable())) {
71097114
CodeTree accessor = createNodeAccess(frameState, specialization);
@@ -8074,6 +8079,10 @@ public boolean getBoolean(String name, boolean defaultValue) {
80748079
}
80758080
}
80768081

8082+
public TypeMirror getFrameType() {
8083+
return factory.node.getFrameType();
8084+
}
8085+
80778086
public boolean isSpecializationClassInitialized(SpecializationData specialization) {
80788087
return getBoolean(createSpecializationClassInitialized(specialization), false);
80798088
}

truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/generator/NodeGeneratorPlugs.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,15 +47,19 @@
4747
import javax.lang.model.element.VariableElement;
4848
import javax.lang.model.type.TypeMirror;
4949

50+
import com.oracle.truffle.dsl.processor.ProcessorContext;
5051
import com.oracle.truffle.dsl.processor.expression.DSLExpression.Variable;
5152
import com.oracle.truffle.dsl.processor.generator.FlatNodeGenFactory.ChildExecutionResult;
5253
import com.oracle.truffle.dsl.processor.generator.FlatNodeGenFactory.FrameState;
5354
import com.oracle.truffle.dsl.processor.generator.FlatNodeGenFactory.LocalVariable;
55+
import com.oracle.truffle.dsl.processor.java.ElementUtils;
5456
import com.oracle.truffle.dsl.processor.java.model.CodeTree;
5557
import com.oracle.truffle.dsl.processor.java.model.CodeTreeBuilder;
5658
import com.oracle.truffle.dsl.processor.model.NodeChildData;
5759
import com.oracle.truffle.dsl.processor.model.NodeExecutionData;
5860
import com.oracle.truffle.dsl.processor.model.SpecializationData;
61+
import com.oracle.truffle.dsl.processor.model.TemplateMethod;
62+
import com.oracle.truffle.dsl.processor.parser.NodeParser;
5963

6064
/**
6165
* Interface that allows node generators to customize the way {@link FlatNodeGenFactory} generates
@@ -94,7 +98,15 @@ default void notifySpecialize(FlatNodeGenFactory nodeFactory, CodeTreeBuilder bu
9498

9599
@SuppressWarnings("unused")
96100
default CodeTree bindExpressionValue(FrameState frameState, Variable variable) {
97-
return null;
101+
return switch (variable.getName()) {
102+
case NodeParser.SYMBOL_FRAME -> {
103+
if (!ElementUtils.isAssignable(frameState.getFrameType(), ProcessorContext.types().Frame)) {
104+
throw new AssertionError("Expression binds the frame, but the frame is unavailable. This should have been validated already.");
105+
}
106+
yield CodeTreeBuilder.singleString(TemplateMethod.FRAME_NAME);
107+
}
108+
default -> null;
109+
};
98110
}
99111

100112
}

0 commit comments

Comments
 (0)