Skip to content

Commit caef63b

Browse files
committed
[GR-77583] Add Bytecode DSL instrumentation value interception.
PullRequest: graal/24736
2 parents be93306 + c9965b8 commit caef63b

10 files changed

Lines changed: 823 additions & 3 deletions

File tree

truffle/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
This changelog summarizes major changes between Truffle versions relevant to languages implementors building upon the Truffle framework. The main focus is on APIs exported by Truffle.
44

55
## Version 25.2
6+
* GR-77583: Bytecode DSL: Added `BytecodeRootNode.interceptIncomingValue(Object)` and `BytecodeRootNode.interceptOutgoingValue(Object)` to convert values exchanged with tag instrumentation.
67
* GR-75459: Bytecode DSL: Added multi-operand support for `@Yield` operations.
78
* GR-75438: Bytecode DSL: Added `@Return` for user-defined return operations that customize the value returned from a bytecode root node.
89
* GR-77108: Added `HostCompilerDirectives.BytecodeInterpreterHandlerConfig#secondarySwitch()` to prevent handler outlining when a secondary bytecode interpreter switch is compiled separately.

truffle/docs/bytecode_dsl/UserGuide.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -554,6 +554,8 @@ The Bytecode DSL supports two forms of instrumentation:
554554
1. [`@Instrumentation`](https://github.com/oracle/graal/blob/master/truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/Instrumentation.java) operations, which are emitted and behave just like custom [`@Operation`](https://github.com/oracle/graal/blob/master/truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/Operation.java)s. These operations can perform special actions like logging or modifying the value produced by another operation. `@Instrumentation` operations must have no stack effects, so they can either have no children and produce no value, or have one child and produce a value (which allows you to modify the result of an instrumented operation).
555555
2. Tag-based instrumentation associates operations with particular instrumentation [`Tag`](https://github.com/oracle/graal/blob/master/truffle/src/com.oracle.truffle.api.instrumentation/src/com/oracle/truffle/api/instrumentation/Tag.java)s using `Tag` operations. If these instrumentations are enabled, the bytecode will include instructions that invoke the various event callbacks on any attached [`ExecutionEventNode`](https://github.com/oracle/graal/blob/master/truffle/src/com.oracle.truffle.api.instrumentation/src/com/oracle/truffle/api/instrumentation/ExecutionEventNode.java)s (e.g., `onEnter`, `onReturnValue`) when executing the enclosed operation. Tag-based instrumentation can be enabled using the `enableTagInstrumentation` flag in [`@GenerateBytecode`](https://github.com/oracle/graal/blob/master/truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/GenerateBytecode.java).
556556

557+
Override [`BytecodeRootNode.interceptOutgoingValue`](https://github.com/oracle/graal/blob/master/truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/BytecodeRootNode.java) to convert every guest-language value before it is exposed to tag instrumentation, for example to replace an internal representation with an interop-capable value. Override [`BytecodeRootNode.interceptIncomingValue`](https://github.com/oracle/graal/blob/master/truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/BytecodeRootNode.java) to convert values supplied by tag instrumentation to representations supported by the guest language. The hooks add no generated calls unless they are overridden and do not apply to `@Instrumentation` operations.
558+
557559
Note: once instrumentation instructions are added, they cannot be removed from the bytecode. However, in tag-based instrumentation you can still disable the instruments so that the instrumentation instructions have no effect.
558560

559561
### Reparsing

truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/ValueInterceptionTest.java

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

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,25 @@ protected UnusedUncachedThreshold(ErrorLanguage language, FrameDescriptor frameD
155155
}
156156
}
157157

158+
@GenerateBytecode(languageClass = ErrorLanguage.class)
159+
public abstract static class InterceptionWithoutTagInstrumentation extends RootNode implements BytecodeRootNode {
160+
protected InterceptionWithoutTagInstrumentation(ErrorLanguage language, FrameDescriptor frameDescriptor) {
161+
super(language, frameDescriptor);
162+
}
163+
164+
@ExpectError("interceptIncomingValue can only be overridden when tag instrumentation is enabled. Enable tag instrumentation or remove this override.")
165+
@Override
166+
public Object interceptIncomingValue(Object value) {
167+
return value;
168+
}
169+
170+
@ExpectError("interceptOutgoingValue can only be overridden when tag instrumentation is enabled. Enable tag instrumentation or remove this override.")
171+
@Override
172+
public Object interceptOutgoingValue(Object value) {
173+
return value;
174+
}
175+
}
176+
158177
@ExpectError("Error parsing expression%")
159178
@GenerateBytecode(languageClass = ErrorLanguage.class, enableUncachedInterpreter = true, defaultUncachedThreshold = "-1")
160179
public abstract static class BadUncachedThreshold extends RootNode implements BytecodeRootNode {

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,8 @@ meth public com.oracle.truffle.api.source.Source getSource()
221221
meth public com.oracle.truffle.api.source.SourceSection ensureSourceSection()
222222
meth public com.oracle.truffle.api.source.SourceSection ensureSourceSectionWithContent()
223223
meth public java.lang.Object interceptControlFlowException(com.oracle.truffle.api.nodes.ControlFlowException,com.oracle.truffle.api.frame.VirtualFrame,com.oracle.truffle.api.bytecode.BytecodeNode,int) throws java.lang.Throwable
224+
meth public java.lang.Object interceptIncomingValue(java.lang.Object)
225+
meth public java.lang.Object interceptOutgoingValue(java.lang.Object)
224226
meth public java.lang.String dump()
225227
meth public java.lang.Throwable interceptInternalException(java.lang.Throwable,com.oracle.truffle.api.frame.VirtualFrame,com.oracle.truffle.api.bytecode.BytecodeNode,int)
226228
meth public void traceTransition(com.oracle.truffle.api.bytecode.BytecodeTransition,com.oracle.truffle.api.frame.Frame)

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

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@
4545
import com.oracle.truffle.api.exception.AbstractTruffleException;
4646
import com.oracle.truffle.api.frame.Frame;
4747
import com.oracle.truffle.api.frame.VirtualFrame;
48+
import com.oracle.truffle.api.instrumentation.EventContext;
49+
import com.oracle.truffle.api.instrumentation.ExecutionEventListener;
50+
import com.oracle.truffle.api.instrumentation.ExecutionEventNode;
4851
import com.oracle.truffle.api.nodes.ControlFlowException;
4952
import com.oracle.truffle.api.source.Source;
5053
import com.oracle.truffle.api.source.SourceSection;
@@ -80,6 +83,45 @@ public interface BytecodeRootNode {
8083
*/
8184
Object execute(VirtualFrame frame);
8285

86+
/**
87+
* Optional hook invoked to convert a value received from tag instrumentation before it resumes
88+
* guest-language execution. Instruments can introduce values by returning a non-{@code null}
89+
* value from an unwind callback such as
90+
* {@link ExecutionEventNode#onUnwind(VirtualFrame, Object)} or
91+
* {@link ExecutionEventListener#onUnwind(EventContext, VirtualFrame, Object)}. Such values are
92+
* interop values and may need to be converted to a representation supported by the guest
93+
* language.
94+
*
95+
* @param value the interop value received from tag instrumentation
96+
* @return the value to resume execution with
97+
* @see EventContext#createUnwind(Object)
98+
* @since 25.2
99+
*/
100+
@SuppressWarnings("unused")
101+
default Object interceptIncomingValue(Object value) {
102+
return value;
103+
}
104+
105+
/**
106+
* Optional hook invoked to convert every guest-language value before it is exposed to tag
107+
* instrumentation. This can be used to replace an internal representation with an
108+
* interop-capable value. The original value continues to be used for guest-language execution.
109+
* <p>
110+
* The returned value must be an interop value or {@code null}. For values reported after
111+
* successful execution of a standard tag, this requirement is checked when Java assertions are
112+
* enabled, and a violation results in a {@link ClassCastException}. Values not subject to this
113+
* check are passed to tag instrumentation unchanged. The hook is not invoked when the
114+
* instrumented operation does not produce a value.
115+
*
116+
* @param value the guest-language value being exposed to tag instrumentation
117+
* @return the interop value to report to tag instrumentation
118+
* @since 25.2
119+
*/
120+
@SuppressWarnings("unused")
121+
default Object interceptOutgoingValue(Object value) {
122+
return value;
123+
}
124+
83125
/**
84126
* Optional hook invoked when a {@link ControlFlowException} is thrown during execution. This
85127
* hook can do one of four things:

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

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1536,6 +1536,14 @@ private TypeMirror emitLoadException(CodeTreeBuilder b, String resultLocalName)
15361536
return type(Throwable.class);
15371537
}
15381538

1539+
private void emitOutgoingValue(CodeTreeBuilder b, String value) {
1540+
if (model().interceptOutgoingValue == null) {
1541+
b.string(value);
1542+
} else {
1543+
b.startCall("getRoot()", model().interceptOutgoingValue).string(value).end();
1544+
}
1545+
}
1546+
15391547
private TypeMirror emitTagLeave(CodeTreeBuilder b, ExecutionMode mode) {
15401548
Operand valueOperand = instruction.signature.singleDynamicOperand();
15411549
if (mode.isFastPath()) {
@@ -1545,7 +1553,7 @@ private TypeMirror emitTagLeave(CodeTreeBuilder b, ExecutionMode mode) {
15451553
b.end();
15461554
b.startStatement().startCall("tagNode.findProbe().onReturnValue");
15471555
b.string(parent.localFrame());
1548-
b.string(valueOperand.localName());
1556+
emitOutgoingValue(b, valueOperand.localName());
15491557
b.end().end();
15501558
} else { // slow-path
15511559

@@ -1607,7 +1615,7 @@ private TypeMirror emitTagLeave(CodeTreeBuilder b, ExecutionMode mode) {
16071615
b.end();
16081616
b.startStatement().startCall("tagNode.findProbe().onReturnValue");
16091617
b.string(parent.localFrame());
1610-
b.string(valueOperand.localName());
1618+
emitOutgoingValue(b, valueOperand.localName());
16111619
b.end(2);
16121620
}
16131621
return null;
@@ -1638,7 +1646,7 @@ private TypeMirror emitTagYield(CodeTreeBuilder b) {
16381646

16391647
b.startStatement().startCall("tagNode.findProbe().onYield");
16401648
b.string(parent.localFrame());
1641-
b.string("result_");
1649+
emitOutgoingValue(b, "result_");
16421650
b.end(2);
16431651
return null;
16441652
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2244,6 +2244,10 @@ private CodeExecutableElement createHandleException() {
22442244
TypeMirror targetType = entry.getKey();
22452245
b.startCaseBlock();
22462246

2247+
if (parent.model.interceptIncomingValue != null) {
2248+
b.startStatement().string("result = ").startCall("root", parent.model.interceptIncomingValue).string("result").end(2);
2249+
}
2250+
22472251
CodeExecutableElement expectMethod = null;
22482252
if (!ElementUtils.isObject(targetType)) {
22492253
expectMethod = parent.lookupExpectMethod(parent.parserType, targetType);

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,9 @@ public boolean binds(ParameterKind parameter) {
185185
public ExecutableElement fdConstructor;
186186
public ExecutableElement fdBuilderConstructor;
187187
public ExecutableElement interceptControlFlowException;
188+
public ExecutableElement interceptIncomingValue;
188189
public ExecutableElement interceptInternalException;
190+
public ExecutableElement interceptOutgoingValue;
189191
public ExecutableElement interceptTruffleException;
190192
public ExecutableElement traceTransition;
191193

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,10 +384,23 @@ private void parseBytecodeDSLModel(TypeElement typeElement, BytecodeDSLModel mod
384384

385385
// Extract hook implementations.
386386
model.interceptControlFlowException = ElementUtils.findMethod(typeElement, "interceptControlFlowException");
387+
model.interceptIncomingValue = ElementUtils.findMethod(typeElement, "interceptIncomingValue");
387388
model.interceptInternalException = ElementUtils.findMethod(typeElement, "interceptInternalException");
389+
model.interceptOutgoingValue = ElementUtils.findMethod(typeElement, "interceptOutgoingValue");
388390
model.interceptTruffleException = ElementUtils.findMethod(typeElement, "interceptTruffleException");
389391
model.traceTransition = ElementUtils.findMethod(typeElement, "traceTransition");
390392

393+
if (!model.enableTagInstrumentation) {
394+
if (model.interceptIncomingValue != null) {
395+
model.addError(model.interceptIncomingValue,
396+
"interceptIncomingValue can only be overridden when tag instrumentation is enabled. Enable tag instrumentation or remove this override.");
397+
}
398+
if (model.interceptOutgoingValue != null) {
399+
model.addError(model.interceptOutgoingValue,
400+
"interceptOutgoingValue can only be overridden when tag instrumentation is enabled. Enable tag instrumentation or remove this override.");
401+
}
402+
}
403+
391404
checkRootNodeOverrides(typeElement, model);
392405
if (model.hasErrors()) {
393406
return;

0 commit comments

Comments
 (0)