Skip to content

Commit 925fe8e

Browse files
committed
[GR-71887] Add ClearLocal operation.
PullRequest: graal/22798
2 parents c02c7fd + f22bed2 commit 925fe8e

File tree

7 files changed

+120
-14
lines changed

7 files changed

+120
-14
lines changed

truffle/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ This changelog summarizes major changes between Truffle versions relevant to lan
6060
* GR-71088 Added `CompilerDirectives.EarlyInline` annotation that performs a conservative early inlining pass for methods before partial evaluation. This is intended to expose small branch/bytecode handlers and similar helpers to optimizations such as @ExplodeLoop, in particular for MERGE_EXPLODE bytecode interpreter loops.
6161
* GR-71088 Added `CompilerDirectives.EarlyEscapeAnalysis` annotation that runs partial escape analysis early before partial evaluation enabling partial-evaluation-constant scalar replacements.
6262
* GR-71870 Truffle DSL no longer supports mixed exclusive and shared inlined caches. Sharing will now be disabled if mixing was used. To resolve the new warnings it is typically necessary to use either `@Exclusive` or `@Shared` for all caches.
63+
* GR-71887: Bytecode DSL: Added a `ClearLocal` operation for fast clearing of local values.
6364

6465
## Version 25.0
6566
* 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/basic_interpreter/LocalsTest.java

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,14 +57,26 @@
5757
import com.oracle.truffle.api.bytecode.BytecodeLocal;
5858
import com.oracle.truffle.api.bytecode.BytecodeNode;
5959
import com.oracle.truffle.api.bytecode.BytecodeParser;
60+
import com.oracle.truffle.api.bytecode.BytecodeRootNode;
6061
import com.oracle.truffle.api.bytecode.BytecodeRootNodes;
62+
import com.oracle.truffle.api.bytecode.ConstantOperand;
63+
import com.oracle.truffle.api.bytecode.GenerateBytecode;
6164
import com.oracle.truffle.api.bytecode.Instruction;
65+
import com.oracle.truffle.api.bytecode.LocalAccessor;
6266
import com.oracle.truffle.api.bytecode.Instruction.Argument;
6367
import com.oracle.truffle.api.bytecode.Instruction.Argument.Kind;
68+
import com.oracle.truffle.api.bytecode.test.BytecodeDSLTestLanguage;
69+
import com.oracle.truffle.api.bytecode.test.error_tests.ExpectWarning;
70+
import com.oracle.truffle.api.dsl.Bind;
71+
import com.oracle.truffle.api.dsl.Specialization;
6472
import com.oracle.truffle.api.bytecode.LocalVariable;
73+
import com.oracle.truffle.api.bytecode.Operation;
74+
import com.oracle.truffle.api.frame.FrameDescriptor;
6575
import com.oracle.truffle.api.frame.FrameSlotKind;
6676
import com.oracle.truffle.api.frame.FrameSlotTypeException;
6777
import com.oracle.truffle.api.frame.MaterializedFrame;
78+
import com.oracle.truffle.api.frame.VirtualFrame;
79+
import com.oracle.truffle.api.nodes.RootNode;
6880

