Skip to content

Commit c3e001b

Browse files
committed
Add GenerateBytecoded#illegalLocalException.
1 parent 6e95459 commit c3e001b

File tree

13 files changed

+1959
-447
lines changed

13 files changed

+1959
-447
lines changed

truffle/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ This changelog summarizes major changes between Truffle versions relevant to lan
6969
* GR-44829: `TruffleString` nodes no longer profile the `expectedEncoding` parameter for interpreter performance reasons. Languages with non-constant string encodings should profile the encoding before passing it to `TruffleString` nodes.
7070

7171
* GR-61161: Bytecode DSL: Added support for basic instruction rewriting. At bytecode build time, the builder can perform peephole optimization to remove redundant loads. This new optimization can be configured using `@GenerateBytecode(enableInstructionRewriting=true|false)`.
72+
* GR-71765: Bytecode DSL: Added support for specifying an illegal local exception via `@GenerateBytecode(illegalLocalException=SomeException.class)`. This configures the interpreter to throw a custom exception when loading a cleared local, as an alternative to the default behaviour (throwing a `FrameSlotTypeException`). This option is mutually exclusive with the default local value option (`@GenerateBytecode(defaultLocalValue = "someValue")`).
7273

7374
## Version 25.0
7475
* 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/IllegalLocalExceptionTest.java

Lines changed: 1162 additions & 0 deletions
Large diffs are not rendered by default.

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,10 +374,12 @@ meth public abstract !hasdefault boolean enableUncachedInterpreter()
374374
meth public abstract !hasdefault boolean enableYield()
375375
meth public abstract !hasdefault boolean inlinePrimitiveConstants()
376376
meth public abstract !hasdefault boolean storeBytecodeIndexInFrame()
377+
meth public abstract !hasdefault java.lang.Class<? extends java.lang.RuntimeException> illegalLocalException()
377378
meth public abstract !hasdefault java.lang.Class<?> tagTreeNodeLibrary()
378379
meth public abstract !hasdefault java.lang.Class<?>[] boxingEliminationTypes()
379380
meth public abstract !hasdefault java.lang.String defaultLocalValue()
380381
meth public abstract !hasdefault java.lang.String defaultUncachedThreshold()
382+
meth public abstract !hasdefault java.lang.String illegalLocalExceptionFactory()
381383
meth public abstract !hasdefault java.lang.String variadicStackLimit()
382384
meth public abstract java.lang.Class<? extends com.oracle.truffle.api.TruffleLanguage<?>> languageClass()
383385

truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/BytecodeNode.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -521,6 +521,9 @@ public final Object[] getLocalValues(int bytecodeIndex, Frame frame) {
521521
* method should be used for uncommon scenarios, like when a node needs to read a local directly
522522
* from the frame. Prefer reading locals directly in the bytecode (via {@code LoadLocal}
523523
* operations or {@link LocalAccessor}) when possible.
524+
* <p>
525+
* This accessor does not respect the {@link GenerateBytecode#illegalLocalException()}
526+
* attribute; if the attribute is set, this method will return null for cleared locals.
524527
*
525528
* @param bytecodeIndex the current bytecode index of the given frame. A valid bytecode index
526529
* can be obtained by calling {@link BytecodeLocation#getBytecodeIndex()} or

truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/GenerateBytecode.java

Lines changed: 77 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -507,9 +507,12 @@
507507
boolean enableSpecializationIntrospection() default false;
508508

509509
/**
510-
* Sets the default value that {@link BytecodeLocal locals} return when they are read without
511-
* ever being written. Unless a default local value is specified, loading from a
512-
* {@link BytecodeLocal local} that was never stored into throws a
510+
* Specifies the default value produced when attempting to load a cleared {@link BytecodeLocal
511+
* local} (i.e., a local that has not been written to).
512+
* <p>
513+
* This attribute is mutually exclusive with {@link #illegalLocalException()}: the interpreter
514+
* can either produce a default value or throw a user-provided exception when loading a cleared
515+
* local. If neither is explicitly specified, the interpreter defaults to throwing a
513516
* {@link FrameSlotTypeException}.
514517
* <p>
515518
* It is recommended for the default local value expression to refer to a static and final
@@ -534,6 +537,77 @@
534537
*/
535538
String defaultLocalValue() default "";
536539

540+
/**
541+
* Specifies the exception to throw when attempting to load a cleared {@link BytecodeLocal
542+
* local} (i.e., a local that has not been written to).
543+
* <p>
544+
* This attribute is mutually exclusive with {@link #defaultLocalValue()}: the interpreter can
545+
* either produce a default value or throw a user-provided exception when loading a cleared
546+
* local. If neither is explicitly specified, the interpreter defaults to throwing a
547+
* {@link FrameSlotTypeException}.
548+
* <p>
549+
* When an illegal local exception is specified, the interpreter checks if a local is cleared
550+
* before each load; if the local is cleared, the interpreter creates and throws an instance of
551+
* the exception using the factory method. The interpreter will attempt to
552+
* {@link #enableQuickening() quicken} away the clear check when possible.
553+
* <p>
554+
* Below is an example:
555+
*
556+
* <pre>
557+
* &#64;GenerateBytecode(..., illegalLocalException = MyIllegalLocalException.class)
558+
* abstract class MyBytecodeRootNode extends RootNode implements BytecodeRootNode {
559+
* // ...
560+
* }
561+
*
562+
* class MyIllegaLocalException extends AbstractTruffleException {
563+
* public static MyIllegalLocalException create(Node location, BytecodeNode bytecode, BytecodeLocation location, LocalVariable variable) {
564+
* // ...
565+
* }
566+
* }
567+
* </pre>
568+
*
569+
* The provided exception class must declare a static factory method for instantiating
570+
* exceptions. By default, this method is named {@code create}, but this can be overridden using
571+
* {@link #illegalLocalExceptionFactory()}.
572+
* <p>
573+
* The factory method should return an instance of the exception class. It may take zero or more
574+
* parameters of the following types (in any order):
575+
* <ul>
576+
* <li>{@link Node}: the current location (equivalent to {@code @Bind("$node")})</li>
577+
* <li>{@link BytecodeNode}: the current bytecode node</li>
578+
* <li>{@link BytecodeLocation}: the current bytecode location</li>
579+
* <li>{@link LocalVariable}: an introspection object modeling the local's metadata (name, info,
580+
* etc.)</li>
581+
* </ul>
582+
*
583+
* <p>
584+
* If the provided exception class is a Truffle exception (i.e., it extends
585+
* {@link com.oracle.truffle.api.exception.AbstractTruffleException}), the exception can be
586+
* thrown from runtime compiled code without deoptimization; otherwise, it is considered an
587+
* internal error and throwing the exception will always trigger deoptimization.
588+
* <p>
589+
* Note: due to current limitations of the Bytecode DSL implementation, illegal local exceptions
590+
* are not yet fully supported with local accessors:
591+
* <ul>
592+
* <li>If an operation uses a {@link LocalAccessor} or {@link LocalRangeAccessor}, the factory
593+
* method cannot declare a {@link BytecodeLocation} parameter.</li>
594+
* <li>If an operation uses a {@link MaterializedLocalAccessor}, illegal local exceptions cannot
595+
* be used.</li>
596+
* </ul>
597+
*
598+
* @since 25.1
599+
*/
600+
Class<? extends RuntimeException> illegalLocalException() default FrameSlotTypeException.class;
601+
602+
/**
603+
* Specifies the name of the static factory method used to instantiate illegal local exceptions.
604+
* This attribute is only applicable if an {@link #illegalLocalException()} class is specified.
605+
*
606+
* @see #illegalLocalException()
607+
* @since 25.1
608+
*/
609+
String illegalLocalExceptionFactory() default "create";
610+
537611
/**
538612
* Whether the {@link BytecodeDebugListener} methods should be notified by generated code. By
539613
* default the debug bytecode listener is enabled if the root node implements

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

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,11 @@
6767

6868
import com.oracle.truffle.dsl.processor.ProcessorContext;
6969
import com.oracle.truffle.dsl.processor.bytecode.model.BytecodeDSLModel;
70+
import com.oracle.truffle.dsl.processor.bytecode.model.BytecodeDSLModel.LoadIllegalLocalStrategy;
7071
import com.oracle.truffle.dsl.processor.bytecode.model.InstructionModel;
7172
import com.oracle.truffle.dsl.processor.bytecode.model.InstructionModel.ImmediateKind;
7273
import com.oracle.truffle.dsl.processor.bytecode.model.InstructionModel.InstructionImmediate;
74+
import com.oracle.truffle.dsl.processor.bytecode.model.InstructionModel.QuickeningKind;
7375
import com.oracle.truffle.dsl.processor.generator.GeneratorUtils;
7476
import com.oracle.truffle.dsl.processor.java.ElementUtils;
7577
import com.oracle.truffle.dsl.processor.java.model.CodeAnnotationMirror;
@@ -357,7 +359,19 @@ private CodeExecutableElement createGetLocalValueInternal() {
357359
buildVerifyFrameDescriptor(b, true);
358360

359361
b.declaration(type(int.class), "frameIndex", "USER_LOCALS_START_INDEX + localOffset");
362+
363+
if (parent.model.loadIllegalLocalStrategy == LoadIllegalLocalStrategy.CUSTOM_EXCEPTION) {
364+
b.startIf();
365+
b.startCall("frame", "getTag").string("frameIndex").end();
366+
b.string(" == ");
367+
b.staticReference(parent.frameTagsElement.getIllegal());
368+
b.end().startBlock();
369+
BytecodeNodeElement.emitThrowIllegalLocalException(parent.model, b, null, CodeTreeBuilder.singleString("this"), CodeTreeBuilder.singleString("localIndex"));
370+
b.end();
371+
}
372+
360373
b.startReturn().string("frame.getObject(frameIndex)").end();
374+
361375
return ex;
362376
}
363377

@@ -1035,7 +1049,7 @@ private static boolean acceptsInvalidChildBci(BytecodeDSLModel model, Instructio
10351049
}
10361050

10371051
private static boolean isSameOrGenericQuickening(InstructionModel instr, InstructionModel expected) {
1038-
return instr == expected || instr.getQuickeningRoot() == expected && instr.specializedType == null;
1052+
return instr == expected || instr.getQuickeningRoot() == expected && instr.quickeningKind == QuickeningKind.GENERIC;
10391053
}
10401054

10411055
// calls dump, but catches any exceptions and falls back on an error string
@@ -1269,7 +1283,7 @@ private CodeExecutableElement createTransition() {
12691283

12701284
record InstrumentationGroup(int instructionLength, boolean instrumentation, boolean tagInstrumentation, InstructionImmediate tagNodeImmediate)
12711285
implements
1272-
Comparable<AbstractBytecodeNodeElement.InstrumentationGroup> {
1286+
Comparable<AbstractBytecodeNodeElement.InstrumentationGroup> {
12731287
InstrumentationGroup(InstructionModel instr) {
12741288
this(instr.getInstructionLength(), instr.isInstrumentation(), instr.isTagInstrumentation(),
12751289
instr.isTagInstrumentation() ? instr.getImmediate(ImmediateKind.TAG_NODE) : null);

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@
9393
import com.oracle.truffle.dsl.processor.bytecode.model.InstructionRewriteRuleModel;
9494
import com.oracle.truffle.dsl.processor.bytecode.model.OperationModel;
9595
import com.oracle.truffle.dsl.processor.bytecode.model.ShortCircuitInstructionModel;
96+
import com.oracle.truffle.dsl.processor.bytecode.model.BytecodeDSLModel.LoadIllegalLocalStrategy;
9697
import com.oracle.truffle.dsl.processor.bytecode.model.DFABuilder.DFAModel;
9798
import com.oracle.truffle.dsl.processor.bytecode.model.DFABuilder.RewriteRuleState;
9899
import com.oracle.truffle.dsl.processor.bytecode.model.InstructionModel.ImmediateKind;
@@ -2706,7 +2707,7 @@ private CodeExecutableElement createEndRoot(OperationModel rootOperation) {
27062707

27072708
b.startDeclaration(types.FrameDescriptor_Builder, "frameDescriptorBuilder").startStaticCall(types.FrameDescriptor, "newBuilder").end().end();
27082709

2709-
if (model.defaultLocalValueExpression != null) {
2710+
if (model.loadIllegalLocalStrategy == LoadIllegalLocalStrategy.DEFAULT_VALUE) {
27102711
b.statement("frameDescriptorBuilder.defaultValue(DEFAULT_LOCAL_VALUE)");
27112712
} else {
27122713
b.statement("frameDescriptorBuilder.defaultValueIllegal()");

0 commit comments

Comments
 (0)