6981
public class LocalsTest extends AbstractBasicInterpreterTest {
7082

@@ -729,6 +741,38 @@ public void testMaterializedAccessUpdatesTag() {
729741
assertEquals(42L, outer.getCallTarget().call(false));
730742
}
731743

744+
@Test
745+
public void testClearLocal() {
746+
// l0 = 42L;
747+
// clearLocal(l0);
748+
// return l0;
749+
BasicInterpreter root = parseNode("clearLocal", b -> {
750+
b.beginRoot();
751+
752+
BytecodeLocal l0 = b.createLocal("l0", null);
753+
b.beginStoreLocal(l0);
754+
b.emitLoadConstant(42L);
755+
b.endStoreLocal();
756+
757+
b.emitClearLocal(l0);
758+
759+
b.beginReturn();
760+
b.emitLoadLocal(l0);
761+
b.endReturn();
762+
763+
b.endRoot();
764+
});
765+
766+
Object defaultLocal = this.run.getDefaultLocalValue();
767+
if (defaultLocal == null) {
768+
assertThrows(FrameSlotTypeException.class, () -> {
769+
root.getCallTarget().call();
770+
});
771+
} else {
772+
assertSame(defaultLocal, root.getCallTarget().call());
773+
}
774+
}
775+
732776
@Test
733777
public void testIllegalOrDefault() {
734778
// @formatter:off
@@ -873,6 +917,10 @@ private static <T extends BasicInterpreterBuilder> void storeLocal(T b, Bytecode
873917
b.endStoreLocal();
874918
}
875919

920+
private static <T extends BasicInterpreterBuilder> void clearLocal(T b, BytecodeLocal local) {
921+
b.emitClearLocal(local);
922+
}
923+
876924
private static <T extends BasicInterpreterBuilder> void teeLocal(T b, BytecodeLocal local) {
877925
b.beginTeeLocal(local);
878926
b.emitLoadNull();
@@ -889,22 +937,26 @@ private static <T extends BasicInterpreterBuilder> void teeLocalRange(T b, Bytec
889937
public void testInvalidLocalAccesses() {
890938
assertParseFailure(siblingRootsTest(LocalsTest::loadLocal));
891939
assertParseFailure(siblingRootsTest(LocalsTest::storeLocal));
940+
assertParseFailure(siblingRootsTest(LocalsTest::clearLocal));
892941
assertParseFailure(siblingRootsTest(LocalsTest::teeLocal));
893942
assertParseFailure(siblingRootsTest(LocalsTest::teeLocalRange));
894943

895944
assertParseFailure(nestedRootsInnerAccessTest(LocalsTest::loadLocal));
896945
assertParseFailure(nestedRootsInnerAccessTest(LocalsTest::storeLocal));
946+
assertParseFailure(nestedRootsInnerAccessTest(LocalsTest::clearLocal));
897947
assertParseFailure(nestedRootsInnerAccessTest(LocalsTest::teeLocal));
898948
assertParseFailure(nestedRootsInnerAccessTest(LocalsTest::teeLocalRange));
899949

900950
assertParseFailure(nestedRootsOuterAccessTest(LocalsTest::loadLocal));
901951
assertParseFailure(nestedRootsOuterAccessTest(LocalsTest::storeLocal));
952+
assertParseFailure(nestedRootsOuterAccessTest(LocalsTest::clearLocal));
902953
assertParseFailure(nestedRootsOuterAccessTest(LocalsTest::teeLocal));
903954
assertParseFailure(nestedRootsOuterAccessTest(LocalsTest::teeLocalRange));
904955

905956
if (run.hasBlockScoping()) {
906957
assertParseFailure(outOfScopeTest(LocalsTest::loadLocal));
907958
assertParseFailure(outOfScopeTest(LocalsTest::storeLocal));
959+
assertParseFailure(outOfScopeTest(LocalsTest::clearLocal));
908960
assertParseFailure(outOfScopeTest(LocalsTest::teeLocal));
909961
assertParseFailure(outOfScopeTest(LocalsTest::teeLocalRange));
910962
}
@@ -997,3 +1049,22 @@ public void testInvalidMaterializedLocalAccesses() {
9971049
}
9981050

9991051
}
1052+
1053+
@ExpectWarning("Custom operation with name ClearLocal conflicts with a built-in operation with the same name. The built-in operation will not be generated.%")
1054+
@GenerateBytecode(languageClass = BytecodeDSLTestLanguage.class)
1055+
abstract class HidesBuiltin extends RootNode implements BytecodeRootNode {
1056+
1057+
protected HidesBuiltin(BytecodeDSLTestLanguage language, FrameDescriptor frameDescriptor) {
1058+
super(language, frameDescriptor);
1059+
}
1060+
1061+
@Operation
1062+
@ConstantOperand(type = LocalAccessor.class)
1063+
public static final class ClearLocal {
1064+
@Specialization
1065+
public static void doClear(VirtualFrame frame, LocalAccessor accessor, @Bind BytecodeNode bytecode) {
1066+
accessor.clear(bytecode, frame);
1067+
}
1068+
}
1069+
1070+
}

truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/TruffleSuppressedWarnings.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,9 @@ private TruffleSuppressedWarnings() {
7373
public static final String DEPRECATION = "deprecation";
7474
public static final String INTERPRETED_PERFORMANCE = "truffle-interpreted-performance";
7575
public static final String FORCE_CACHED = "truffle-force-cached";
76+
public static final String HIDE_BUILTIN = "truffle-hide-builtin";
7677
public static final List<String> ALL_KEYS = List.of(ALL, TRUFFLE, STATIC_METHOD, UNEXPECTED_RESULT_REWRITE, LIMIT, UNUSED, NEVERDEFAULT, INLINING_RECOMMENDATION, SHARING_RECOMMENDATION,
77-
ABSTRACT_LIBRARY_EXPORT, DEPRECATION, INTERPRETED_PERFORMANCE, FORCE_CACHED);
78+
ABSTRACT_LIBRARY_EXPORT, DEPRECATION, INTERPRETED_PERFORMANCE, FORCE_CACHED, HIDE_BUILTIN);
7879

7980
public static Set<String> getWarnings(Element element) {
8081
AnnotationMirror currentWarnings = ElementUtils.findAnnotationMirror(element, SuppressWarnings.class);

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

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4221,7 +4221,7 @@ private CodeExecutableElement createBegin(OperationModel operation) {
42214221
b.declaration(operationStack.asType(), "parentScope", "getCurrentScope()");
42224222
}
42234223
break;
4224-
case STORE_LOCAL: /* LOAD_LOCAL handled by createEmit */
4224+
case STORE_LOCAL: /* LOAD_LOCAL, CLEAR_LOCAL handled by createEmit */
42254225
case STORE_LOCAL_MATERIALIZED:
42264226
case LOAD_LOCAL_MATERIALIZED:
42274227
emitValidateLocalScope(b, operation);
@@ -4515,6 +4515,7 @@ private Map<OperationField, String> initOperationBeginData(CodeTreeBuilder b, Op
45154515
case STORE_LOCAL_MATERIALIZED:
45164516
case LOAD_LOCAL_MATERIALIZED:
45174517
case LOAD_LOCAL:
4518+
case CLEAR_LOCAL:
45184519
values.put(operationFields.local, "(BytecodeLocalImpl)" + operation.getOperationBeginArgumentName(0));
45194520
break;
45204521
case IF_THEN:
@@ -4984,7 +4985,7 @@ private CodeExecutableElement createEnd(OperationModel operation) {
49844985

49854986
if (model.enableBlockScoping) {
49864987
// local table entries are emitted at the end of the block.
4987-
createEndLocalsBlock(b, operation);
4988+
emitEndBlockScope(b, operation);
49884989
}
49894990

49904991
break;
@@ -5127,7 +5128,7 @@ private void createSerializeEnd(OperationModel operation, CodeTreeBuilder b) {
51275128
});
51285129
}
51295130

5130-
private void createEndLocalsBlock(CodeTreeBuilder b, OperationModel operation) {
5131+
private void emitEndBlockScope(CodeTreeBuilder b, OperationModel operation) {
51315132
b.startIf().string(operationStack.read(operation, operationFields.numLocals), " > 0").end().startBlock();
51325133

51335134
b.statement("state.maxLocals = Math.max(state.maxLocals, ", operationStack.read(operation, operationFields.frameOffset), " + ",
@@ -5247,7 +5248,7 @@ private CodeExecutableElement createEndRoot(OperationModel rootOperation) {
52475248
b.end(2);
52485249

52495250
if (model.enableBlockScoping) {
5250-
createEndLocalsBlock(b, rootOperation);
5251+
emitEndBlockScope(b, rootOperation);
52515252
}
52525253

52535254
for (VariableElement e : ElementFilter.fieldsIn(abstractBytecodeNode.getEnclosedElements())) {
@@ -5674,6 +5675,7 @@ private void buildEmitOperationInstruction(CodeTreeBuilder b, OperationModel ope
56745675
}
56755676
yield immediates.toArray(String[]::new);
56765677
}
5678+
case CLEAR_LOCAL -> new String[]{"((BytecodeLocalImpl) " + operation.getOperationBeginArgumentName(0) + ").frameIndex"};
56775679
case STORE_LOCAL_MATERIALIZED -> {
56785680
List<String> immediates = new ArrayList<>();
56795681
immediates.add(operationStack.read(operation, operationFields.local) + ".frameIndex");
@@ -5937,7 +5939,7 @@ private CodeExecutableElement createEmit(OperationModel operation) {
59375939

59385940
b.startStatement().startCall("beforeChild").end(2);
59395941

5940-
if (operation.kind == OperationKind.LOAD_LOCAL) {
5942+
if (operation.kind == OperationKind.LOAD_LOCAL || operation.kind == OperationKind.CLEAR_LOCAL) {
59415943
emitValidateLocalScope(b, operation);
59425944
}
59435945

@@ -8643,6 +8645,9 @@ public List<OperationField> mapFieldsToOperation(OperationModel operation) {
86438645
fields.add(childBci);
86448646
}
86458647
break;
8648+
case CLEAR_LOCAL:
8649+
fields.add(local); // init
8650+
break;
86468651
case IF_THEN:
86478652
fields.add(thenReachable); // init
86488653
fields.add(falseBranchFixupBci);

truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/bytecode/model/BytecodeDSLBuiltins.java

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,8 @@ Conditional implements a conditional expression (e.g., {@code condition ? thens
245245
.setOperationBeginArguments(new OperationArgument(types.BytecodeLocal, Encoding.LOCAL, "local", "the local to store to")) //
246246
.setDynamicOperands(child("value")) //
247247
.setInstruction(m.storeLocalInstruction);
248+
m.clearLocalInstruction = m.instruction(InstructionKind.CLEAR_LOCAL, "clear.local", m.signature(void.class))//
249+
.addImmediate(ImmediateKind.FRAME_INDEX, "frame_index");
248250
if (m.enableMaterializedLocalAccesses) {
249251
m.loadLocalMaterializedOperation = m.operation(OperationKind.LOAD_LOCAL_MATERIALIZED, "LoadLocalMaterialized",
250252
String.format("""
@@ -346,17 +348,16 @@ This operation must be (directly or indirectly) enclosed within a Source operati
346348

347349
}
348350

349-
m.clearLocalInstruction = m.instruction(InstructionKind.CLEAR_LOCAL, "clear.local", m.signature(void.class));
350-
m.clearLocalInstruction.addImmediate(ImmediateKind.FRAME_INDEX, "frame_index");
351-
352351
m.sortInstructionsByKind();
353352
}
354353

355354
/*
356355
* Invoked when instructions are being finalized. Allows to conditionally add builtin
357-
* instructions depending on the almost final model.
356+
* instructions/operations depending on the almost final model.
358357
*/
359-
public static void addBuiltinsOnFinalize(BytecodeDSLModel m) {
358+
public static void addBuiltinsOnFinalize(BytecodeDSLModel m, TruffleTypes types) {
359+
addBackwardCompatibleOperations(m, types);
360+
360361
if (m.hasCustomVariadic) {
361362
m.loadVariadicInstruction = m.instruction(InstructionKind.LOAD_VARIADIC, "load.variadic", m.signature(void.class, Object.class));
362363
m.createVariadicInstruction = m.instruction(InstructionKind.CREATE_VARIADIC, "create.variadic", m.signature(Object.class, Object.class));
@@ -421,6 +422,23 @@ public static void addBuiltinsOnFinalize(BytecodeDSLModel m) {
421422
}
422423
}
423424

425+
/**
426+
* Built-in operations introduced after the initial release of the Bytecode DSL can potentially
427+
* conflict with existing user-defined operations. We add such operations after parsing the
428+
* specification only if an operation with the same name is not already defined.
429+
*/
430+
private static void addBackwardCompatibleOperations(BytecodeDSLModel m, TruffleTypes types) {
431+
OperationModel clearLocalOperation = m.operation(OperationKind.CLEAR_LOCAL, "ClearLocal", String.format("""
432+
ClearLocal clears {@code local} in the current frame.
433+
Until a value is written to the local, a subsequent LoadLocal %s.
434+
""", loadLocalUndefinedBehaviour(m)), "ClearLocal", true);
435+
if (clearLocalOperation != null) {
436+
clearLocalOperation.setVoid(true)//
437+
.setOperationBeginArguments(new OperationArgument(types.BytecodeLocal, Encoding.LOCAL, "local", "the local to clear"))//
438+
.setInstruction(m.clearLocalInstruction);
439+
}
440+
}
441+
424442
private static String rootOperationJavadoc(BytecodeDSLModel m) {
425443
String rootClass = m.templateType.getSimpleName().toString();
426444
String innerRootBehaviour;

truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/bytecode/model/BytecodeDSLModel.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
import javax.lang.model.type.TypeMirror;
6363

6464
import com.oracle.truffle.dsl.processor.ProcessorContext;
65+
import com.oracle.truffle.dsl.processor.TruffleSuppressedWarnings;
6566
import com.oracle.truffle.dsl.processor.bytecode.model.InstructionModel.ImmediateKind;
6667
import com.oracle.truffle.dsl.processor.bytecode.model.InstructionModel.InstructionKind;
6768
import com.oracle.truffle.dsl.processor.bytecode.model.OperationModel.OperationKind;
@@ -171,7 +172,6 @@ public BytecodeDSLModel(ProcessorContext context, TypeElement templateType, Anno
171172
public CustomOperationModel epilogReturn = null;
172173
public CustomOperationModel epilogExceptional = null;
173174

174-
public InstructionModel nullInstruction;
175175
public InstructionModel popInstruction;
176176
public InstructionModel dupInstruction;
177177
public InstructionModel returnInstruction;
@@ -298,8 +298,17 @@ public OperationModel operation(OperationKind kind, String name, String javadoc)
298298
}
299299

300300
public OperationModel operation(OperationKind kind, String name, String javadoc, String builderName) {
301+
return operation(kind, name, javadoc, builderName, false);
302+
}
303+
304+
public OperationModel operation(OperationKind kind, String name, String javadoc, String builderName, boolean optionalBuilltin) {
301305
if (operations.containsKey(name)) {
302-
addError("Multiple operations declared with name %s. Operation names must be distinct.", name);
306+
if (optionalBuilltin) {
307+
addSuppressableWarning(TruffleSuppressedWarnings.HIDE_BUILTIN, "Custom operation with name %s conflicts with a built-in operation with the same name. " +
308+
"The built-in operation will not be generated. ", name);
309+
} else {
310+
addError("Multiple operations declared with name %s. Operation names must be distinct.", name);
311+
}
303312
return null;
304313
}
305314
OperationModel op = new OperationModel(this, operationId++, kind, name, builderName, javadoc);
@@ -475,7 +484,7 @@ public void finalizeInstructions() {
475484
}
476485
}
477486

478-
BytecodeDSLBuiltins.addBuiltinsOnFinalize(this);
487+
BytecodeDSLBuiltins.addBuiltinsOnFinalize(this, types);
479488

480489
LinkedHashMap<String, InstructionModel> newInstructions = new LinkedHashMap<>();
481490
for (var entry : instructions.entrySet()) {

truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/bytecode/model/OperationModel.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ public enum OperationKind {
8181
LOAD_LOCAL_MATERIALIZED,
8282
STORE_LOCAL,
8383
STORE_LOCAL_MATERIALIZED,
84+
CLEAR_LOCAL,
8485

8586
CUSTOM,
8687
CUSTOM_SHORT_CIRCUIT,

0 commit comments

Comments
 (0)