From 75c5ea6a84ce437fd00b5cc6e9730a78f5a74621 Mon Sep 17 00:00:00 2001 From: Christian Humer Date: Mon, 3 Nov 2025 13:12:46 +0100 Subject: [PATCH] Bytecode DSL now generates a BytecodeDescriptor subclass that supports parsing, (de)serialization, and instruction introspection; added instruction-level dynamic tracing via InstructionTracer and engine options engine.TraceBytecode, engine.BytecodeHistogram; added BytecodeDescriptor.update(Language, BytecodeConfig) to apply bytecode configuration (e.g. instrumentation, source info) to all existing and future roots for a language. --- .../test/BytecodeDSLCompilationTest.java | 50 +- .../BytecodeDSLPartialEvaluationTest.java | 49 +- truffle/CHANGELOG.md | 9 + truffle/docs/Options.md | 5 + truffle/docs/bytecode_dsl/UserGuide.md | 128 ++ truffle/mx.truffle/suite.py | 4 +- .../test/AbstractInstructionTest.java | 10 +- .../bytecode/test/ConstantOperandTest.java | 31 +- .../bytecode/test/ConstantsBufferTest.java | 14 + .../bytecode/test/InstructionTracingTest.java | 1003 +++++++++++ .../bytecode/test/InstrumentationTest.java | 85 +- .../api/bytecode/test/LocalHelpersTest.java | 42 +- .../api/bytecode/test/ShortCircuitTest.java | 20 +- .../api/bytecode/test/StackTraceTest.java | 20 +- .../AbstractBasicInterpreterTest.java | 176 +- .../basic_interpreter/BasicInterpreter.java | 16 +- .../BasicInterpreterTest.java | 45 +- .../test/basic_interpreter/BindingsTest.java | 4 + .../test/basic_interpreter/BranchTest.java | 1 - .../BytecodeDescriptorTest.java | 118 ++ .../basic_interpreter/CopyLocalsTest.java | 4 +- .../test/basic_interpreter/LocalsTest.java | 4 +- .../test/basic_interpreter/SourcesTest.java | 73 +- .../test/basic_interpreter/YieldTest.java | 10 +- .../snapshot.sigtest | 143 +- .../api/bytecode/BytecodeAccessor.java | 21 + .../truffle/api/bytecode/BytecodeConfig.java | 17 +- .../api/bytecode/BytecodeDescriptor.java | 506 ++++++ .../api/bytecode/BytecodeEngineData.java | 273 +++ .../api/bytecode/BytecodeLocation.java | 2 +- .../api/bytecode/BytecodeRootNodes.java | 70 + .../truffle/api/bytecode/BytecodeSupport.java | 3 +- .../api/bytecode/GenerateBytecode.java | 18 +- .../truffle/api/bytecode/Instruction.java | 186 ++- .../api/bytecode/InstructionDescriptor.java | 225 +++ .../api/bytecode/InstructionTracer.java | 158 ++ .../debug/HistogramInstructionTracer.java | 689 ++++++++ .../debug/PrintInstructionTracer.java | 234 +++ .../com/oracle/truffle/api/impl/Accessor.java | 26 + .../truffle/dsl/processor/TruffleTypes.java | 10 + .../generator/BytecodeDSLCodeGenerator.java | 147 +- .../generator/BytecodeRootNodeElement.java | 1469 +++++++++++++---- .../BytecodeRootNodeErrorElement.java | 23 +- .../bytecode/model/BytecodeDSLBuiltins.java | 5 + .../bytecode/model/BytecodeDSLModel.java | 25 +- .../bytecode/model/InstructionModel.java | 9 +- .../bytecode/parser/BytecodeDSLParser.java | 21 +- .../parser/CustomOperationParser.java | 2 +- .../processor/generator/GeneratorUtils.java | 8 + .../processor/java/model/CodeTypeElement.java | 9 + .../truffle/polyglot/EngineAccessor.java | 31 + .../truffle/polyglot/PolyglotContextImpl.java | 1 + .../truffle/polyglot/PolyglotEngineImpl.java | 4 + .../polyglot/PolyglotEngineOptions.java | 137 ++ .../polyglot/PolyglotInstructionTracers.java | 353 ++++ .../polyglot/PolyglotSharingLayer.java | 45 +- .../src/com/oracle/truffle/sl/SLLanguage.java | 5 +- .../sl/bytecode/SLBytecodeSerialization.java | 6 +- .../truffle/sl/parser/SLBytecodeParser.java | 6 +- .../bytecode_dsl/BenchmarkLanguage.java | 6 +- .../BytecodeBuilderBenchmark.java | 5 +- .../ConstantOperandBenchmark.java | 9 +- .../bytecode_dsl/SimpleBytecodeBenchmark.java | 9 +- 63 files changed, 6106 insertions(+), 731 deletions(-) create mode 100644 truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/InstructionTracingTest.java create mode 100644 truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/basic_interpreter/BytecodeDescriptorTest.java create mode 100644 truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/BytecodeDescriptor.java create mode 100644 truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/BytecodeEngineData.java create mode 100644 truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/InstructionDescriptor.java create mode 100644 truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/InstructionTracer.java create mode 100644 truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/debug/HistogramInstructionTracer.java create mode 100644 truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/debug/PrintInstructionTracer.java create mode 100644 truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotInstructionTracers.java diff --git a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/truffle/test/BytecodeDSLCompilationTest.java b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/truffle/test/BytecodeDSLCompilationTest.java index b259aa392f2d..ce6aa48a12a7 100644 --- a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/truffle/test/BytecodeDSLCompilationTest.java +++ b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/truffle/test/BytecodeDSLCompilationTest.java @@ -31,6 +31,7 @@ import static org.junit.Assert.fail; import static org.junit.Assume.assumeTrue; +import java.util.ArrayList; import java.util.List; import org.graalvm.polyglot.Context; @@ -51,8 +52,10 @@ import com.oracle.truffle.api.bytecode.ContinuationResult; import com.oracle.truffle.api.bytecode.test.BytecodeDSLTestLanguage; import com.oracle.truffle.api.bytecode.test.basic_interpreter.AbstractBasicInterpreterTest; +import com.oracle.truffle.api.bytecode.test.basic_interpreter.AbstractBasicInterpreterTest.TestRun; import com.oracle.truffle.api.bytecode.test.basic_interpreter.BasicInterpreter; import com.oracle.truffle.api.bytecode.test.basic_interpreter.BasicInterpreterBuilder; +import com.oracle.truffle.api.bytecode.test.basic_interpreter.BasicInterpreterBuilder.BytecodeVariant; import com.oracle.truffle.api.frame.FrameSlotKind; import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.instrumentation.ExecutionEventNode; @@ -69,14 +72,18 @@ public class BytecodeDSLCompilationTest extends TestWithSynchronousCompiling { @Parameters(name = "{0}") - public static List> getInterpreterClasses() { - return AbstractBasicInterpreterTest.allInterpreters(); + public static List getParameters() { + List result = new ArrayList<>(); + for (BytecodeVariant bc : AbstractBasicInterpreterTest.allVariants()) { + result.add(new TestRun(bc, false, false)); + } + return result; } - @Parameter(0) public Class interpreterClass; + @Parameter(0) public TestRun run; private boolean hasBoxingElimination() { - return new AbstractBasicInterpreterTest.TestRun(interpreterClass, false).hasBoxingElimination(); + return run.hasBoxingElimination(); } Context context; @@ -123,7 +130,7 @@ public static void beforeClass() { */ @Test public void testOSR1() { - BasicInterpreter root = parseNode(interpreterClass, BytecodeDSLTestLanguage.REF.get(null), false, "osrRoot", b -> { + BasicInterpreter root = parseNode(run, BytecodeDSLTestLanguage.REF.get(null), "osrRoot", b -> { b.beginRoot(); BytecodeLocal iLoc = b.createLocal(); @@ -223,7 +230,7 @@ public void testOSR1() { */ @Test public void testOSR2() { - BasicInterpreter root = parseNode(interpreterClass, BytecodeDSLTestLanguage.REF.get(null), false, "osrRoot", b -> { + BasicInterpreter root = parseNode(run, BytecodeDSLTestLanguage.REF.get(null), "osrRoot", b -> { b.beginRoot(); BytecodeLocal iLoc = b.createLocal(); @@ -341,7 +348,7 @@ public void testOSR2() { @Test public void testCompiles() { - BasicInterpreter root = parseNodeForCompilation(interpreterClass, "addTwoConstants", b -> { + BasicInterpreter root = parseNodeForCompilation(run, "addTwoConstants", b -> { b.beginRoot(); b.beginReturn(); @@ -365,7 +372,7 @@ public void testCompiles() { @Test public void testMultipleReturns() { // return 30 + (arg0 ? 12 : (return 123; 0)) - BasicInterpreter root = parseNodeForCompilation(interpreterClass, "multipleReturns", b -> { + BasicInterpreter root = parseNodeForCompilation(run, "multipleReturns", b -> { b.beginRoot(); b.beginReturn(); @@ -403,7 +410,7 @@ public void testMultipleReturns() { @Test public void testStoreInvalidatesCode() { assumeTrue(hasBoxingElimination()); - BytecodeRootNodes rootNodes = createNodes(interpreterClass, BytecodeDSLTestLanguage.REF.get(null), false, BytecodeConfig.DEFAULT, b -> { + BytecodeRootNodes rootNodes = createNodes(run, BytecodeDSLTestLanguage.REF.get(null), BytecodeConfig.DEFAULT, b -> { b.beginRoot(); BytecodeLocal x = b.createLocal("x", null); b.beginStoreLocal(x); @@ -463,7 +470,7 @@ public void testStoreInvalidatesCode() { @Test public void testBytecodeNodeStoreInvalidatesCode() { assumeTrue(hasBoxingElimination()); - BytecodeRootNodes rootNodes = createNodes(interpreterClass, BytecodeDSLTestLanguage.REF.get(null), false, BytecodeConfig.DEFAULT, b -> { + BytecodeRootNodes rootNodes = createNodes(run, BytecodeDSLTestLanguage.REF.get(null), BytecodeConfig.DEFAULT, b -> { b.beginRoot(); BytecodeLocal x = b.createLocal("x", null); b.beginStoreLocal(x); @@ -537,7 +544,7 @@ public void testBytecodeNodeStoreInvalidatesCode() { @Test public void testMaterializedStoreInvalidatesCode() { assumeTrue(hasBoxingElimination()); - BytecodeRootNodes rootNodes = createNodes(interpreterClass, BytecodeDSLTestLanguage.REF.get(null), false, BytecodeConfig.DEFAULT, b -> { + BytecodeRootNodes rootNodes = createNodes(run, BytecodeDSLTestLanguage.REF.get(null), BytecodeConfig.DEFAULT, b -> { b.beginRoot(); BytecodeLocal x = b.createLocal("x", null); b.beginStoreLocal(x); @@ -615,7 +622,7 @@ public void testMaterializedStoreInvalidatesCode() { @Test public void testMaterializedAccessorStoreInvalidatesCode() { assumeTrue(hasBoxingElimination()); - BytecodeRootNodes rootNodes = createNodes(interpreterClass, BytecodeDSLTestLanguage.REF.get(null), false, BytecodeConfig.DEFAULT, b -> { + BytecodeRootNodes rootNodes = createNodes(run, BytecodeDSLTestLanguage.REF.get(null), BytecodeConfig.DEFAULT, b -> { b.beginRoot(); BytecodeLocal x = b.createLocal("x", null); b.beginStoreLocal(x); @@ -688,7 +695,7 @@ public void testMaterializedAccessorStoreInvalidatesCode() { @Test public void testInstrumentation() { - BasicInterpreter root = parseNodeForCompilation(interpreterClass, "addTwoConstantsInstrumented", b -> { + BasicInterpreter root = parseNodeForCompilation(run, "addTwoConstantsInstrumented", b -> { b.beginRoot(); b.beginReturn(); @@ -710,7 +717,7 @@ public void testInstrumentation() { // Instrumentation should invalidate the compiled code. root.getRootNodes().update( - BasicInterpreterBuilder.invokeNewConfigBuilder(interpreterClass).addInstrumentation(BasicInterpreter.IncrementValue.class).build()); + run.bytecode().newConfigBuilder().addInstrumentation(BasicInterpreter.IncrementValue.class).build()); assertNotCompiled(target); // The instrumented interpreter should be recompiled. @@ -723,7 +730,7 @@ public void testInstrumentation() { @Test public void testYield() { - BasicInterpreter root = parseNodeForCompilation(interpreterClass, "addYield", b -> { + BasicInterpreter root = parseNodeForCompilation(run, "addYield", b -> { b.beginRoot(); b.beginReturn(); @@ -763,7 +770,7 @@ public void testYield() { @Test public void testYieldInstrumentation() { - BasicInterpreter root = parseNodeForCompilation(interpreterClass, "addYieldInstrumented", b -> { + BasicInterpreter root = parseNodeForCompilation(run, "addYieldInstrumented", b -> { b.beginRoot(); b.beginReturn(); @@ -797,7 +804,7 @@ public void testYieldInstrumentation() { // Instrumentation should invalidate the compiled code. root.getRootNodes().update( - BasicInterpreterBuilder.invokeNewConfigBuilder(interpreterClass).addInstrumentation(BasicInterpreter.IncrementValue.class).build()); + run.bytecode().newConfigBuilder().addInstrumentation(BasicInterpreter.IncrementValue.class).build()); assertNotCompiled(target); assertNotCompiled(continuationCallTarget); @@ -815,7 +822,7 @@ public void testYieldInstrumentation() { @Test public void testCompiledSourceInfo() { Source s = Source.newBuilder("test", "return sourcePosition", "compiledSourceInfo").build(); - BasicInterpreter root = parseNodeForCompilation(interpreterClass, "compiledSourceInfo", b -> { + BasicInterpreter root = parseNodeForCompilation(run, "compiledSourceInfo", b -> { b.beginSource(s); b.beginSourceSection(0, 21); b.beginRoot(); @@ -863,7 +870,7 @@ public void testCompiledSourceInfo() { @Test public void testTagInstrumentation() { - BasicInterpreter root = parseNodeForCompilation(interpreterClass, "tagInstrumentation", b -> { + BasicInterpreter root = parseNodeForCompilation(run, "tagInstrumentation", b -> { b.beginRoot(); // i = 0 @@ -990,8 +997,9 @@ public void onEnter(VirtualFrame f) { return c; } - private static BasicInterpreter parseNodeForCompilation(Class interpreterClass, String rootName, BytecodeParser builder) { - BasicInterpreter result = parseNode(interpreterClass, BytecodeDSLTestLanguage.REF.get(null), false, rootName, builder); + private static BasicInterpreter parseNodeForCompilation(TestRun run, + String rootName, BytecodeParser builder) { + BasicInterpreter result = parseNode(run, BytecodeDSLTestLanguage.REF.get(null), rootName, builder); result.getBytecodeNode().setUncachedThreshold(0); // force interpreter to skip tier 0 return result; } diff --git a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/truffle/test/BytecodeDSLPartialEvaluationTest.java b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/truffle/test/BytecodeDSLPartialEvaluationTest.java index 8048cbd9ba90..ef2752c0d754 100644 --- a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/truffle/test/BytecodeDSLPartialEvaluationTest.java +++ b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/truffle/test/BytecodeDSLPartialEvaluationTest.java @@ -26,6 +26,7 @@ import static com.oracle.truffle.api.bytecode.test.basic_interpreter.AbstractBasicInterpreterTest.parseNode; +import java.util.ArrayList; import java.util.List; import java.util.function.Supplier; @@ -43,6 +44,8 @@ import com.oracle.truffle.api.bytecode.test.basic_interpreter.AbstractBasicInterpreterTest; import com.oracle.truffle.api.bytecode.test.basic_interpreter.BasicInterpreter; import com.oracle.truffle.api.bytecode.test.basic_interpreter.BasicInterpreterBuilder; +import com.oracle.truffle.api.bytecode.test.basic_interpreter.AbstractBasicInterpreterTest.TestRun; +import com.oracle.truffle.api.bytecode.test.basic_interpreter.BasicInterpreterBuilder.BytecodeVariant; import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.instrumentation.EventContext; import com.oracle.truffle.api.instrumentation.ExecutionEventListener; @@ -58,17 +61,21 @@ public class BytecodeDSLPartialEvaluationTest extends PartialEvaluationTest { protected static final BytecodeDSLTestLanguage LANGUAGE = null; @Parameters(name = "{0}") - public static List> getInterpreterClasses() { - return AbstractBasicInterpreterTest.allInterpreters(); + public static List getParameters() { + List result = new ArrayList<>(); + for (BytecodeVariant bc : AbstractBasicInterpreterTest.allVariants()) { + result.add(new TestRun(bc, false, false)); + } + return result; } - @Parameter(0) public Class interpreterClass; + @Parameter(0) public TestRun run; @Test public void testAddTwoConstants() { // return 20 + 22; - BasicInterpreter root = parseNodeForPE(interpreterClass, "addTwoConstants", b -> { + BasicInterpreter root = parseNodeForPE(run, "addTwoConstants", b -> { b.beginRoot(); b.beginReturn(); @@ -88,7 +95,7 @@ public void testAddTwoConstants() { public void testAddThreeConstants() { // return 40 + 22 + - 20; - BasicInterpreter root = parseNodeForPE(interpreterClass, "addThreeConstants", b -> { + BasicInterpreter root = parseNodeForPE(run, "addThreeConstants", b -> { b.beginRoot(); b.beginReturn(); @@ -115,7 +122,7 @@ public void testAddThreeConstants() { public void testAddThreeConstantsWithConstantOperands() { // return 40 + 22 + - 20; - BasicInterpreter root = parseNodeForPE(interpreterClass, "addThreeConstantsWithConstantOperands", b -> { + BasicInterpreter root = parseNodeForPE(run, "addThreeConstantsWithConstantOperands", b -> { b.beginRoot(); b.beginReturn(); @@ -147,7 +154,7 @@ public void testSum() { long endValue = 10L; - BasicInterpreter root = parseNodeForPE(interpreterClass, "sum", b -> { + BasicInterpreter root = parseNodeForPE(run, "sum", b -> { b.beginRoot(); BytecodeLocal i = b.createLocal(); @@ -208,7 +215,7 @@ public void testTryCatch() { // return 3; // @formatter:on - BasicInterpreter root = parseNodeForPE(interpreterClass, "sum", b -> { + BasicInterpreter root = parseNodeForPE(run, "sum", b -> { b.beginRoot(); b.beginTryCatch(); @@ -256,7 +263,7 @@ public void testTryCatch2() { // return 42; // @formatter:on - BasicInterpreter root = parseNodeForPE(interpreterClass, "sum", b -> { + BasicInterpreter root = parseNodeForPE(run, "sum", b -> { b.beginRoot(); b.beginTryCatch(); @@ -306,7 +313,7 @@ public void testTryCatch2() { public void testConditionalTrue() { // return true ? 42 : 21; - BasicInterpreter root = parseNodeForPE(interpreterClass, "conditionalTrue", b -> { + BasicInterpreter root = parseNodeForPE(run, "conditionalTrue", b -> { b.beginRoot(); b.beginReturn(); b.beginConditional(); @@ -328,7 +335,7 @@ public void testConditionalTrue() { public void testConditionalFalse() { // return false ? 21 : 42; - BasicInterpreter root = parseNodeForPE(interpreterClass, "conditionalFalse", b -> { + BasicInterpreter root = parseNodeForPE(run, "conditionalFalse", b -> { b.beginRoot(); b.beginReturn(); @@ -354,7 +361,7 @@ public void testEarlyReturn() { // earlyReturn(42) // throws exception caught by intercept hook // return 123 // @formatter:on - BasicInterpreter root = parseNodeForPE(interpreterClass, "earlyReturn", b -> { + BasicInterpreter root = parseNodeForPE(run, "earlyReturn", b -> { b.beginRoot(); b.beginBlock(); @@ -379,7 +386,7 @@ public void testVariadicLength() { // Note: the variadic array length is not PE constant beyond 8 arguments. final int numVariadic = 8; - BasicInterpreter root = parseNodeForPE(interpreterClass, "variadicLength", b -> { + BasicInterpreter root = parseNodeForPE(run, "variadicLength", b -> { b.beginRoot(); b.beginBlock(); @@ -405,7 +412,7 @@ public void testEmptyTagInstrumentation() { try (Context c = Context.create()) { c.enter(); - BasicInterpreter root = parseNodeForPE(interpreterClass, "testEmptyTagInstrumentation", b -> { + BasicInterpreter root = parseNodeForPE(run, "testEmptyTagInstrumentation", b -> { b.beginRoot(); b.beginTag(ExpressionTag.class); @@ -440,7 +447,7 @@ public void testUnwindTagInstrumentation() { String text = "return 20 + 22"; Source s = Source.newBuilder("test", text, "testUnwindTagInstrumentation").build(); - BasicInterpreter root = parseNodeForPE(BytecodeDSLTestLanguage.REF.get(null), interpreterClass, "testUnwindTagInstrumentation", b -> { + BasicInterpreter root = parseNodeForPE(BytecodeDSLTestLanguage.REF.get(null), run, "testUnwindTagInstrumentation", b -> { b.beginSource(s); b.beginSourceSection(0, text.length()); b.beginRoot(); @@ -507,13 +514,15 @@ private static Supplier supplier(Object result) { return () -> result; } - private static BasicInterpreter parseNodeForPE(Class interpreterClass, String rootName, BytecodeParser builder) { - return parseNodeForPE(LANGUAGE, interpreterClass, rootName, builder); + private static BasicInterpreter parseNodeForPE(TestRun run, + String rootName, BytecodeParser builder) { + return parseNodeForPE(LANGUAGE, run, rootName, builder); } - private static BasicInterpreter parseNodeForPE(BytecodeDSLTestLanguage language, Class interpreterClass, String rootName, - BytecodeParser builder) { - BasicInterpreter result = parseNode(interpreterClass, language, false, rootName, builder); + private static BasicInterpreter parseNodeForPE(BytecodeDSLTestLanguage language, + TestRun run, String rootName, + BytecodeParser builder) { + BasicInterpreter result = parseNode(run, language, rootName, builder); result.getBytecodeNode().setUncachedThreshold(0); // force interpreter to skip tier 0 return result; } diff --git a/truffle/CHANGELOG.md b/truffle/CHANGELOG.md index 8eee3cff2b0a..59a233fe4c3e 100644 --- a/truffle/CHANGELOG.md +++ b/truffle/CHANGELOG.md @@ -27,6 +27,15 @@ This changelog summarizes major changes between Truffle versions relevant to lan * GR-69649: Bytecode DSL now encodes primitive constant operands directly in the bytecode, reducing memory indirections in the interpreter. This optimization is enabled by default; you can configure it with `@GenerateBytecode(inlinePrimitiveConstants = true)`. * GR-68993: Added `HostCompilerDirectives.markThreadedSwitch(int)` to mark a switch statement within a loop as a candidate for threaded switch optimization. * GR-68993: Bytecode DSL: All bytecode interpreters are now using the threaded switch optimization by default. This new optimization can be configured using `@GenerateBytecode(enableThreadedSwitch=true|false)`. +* GR-71030: Bytecode DSL now generates a new `MyBytecodeRootNodeGen.Bytecode` class that can be accessed via the `MyBytecodeRootNodeGen.BYTECODE` singleton. This class which extends the added `BytecodeDescriptor` allows you to parse, serialize and deserialize bytecode nodes in addition to the already existing static methods in the generated code. +* GR-71030: Bytecode DSL now provides an `InstructionDescriptor` generated implementation for the bytecode interpreter. The instructions can be accessed via the new BytecodeDescriptor like this: `MyBytecodeRootNodeGen.BYTECODE.getInstructionDescriptors()`. There is also `MyBytecodeRootNodeGen.BYTECODE.dump()` to produce a human-readable instruction format. +* GR-71031: Added new method `BytecodeDescriptor.update(MyLanguage, BytecodeConfig)` to update the bytecode config for all current root nodes and root nodes created in the future of a language. +* GR-51945: Bytecode DSL, added `InstructionTracer` with `onInstructionEnter(InstructionAccess, BytecodeNode, int, Frame)`. Tracers can be attached per root via `BytecodeRootNodes.addInstructionTracer(InstructionTracer)` and per descriptor via `BytecodeDescriptor.addInstructionTracer(TruffleLanguage, InstructionTracer)`. Attaching a tracer invalidates affected roots and may trigger reparse and comes at a significant cost. +* GR-51945: Bytecode DSL: added instruction tracer reference implementations `PrintInstructionTracer` and `InstructionHistogramTracer`. These are intended for diagnostics. +* GR-51945: Added option `engine.TraceBytecode` to enable printing each executed Bytecode DSL instruction. Use the `engine.BytecodeMethodFilter` option to print instructions only for a given method. +* GR-51945: Added option `engine.BytecodeHistogram` to enable printing a bytecode histogram on engine close. Use `engine.BytecodeHistogramInterval` to configure the interval at which the histogram is reset and printed. + + ## Version 25.0 * 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. diff --git a/truffle/docs/Options.md b/truffle/docs/Options.md index 3bc3aa787640..7dfdeec7f1ce 100644 --- a/truffle/docs/Options.md +++ b/truffle/docs/Options.md @@ -48,12 +48,17 @@ They are useful to users and language and tool implementers. ```shell +- `--engine.BytecodeHistogram=true|false|[,...]` : Collect and print a histogram of executed bytecode opcodes. Set to 'true' to enable basic mode or use a comma separated list to configure grouping (e.g. source,root).Available groupings are root, tier, source, language, thread.This feature adds high overhead, use for profiling in non-production runs only. Supported only by Bytecode DSL interpreters. Prints when the engine is closed by default, or periodically if BytecodeHistogramInterval > 0. +- `--engine.BytecodeHistogramInterval` : Print and reset the opcode histogram at a fixed interval while BytecodeHistogram is enabled. Use 0 to disable periodic printing and print only once at shutdown. Examples: 250ms, 2s, 1m. +- `--engine.BytecodeLanguageFilter` : Limit tracing and statistics to specific language IDs. Provide a comma-separated list of language IDs, for example: js, python. Empty means that all languages are included. Applies to engine.TraceBytecode and engine.BytecodeHistogram. +- `--engine.BytecodeMethodFilter` : Limit tracing and statistics to selected methods. Provide a comma-separated list of includes, or excludes prefixed with '~'. Empty means no restriction. Whitespace around commas is ignored. Applies to engine.TraceBytecode and engine.BytecodeHistogram. - `--engine.PreinitializeContexts` : Preinitialize language contexts for given languages. - `--engine.RelaxStaticObjectSafetyChecks` : On property accesses, the Static Object Model does not perform shape checks and uses unsafe casts - `--engine.SourceCacheStatisticDetails` : Print source cache statistics for an engine when the engine is closed. With the details enabled, statistics for all individual sources are printed. - `--engine.SourceCacheStatistics` : Print source cache statistics for an engine when the engine is closed. - `--engine.SynchronousThreadLocalActionMaxWait=[0, inf)` : How long to wait for other threads to reach a synchronous ThreadLocalAction before cancelling it, in seconds. 0 means no limit. - `--engine.SynchronousThreadLocalActionPrintStackTraces` : Print thread stacktraces when a synchronous ThreadLocalAction is waiting for more than SynchronousThreadLocalActionMaxWait seconds. +- `--engine.TraceBytecode` : Trace every executed bytecode instruction. Very high overhead, use only for debugging, never in production. Supported only by Bytecode DSL interpreters. Combine with engine.BytecodeMethodFilter and engine.BytecodeLanguageFilter to limit output. - `--engine.TraceSourceCache` : Print information for source cache misses/evictions/failures. - `--engine.TraceSourceCacheDetails` : Print information for all source cache events including hits and uncached misses. - `--engine.TraceStackTraceInterval=[1, inf)` : Prints the stack trace for all threads for a time interval. By default 0, which disables the output. diff --git a/truffle/docs/bytecode_dsl/UserGuide.md b/truffle/docs/bytecode_dsl/UserGuide.md index 003687f8b6e3..db7fa10b7799 100644 --- a/truffle/docs/bytecode_dsl/UserGuide.md +++ b/truffle/docs/bytecode_dsl/UserGuide.md @@ -30,6 +30,7 @@ This guide presents the conceptual details of the Bytecode DSL; for more concret - [Source information](#source-information) - [Instrumentation](#instrumentation) - [Reparsing](#reparsing) + - [Tracing](#tracing) - [Bytecode introspection](#bytecode-introspection) - [Reachability analysis](#reachability-analysis) - [Interpreter optimizations](#interpreter-optimizations) @@ -571,6 +572,133 @@ Reparsing updates the [`BytecodeNode`](https://github.com/oracle/graal/blob/mast When the bytecode instructions change, any compiled code for the root node is invalidated, and the old bytecode is invalidated in order to transition active (on-stack) invocations to the new bytecode. Note that source information updates [do _not_ invalidate compiled code](RuntimeCompilation.md#source-information). +### Tracing + +Bytecode DSL interpreters support instruction-level tracing. Tracing lets you observe the execution of bytecode programs at runtime without modifying the program itself. It is designed for debugging, profiling, and tooling, not for steady-state production use. + +`InstructionTracer` is a low-overhead callback interface. A tracer is notified immediately before each bytecode instruction executes: + +```java +// 1. Build a bytecode root normally. +BytecodeRootNodes roots = + MyBytecodeRootNodeGen.BYTECODE.create(language, BytecodeConfig.DEFAULT, (b) -> { + b.beginRoot(); + b.beginReturn(); + b.emitLoadArgument(0); + b.endReturn(); + b.endRoot(); + }); + +MyBytecodeRootNode root = roots.getNode(0); + +// 2. Define a tracer. +InstructionTracer tracer = new InstructionTracer() { + @Override + public void onInstructionEnter( + InstructionAccess access, + BytecodeNode bytecode, + int bci, + Frame frame) { + // Print the next instruction about to run + System.out.println(access.getTracedInstruction(bytecode, bci)); + } +}; + +// 3a. Attach the tracer to just these roots: +roots.addInstructionTracer(tracer); + +// 3b. Or attach it to *all* roots of this interpreter in this language: +// MyBytecodeRootNodeGen.BYTECODE.addInstructionTracer(language, tracer); + +// 4. Run guest code. +Object result = root.getCallTarget().call(42); + +// 5. Detach when done. +roots.removeInstructionTracer(tracer); +// or: MyBytecodeRootNodeGen.BYTECODE.removeInstructionTracer(language, tracer); +``` + +Key points: + +* `onInstructionEnter(...)` is called before each instruction executes. +* Tracing runs on the hot path, so it is important to keep the callback implementation computationally cheap. + +You can attach tracers in two ways: + +* To a specific set of root nodes, `roots.addInstructionTracer(...)`, or +* Globally for an entire language instance, `BYTECODE.addInstructionTracer(language, ...)`, which affects all existing and future roots from that interpreter in that language. + +#### Built-in Tracers + +The API provides two ready-made tracers in `com.oracle.truffle.api.bytecode.debug`: + +* `PrintInstructionTracer`: Prints each executed instruction with a counter and the root name. +* `HistogramInstructionTracer`: Counts how many times each instruction executes (optionally grouped by tier, thread, etc.). You can poll and reset the counters. + + +#### Tracing via Polyglot Options + +You can also enable tracing and instruction histograms without writing any Java code, just by passing engine options when creating the Context. This installs internal tracers automatically. + +* `engine.TraceBytecode=true`: Log each executed instruction. +* `engine.BytecodeMethodFilter=`: Include or exclude roots. Supports both inclusion and exclusion (with `~`). +* `engine.BytecodeHistogram=...`: Collect and dump per-instruction execution counts. Set to `true` to enable without grouping, or provide a comma-separated list of available groups: `source,root,tier,language,thread`. +* `engine.BytecodeHistogramInterval=`: Dump statistics periodically instead of only when the context is closed. + +Sample output `engine.TraceBytecode=true`: + +``` +[bc] 1:while-loop.demo:[002] 001 load.argument index(0) +[bc] 2:while-loop.demo:[008] 012 store.local local_offset(0) local_index(0) child0(0002) +[bc] 3:while-loop.demo:[014] 009 load.local local_offset(0) local_index(0) +[bc] 4:while-loop.demo:[01c] 033 c.GreaterZero child0(0014) state_0(0) node(null) +[bc] 5:while-loop.demo:[026] 01c branch.false branch_target(005e) branch_profile(0:never executed) child0(001c) +[bc] 6:while-loop.demo:[036] 009 load.local local_offset(0) local_index(0) +[bc] 7:while-loop.demo:[03e] 02f c.Decrement child0(0036) state_0(0) node(null) +[bc] 8:while-loop.demo:[048] 012 store.local local_offset(0) local_index(0) child0(003e) +[bc] 9:while-loop.demo:[054] 01b branch.backward branch_target(0012) loop_header_branch_profile(0:never executed) +[bc] 10:while-loop.demo:[014] 009 load.local local_offset(0) local_index(0) +[bc] 11:while-loop.demo:[01c] 033 c.GreaterZero child0(0014) state_0(0) node(null) +[bc] 12:while-loop.demo:[026] 01c branch.false branch_target(005e) branch_profile(0:never executed) child0(001c) +[bc] 13:while-loop.demo:[036] 009 load.local local_offset(0) local_index(0) +[bc] 14:while-loop.demo:[03e] 02f c.Decrement child0(0036) state_0(0) node(null) +[bc] 15:while-loop.demo:[048] 012 store.local local_offset(0) local_index(0) child0(003e) +``` + +Sample output `engine.BytecodeHistogram=tier` : + +``` +[bc] Instruction histogram for: com.oracle.truffle.api.bytecode.test.InstructionTracingTest$InstructionTracingRootNode + ----------------------------------------- + Count | Percent | Group / Instruction + ----------------------------------------- + 794 | 78.8 | ▶ Tier 1: Profiled Interpreter + 222 | 22.0 | 00d load.local$Int$unboxed + 112 | 11.1 | 01b branch.backward + 112 | 11.1 | 01e branch.false$Boolean + 112 | 11.1 | 035 c.GreaterZero$Int$unboxed + 110 | 10.9 | 016 store.local$Int$Int + 110 | 10.9 | 031 c.Decrement$Int$unboxed + 6 | 0.6 | 009 load.local + 2 | 0.2 | 012 store.local + 2 | 0.2 | 01c branch.false + 2 | 0.2 | 026 return + 2 | 0.2 | 02f c.Decrement + 2 | 0.2 | 033 c.GreaterZero + 214 | 21.2 | ▶ Tier 0: Unprofiled Interpreter + 60 | 6.0 | 009 load.local + 32 | 3.2 | 012 store.local + 30 | 3.0 | 01b branch.backward + 30 | 3.0 | 01c branch.false + 30 | 3.0 | 02f c.Decrement + 30 | 3.0 | 033 c.GreaterZero + 2 | 0.2 | 001 load.argument + ----------------------------------------- + Total executed instructions: 1008 +``` + + + ### Bytecode introspection Bytecode DSL interpreters have various APIs that allow you to introspect the bytecode. diff --git a/truffle/mx.truffle/suite.py b/truffle/mx.truffle/suite.py index ffaf38d681cd..9936319637bd 100644 --- a/truffle/mx.truffle/suite.py +++ b/truffle/mx.truffle/suite.py @@ -289,8 +289,7 @@ "sourceDirs" : ["src"], "dependencies" : [ "sdk:POLYGLOT", - "com.oracle.truffle.api.instrumentation", - "com.oracle.truffle.api.exception", + "com.oracle.truffle.api.bytecode", "com.oracle.truffle.api.impl.asm", ], "requires" : [ @@ -486,6 +485,7 @@ "mx:JMH_1_21", ], "requires" : [ + "java.logging", "jdk.unsupported", # sun.misc.Unsafe ], "checkstyle" : "com.oracle.truffle.dsl.processor", diff --git a/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/AbstractInstructionTest.java b/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/AbstractInstructionTest.java index b4e05bed07e9..a317ee8cf241 100644 --- a/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/AbstractInstructionTest.java +++ b/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/AbstractInstructionTest.java @@ -52,6 +52,7 @@ import com.oracle.truffle.api.bytecode.BytecodeRootNode; import com.oracle.truffle.api.bytecode.Instruction; import com.oracle.truffle.api.bytecode.Instruction.Argument.Kind; +import com.oracle.truffle.api.bytecode.test.basic_interpreter.AbstractBasicInterpreterTest; public class AbstractInstructionTest { @@ -81,8 +82,11 @@ public static QuickeningCounts assertQuickenings(DebugBytecodeRootNode node, int return new QuickeningCounts(node.quickeningCount.get(), node.specializeCount.get()); } - public static void assertInstructions(BytecodeRootNode node, String... expectedInstructions) { + public static void assertInstructions(BytecodeRootNode node, boolean filterTrace, String... expectedInstructions) { List actualInstructions = node.getBytecodeNode().getInstructionsAsList(); + if (filterTrace) { + actualInstructions = AbstractBasicInterpreterTest.filterTrace(actualInstructions); + } if (actualInstructions.size() != expectedInstructions.length) { throw throwBytecodeNodeAssertion(node, expectedInstructions, String.format("Invalid instruction size. Expected %s got %s.", expectedInstructions.length, actualInstructions.size())); } @@ -96,6 +100,10 @@ public static void assertInstructions(BytecodeRootNode node, String... expectedI } } + public static void assertInstructions(BytecodeRootNode node, String... expectedInstructions) { + assertInstructions(node, true, expectedInstructions); + } + private static AssertionError throwBytecodeNodeAssertion(BytecodeRootNode node, String[] expectedInstructions, String message) { printInstructions(node); return new AssertionError(String.format("%s %nExpected instructions(%s): %n %s %nActual instructions: %s", message, diff --git a/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/ConstantOperandTest.java b/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/ConstantOperandTest.java index 1628251facd7..3f19b60ac31b 100644 --- a/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/ConstantOperandTest.java +++ b/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/ConstantOperandTest.java @@ -104,10 +104,14 @@ public class ConstantOperandTest { private static final BytecodeDSLTestLanguage LANGUAGE = null; - private record TestRun(Class interpreterClass, boolean testSerialize) { + private record TestRun(ConstantOperandTestRootNodeBuilder.BytecodeVariant variant, boolean testSerialize) { + + private Class interpreterClass() { + return variant.getGeneratedClass(); + } private boolean inlinesConstants() { - return interpreterClass == ConstantOperandTestRootNodeCached.class || interpreterClass == ConstantOperandTestRootNodeUncached.class; + return interpreterClass() == ConstantOperandTestRootNodeCached.class || interpreterClass() == ConstantOperandTestRootNodeUncached.class; } private BytecodeSerializer getSerializer() { @@ -216,10 +220,9 @@ private static final class InlinedConstantsSerialization { @Parameters(name = "{0}") public static List getParameters() { List result = new ArrayList<>(); - for (var interpreterClass : List.of(ConstantOperandTestRootNodeCached.class, ConstantOperandTestRootNodeUncached.class, ConstantOperandTestRootNodeCachedNoInlining.class, - ConstantOperandTestRootNodeUncachedNoInlining.class)) { - result.add(new TestRun(interpreterClass, false)); - result.add(new TestRun(interpreterClass, true)); + for (var bc : ConstantOperandTestRootNodeBuilder.variants()) { + result.add(new TestRun(bc, false)); + result.add(new TestRun(bc, true)); } return result; } @@ -227,25 +230,25 @@ public static List getParameters() { @Parameter(0) public TestRun testRun; @SuppressWarnings("unchecked") - private ConstantOperandTestRootNode parse(BytecodeParser parser) { - BytecodeRootNodes rootNodes = ConstantOperandTestRootNodeBuilder.invokeCreate(testRun.interpreterClass, LANGUAGE, BytecodeConfig.DEFAULT, parser); + private ConstantOperandTestRootNode parse(BytecodeParser parser) { + BytecodeRootNodes rootNodes = testRun.variant.create(LANGUAGE, BytecodeConfig.DEFAULT, parser); if (testRun.testSerialize) { - rootNodes = doRoundTrip(testRun.interpreterClass, rootNodes, testRun.getSerializer(), testRun.getDeserializer()); + rootNodes = doRoundTrip(testRun.variant, rootNodes, testRun.getSerializer(), testRun.getDeserializer()); } return rootNodes.getNode(0); } - private static BytecodeRootNodes doRoundTrip(Class interpreterClass, + private static BytecodeRootNodes doRoundTrip(ConstantOperandTestRootNodeBuilder.BytecodeVariant variant, BytecodeRootNodes nodes, BytecodeSerializer serializer, BytecodeDeserializer deserializer) { // Perform a serialize-deserialize round trip. ByteArrayOutputStream output = new ByteArrayOutputStream(); try { nodes.serialize(new DataOutputStream(output), serializer); + Supplier input = () -> SerializationUtils.createDataInput(ByteBuffer.wrap(output.toByteArray())); + return variant.deserialize(LANGUAGE, BytecodeConfig.DEFAULT, input, deserializer); } catch (IOException ex) { throw new AssertionError(ex); } - Supplier input = () -> SerializationUtils.createDataInput(ByteBuffer.wrap(output.toByteArray())); - return ConstantOperandTestRootNodeBuilder.invokeDeserialize(interpreterClass, LANGUAGE, BytecodeConfig.DEFAULT, input, deserializer); } @Test @@ -320,7 +323,7 @@ public void testInstrumentationWithConstant() { }); assertEquals(123, root.getCallTarget().call(123)); - root.getRootNodes().update(ConstantOperandTestRootNodeBuilder.invokeNewConfigBuilder(testRun.interpreterClass).addInstrumentation(ReplaceValue.class).build()); + root.getRootNodes().update(testRun.variant.newConfigBuilder().addInstrumentation(ReplaceValue.class).build()); assertEquals(42, root.getCallTarget().call(123)); } @@ -343,7 +346,7 @@ public void testInstrumentationWithConstantAndYield() { ContinuationResult cont = (ContinuationResult) root.getCallTarget().call(); assertEquals("hello", cont.getResult()); - root.getRootNodes().update(ConstantOperandTestRootNodeBuilder.invokeNewConfigBuilder(testRun.interpreterClass).addInstrumentation(ReplaceValue.class).build()); + root.getRootNodes().update(testRun.variant.newConfigBuilder().addInstrumentation(ReplaceValue.class).build()); cont = (ContinuationResult) root.getCallTarget().call(); assertEquals(123, cont.getResult()); } diff --git a/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/ConstantsBufferTest.java b/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/ConstantsBufferTest.java index c90011d3caf5..de098e2af80a 100644 --- a/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/ConstantsBufferTest.java +++ b/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/ConstantsBufferTest.java @@ -170,6 +170,20 @@ public void manyNulls2() { b.clear(); } + @Test + public void testsNullsTransitionToHash() { + for (int nulls = 0; nulls < 16; nulls++) { + ConstantsBuffer b = new ConstantsBuffer(); + int i0 = b.add(4L); + for (int i = 0; i < nulls; i++) { + b.addNull(); + } + int i1 = b.add(4L); + assertEquals("constants must match with " + nulls + " nulls.", i0, i1); + } + + } + /** distinct objects that all share the same hashCode to stress collision handling. */ private static final class Colliding { final int id; diff --git a/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/InstructionTracingTest.java b/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/InstructionTracingTest.java new file mode 100644 index 000000000000..56d42ee4b3f8 --- /dev/null +++ b/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/InstructionTracingTest.java @@ -0,0 +1,1003 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.oracle.truffle.api.bytecode.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; +import java.util.logging.Handler; +import java.util.logging.LogRecord; + +import org.graalvm.polyglot.Context; +import org.junit.After; +import org.junit.Test; + +import com.oracle.truffle.api.CallTarget; +import com.oracle.truffle.api.TruffleLanguage; +import com.oracle.truffle.api.bytecode.BytecodeConfig; +import com.oracle.truffle.api.bytecode.BytecodeDescriptor; +import com.oracle.truffle.api.bytecode.BytecodeLocal; +import com.oracle.truffle.api.bytecode.BytecodeNode; +import com.oracle.truffle.api.bytecode.BytecodeRootNode; +import com.oracle.truffle.api.bytecode.BytecodeTier; +import com.oracle.truffle.api.bytecode.GenerateBytecode; +import com.oracle.truffle.api.bytecode.Instruction; +import com.oracle.truffle.api.bytecode.InstructionTracer; +import com.oracle.truffle.api.bytecode.Instrumentation; +import com.oracle.truffle.api.bytecode.Operation; +import com.oracle.truffle.api.bytecode.debug.HistogramInstructionTracer; +import com.oracle.truffle.api.bytecode.debug.HistogramInstructionTracer.Histogram; +import com.oracle.truffle.api.bytecode.debug.PrintInstructionTracer; +import com.oracle.truffle.api.bytecode.test.InstructionTracingTest.InstructionTracingRootNode.OtherInstrument; +import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.frame.Frame; +import com.oracle.truffle.api.frame.FrameDescriptor; +import com.oracle.truffle.api.nodes.RootNode; +import com.oracle.truffle.api.source.Source; +import com.oracle.truffle.api.source.SourceSection; + +public class InstructionTracingTest extends AbstractInstructionTest { + + static final InstructionTracingRootNodeGen.Bytecode BYTECODE = InstructionTracingRootNodeGen.BYTECODE; + + @Test + public void testLocalTracing() { + InstructionTracingRootNode node = BYTECODE.create(null, BytecodeConfig.DEFAULT, (b) -> { + b.beginRoot(); + b.beginReturn(); + b.beginOtherInstrument(); + b.emitLoadArgument(0); + b.endOtherInstrument(); + b.endReturn(); + b.endRoot(); + }).getNode(0); + + List instructions = new ArrayList<>(); + InstructionTracer t = new InstructionTracer() { + public void onInstructionEnter(InstructionAccess access, BytecodeNode bytecode, int bytecodeIndex, Frame frame) { + instructions.add(access.getTracedInstruction(bytecode, bytecodeIndex)); + } + }; + node.getRootNodes().addInstructionTracer(t); + + assertEquals(0, instructions.size()); + + assertEquals(42, node.getCallTarget().call(42)); + + assertEquals(2, instructions.size()); + assertEquals("load.argument", instructions.get(0).getName()); + assertEquals("return", instructions.get(1).getName()); + + instructions.clear(); + + assertEquals(42, node.getCallTarget().call(42)); + assertEquals(2, instructions.size()); + assertEquals("load.argument", instructions.get(0).getName()); + assertEquals("return", instructions.get(1).getName()); + + instructions.clear(); + + node.getRootNodes().removeInstructionTracer(t); + assertEquals(42, node.getCallTarget().call(42)); + assertTrue(instructions.isEmpty()); + } + + private Context context; + + private InstructionTracingLanguage setupLanguage(Context.Builder b) { + tearDownContext(); + context = b.build(); + context.initialize(InstructionTracingLanguage.ID); + context.enter(); + return InstructionTracingLanguage.REF.get(null); + } + + private InstructionTracingLanguage setupLanguage() { + return setupLanguage(Context.newBuilder(InstructionTracingLanguage.ID)); + } + + @After + public void tearDownContext() { + if (context != null) { + context.close(); + context = null; + } + } + + static final InstructionTracingDisabledRootNodeGen.Bytecode DISABLED_BYTECODE = InstructionTracingDisabledRootNodeGen.BYTECODE; + + @Test + public void testInstructionTracingDisabled() { + InstructionTracingLanguage language = setupLanguage(); + InstructionTracingDisabledRootNode node = DISABLED_BYTECODE.create(language, BytecodeConfig.DEFAULT, (b) -> { + b.beginRoot(); + b.beginReturn(); + b.emitLoadArgument(0); + b.endReturn(); + b.endRoot(); + }).getNode(0); + + InstructionTracer disabledTracer = new InstructionTracer() { + @Override + public void onInstructionEnter(InstructionAccess access, BytecodeNode bytecode, int bytecodeIndex, Frame frame) { + throw new UnsupportedOperationException(); + } + }; + + assertFails(() -> { + node.getRootNodes().addInstructionTracer(disabledTracer); + }, UnsupportedOperationException.class, (e) -> { + assertEquals("Instruction tracing is not enabled for this bytecode root node. Enable with @GenerateBytecode(enableInstructionTracing=true) to use instruction tracing.", e.getMessage()); + }); + + assertFails(() -> { + node.getRootNodes().removeInstructionTracer(disabledTracer); + }, UnsupportedOperationException.class, (e) -> { + assertEquals("Instruction tracing is not enabled for this bytecode root node. Enable with @GenerateBytecode(enableInstructionTracing=true) to use instruction tracing.", e.getMessage()); + }); + assertFails(() -> { + DISABLED_BYTECODE.addInstructionTracer(language, disabledTracer); + }, UnsupportedOperationException.class, (e) -> { + assertEquals("Instruction tracing is not enabled for this bytecode root node. Enable with @GenerateBytecode(enableInstructionTracing=true) to use instruction tracing.", e.getMessage()); + }); + + assertFails(() -> { + DISABLED_BYTECODE.removeInstructionTracer(language, disabledTracer); + }, UnsupportedOperationException.class, (e) -> { + assertEquals("Instruction tracing is not enabled for this bytecode root node. Enable with @GenerateBytecode(enableInstructionTracing=true) to use instruction tracing.", e.getMessage()); + }); + + // make sure interpreters without instruction tracers work + assertEquals(42, node.getCallTarget().call(42)); + } + + /* + * Create call target and then attach the instruction tracer. + */ + @Test + public void testLanguageTracing1() { + InstructionTracingLanguage language = setupLanguage(); + InstructionTracingRootNode node = BYTECODE.create(language, BytecodeConfig.DEFAULT, (b) -> { + b.beginRoot(); + b.beginReturn(); + b.beginOtherInstrument(); + b.emitLoadArgument(0); + b.endOtherInstrument(); + b.endReturn(); + b.endRoot(); + }).getNode(0); + + assertInstructions(node, + "load.argument", + "return"); + + List instructions = new ArrayList<>(); + InstructionTracer t = new InstructionTracer() { + public void onInstructionEnter(InstructionAccess access, BytecodeNode bytecode, int bytecodeIndex, Frame frame) { + instructions.add(access.getTracedInstruction(bytecode, bytecodeIndex)); + } + }; + + assertEquals(42, node.getCallTarget().call(42)); + + assertEquals(0, instructions.size()); + BYTECODE.addInstructionTracer(language, t); + + assertInstructions(node, false, + "trace.instruction", + "load.argument", + "trace.instruction", + "return"); + + assertEquals(42, node.getCallTarget().call(42)); + + assertEquals(2, instructions.size()); + assertEquals("load.argument", instructions.get(0).getName()); + assertEquals("return", instructions.get(1).getName()); + + instructions.clear(); + + assertEquals(42, node.getCallTarget().call(42)); + + assertEquals(2, instructions.size()); + assertEquals("load.argument", instructions.get(0).getName()); + assertEquals("return", instructions.get(1).getName()); + + instructions.clear(); + + BYTECODE.removeInstructionTracer(language, t); + + assertEquals(42, node.getCallTarget().call(42)); + assertEquals(0, instructions.size()); + } + + /* + * Attach instruction tracer first and then create the call target. + */ + @Test + public void testLanguageTracing2() { + List instructions = new ArrayList<>(); + InstructionTracer t = new InstructionTracer() { + public void onInstructionEnter(InstructionAccess access, BytecodeNode bytecode, int bytecodeIndex, Frame frame) { + instructions.add(access.getTracedInstruction(bytecode, bytecodeIndex)); + } + }; + InstructionTracingLanguage language = setupLanguage(); + BYTECODE.addInstructionTracer(language, t); + + InstructionTracingRootNode node = BYTECODE.create(language, BytecodeConfig.DEFAULT, (b) -> { + b.beginRoot(); + b.beginReturn(); + b.beginOtherInstrument(); + b.emitLoadArgument(0); + b.endOtherInstrument(); + b.endReturn(); + b.endRoot(); + }).getNode(0); + + assertInstructions(node, false, + "trace.instruction", + "load.argument", + "trace.instruction", + "return"); + + assertEquals(0, instructions.size()); + + assertEquals(42, node.getCallTarget().call(42)); + + assertEquals(2, instructions.size()); + assertEquals("load.argument", instructions.get(0).getName()); + assertEquals("return", instructions.get(1).getName()); + instructions.clear(); + + assertEquals(42, node.getCallTarget().call(42)); + + assertEquals(2, instructions.size()); + assertEquals("load.argument", instructions.get(0).getName()); + assertEquals("return", instructions.get(1).getName()); + + instructions.clear(); + + assertEquals(42, node.getCallTarget().call(42)); + + assertEquals(2, instructions.size()); + assertEquals("load.argument", instructions.get(0).getName()); + assertEquals("return", instructions.get(1).getName()); + + instructions.clear(); + + BYTECODE.removeInstructionTracer(language, t); + + assertEquals(42, node.getCallTarget().call(42)); + assertEquals(0, instructions.size()); + } + + @Test + public void testInstrumentation() { + InstructionTracingLanguage language = setupLanguage(); + InstructionTracingRootNode node = BYTECODE.create(language, BytecodeConfig.DEFAULT, (b) -> { + b.beginRoot(); + b.beginReturn(); + b.beginOtherInstrument(); + b.emitLoadArgument(0); + b.endOtherInstrument(); + b.endReturn(); + b.endRoot(); + }).getNode(0); + + // we need to at least call getCallTarget() here + assertEquals(42, node.getCallTarget().call(42)); + + assertInstructions(node, + "load.argument", + "return"); + + // add the instruc + BYTECODE.update(language, BYTECODE.newConfigBuilder().addInstrumentation(OtherInstrument.class).build()); + + assertInstructions(node, + "load.argument", + "c.OtherInstrument", + "return"); + + assertEquals(43, node.getCallTarget().call(42)); + + List instructions = new ArrayList<>(); + InstructionTracer t = new InstructionTracer() { + public void onInstructionEnter(InstructionAccess access, BytecodeNode bytecode, int bytecodeIndex, Frame frame) { + instructions.add(access.getTracedInstruction(bytecode, bytecodeIndex)); + } + }; + BYTECODE.addInstructionTracer(language, t); + + assertInstructions(node, false, + "trace.instruction", + "load.argument", + "trace.instruction", + "c.OtherInstrument", + "trace.instruction", + "return"); + + assertEquals(43, node.getCallTarget().call(42)); + + assertEquals(3, instructions.size()); + assertEquals("load.argument", instructions.get(0).getName()); + assertEquals("c.OtherInstrument", instructions.get(1).getName()); + assertTrue(instructions.get(1).getDescriptor().isInstrumentation()); + assertEquals("return", instructions.get(2).getName()); + + /* + * Test new node now already has all config enabled on create without execution. + */ + InstructionTracingRootNode newNode = BYTECODE.create(language, BytecodeConfig.DEFAULT, (b) -> { + b.beginRoot(); + b.beginReturn(); + b.beginOtherInstrument(); + b.emitLoadArgument(0); + b.endOtherInstrument(); + b.endReturn(); + b.endRoot(); + }).getNode(0); + + assertInstructions(newNode, false, + "trace.instruction", + "load.argument", + "trace.instruction", + "c.OtherInstrument", + "trace.instruction", + "return"); + } + + @Test + public void testEngineTracing() { + Context.Builder cb = Context.newBuilder(InstructionTracingLanguage.ID).option("engine.TraceBytecode", "true"); + List messages = captureLog(cb); + + InstructionTracingLanguage language = setupLanguage(cb); + InstructionTracingRootNode node = BYTECODE.create(language, BytecodeConfig.WITH_SOURCE, this::emitWhileLoopRoot).getNode(0); + + node.getCallTarget().call(42); + + boolean found = false; + for (String string : messages) { + if (string.startsWith("[bc]")) { + found = true; + break; + } + } + assertTrue(found); + } + + @Test + public void testEngineTracingWithFilter1() { + Context.Builder cb = Context.newBuilder(InstructionTracingLanguage.ID).// + option("engine.TraceBytecode", "true").// + option("engine.BytecodeMethodFilter", "while-loop"); + List messages = captureLog(cb); + + InstructionTracingLanguage language = setupLanguage(cb); + InstructionTracingRootNode node = BYTECODE.create(language, BytecodeConfig.WITH_SOURCE, this::emitWhileLoopRoot).getNode(0); + node.getCallTarget().call(42); + boolean found = false; + for (String string : messages) { + if (string.startsWith("[bc]")) { + found = true; + break; + } + } + assertTrue(found); + } + + @Test + public void testEngineTracingWithFilter2() { + Context.Builder cb = Context.newBuilder(InstructionTracingLanguage.ID).// + option("engine.TraceBytecode", "true").// + option("engine.BytecodeMethodFilter", "~while-loop"); + List messages = captureLog(cb); + + InstructionTracingLanguage language = setupLanguage(cb); + InstructionTracingRootNode node = BYTECODE.create(language, BytecodeConfig.WITH_SOURCE, this::emitWhileLoopRoot).getNode(0); + node.getCallTarget().call(42); + boolean found = false; + for (String string : messages) { + if (string.startsWith("[bc]")) { + found = true; + break; + } + } + assertFalse(found); + } + + @Test + public void testEngineTracingWithFilter3() { + Context.Builder cb = Context.newBuilder(InstructionTracingLanguage.ID).// + option("engine.TraceBytecode", "true").// + option("engine.BytecodeMethodFilter", "asdf"); + List messages = captureLog(cb); + + InstructionTracingLanguage language = setupLanguage(cb); + InstructionTracingRootNode node = BYTECODE.create(language, BytecodeConfig.WITH_SOURCE, this::emitWhileLoopRoot).getNode(0); + node.getCallTarget().call(42); + boolean found = false; + for (String string : messages) { + if (string.startsWith("[bc]")) { + found = true; + break; + } + } + assertFalse(found); + } + + @Test + public void testEngineTracingWithFilter4() { + Context.Builder cb = Context.newBuilder(InstructionTracingLanguage.ID).// + option("engine.TraceBytecode", "true").// + option("engine.BytecodeMethodFilter", "~asdf"); + List messages = captureLog(cb); + + InstructionTracingLanguage language = setupLanguage(cb); + InstructionTracingRootNode node = BYTECODE.create(language, BytecodeConfig.WITH_SOURCE, this::emitWhileLoopRoot).getNode(0); + node.getCallTarget().call(42); + boolean found = false; + for (String string : messages) { + if (string.startsWith("[bc]")) { + found = true; + break; + } + } + assertTrue(found); + } + + @Test + public void testEngineHistogram() { + Context.Builder cb = Context.newBuilder(InstructionTracingLanguage.ID).option("engine.BytecodeHistogram", "tier,root,thread"); + List messages = captureLog(cb); + + InstructionTracingLanguage language = setupLanguage(cb); + InstructionTracingRootNode node = BYTECODE.create(language, BytecodeConfig.DEFAULT, this::emitWhileLoopRoot).getNode(0); + + node.getCallTarget().call(100); + + tearDownContext(); // print histogram + + boolean found = false; + for (String string : messages) { + if (string.startsWith("[bc] Instruction histogram for")) { + found = true; + break; + } + } + assertTrue(found); + } + + @Test + public void testEngineHistogramGrouping() { + Context.Builder cb = Context.newBuilder(InstructionTracingLanguage.ID).option("engine.BytecodeHistogram", "source,root,tier,language,thread"); + List messages = captureLog(cb); + + InstructionTracingLanguage language = setupLanguage(cb); + InstructionTracingRootNode node = BYTECODE.create(language, BytecodeConfig.WITH_SOURCE, this::emitWhileLoopRoot).getNode(0); + + node.getCallTarget().call(42); + + tearDownContext(); // print histogram + + Thread t = Thread.currentThread(); + boolean found = false; + for (String string : messages) { + if (string.startsWith("[bc] Instruction histogram for")) { + assertTrue(string.contains("\u25B6 Source: 0x420c5f39 " + whileLoopSource.getName())); + assertTrue(string.contains("\u25B6 Root: " + whileLoopSource.getName())); + assertTrue(string.contains("\u25B6 " + InstructionTracingLanguage.ID)); + assertTrue(string.contains("\u25B6 Tier 1: Profiled Interpreter")); + assertTrue(string.contains("\u25B6 Tier 0: Unprofiled Interpreter")); + assertTrue(string.contains("\u25B6 " + t.toString())); + found = true; + break; + } + } + assertTrue(found); + } + + /* + * Tests that the bytecode histogram is printed asynchronously even if the context is not yet + * closed. + */ + @Test + public void testEngineHistogramInterval() throws InterruptedException { + Context.Builder cb = Context.newBuilder(InstructionTracingLanguage.ID).// + option("engine.BytecodeHistogram", "true").// + option("engine.BytecodeHistogramInterval", "1ms"); + List messages = new ArrayList<>(); + CountDownLatch latch = new CountDownLatch(1); + cb.logHandler(new Handler() { + @Override + public void publish(LogRecord record) { + synchronized (messages) { + if (record.getMessage().startsWith("[bc] Instruction histogram for")) { + messages.add(record.getMessage()); + latch.countDown(); + } + } + } + + @Override + public void close() { + } + + @Override + public void flush() { + } + }); + + InstructionTracingLanguage language = setupLanguage(cb); + InstructionTracingRootNode node = BYTECODE.create(language, BytecodeConfig.WITH_SOURCE, this::emitWhileLoopRoot).getNode(0); + + node.getCallTarget().call(100); + + latch.await(20, TimeUnit.SECONDS); + boolean found = false; + synchronized (messages) { + for (String string : messages) { + if (string.startsWith("[bc] Instruction histogram for")) { + found = true; + } + } + } + assertTrue(found); + tearDownContext(); + } + + @Test + public void testExclusiveDescriptor() { + Context.Builder cb = Context.newBuilder(InstructionTracingLanguage.ID); + InstructionTracingLanguage language = setupLanguage(cb); + InstructionTracingRootNode node = BYTECODE.create(language, BytecodeConfig.DEFAULT, this::emitWhileLoopRoot).getNode(0); + InstructionTracer exclusiveTracer = new InstructionTracer() { + public void onInstructionEnter(InstructionAccess access, BytecodeNode bytecode, int bytecodeIndex, Frame frame) { + } + + public BytecodeDescriptor getExclusiveBytecodeDescriptor() { + return DISABLED_BYTECODE; + } + }; + + assertFails(() -> { + BYTECODE.addInstructionTracer(language, exclusiveTracer); + }, IllegalArgumentException.class, (e) -> { + assertEquals("The passed instruction tracer is exclusive to BytecodeDescriptor[InstructionTracingDisabledRootNodeGen], but it was installed for BytecodeDescriptor[InstructionTracingRootNodeGen].", + e.getMessage()); + }); + + assertFails(() -> { + BYTECODE.removeInstructionTracer(language, exclusiveTracer); + }, IllegalArgumentException.class, (e) -> { + assertEquals("The passed instruction tracer is exclusive to BytecodeDescriptor[InstructionTracingDisabledRootNodeGen], but it was installed for BytecodeDescriptor[InstructionTracingRootNodeGen].", + e.getMessage()); + }); + + assertFails(() -> { + node.getRootNodes().addInstructionTracer(exclusiveTracer); + }, IllegalArgumentException.class, (e) -> { + assertEquals("The passed instruction tracer is exclusive to BytecodeDescriptor[InstructionTracingDisabledRootNodeGen], but it was installed for BytecodeDescriptor[InstructionTracingRootNodeGen].", + e.getMessage()); + }); + + assertFails(() -> { + node.getRootNodes().removeInstructionTracer(exclusiveTracer); + }, IllegalArgumentException.class, (e) -> { + assertEquals("The passed instruction tracer is exclusive to BytecodeDescriptor[InstructionTracingDisabledRootNodeGen], but it was installed for BytecodeDescriptor[InstructionTracingRootNodeGen].", + e.getMessage()); + }); + + } + + @Test + public void testPrintTracing() { + Context.Builder cb = Context.newBuilder(InstructionTracingLanguage.ID); + InstructionTracingLanguage language = setupLanguage(cb); + List instructions = new ArrayList<>(); + PrintInstructionTracer tracer = PrintInstructionTracer.newBuilder((s) -> instructions.add(s)).filter((node) -> { + return node.getTier() == BytecodeTier.UNCACHED; + }).build(); + + InstructionTracingRootNode node = BYTECODE.create(language, BytecodeConfig.WITH_SOURCE, this::emitWhileLoopRoot).getNode(0); + node.getRootNodes().addInstructionTracer(tracer); + + node.getBytecodeNode().setUncachedThreshold(32); + CallTarget target = node.getCallTarget(); + target.call(30); + assertNotEquals(0, instructions.size()); + instructions.clear(); + + // node no longer uncached filter triggers + tracer.reset(); + target.call(32); + + assertEquals(0, instructions.size()); + } + + /* + * Tests that histograms taken asynchronous are always consistent and does not loose any + * instructions. + */ + @Test + public void testHistogramAsynchronousReset() throws InterruptedException { + // we compute the number of expected instructions per invocation + int argument = 100; + int iterations = 10; + long expectedInstructionCountPerInvocation = this.expectedInstructionCountPerInvocation(argument); + + Context.Builder cb = Context.newBuilder(InstructionTracingLanguage.ID); + InstructionTracingLanguage language = setupLanguage(cb); + HistogramInstructionTracer histogramTracer = HistogramInstructionTracer.newBuilder().build(BYTECODE); + InstructionTracingRootNode node = BYTECODE.create(language, BytecodeConfig.WITH_SOURCE, this::emitWhileLoopRoot).getNode(0); + node.getRootNodes().addInstructionTracer(histogramTracer); + CallTarget target = node.getCallTarget(); + AtomicReference error = new AtomicReference<>(); + + CountDownLatch startLatch = new CountDownLatch(2); + Thread executorThread = new Thread(() -> { + context.enter(); + try { + startLatch.countDown(); + startLatch.await(); + for (int i = 0; i < iterations; i++) { + Thread.sleep(1); + target.call(argument); + } + } catch (Throwable t) { + error.set(t); + } finally { + context.leave(); + } + }); + AtomicReference> collectedHistograms = new AtomicReference<>(); + Thread histogramThread = new Thread(() -> { + try { + startLatch.countDown(); + startLatch.await(); + List histograms = new ArrayList<>(); + for (int i = 0; i < iterations; i++) { + Thread.sleep(1); + histograms.add(histogramTracer.getHistogramAndReset()); + } + collectedHistograms.set(histograms); + } catch (Throwable t) { + error.set(t); + } + }); + + executorThread.start(); + histogramThread.start(); + + executorThread.join(); + histogramThread.join(); + + Throwable t = error.get(); + if (t != null) { + throw new AssertionError("Error encountered in thread", t); + } + + List histograms = collectedHistograms.get(); + assertNotNull(histograms); + + // we add the remaining executed instructions + histograms.add(histogramTracer.getHistogramAndReset()); + tearDownContext(); + + // now we check we got consistent instruction counts + long expectedExecutedInstructions = expectedInstructionCountPerInvocation * iterations; + long actualExecutedInstructions = 0L; + for (Histogram histogram : histograms) { + actualExecutedInstructions += histogram.getInstructionsExecuted(); + assertHistogram(histogram); + } + + // if these numbers don't match we had a race condition in the collection + // of the histogram data. + assertEquals(expectedExecutedInstructions, actualExecutedInstructions); + } + + @Test + public void testHistogramFilterAndGroup() { + Context.Builder cb = Context.newBuilder(InstructionTracingLanguage.ID); + InstructionTracingLanguage language = setupLanguage(cb); + HistogramInstructionTracer histogramTracer = HistogramInstructionTracer.newBuilder().filter((node) -> { + return node.getTier() == BytecodeTier.CACHED; + }).groupBy((bytecodeNode, thread, compilationTier) -> { + return bytecodeNode.getTier(); + }).groupBy((bytecodeNode, thread, compilationTier) -> { + return thread; + }).build(BYTECODE); + + InstructionTracingRootNode node = BYTECODE.create(language, BytecodeConfig.WITH_SOURCE, this::emitWhileLoopRoot).getNode(0); + node.getRootNodes().addInstructionTracer(histogramTracer); + + CallTarget target = node.getCallTarget(); + target.call(100); + + Histogram histogram = histogramTracer.getHistogram(); + histogramTracer.reset(); + + // this number might regularly change with further optimization. + // just update it especially if its lower + assertNotEquals(0L, histogram.getInstructionsExecuted()); + assertHistogram(histogram); + + Histogram uncached = histogram.getGroups().get(BytecodeTier.UNCACHED); + assertHistogram(uncached); + assertEquals(0L, uncached.getInstructionsExecuted()); + assertEquals(1, uncached.getGroups().size()); + + uncached = uncached.getGroups().get(Thread.currentThread()); + assertHistogram(uncached); + assertEquals(0L, uncached.getInstructionsExecuted()); + assertNull(uncached.getGroups()); + + Histogram cached = histogram.getGroups().get(BytecodeTier.CACHED); + assertHistogram(cached); + assertNotEquals(0L, cached.getInstructionsExecuted()); + assertFalse(cached.getGroups().isEmpty()); + + cached = cached.getGroups().get(Thread.currentThread()); + assertHistogram(cached); + assertNotEquals(0L, cached.getInstructionsExecuted()); + assertNull(cached.getGroups()); + } + + private static void assertHistogram(Histogram histogram) { + long localSum = 0; + for (var entry : histogram.getCounters().entrySet()) { + localSum += entry.getValue(); + } + assertEquals(histogram.getInstructionsExecuted(), localSum); + long localStatisticSum = 0; + for (var entry : histogram.getStatistics().entrySet()) { + localStatisticSum += entry.getValue().getSum(); + } + assertEquals(localSum, localStatisticSum); + assertEquals(localSum, histogram.getInstructionsExecuted()); + + assertNotNull(histogram.dump()); + assertNotNull(histogram.toString()); + assertNotNull(histogram.getCounters()); + assertNotNull(histogram.getStatistics()); + histogram.getGroups(); + + } + + private long expectedInstructionCountPerInvocation(int argument) { + Context.Builder cb = Context.newBuilder(InstructionTracingLanguage.ID); + InstructionTracingLanguage language = setupLanguage(cb); + InstructionTracingRootNode node = BYTECODE.create(language, BytecodeConfig.WITH_SOURCE, this::emitWhileLoopRoot).getNode(0); + AtomicLong instructionCounter = new AtomicLong(); + node.getRootNodes().addInstructionTracer(new InstructionTracer() { + public void onInstructionEnter(InstructionAccess access, BytecodeNode bytecode, int bytecodeIndex, Frame frame) { + instructionCounter.incrementAndGet(); + } + }); + node.getCallTarget().call(argument); + long counter = instructionCounter.get(); + node.getCallTarget().call(argument); + + // make sure the instruction counter is stable + assertEquals(counter * 2, instructionCounter.get()); + return counter; + } + + private static List captureLog(Context.Builder cb) { + List messages = new ArrayList<>(); + cb.logHandler(new Handler() { + @Override + public void publish(LogRecord record) { + synchronized (messages) { + messages.add(record.getMessage()); + } + } + + @Override + public void close() { + + } + + @Override + public void flush() { + + } + }); + return messages; + } + + private static final Source whileLoopSource = Source.newBuilder(InstructionTracingLanguage.ID, "source-code", "while-loop.demo").build(); + + private void emitWhileLoopRoot(InstructionTracingRootNodeGen.Builder b) { + // local = arguments[0] + // while (local > 0) [ + // local = local -1; + // } + // return local; + + b.beginSource(whileLoopSource); + b.beginSourceSection(); + b.beginRoot(); + BytecodeLocal local = b.createLocal(); + + // local = arg0 + b.beginStoreLocal(local); + b.emitLoadArgument(0); + b.endStoreLocal(); + + // while + b.beginWhile(); + + // local > 0 + b.beginGreaterZero(); + b.emitLoadLocal(local); + b.endGreaterZero(); + + // local = local - 1 + b.beginStoreLocal(local); + b.beginDecrement(); + b.emitLoadLocal(local); + b.endDecrement(); + b.endStoreLocal(); + + b.endWhile(); + + // return local + b.beginReturn(); + b.emitLoadLocal(local); + b.endReturn(); + + b.endRoot(); + b.endSourceSection(0, whileLoopSource.getLength()); + b.endSource(); + } + + @GenerateBytecode(languageClass = InstructionTracingLanguage.class, // + enableYield = true, enableSerialization = true, // + enableUncachedInterpreter = true, // + boxingEliminationTypes = {long.class, int.class, boolean.class}) + public abstract static class InstructionTracingRootNode extends RootNode implements BytecodeRootNode { + + protected InstructionTracingRootNode(InstructionTracingLanguage language, FrameDescriptor frameDescriptor) { + super(language, frameDescriptor); + } + + @Override + public String getName() { + SourceSection sc = getSourceSection(); + if (sc != null) { + return sc.getSource().getName(); + } + return super.getName(); + } + + @Instrumentation + static final class OtherInstrument { + @Specialization + public static int doDefault(int v) { + return v + 1; + } + } + + @Operation + static final class Invoke { + @Specialization + public static void doDefault() { + } + } + + @Operation + static final class Decrement { + @Specialization + public static int doInt(int v) { + return v - 1; + } + } + + @Operation + static final class GreaterZero { + @Specialization + public static boolean doInt(int v) { + return v > 0; + } + } + + @Operation + static final class GreaterEqualZero { + @Specialization + public static boolean doInt(int v) { + return v >= 0; + } + } + + } + + @GenerateBytecode(languageClass = InstructionTracingLanguage.class, // + enableYield = true, enableSerialization = true, // + enableUncachedInterpreter = true, // + enableInstructionTracing = false, boxingEliminationTypes = {long.class, int.class, boolean.class}) + public abstract static class InstructionTracingDisabledRootNode extends RootNode implements BytecodeRootNode { + + protected InstructionTracingDisabledRootNode(InstructionTracingLanguage language, FrameDescriptor frameDescriptor) { + super(language, frameDescriptor); + } + + @Instrumentation + static final class OtherInstrument { + @Specialization + public static int doDefault(int v) { + return v + 1; + } + } + + } + + @TruffleLanguage.Registration(id = InstructionTracingLanguage.ID) + public static class InstructionTracingLanguage extends TruffleLanguage { + public static final String ID = "InstructionTracingLanguage"; + + @Override + protected boolean isThreadAccessAllowed(Thread thread, boolean singleThreaded) { + return true; + } + + @Override + protected Object createContext(Env env) { + return new Object(); + } + + static final LanguageReference REF = LanguageReference.create(InstructionTracingLanguage.class); + } + +} diff --git a/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/InstrumentationTest.java b/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/InstrumentationTest.java index 969c62804457..eaec08d30778 100644 --- a/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/InstrumentationTest.java +++ b/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/InstrumentationTest.java @@ -60,10 +60,12 @@ import com.oracle.truffle.api.bytecode.BytecodeConfig; import com.oracle.truffle.api.bytecode.BytecodeLocal; import com.oracle.truffle.api.bytecode.BytecodeLocation; +import com.oracle.truffle.api.bytecode.BytecodeNode; import com.oracle.truffle.api.bytecode.BytecodeParser; import com.oracle.truffle.api.bytecode.BytecodeRootNode; import com.oracle.truffle.api.bytecode.BytecodeRootNodes; import com.oracle.truffle.api.bytecode.GenerateBytecode; +import com.oracle.truffle.api.bytecode.InstructionTracer; import com.oracle.truffle.api.bytecode.Instrumentation; import com.oracle.truffle.api.bytecode.Operation; import com.oracle.truffle.api.bytecode.Variadic; @@ -75,6 +77,7 @@ import com.oracle.truffle.api.bytecode.test.error_tests.ExpectError; import com.oracle.truffle.api.dsl.Bind; import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.frame.Frame; import com.oracle.truffle.api.frame.FrameDescriptor; import com.oracle.truffle.api.instrumentation.ProvidedTags; import com.oracle.truffle.api.instrumentation.StandardTags; @@ -374,6 +377,74 @@ public void testCachedTagsPreservedInInstrumentationWithSplitting() { assertEquals("load.local$Int", clonedNode.getBytecodeNode().getInstructionsAsList().get(4).getName()); } + @Test + public void testManyInstrumentation() { + Class[] allInstrumentations = new Class[]{ + ManyInstrumentationsRootNode.Instrumentation1.class, + ManyInstrumentationsRootNode.Instrumentation2.class, + ManyInstrumentationsRootNode.Instrumentation3.class, + ManyInstrumentationsRootNode.Instrumentation4.class, + ManyInstrumentationsRootNode.Instrumentation5.class, + ManyInstrumentationsRootNode.Instrumentation6.class, + ManyInstrumentationsRootNode.Instrumentation7.class, + ManyInstrumentationsRootNode.Instrumentation8.class, + ManyInstrumentationsRootNode.Instrumentation9.class, + ManyInstrumentationsRootNode.Instrumentation10.class, + ManyInstrumentationsRootNode.Instrumentation11.class, + ManyInstrumentationsRootNode.Instrumentation12.class, + ManyInstrumentationsRootNode.Instrumentation13.class, + ManyInstrumentationsRootNode.Instrumentation14.class, + ManyInstrumentationsRootNode.Instrumentation15.class, + ManyInstrumentationsRootNode.Instrumentation16.class, + ManyInstrumentationsRootNode.Instrumentation17.class, + ManyInstrumentationsRootNode.Instrumentation18.class, + ManyInstrumentationsRootNode.Instrumentation19.class, + ManyInstrumentationsRootNode.Instrumentation20.class, + ManyInstrumentationsRootNode.Instrumentation21.class, + ManyInstrumentationsRootNode.Instrumentation22.class, + ManyInstrumentationsRootNode.Instrumentation23.class, + ManyInstrumentationsRootNode.Instrumentation24.class, + ManyInstrumentationsRootNode.Instrumentation25.class, + ManyInstrumentationsRootNode.Instrumentation26.class, + ManyInstrumentationsRootNode.Instrumentation27.class, + ManyInstrumentationsRootNode.Instrumentation28.class, + ManyInstrumentationsRootNode.Instrumentation29.class, + ManyInstrumentationsRootNode.Instrumentation30.class, + }; + ManyInstrumentationsRootNode root = ManyInstrumentationsRootNodeGen.BYTECODE.create(null, BytecodeConfig.DEFAULT, (b) -> { + b.beginRoot(); + for (Class instrument : allInstrumentations) { + String simpleName = instrument.getSimpleName(); + try { + b.getClass().getMethod("emit" + simpleName).invoke(b); + } catch (ReflectiveOperationException e) { + throw new AssertionError(e); + } + } + b.emitLoadConstant(42); + b.endRoot(); + }).getNode(0); + + assertEquals(2, root.getBytecodeNode().getInstructionsAsList().size()); + int expectedInstructionCount = 2; + for (Class instrumentation : allInstrumentations) { + assertEquals(expectedInstructionCount, root.getBytecodeNode().getInstructionsAsList().size()); + root.getRootNodes().update(ManyInstrumentationsRootNodeGen.BYTECODE.newConfigBuilder().addInstrumentation(instrumentation).build()); + assertEquals(expectedInstructionCount + 1, + root.getBytecodeNode().getInstructionsAsList().size()); + expectedInstructionCount++; + } + root.getRootNodes().addInstructionTracer(new InstructionTracer() { + @Override + public void onInstructionEnter(InstructionAccess access, BytecodeNode bytecode, int bytecodeIndex, Frame frame) { + } + }); + + // instruction tracing doubles all instructions + assertEquals(expectedInstructionCount * 2, + root.getBytecodeNode().getInstructionsAsList().size()); + } + @GenerateBytecode(languageClass = BytecodeInstrumentationTestLanguage.class, // enableQuickening = true, // enableUncachedInterpreter = true, // @@ -851,13 +922,6 @@ public static void doDefault() { } } - @Instrumentation - static final class Instrumentation31 { - @Specialization - public static void doDefault() { - } - } - } @ExpectError("Too many @Instrumentation annotated operations specified. %") @@ -1096,13 +1160,6 @@ public static void doDefault() { } } - @Instrumentation - static final class Instrumentation32 { - @Specialization - public static void doDefault() { - } - } - } } diff --git a/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/LocalHelpersTest.java b/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/LocalHelpersTest.java index 3a6b000251e7..3594e3979313 100644 --- a/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/LocalHelpersTest.java +++ b/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/LocalHelpersTest.java @@ -61,9 +61,9 @@ import com.oracle.truffle.api.CallTarget; import com.oracle.truffle.api.CompilerAsserts; import com.oracle.truffle.api.CompilerDirectives; -import com.oracle.truffle.api.RootCallTarget; import com.oracle.truffle.api.CompilerDirectives.CompilationFinal; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; +import com.oracle.truffle.api.RootCallTarget; import com.oracle.truffle.api.Truffle; import com.oracle.truffle.api.bytecode.BytecodeConfig; import com.oracle.truffle.api.bytecode.BytecodeLocal; @@ -100,34 +100,18 @@ @RunWith(Parameterized.class) public class LocalHelpersTest { @Parameters(name = "{0}") - public static List> getInterpreterClasses() { - return List.of(BytecodeNodeWithLocalIntrospectionBase.class, - BytecodeNodeWithLocalIntrospectionBaseDefault.class, - BytecodeNodeWithLocalIntrospectionWithBEObjectDefault.class, - BytecodeNodeWithLocalIntrospectionWithBENullDefault.class, - BytecodeNodeWithLocalIntrospectionWithBEIllegal.class, - BytecodeNodeWithLocalIntrospectionWithBEIllegalRootScoped.class); + public static List getVariants() { + return BytecodeNodeWithLocalIntrospectionBuilder.variants(); } - @Parameter(0) public Class interpreterClass; + @Parameter(0) public BytecodeNodeWithLocalIntrospectionBuilder.BytecodeVariant bytecode; public static BytecodeLocal makeLocal(BytecodeNodeWithLocalIntrospectionBuilder b, String name) { return b.createLocal(name, null); } - public static BytecodeRootNodes parseNodes( - Class interpreterClass, - BytecodeParser builder) { - return BytecodeNodeWithLocalIntrospectionBuilder.invokeCreate((Class) interpreterClass, - null, BytecodeConfig.DEFAULT, builder); - } - - public static BytecodeNodeWithLocalIntrospection parseNode(Class interpreterClass, - BytecodeParser builder) { - return parseNodes(interpreterClass, builder).getNode(0); - } - private Object getLocalDefaultValue() { + Class interpreterClass = bytecode.getGeneratedClass(); if (interpreterClass == BytecodeNodeWithLocalIntrospectionBaseDefault.class || interpreterClass == BytecodeNodeWithLocalIntrospectionWithBEObjectDefault.class) { return BytecodeNodeWithLocalIntrospection.DEFAULT; } @@ -138,17 +122,23 @@ private Object getLocalDefaultValue() { } private boolean hasLocalDefaultValue() { + Class interpreterClass = bytecode.getGeneratedClass(); return interpreterClass == BytecodeNodeWithLocalIntrospectionBaseDefault.class || interpreterClass == BytecodeNodeWithLocalIntrospectionWithBEObjectDefault.class || interpreterClass == BytecodeNodeWithLocalIntrospectionWithBENullDefault.class; } private boolean hasBoxingElimination() { + Class interpreterClass = bytecode.getGeneratedClass(); return interpreterClass == BytecodeNodeWithLocalIntrospectionWithBEObjectDefault.class || interpreterClass == BytecodeNodeWithLocalIntrospectionWithBENullDefault.class || interpreterClass == BytecodeNodeWithLocalIntrospectionWithBEIllegal.class; } - public BytecodeNodeWithLocalIntrospection parseNode(BytecodeParser builder) { - return parseNode(interpreterClass, builder); + public BytecodeRootNodes parseNodes(BytecodeParser builder) { + return bytecode.create(null, BytecodeConfig.DEFAULT, builder); + } + + public BytecodeNodeWithLocalIntrospection parseNode(BytecodeParser builder) { + return parseNodes(builder).getNode(0); } @Test @@ -463,7 +453,7 @@ public void testGetSetMaterializedLocalAccessor() { * return foo * @formatter:on */ - BytecodeRootNodes roots = parseNodes(interpreterClass, b -> { + BytecodeRootNodes roots = parseNodes(b -> { b.beginRoot(); b.beginBlock(); BytecodeLocal foo = makeLocal(b, "foo"); @@ -1468,7 +1458,7 @@ public void testIsClearedMaterializedAccessor() { // return isCleared l0 // @formatter:on - BytecodeRootNodes roots = parseNodes(interpreterClass, b -> { + BytecodeRootNodes roots = parseNodes(b -> { b.beginRoot(); BytecodeLocal l = makeLocal(b, "l0"); @@ -1606,7 +1596,7 @@ public void testGetLocalMetadataMaterializedAccessor() { // } // @formatter:on - BytecodeRootNodes roots = parseNodes(interpreterClass, b -> { + BytecodeRootNodes roots = parseNodes(b -> { b.beginRoot(); b.beginBlock(); diff --git a/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/ShortCircuitTest.java b/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/ShortCircuitTest.java index 69e7c412d9b7..0e7270bac7d6 100644 --- a/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/ShortCircuitTest.java +++ b/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/ShortCircuitTest.java @@ -52,9 +52,9 @@ import org.junit.runners.Parameterized.Parameters; import com.oracle.truffle.api.bytecode.BytecodeConfig; -import com.oracle.truffle.api.bytecode.BytecodeRootNodes; import com.oracle.truffle.api.bytecode.BytecodeParser; import com.oracle.truffle.api.bytecode.BytecodeRootNode; +import com.oracle.truffle.api.bytecode.BytecodeRootNodes; import com.oracle.truffle.api.bytecode.GenerateBytecode; import com.oracle.truffle.api.bytecode.GenerateBytecodeTestVariants; import com.oracle.truffle.api.bytecode.GenerateBytecodeTestVariants.Variant; @@ -69,22 +69,22 @@ @RunWith(Parameterized.class) public class ShortCircuitTest { + @Parameters(name = "{0}") - public static List> getInterpreterClasses() { - return List.of(BytecodeNodeWithShortCircuitBase.class, BytecodeNodeWithShortCircuitWithBE.class); + public static List getVariants() { + return BytecodeNodeWithShortCircuitBuilder.variants(); } - @Parameter(0) public Class interpreterClass; + @Parameter(0) public BytecodeNodeWithShortCircuitBuilder.BytecodeVariant variant; - public static BytecodeNodeWithShortCircuit parseNode(Class interpreterClass, - BytecodeParser builder) { - BytecodeRootNodes nodes = BytecodeNodeWithShortCircuitBuilder.invokeCreate((Class) interpreterClass, - null, BytecodeConfig.DEFAULT, builder); + public static BytecodeNodeWithShortCircuit parseNode(BytecodeNodeWithShortCircuitBuilder.BytecodeVariant variant, + BytecodeParser builder) { + BytecodeRootNodes nodes = variant.create(null, BytecodeConfig.DEFAULT, builder); return nodes.getNode(nodes.count() - 1); } - public BytecodeNodeWithShortCircuit parseNode(BytecodeParser builder) { - return parseNode(interpreterClass, builder); + public BytecodeNodeWithShortCircuit parseNode(BytecodeParser builder) { + return parseNode(variant, builder); } @Test diff --git a/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/StackTraceTest.java b/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/StackTraceTest.java index efeadd2bb7ce..808df07448db 100644 --- a/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/StackTraceTest.java +++ b/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/StackTraceTest.java @@ -98,16 +98,16 @@ public class StackTraceTest extends AbstractInstructionTest { enum Interpreter { - CACHED_DEFAULT(StackTraceTestRootNodeCachedDefault.class, true), - UNCACHED_DEFAULT(StackTraceTestRootNodeUncachedDefault.class, false), - CACHED_BCI_IN_FRAME(StackTraceTestRootNodeCachedBciInFrame.class, true), - UNCACHED_BCI_IN_FRAME(StackTraceTestRootNodeUncachedBciInFrame.class, false); + CACHED_DEFAULT(StackTraceTestRootNodeCachedDefault.BYTECODE, true), + UNCACHED_DEFAULT(StackTraceTestRootNodeUncachedDefault.BYTECODE, false), + CACHED_BCI_IN_FRAME(StackTraceTestRootNodeCachedBciInFrame.BYTECODE, true), + UNCACHED_BCI_IN_FRAME(StackTraceTestRootNodeUncachedBciInFrame.BYTECODE, false); - final Class clazz; + final StackTraceTestRootNodeBuilder.BytecodeVariant variant; final boolean cached; - Interpreter(Class clazz, boolean cached) { - this.clazz = clazz; + Interpreter(StackTraceTestRootNodeBuilder.BytecodeVariant clazz, boolean cached) { + this.variant = clazz; this.cached = cached; } } @@ -118,7 +118,7 @@ enum Interpreter { record Run(Interpreter interpreter, int depth) { @Override public String toString() { - return interpreter.clazz.getSimpleName() + "(depth=" + depth + ")"; + return interpreter.variant.getGeneratedClass().getSimpleName() + "(depth=" + depth + ")"; } } @@ -445,7 +445,6 @@ static Object doDefault(@Bind Node node) { return TruffleStackTrace.getStackTrace(ex); } } - } @ExportLibrary(InteropLibrary.class) @@ -485,8 +484,7 @@ private static Node resolveLocation(Node location) { } private StackTraceTestRootNode parse(BytecodeParser parser) { - BytecodeRootNodes nodes = StackTraceTestRootNodeBuilder.invokeCreate((Class) run.interpreter.clazz, - LANGUAGE, BytecodeConfig.WITH_SOURCE, (BytecodeParser) parser); + BytecodeRootNodes nodes = run.interpreter.variant.create(LANGUAGE, BytecodeConfig.WITH_SOURCE, parser); StackTraceTestRootNode root = nodes.getNodes().get(nodes.getNodes().size() - 1); return root; } diff --git a/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/basic_interpreter/AbstractBasicInterpreterTest.java b/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/basic_interpreter/AbstractBasicInterpreterTest.java index 49c06c649268..8430c1153e81 100644 --- a/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/basic_interpreter/AbstractBasicInterpreterTest.java +++ b/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/basic_interpreter/AbstractBasicInterpreterTest.java @@ -68,6 +68,7 @@ import org.junit.runners.Parameterized.Parameters; import com.oracle.truffle.api.CompilerDirectives; +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.RootCallTarget; import com.oracle.truffle.api.bytecode.BytecodeConfig; import com.oracle.truffle.api.bytecode.BytecodeLabel; @@ -81,6 +82,8 @@ import com.oracle.truffle.api.bytecode.Instruction; import com.oracle.truffle.api.bytecode.Instruction.Argument; import com.oracle.truffle.api.bytecode.Instruction.Argument.Kind; +import com.oracle.truffle.api.bytecode.InstructionDescriptor; +import com.oracle.truffle.api.bytecode.InstructionTracer; import com.oracle.truffle.api.bytecode.LocalVariable; import com.oracle.truffle.api.bytecode.SourceInformation; import com.oracle.truffle.api.bytecode.SourceInformationTree; @@ -89,6 +92,8 @@ import com.oracle.truffle.api.bytecode.serialization.BytecodeSerializer; import com.oracle.truffle.api.bytecode.serialization.SerializationUtils; import com.oracle.truffle.api.bytecode.test.BytecodeDSLTestLanguage; +import com.oracle.truffle.api.bytecode.test.basic_interpreter.BasicInterpreterBuilder.BytecodeVariant; +import com.oracle.truffle.api.frame.Frame; import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.nodes.RootNode; import com.oracle.truffle.api.source.Source; @@ -96,20 +101,24 @@ @RunWith(Parameterized.class) public abstract class AbstractBasicInterpreterTest { - public record TestRun(Class interpreterClass, boolean testSerialize) { + public record TestRun(BytecodeVariant bytecode, boolean testSerialize, boolean testTracer) { + + public Class interpreterClass() { + return bytecode.getGeneratedClass(); + } public boolean hasBoxingElimination() { - return interpreterClass == BasicInterpreterWithBE.class || - interpreterClass == BasicInterpreterWithStoreBytecodeIndexInFrame.class || - interpreterClass == BasicInterpreterProductionBlockScoping.class || - interpreterClass == BasicInterpreterProductionRootScoping.class; + return interpreterClass() == BasicInterpreterWithBE.class || + interpreterClass() == BasicInterpreterWithStoreBytecodeIndexInFrame.class || + interpreterClass() == BasicInterpreterProductionBlockScoping.class || + interpreterClass() == BasicInterpreterProductionRootScoping.class; } public boolean hasUncachedInterpreter() { - return interpreterClass == BasicInterpreterWithUncached.class || - interpreterClass == BasicInterpreterWithStoreBytecodeIndexInFrame.class || - interpreterClass == BasicInterpreterProductionBlockScoping.class || - interpreterClass == BasicInterpreterProductionRootScoping.class; + return interpreterClass() == BasicInterpreterWithUncached.class || + interpreterClass() == BasicInterpreterWithStoreBytecodeIndexInFrame.class || + interpreterClass() == BasicInterpreterProductionBlockScoping.class || + interpreterClass() == BasicInterpreterProductionRootScoping.class; } @SuppressWarnings("static-method") @@ -118,8 +127,8 @@ public boolean hasYield() { } public boolean hasRootScoping() { - return interpreterClass == BasicInterpreterWithRootScoping.class || - interpreterClass == BasicInterpreterProductionRootScoping.class; + return interpreterClass() == BasicInterpreterWithRootScoping.class || + interpreterClass() == BasicInterpreterProductionRootScoping.class; } public boolean hasBlockScoping() { @@ -127,12 +136,12 @@ public boolean hasBlockScoping() { } public boolean storesBciInFrame() { - return interpreterClass == BasicInterpreterWithStoreBytecodeIndexInFrame.class; + return interpreterClass() == BasicInterpreterWithStoreBytecodeIndexInFrame.class; } @Override public String toString() { - return interpreterClass.getSimpleName() + "[serialize=" + testSerialize + "]"; + return interpreterClass().getSimpleName() + "[serialize=" + testSerialize + ",trace=" + testTracer + "]"; } public int getFrameBaseSlots() { @@ -148,17 +157,17 @@ public int getFrameBaseSlots() { @SuppressWarnings("static-method") public int getVariadicsLimit() { - if (interpreterClass == BasicInterpreterBase.class // - || interpreterClass == BasicInterpreterWithBE.class // - || interpreterClass == BasicInterpreterWithStoreBytecodeIndexInFrame.class) { + if (interpreterClass() == BasicInterpreterBase.class // + || interpreterClass() == BasicInterpreterWithBE.class // + || interpreterClass() == BasicInterpreterWithStoreBytecodeIndexInFrame.class) { return 4; - } else if (interpreterClass == BasicInterpreterUnsafe.class // - || interpreterClass == BasicInterpreterWithOptimizations.class // - || interpreterClass == BasicInterpreterProductionBlockScoping.class) { + } else if (interpreterClass() == BasicInterpreterUnsafe.class // + || interpreterClass() == BasicInterpreterWithOptimizations.class // + || interpreterClass() == BasicInterpreterProductionBlockScoping.class) { return 8; - } else if (interpreterClass == BasicInterpreterWithUncached.class // - || interpreterClass == BasicInterpreterWithRootScoping.class // - || interpreterClass == BasicInterpreterProductionRootScoping.class) { + } else if (interpreterClass() == BasicInterpreterWithUncached.class // + || interpreterClass() == BasicInterpreterWithRootScoping.class // + || interpreterClass() == BasicInterpreterProductionRootScoping.class) { return 16; } @@ -166,7 +175,7 @@ public int getVariadicsLimit() { } public Object getDefaultLocalValue() { - if (interpreterClass == BasicInterpreterWithOptimizations.class || interpreterClass == BasicInterpreterWithRootScoping.class) { + if (interpreterClass() == BasicInterpreterWithOptimizations.class || interpreterClass() == BasicInterpreterWithRootScoping.class) { return BasicInterpreter.LOCAL_DEFAULT_VALUE; } return null; @@ -255,9 +264,10 @@ yield switch (arrayCode) { @Parameters(name = "{0}") public static List getParameters() { List result = new ArrayList<>(); - for (Class interpreterClass : allInterpreters()) { - result.add(new TestRun(interpreterClass, false)); - result.add(new TestRun(interpreterClass, true)); + for (BytecodeVariant bc : allVariants()) { + result.add(new TestRun(bc, false, false)); + result.add(new TestRun(bc, true, false)); + result.add(new TestRun(bc, false, true)); } return result; } @@ -268,25 +278,34 @@ public AbstractBasicInterpreterTest(TestRun run) { this.run = run; } - public RootCallTarget parse(String rootName, BytecodeParser builder) { - BytecodeRootNode rootNode = parseNode(run.interpreterClass, LANGUAGE, run.testSerialize, rootName, builder); + public RootCallTarget parse(String rootName, BytecodeParser builder) { + BytecodeRootNode rootNode = parseNode(run, LANGUAGE, rootName, builder); return ((RootNode) rootNode).getCallTarget(); } - public BasicInterpreter parseNode(String rootName, BytecodeParser builder) { - return parseNode(run.interpreterClass, LANGUAGE, run.testSerialize, rootName, builder); + public BasicInterpreter parseNode(String rootName, BytecodeParser builder) { + return parseNode(run, LANGUAGE, rootName, builder); } - public BasicInterpreter parseNodeWithSource(String rootName, BytecodeParser builder) { - return parseNodeWithSource(run.interpreterClass, LANGUAGE, run.testSerialize, rootName, builder); + public BasicInterpreter parseNodeWithSource(String rootName, BytecodeParser builder) { + return parseNodeWithSource(run, LANGUAGE, rootName, builder); } - public BytecodeRootNodes createNodes(BytecodeConfig config, BytecodeParser builder) { - return createNodes(run.interpreterClass, LANGUAGE, run.testSerialize, config, builder); + public BytecodeRootNodes createNodes(BytecodeConfig config, BytecodeParser builder) { + return createNodes(run, LANGUAGE, config, builder); } public BytecodeConfig.Builder createBytecodeConfigBuilder() { - return BasicInterpreterBuilder.invokeNewConfigBuilder(run.interpreterClass); + return run.bytecode().newConfigBuilder(); + } + + public static BytecodeVariant lookupVariant(BasicInterpreter interpreter) { + for (BasicInterpreterBuilder.BytecodeVariant variant : BasicInterpreterBuilder.variants()) { + if (variant.getGeneratedClass() == interpreter.getClass()) { + return variant; + } + } + return null; } /** @@ -298,13 +317,39 @@ public BytecodeConfig.Builder createBytecodeConfigBuilder() { * reflection. */ @SuppressWarnings("unchecked") - public static BytecodeRootNodes createNodes(Class interpreterClass, - BytecodeDSLTestLanguage language, boolean testSerialize, BytecodeConfig config, BytecodeParser builder) { + public static BytecodeRootNodes createNodes(TestRun run, + BytecodeDSLTestLanguage language, BytecodeConfig config, BytecodeParser builder) { - BytecodeRootNodes result = BasicInterpreterBuilder.invokeCreate((Class) interpreterClass, - language, config, (BytecodeParser) builder); - if (testSerialize) { - assertBytecodeNodesEqual(result, doRoundTrip(interpreterClass, language, config, result)); + BytecodeRootNodes result = run.bytecode.create(language, config, builder); + if (run.testSerialize) { + assertBytecodeNodesEqual(result, doRoundTrip(run, language, config, result)); + } + + if (run.testTracer) { + InstructionDescriptor traceInstruction = null; + for (InstructionDescriptor d : run.bytecode().getInstructionDescriptors()) { + if (d.getName().equals("trace.instruction")) { + traceInstruction = d; + break; + } + } + assertNotNull(traceInstruction); + final InstructionDescriptor trace = traceInstruction; + result.addInstructionTracer(new InstructionTracer() { + + public void onInstructionEnter(InstructionAccess access, BytecodeNode bytecode, int bytecodeIndex, Frame frame) { + assertInstruction(access, bytecode, bytecodeIndex); + } + + @TruffleBoundary + private void assertInstruction(InstructionAccess access, BytecodeNode bytecode, int bytecodeIndex) { + Instruction current = bytecode.getInstruction(bytecodeIndex); + assertSame(trace, current.getDescriptor()); + Instruction traced = access.getTracedInstruction(bytecode, bytecodeIndex); + assertEquals(traced.getBytecodeIndex(), current.getNextBytecodeIndex()); + assertEquals(traced.getOperationCode(), access.getTracedOperationCode(bytecode, bytecodeIndex)); + } + }); } for (BasicInterpreter interpreter : result.getNodes()) { @@ -507,7 +552,9 @@ private static void testSourceTree(BytecodeNode bytecode, SourceInformationTree } } - public static BytecodeRootNodes doRoundTrip(Class interpreterClass, BytecodeDSLTestLanguage language, BytecodeConfig config, + public static BytecodeRootNodes doRoundTrip( + TestRun run, + BytecodeDSLTestLanguage language, BytecodeConfig config, BytecodeRootNodes nodes) { // Perform a serialize-deserialize round trip. ByteArrayOutputStream output = new ByteArrayOutputStream(); @@ -517,30 +564,41 @@ public static BytecodeRootNodes doRoundTrip(Clas throw new AssertionError(ex); } Supplier input = () -> SerializationUtils.createDataInput(ByteBuffer.wrap(output.toByteArray())); - return BasicInterpreterBuilder.invokeDeserialize((Class) interpreterClass, language, config, input, DESERIALIZER); + try { + return run.bytecode().deserialize(language, config, input, DESERIALIZER); + } catch (IOException e) { + throw new AssertionError(e); + } } public BytecodeRootNodes doRoundTrip(BytecodeRootNodes nodes) { - return AbstractBasicInterpreterTest.doRoundTrip(run.interpreterClass, LANGUAGE, BytecodeConfig.DEFAULT, nodes); + return AbstractBasicInterpreterTest.doRoundTrip(run, LANGUAGE, BytecodeConfig.DEFAULT, nodes); } - public static RootCallTarget parse(Class interpreterClass, BytecodeDSLTestLanguage language, boolean testSerialize, String rootName, - BytecodeParser builder) { - BytecodeRootNode rootNode = parseNode(interpreterClass, language, testSerialize, rootName, builder); + public static RootCallTarget parse( + TestRun run, + BytecodeDSLTestLanguage language, String rootName, + BytecodeParser builder) { + BytecodeRootNode rootNode = parseNode(run, language, rootName, builder); return ((RootNode) rootNode).getCallTarget(); } - public static BasicInterpreter parseNode(Class interpreterClass, BytecodeDSLTestLanguage language, boolean testSerialize, - String rootName, BytecodeParser builder) { - BytecodeRootNodes nodes = createNodes(interpreterClass, language, testSerialize, BytecodeConfig.DEFAULT, builder); + public static BasicInterpreter parseNode( + TestRun run, + BytecodeDSLTestLanguage language, + String rootName, BytecodeParser builder) { + + BytecodeRootNodes nodes = createNodes(run, language, BytecodeConfig.DEFAULT, builder); BasicInterpreter op = nodes.getNode(0); op.setName(rootName); return op; } - public static BasicInterpreter parseNodeWithSource(Class interpreterClass, BytecodeDSLTestLanguage language, boolean testSerialize, - String rootName, BytecodeParser builder) { - BytecodeRootNodes nodes = createNodes(interpreterClass, language, testSerialize, BytecodeConfig.WITH_SOURCE, builder); + public static BasicInterpreter parseNodeWithSource( + TestRun run, + BytecodeDSLTestLanguage language, + String rootName, BytecodeParser builder) { + BytecodeRootNodes nodes = createNodes(run, language, BytecodeConfig.WITH_SOURCE, builder); BasicInterpreter op = nodes.getNode(0); op.setName(rootName); return op; @@ -631,10 +689,9 @@ public static ExpectedSourceTree expectedSourceTreeUnavailable(ExpectedSourceTre } } - public static List> allInterpreters() { - return List.of(BasicInterpreterBase.class, BasicInterpreterUnsafe.class, BasicInterpreterWithUncached.class, BasicInterpreterWithBE.class, BasicInterpreterWithOptimizations.class, - BasicInterpreterWithStoreBytecodeIndexInFrame.class, - BasicInterpreterWithRootScoping.class, BasicInterpreterProductionRootScoping.class, BasicInterpreterProductionBlockScoping.class); + @SuppressWarnings("unchecked") + public static List allVariants() { + return BasicInterpreterBuilder.variants(); } /// Code gen helpers @@ -678,4 +735,9 @@ protected static void emitThrowIf(BasicInterpreterBuilder b, int arg, long value emitThrow(b, value); b.endIfThen(); } + + public static List filterTrace(List instructions) { + return instructions.stream().filter((i) -> !i.getName().equals("trace.instruction")).toList(); + } + } diff --git a/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/basic_interpreter/BasicInterpreter.java b/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/basic_interpreter/BasicInterpreter.java index 32285c040d95..c9b42dea7847 100644 --- a/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/basic_interpreter/BasicInterpreter.java +++ b/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/basic_interpreter/BasicInterpreter.java @@ -45,6 +45,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.function.Function; import com.oracle.truffle.api.CallTarget; import com.oracle.truffle.api.CompilerAsserts; @@ -812,7 +813,7 @@ public static void doEnable( @TruffleBoundary protected static BytecodeConfig getConfig(BasicInterpreter root) { - BytecodeConfig.Builder configBuilder = BasicInterpreterBuilder.invokeNewConfigBuilder(root.getClass()); + BytecodeConfig.Builder configBuilder = AbstractBasicInterpreterTest.lookupVariant(root).newConfigBuilder(); configBuilder.addInstrumentation(IncrementValue.class); return configBuilder.build(); } @@ -845,7 +846,7 @@ public static void doEnable( @TruffleBoundary protected static BytecodeConfig getConfig(BasicInterpreter root) { - BytecodeConfig.Builder configBuilder = BasicInterpreterBuilder.invokeNewConfigBuilder(root.getClass()); + BytecodeConfig.Builder configBuilder = AbstractBasicInterpreterTest.lookupVariant(root).newConfigBuilder(); configBuilder.addInstrumentation(DoubleValue.class); return configBuilder.build(); } @@ -923,6 +924,17 @@ public static Object[] doDefault(long arg0, @Variadic Object[] args) { } } + @Operation(storeBytecodeIndex = false) + @ConstantOperand(name = "f", type = Function.class) + static final class Run { + @SuppressWarnings("unchecked") + @Specialization + @TruffleBoundary + public static Object doDefault(Function supplier, @Bind BytecodeNode bc) { + return ((Function) supplier).apply(bc); + } + } + @Operation static final class VariadicOffsetOperation { @Specialization diff --git a/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/basic_interpreter/BasicInterpreterTest.java b/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/basic_interpreter/BasicInterpreterTest.java index 9fa2489bcf60..dd5e4b3ee91d 100644 --- a/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/basic_interpreter/BasicInterpreterTest.java +++ b/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/basic_interpreter/BasicInterpreterTest.java @@ -147,14 +147,19 @@ private static ExpectedInstruction.Builder instr(String name) { return new ExpectedInstruction.Builder(name); } - private static void assertInstructionsEqual(List actualInstructions, ExpectedInstruction... expectedInstructions) { + private static void assertInstructionsEqual(List actualInstructionsOriginal, ExpectedInstruction... expectedInstructions) { + List actualInstructions = filterTrace(actualInstructionsOriginal); if (actualInstructions.size() != expectedInstructions.length) { fail(String.format("Expected %d instructions, but %d found.\nExpected: %s.\nActual: %s", expectedInstructions.length, actualInstructions.size(), expectedInstructions, actualInstructions)); } - int bci = 0; + int bci = actualInstructions.get(0).getBytecodeIndex(); for (int i = 0; i < expectedInstructions.length; i++) { assertInstructionEquals(actualInstructions.get(i), expectedInstructions[i].withBci(bci)); - bci = actualInstructions.get(i).getNextBytecodeIndex(); + + if (i + 1 < actualInstructions.size()) { + bci = actualInstructions.get(i + 1).getBytecodeIndex(); + } + } } @@ -174,6 +179,14 @@ private static void assertInstructionEquals(Instruction actual, ExpectedInstruct fail(String.format("Argument %s missing from instruction %s", expectedArgument.name, actual.getName())); } assertEquals(expectedArgument.kind, actualArgument.getKind()); + + if (TruffleTestAssumptions.isOptimizingRuntime() && expectedArgument.kind == Kind.BRANCH_PROFILE) { + // no validation of branch profile frequency for the optimizing runtime + // it is generally race to assume they are stable with the optimizing runtime + // we can only safely assume they are deterministic in the fallback runtime. + continue; + } + switch (expectedArgument.kind) { case CONSTANT -> assertEquals(expectedArgument.value, actualArgument.asConstant()); case INTEGER -> assertEquals(expectedArgument.value, actualArgument.asInteger()); @@ -2884,7 +2897,10 @@ private static String[] collectInstructions(BytecodeNode bytecode, int startBci, for (Instruction instruction : bytecode.getInstructions()) { int bci = instruction.getBytecodeIndex(); if (startBci <= bci && bci < endBci) { - result.add(instruction.getName()); + // filter trace instructions + if (!instruction.getName().equals("trace.instruction")) { + result.add(instruction.getName()); + } } } return result.toArray(new String[0]); @@ -3149,19 +3165,22 @@ public void testIntrospectionDataSourceInformation() { assertEquals("1 + 2", s3.getSourceSection().getCharacters().toString()); assertEquals("return 1 + 2", s4.getSourceSection().getCharacters().toString()); - List instructions = node.getBytecodeNode().getInstructionsAsList(); + if (!run.testTracer()) { + List instructions = node.getBytecodeNode().getInstructionsAsList(); - assertEquals(0, s1.getStartBytecodeIndex()); - assertEquals(instructions.get(1).getBytecodeIndex(), s1.getEndBytecodeIndex()); + assertEquals(0, s1.getStartBytecodeIndex()); + assertEquals(instructions.get(1).getBytecodeIndex(), s1.getEndBytecodeIndex()); - assertEquals(6, s2.getStartBytecodeIndex()); - assertEquals(instructions.get(2).getBytecodeIndex(), s2.getEndBytecodeIndex()); + assertEquals(6, s2.getStartBytecodeIndex()); + assertEquals(instructions.get(2).getBytecodeIndex(), s2.getEndBytecodeIndex()); - assertEquals(0, s3.getStartBytecodeIndex()); - assertEquals(instructions.get(3).getBytecodeIndex(), s3.getEndBytecodeIndex()); + assertEquals(0, s3.getStartBytecodeIndex()); + assertEquals(instructions.get(3).getBytecodeIndex(), s3.getEndBytecodeIndex()); + + assertEquals(0, s4.getStartBytecodeIndex()); + assertEquals(instructions.get(3).getNextBytecodeIndex(), s4.getEndBytecodeIndex()); + } - assertEquals(0, s4.getStartBytecodeIndex()); - assertEquals(instructions.get(3).getNextBytecodeIndex(), s4.getEndBytecodeIndex()); } @Test diff --git a/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/basic_interpreter/BindingsTest.java b/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/basic_interpreter/BindingsTest.java index 6aac4d81e12d..df55a8d01ef1 100644 --- a/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/basic_interpreter/BindingsTest.java +++ b/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/basic_interpreter/BindingsTest.java @@ -42,6 +42,7 @@ import static org.junit.Assert.assertEquals; +import org.junit.Assume; import org.junit.Test; import com.oracle.truffle.api.bytecode.test.basic_interpreter.BasicInterpreter.Bindings; @@ -54,6 +55,8 @@ public BindingsTest(TestRun run) { @Test public void testExplicit() { + Assume.assumeFalse(run.testTracer()); + BasicInterpreter node = parseNode("explicitBindings", b -> { b.beginRoot(); b.beginReturn(); @@ -78,6 +81,7 @@ public void testExplicit() { @Test public void testImplicit() { + Assume.assumeFalse(run.testTracer()); BasicInterpreter node = parseNode("explicitBindings", b -> { b.beginRoot(); b.beginReturn(); diff --git a/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/basic_interpreter/BranchTest.java b/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/basic_interpreter/BranchTest.java index 123347642278..0cb11589049f 100644 --- a/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/basic_interpreter/BranchTest.java +++ b/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/basic_interpreter/BranchTest.java @@ -412,5 +412,4 @@ public void testDanglingLabel() { assertEquals(42L, root.call()); } - } diff --git a/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/basic_interpreter/BytecodeDescriptorTest.java b/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/basic_interpreter/BytecodeDescriptorTest.java new file mode 100644 index 000000000000..1ecd87292a42 --- /dev/null +++ b/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/basic_interpreter/BytecodeDescriptorTest.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.oracle.truffle.api.bytecode.test.basic_interpreter; + +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertThrows; + +import org.junit.Test; + +import com.oracle.truffle.api.CallTarget; +import com.oracle.truffle.api.bytecode.BytecodeConfig; +import com.oracle.truffle.api.bytecode.InstructionDescriptor; +import com.oracle.truffle.api.bytecode.InstructionDescriptor.ArgumentDescriptor; +import com.oracle.truffle.api.bytecode.test.BytecodeDSLTestLanguage; +import com.oracle.truffle.api.bytecode.test.InstructionTracingTest; +import com.oracle.truffle.api.frame.VirtualFrame; +import com.oracle.truffle.api.nodes.RootNode; + +/** + * Tests basic bytecode descriptor functionality like instruction descriptor reflection and format + * dumping. For instruction tracing tests see {@link InstructionTracingTest}. + */ +public class BytecodeDescriptorTest extends AbstractBasicInterpreterTest { + + public BytecodeDescriptorTest(TestRun run) { + super(run); + } + + @Test + public void testDescriptor() { + var descriptor = run.bytecode(); + assertSame(BasicInterpreter.class, descriptor.getSpecificationClass()); + assertSame(BytecodeDSLTestLanguage.class, descriptor.getLanguageClass()); + assertNotNull(descriptor.dump()); + assertNotNull(descriptor.toString()); + for (InstructionDescriptor d : descriptor.getInstructionDescriptors()) { + assertSame(d, descriptor.getInstructionDescriptor(d.getOperationCode())); + assertNotEquals(0, d.getLength()); + assertNotNull(d.getName()); + for (ArgumentDescriptor ad : d.getArgumentDescriptors()) { + assertNotNull(ad.getKind()); + assertNotNull(ad.getName()); + ad.getLength(); + assertNotNull(ad.toString()); + } + assertNotNull(d.toString()); + } + } + + @Test + public void testCast() { + var descriptor = run.bytecode(); + BasicInterpreter node = descriptor.create(LANGUAGE, BytecodeConfig.DEFAULT, (b) -> { + b.beginRoot(); + b.beginReturn(); + b.emitLoadConstant(42); + b.endReturn(); + b.endRoot(); + }).getNode(0); + assertNotNull(descriptor.toString()); + assertSame(node.getRootNode(), descriptor.cast(node.getRootNode())); + assertSame(node.getRootNode(), descriptor.cast(node.getRootNode().getCallTarget())); + assertThrows(NullPointerException.class, () -> { + descriptor.cast((RootNode) null); + }); + assertThrows(NullPointerException.class, () -> { + descriptor.cast((CallTarget) null); + }); + assertNull(descriptor.cast(new RootNode(null) { + @Override + public Object execute(VirtualFrame frame) { + return null; + } + })); + } + +} diff --git a/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/basic_interpreter/CopyLocalsTest.java b/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/basic_interpreter/CopyLocalsTest.java index df4940e28edf..9600d403789b 100644 --- a/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/basic_interpreter/CopyLocalsTest.java +++ b/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/basic_interpreter/CopyLocalsTest.java @@ -97,7 +97,7 @@ public void testCopyAllLocals() { Frame frame = (Frame) foo.getCallTarget().call(); BytecodeNode bytecode = foo.getBytecodeNode(); - Instruction instr = bytecode.getInstructionsAsList().get(6); + Instruction instr = bytecode.getInstructionsAsList().get(run.testTracer() ? 12 : 6); Object[] locals = foo.getBytecodeNode().getLocalValues(instr.getBytecodeIndex(), frame); assertArrayEquals(new Object[]{42L, "abcd", true}, locals); } @@ -143,7 +143,7 @@ public void testCopySomeLocals() { Frame frame = (Frame) foo.getCallTarget().call(); BytecodeNode bytecode = foo.getBytecodeNode(); - Instruction instr = bytecode.getInstructionsAsList().get(6); + Instruction instr = bytecode.getInstructionsAsList().get(run.testTracer() ? 12 : 6); Object[] locals = bytecode.getLocalValues(instr.getBytecodeIndex(), frame); assertArrayEquals(new Object[]{42L, "abcd", run.getDefaultLocalValue()}, locals); } diff --git a/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/basic_interpreter/LocalsTest.java b/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/basic_interpreter/LocalsTest.java index 9686054c3b8e..56e2a2060dd9 100644 --- a/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/basic_interpreter/LocalsTest.java +++ b/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/basic_interpreter/LocalsTest.java @@ -373,7 +373,7 @@ public void testScopedLocals() { List locals = b.getLocals(); assertEquals(5, locals.size()); assertEquals(42L, root.getCallTarget().call()); - if (run.hasBlockScoping()) { + if (run.hasBlockScoping() && !run.testTracer()) { assertEquals(0, locals.get(0).getStartIndex()); assertEquals(endBci, locals.get(0).getEndIndex()); assertEquals("l0", locals.get(0).getName()); @@ -804,7 +804,7 @@ public void testIllegalOrDefault() { } - private void assertParseFailure(BytecodeParser parser) { + private void assertParseFailure(BytecodeParser parser) { assertThrows(IllegalArgumentException.class, () -> parseNode("invalid", parser)); } diff --git a/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/basic_interpreter/SourcesTest.java b/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/basic_interpreter/SourcesTest.java index e07ae46f877e..120f6bbb4c6d 100644 --- a/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/basic_interpreter/SourcesTest.java +++ b/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/basic_interpreter/SourcesTest.java @@ -124,8 +124,15 @@ public void testSource() { BytecodeNode bytecode = node.getBytecodeNode(); List instructions = bytecode.getInstructionsAsList(); - assertInstructionSourceSection(instructions.get(0), source, 7, 1); - assertInstructionSourceSection(instructions.get(1), source, 0, 8); + if (run.testTracer()) { + assertInstructionSourceSection(instructions.get(0), source, 7, 1); + assertInstructionSourceSection(instructions.get(1), source, 7, 1); + assertInstructionSourceSection(instructions.get(2), source, 0, 8); + assertInstructionSourceSection(instructions.get(3), source, 0, 8); + } else { + assertInstructionSourceSection(instructions.get(0), source, 7, 1); + assertInstructionSourceSection(instructions.get(1), source, 0, 8); + } assertSourceInformationTree(bytecode, est("return 1", est("1"))); @@ -303,7 +310,7 @@ public void testSourceUnavailable() { BytecodeNode bytecode = node.getBytecodeNode(); List instructions = bytecode.getInstructionsAsList(); assertInstructionSourceSection(instructions.get(0), source, 7, 1); - assertTrue(!instructions.get(1).getSourceSection().isAvailable()); + assertTrue(!instructions.get(run.testTracer() ? 3 : 1).getSourceSection().isAvailable()); assertSourceInformationTree(bytecode, ExpectedSourceTree.expectedSourceTreeUnavailable(est("1"))); } @@ -389,17 +396,42 @@ public void testSourceMultipleSources() { assertSourceSection(root.getSourceSection(), source1, 0, source1.getLength()); List instructions = root.getBytecodeNode().getInstructionsAsList(); - assertInstructionSourceSection(instructions.get(0), source1, 0, source1.getLength()); - assertInstructionSourceSection(instructions.get(1), source1, 0, source1.getLength()); - assertInstructionSourceSection(instructions.get(2), source1, 1, 2); - assertInstructionSourceSection(instructions.get(3), source1, 1, 2); - assertInstructionSourceSection(instructions.get(4), source2, 3, 4); - assertInstructionSourceSection(instructions.get(5), source2, 5, 1); - assertInstructionSourceSection(instructions.get(6), source2, 3, 4); - assertInstructionSourceSection(instructions.get(7), source1, 1, 2); - assertInstructionSourceSection(instructions.get(8), source1, 1, 2); - assertInstructionSourceSection(instructions.get(9), source1, 0, source1.getLength()); - assertInstructionSourceSection(instructions.get(10), source1, 0, source1.getLength()); + if (run.testTracer()) { + assertInstructionSourceSection(instructions.get(0), source1, 0, source1.getLength()); + assertInstructionSourceSection(instructions.get(1), source1, 0, source1.getLength()); + assertInstructionSourceSection(instructions.get(2), source1, 0, source1.getLength()); + assertInstructionSourceSection(instructions.get(3), source1, 0, source1.getLength()); + assertInstructionSourceSection(instructions.get(4), source1, 1, 2); + assertInstructionSourceSection(instructions.get(5), source1, 1, 2); + assertInstructionSourceSection(instructions.get(6), source1, 1, 2); + assertInstructionSourceSection(instructions.get(7), source1, 1, 2); + assertInstructionSourceSection(instructions.get(8), source2, 3, 4); + assertInstructionSourceSection(instructions.get(9), source2, 3, 4); + assertInstructionSourceSection(instructions.get(10), source2, 5, 1); + assertInstructionSourceSection(instructions.get(11), source2, 5, 1); + assertInstructionSourceSection(instructions.get(12), source2, 3, 4); + assertInstructionSourceSection(instructions.get(13), source2, 3, 4); + assertInstructionSourceSection(instructions.get(14), source1, 1, 2); + assertInstructionSourceSection(instructions.get(15), source1, 1, 2); + assertInstructionSourceSection(instructions.get(16), source1, 1, 2); + assertInstructionSourceSection(instructions.get(17), source1, 1, 2); + assertInstructionSourceSection(instructions.get(18), source1, 0, source1.getLength()); + assertInstructionSourceSection(instructions.get(19), source1, 0, source1.getLength()); + assertInstructionSourceSection(instructions.get(20), source1, 0, source1.getLength()); + assertInstructionSourceSection(instructions.get(21), source1, 0, source1.getLength()); + } else { + assertInstructionSourceSection(instructions.get(0), source1, 0, source1.getLength()); + assertInstructionSourceSection(instructions.get(1), source1, 0, source1.getLength()); + assertInstructionSourceSection(instructions.get(2), source1, 1, 2); + assertInstructionSourceSection(instructions.get(3), source1, 1, 2); + assertInstructionSourceSection(instructions.get(4), source2, 3, 4); + assertInstructionSourceSection(instructions.get(5), source2, 5, 1); + assertInstructionSourceSection(instructions.get(6), source2, 3, 4); + assertInstructionSourceSection(instructions.get(7), source1, 1, 2); + assertInstructionSourceSection(instructions.get(8), source1, 1, 2); + assertInstructionSourceSection(instructions.get(9), source1, 0, source1.getLength()); + assertInstructionSourceSection(instructions.get(10), source1, 0, source1.getLength()); + } assertSourceInformationTree(root.getBytecodeNode(), est("abc", est("bc", est("3456", est("5"))))); @@ -911,8 +943,17 @@ public void testSourceReparse() { BasicInterpreter node = nodes.getNode(0); assertSourceSection(node.getSourceSection(), source, 0, 8); List instructions = node.getBytecodeNode().getInstructionsAsList(); - assertInstructionSourceSection(instructions.get(0), source, 7, 1); - assertInstructionSourceSection(instructions.get(1), source, 0, 8); + + if (run.testTracer()) { + assertInstructionSourceSection(instructions.get(0), source, 7, 1); + assertInstructionSourceSection(instructions.get(1), source, 7, 1); + assertInstructionSourceSection(instructions.get(2), source, 0, 8); + assertInstructionSourceSection(instructions.get(3), source, 0, 8); + } else { + assertInstructionSourceSection(instructions.get(0), source, 7, 1); + assertInstructionSourceSection(instructions.get(1), source, 0, 8); + } + } @Test diff --git a/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/basic_interpreter/YieldTest.java b/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/basic_interpreter/YieldTest.java index 9b0a53c7195b..34c0e39a1eb4 100644 --- a/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/basic_interpreter/YieldTest.java +++ b/truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/basic_interpreter/YieldTest.java @@ -47,13 +47,13 @@ import org.junit.Test; import com.oracle.truffle.api.RootCallTarget; +import com.oracle.truffle.api.bytecode.BytecodeLocal; +import com.oracle.truffle.api.bytecode.BytecodeLocation; +import com.oracle.truffle.api.bytecode.BytecodeTier; import com.oracle.truffle.api.bytecode.ContinuationResult; import com.oracle.truffle.api.bytecode.ContinuationRootNode; import com.oracle.truffle.api.source.Source; import com.oracle.truffle.api.source.SourceSection; -import com.oracle.truffle.api.bytecode.BytecodeLocal; -import com.oracle.truffle.api.bytecode.BytecodeLocation; -import com.oracle.truffle.api.bytecode.BytecodeTier; public class YieldTest extends AbstractBasicInterpreterTest { @@ -243,7 +243,7 @@ public void testYieldFromFinally() { // } // @formatter:on - RootCallTarget root = parse("yieldFromFinally", b -> { + BasicInterpreter root = parseNode("yieldFromFinally", b -> { b.beginRoot(); b.beginTryFinally(() -> { @@ -269,7 +269,7 @@ public void testYieldFromFinally() { b.endRoot(); }); - ContinuationResult r1 = (ContinuationResult) root.call(); + ContinuationResult r1 = (ContinuationResult) root.getCallTarget().call(); assertEquals(1L, r1.getResult()); ContinuationResult r2 = (ContinuationResult) r1.continueWith(3L); diff --git a/truffle/src/com.oracle.truffle.api.bytecode/snapshot.sigtest b/truffle/src/com.oracle.truffle.api.bytecode/snapshot.sigtest index f953bbf828ad..e182a77bc20f 100644 --- a/truffle/src/com.oracle.truffle.api.bytecode/snapshot.sigtest +++ b/truffle/src/com.oracle.truffle.api.bytecode/snapshot.sigtest @@ -9,12 +9,12 @@ CLSS public final com.oracle.truffle.api.bytecode.BytecodeConfig fld public final static com.oracle.truffle.api.bytecode.BytecodeConfig COMPLETE fld public final static com.oracle.truffle.api.bytecode.BytecodeConfig DEFAULT fld public final static com.oracle.truffle.api.bytecode.BytecodeConfig WITH_SOURCE -innr public static Builder +innr public final static Builder meth public static com.oracle.truffle.api.bytecode.BytecodeConfig$Builder newBuilder(com.oracle.truffle.api.bytecode.BytecodeConfigEncoder) supr java.lang.Object hfds SOURCE_ENCODING,encoder,encoding -CLSS public static com.oracle.truffle.api.bytecode.BytecodeConfig$Builder +CLSS public final static com.oracle.truffle.api.bytecode.BytecodeConfig$Builder outer com.oracle.truffle.api.bytecode.BytecodeConfig meth public com.oracle.truffle.api.bytecode.BytecodeConfig build() meth public com.oracle.truffle.api.bytecode.BytecodeConfig$Builder addInstrumentation(java.lang.Class) @@ -43,6 +43,29 @@ meth public final static com.oracle.truffle.api.bytecode.BytecodeDSLAccess looku supr java.lang.Object hfds safeSingleton,unsafeSingleton +CLSS public abstract com.oracle.truffle.api.bytecode.BytecodeDescriptor<%0 extends com.oracle.truffle.api.nodes.RootNode & com.oracle.truffle.api.bytecode.BytecodeRootNode, %1 extends com.oracle.truffle.api.TruffleLanguage, %2 extends com.oracle.truffle.api.bytecode.BytecodeBuilder> +cons protected init(java.lang.Object) +meth protected final long withGlobalConfig(com.oracle.truffle.api.TruffleLanguage,long) +meth protected final void onPrepareForLoad(com.oracle.truffle.api.TruffleLanguage,{com.oracle.truffle.api.bytecode.BytecodeDescriptor%0}) +meth public abstract com.oracle.truffle.api.bytecode.BytecodeConfig$Builder newConfigBuilder() +meth public abstract com.oracle.truffle.api.bytecode.BytecodeRootNodes<{com.oracle.truffle.api.bytecode.BytecodeDescriptor%0}> create({com.oracle.truffle.api.bytecode.BytecodeDescriptor%1},com.oracle.truffle.api.bytecode.BytecodeConfig,com.oracle.truffle.api.bytecode.BytecodeParser<{com.oracle.truffle.api.bytecode.BytecodeDescriptor%2}>) +meth public abstract com.oracle.truffle.api.bytecode.InstructionDescriptor getInstructionDescriptor(int) +meth public abstract java.lang.Class getGeneratedClass() +meth public abstract java.lang.Class<{com.oracle.truffle.api.bytecode.BytecodeDescriptor%0}> getSpecificationClass() +meth public abstract java.lang.Class<{com.oracle.truffle.api.bytecode.BytecodeDescriptor%1}> getLanguageClass() +meth public abstract java.util.List getInstructionDescriptors() +meth public abstract {com.oracle.truffle.api.bytecode.BytecodeDescriptor%0} cast(com.oracle.truffle.api.nodes.RootNode) +meth public com.oracle.truffle.api.bytecode.BytecodeRootNodes<{com.oracle.truffle.api.bytecode.BytecodeDescriptor%0}> deserialize({com.oracle.truffle.api.bytecode.BytecodeDescriptor%1},com.oracle.truffle.api.bytecode.BytecodeConfig,java.util.function.Supplier,com.oracle.truffle.api.bytecode.serialization.BytecodeDeserializer) throws java.io.IOException +meth public final {com.oracle.truffle.api.bytecode.BytecodeDescriptor%0} cast(com.oracle.truffle.api.CallTarget) +meth public java.lang.String dump() +meth public java.lang.String toString() +meth public void addInstructionTracer({com.oracle.truffle.api.bytecode.BytecodeDescriptor%1},com.oracle.truffle.api.bytecode.InstructionTracer) +meth public void removeInstructionTracer({com.oracle.truffle.api.bytecode.BytecodeDescriptor%1},com.oracle.truffle.api.bytecode.InstructionTracer) +meth public void serialize(java.io.DataOutput,com.oracle.truffle.api.bytecode.serialization.BytecodeSerializer,com.oracle.truffle.api.bytecode.BytecodeParser<{com.oracle.truffle.api.bytecode.BytecodeDescriptor%2}>) throws java.io.IOException +meth public void update({com.oracle.truffle.api.bytecode.BytecodeDescriptor%1},com.oracle.truffle.api.bytecode.BytecodeConfig) +supr java.lang.Object +hfds descriptorLookupEnabled,engineDescriptorLookupEnabled + CLSS public final com.oracle.truffle.api.bytecode.BytecodeEncodingException meth public static com.oracle.truffle.api.bytecode.BytecodeEncodingException create(java.lang.String) supr java.lang.RuntimeException @@ -171,6 +194,7 @@ fld protected final static java.lang.Object TOKEN fld protected {com.oracle.truffle.api.bytecode.BytecodeRootNodes%0}[] nodes meth protected abstract boolean updateImpl(com.oracle.truffle.api.bytecode.BytecodeConfigEncoder,long) meth protected final com.oracle.truffle.api.bytecode.BytecodeParser getParser() +meth protected void updateGlobalInstructionTracers(com.oracle.truffle.api.bytecode.InstructionTracer[]) meth public final boolean ensureComplete() meth public final boolean ensureSourceInformation() meth public final boolean update(com.oracle.truffle.api.bytecode.BytecodeConfig) @@ -178,6 +202,8 @@ meth public final int count() meth public final java.util.List<{com.oracle.truffle.api.bytecode.BytecodeRootNodes%0}> getNodes() meth public final {com.oracle.truffle.api.bytecode.BytecodeRootNodes%0} getNode(int) meth public java.lang.String toString() +meth public void addInstructionTracer(com.oracle.truffle.api.bytecode.InstructionTracer) +meth public void removeInstructionTracer(com.oracle.truffle.api.bytecode.InstructionTracer) meth public void serialize(java.io.DataOutput,com.oracle.truffle.api.bytecode.serialization.BytecodeSerializer) throws java.io.IOException supr java.lang.Object hfds parser @@ -308,6 +334,7 @@ meth public abstract !hasdefault boolean additionalAssertions() meth public abstract !hasdefault boolean allowUnsafe() meth public abstract !hasdefault boolean enableBlockScoping() meth public abstract !hasdefault boolean enableBytecodeDebugListener() +meth public abstract !hasdefault boolean enableInstructionTracing() meth public abstract !hasdefault boolean enableMaterializedLocalAccesses() meth public abstract !hasdefault boolean enableQuickening() meth public abstract !hasdefault boolean enableRootBodyTagging() @@ -346,13 +373,11 @@ CLSS public abstract com.oracle.truffle.api.bytecode.Instruction cons protected init(java.lang.Object) innr public abstract static Argument meth protected abstract com.oracle.truffle.api.bytecode.Instruction next() -meth public abstract boolean isInstrumentation() meth public abstract com.oracle.truffle.api.bytecode.BytecodeNode getBytecodeNode() meth public abstract int getBytecodeIndex() -meth public abstract int getLength() -meth public abstract int getOperationCode() -meth public abstract java.lang.String getName() meth public abstract java.util.List getArguments() +meth public boolean isInstrumentation() +meth public com.oracle.truffle.api.bytecode.InstructionDescriptor getDescriptor() meth public final boolean equals(java.lang.Object) meth public final com.oracle.truffle.api.bytecode.BytecodeLocation getLocation() meth public final com.oracle.truffle.api.source.SourceSection getSourceSection() @@ -360,6 +385,9 @@ meth public final com.oracle.truffle.api.source.SourceSection[] getSourceSection meth public final int getNextBytecodeIndex() meth public final int hashCode() meth public final java.lang.String toString() +meth public int getLength() +meth public int getOperationCode() +meth public java.lang.String getName() supr java.lang.Object hcls InstructionIterable,InstructionIterator @@ -369,9 +397,9 @@ cons protected init(java.lang.Object) innr public final static !enum Kind innr public final static BranchProfile meth protected java.util.List getSpecializationInfoInternal() -meth public abstract com.oracle.truffle.api.bytecode.Instruction$Argument$Kind getKind() -meth public abstract java.lang.String getName() meth public com.oracle.truffle.api.bytecode.Instruction$Argument$BranchProfile asBranchProfile() +meth public com.oracle.truffle.api.bytecode.Instruction$Argument$Kind getKind() +meth public com.oracle.truffle.api.bytecode.InstructionDescriptor$ArgumentDescriptor getDescriptor() meth public com.oracle.truffle.api.bytecode.TagTreeNode asTagNode() meth public com.oracle.truffle.api.nodes.Node asCachedNode() meth public final java.lang.String toString() @@ -381,6 +409,7 @@ meth public int asInteger() meth public int asLocalIndex() meth public int asLocalOffset() meth public java.lang.Object asConstant() +meth public java.lang.String getName() supr java.lang.Object CLSS public final static com.oracle.truffle.api.bytecode.Instruction$Argument$BranchProfile @@ -410,6 +439,40 @@ meth public static com.oracle.truffle.api.bytecode.Instruction$Argument$Kind val meth public static com.oracle.truffle.api.bytecode.Instruction$Argument$Kind[] values() supr java.lang.Enum +CLSS public abstract com.oracle.truffle.api.bytecode.InstructionDescriptor +cons protected init(java.lang.Object) +innr public abstract static ArgumentDescriptor +meth public abstract boolean isInstrumentation() +meth public abstract int getLength() +meth public abstract int getOperationCode() +meth public abstract java.lang.String getName() +meth public abstract java.util.List getArgumentDescriptors() +meth public boolean equals(java.lang.Object) +meth public int hashCode() +meth public java.lang.String toString() +supr java.lang.Object + +CLSS public abstract static com.oracle.truffle.api.bytecode.InstructionDescriptor$ArgumentDescriptor + outer com.oracle.truffle.api.bytecode.InstructionDescriptor +cons protected init(java.lang.Object) +meth public abstract com.oracle.truffle.api.bytecode.Instruction$Argument$Kind getKind() +meth public abstract int getLength() +meth public abstract java.lang.String getName() +meth public java.lang.String toString() +supr java.lang.Object + +CLSS public abstract interface com.oracle.truffle.api.bytecode.InstructionTracer +innr public abstract static InstructionAccess +meth public abstract void onInstructionEnter(com.oracle.truffle.api.bytecode.InstructionTracer$InstructionAccess,com.oracle.truffle.api.bytecode.BytecodeNode,int,com.oracle.truffle.api.frame.Frame) +meth public com.oracle.truffle.api.bytecode.BytecodeDescriptor getExclusiveBytecodeDescriptor() + +CLSS public abstract static com.oracle.truffle.api.bytecode.InstructionTracer$InstructionAccess + outer com.oracle.truffle.api.bytecode.InstructionTracer +cons protected init(java.lang.Object) +meth public abstract com.oracle.truffle.api.bytecode.Instruction getTracedInstruction(com.oracle.truffle.api.bytecode.BytecodeNode,int) +meth public abstract int getTracedOperationCode(com.oracle.truffle.api.bytecode.BytecodeNode,int) +supr java.lang.Object + CLSS public abstract interface !annotation com.oracle.truffle.api.bytecode.Instrumentation anno 0 java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy value=SOURCE) anno 0 java.lang.annotation.Target(java.lang.annotation.ElementType[] value=[TYPE]) @@ -680,6 +743,65 @@ meth public void onQuicken(com.oracle.truffle.api.bytecode.Instruction,com.oracl meth public void onQuickenOperand(com.oracle.truffle.api.bytecode.Instruction,int,com.oracle.truffle.api.bytecode.Instruction,com.oracle.truffle.api.bytecode.Instruction) meth public void onSpecialize(com.oracle.truffle.api.bytecode.Instruction,java.lang.String) +CLSS public final com.oracle.truffle.api.bytecode.debug.HistogramInstructionTracer +innr public abstract interface static GroupClause +innr public final static Builder +innr public final static Histogram +intf com.oracle.truffle.api.bytecode.InstructionTracer +meth public com.oracle.truffle.api.bytecode.BytecodeDescriptor getExclusiveBytecodeDescriptor() +meth public com.oracle.truffle.api.bytecode.debug.HistogramInstructionTracer$Histogram getHistogram() +meth public com.oracle.truffle.api.bytecode.debug.HistogramInstructionTracer$Histogram getHistogramAndReset() +meth public java.lang.String dumpHistogram() +meth public static com.oracle.truffle.api.bytecode.debug.HistogramInstructionTracer$Builder newBuilder() +meth public void onInstructionEnter(com.oracle.truffle.api.bytecode.InstructionTracer$InstructionAccess,com.oracle.truffle.api.bytecode.BytecodeNode,int,com.oracle.truffle.api.frame.Frame) +meth public void printHistogram(java.io.PrintStream) +meth public void reset() +supr java.lang.Object +hfds cache,counters,descriptor,filterClause,groupClauses,rootCounters +hcls Counters,LRUCache + +CLSS public final static com.oracle.truffle.api.bytecode.debug.HistogramInstructionTracer$Builder + outer com.oracle.truffle.api.bytecode.debug.HistogramInstructionTracer +meth public com.oracle.truffle.api.bytecode.debug.HistogramInstructionTracer build(com.oracle.truffle.api.bytecode.BytecodeDescriptor) +meth public com.oracle.truffle.api.bytecode.debug.HistogramInstructionTracer$Builder filter(java.util.function.Predicate) +meth public com.oracle.truffle.api.bytecode.debug.HistogramInstructionTracer$Builder groupBy(com.oracle.truffle.api.bytecode.debug.HistogramInstructionTracer$GroupClause) +supr java.lang.Object +hfds filter,groups + +CLSS public abstract interface static com.oracle.truffle.api.bytecode.debug.HistogramInstructionTracer$GroupClause + outer com.oracle.truffle.api.bytecode.debug.HistogramInstructionTracer + anno 0 java.lang.FunctionalInterface() +meth public abstract java.lang.Object group(com.oracle.truffle.api.bytecode.BytecodeNode,java.lang.Thread,int) + +CLSS public final static com.oracle.truffle.api.bytecode.debug.HistogramInstructionTracer$Histogram + outer com.oracle.truffle.api.bytecode.debug.HistogramInstructionTracer +meth public java.lang.String dump() +meth public java.util.Map getCounters() +meth public java.util.Map getStatistics() +meth public java.util.Map getGroups() +meth public long getInstructionsExecuted() +meth public void print(java.io.PrintStream) +supr java.lang.Object +hfds data,descriptor,instructionsExecuted,subGroups + +CLSS public final com.oracle.truffle.api.bytecode.debug.PrintInstructionTracer +innr public final static Builder +intf com.oracle.truffle.api.bytecode.InstructionTracer +meth public static com.oracle.truffle.api.bytecode.debug.PrintInstructionTracer$Builder newBuilder(java.io.PrintStream) +meth public static com.oracle.truffle.api.bytecode.debug.PrintInstructionTracer$Builder newBuilder(java.util.function.Consumer) +meth public void onInstructionEnter(com.oracle.truffle.api.bytecode.InstructionTracer$InstructionAccess,com.oracle.truffle.api.bytecode.BytecodeNode,int,com.oracle.truffle.api.frame.Frame) +meth public void reset() +supr java.lang.Object +hfds cache,executedInstructions,filter,out +hcls LRUCache + +CLSS public final static com.oracle.truffle.api.bytecode.debug.PrintInstructionTracer$Builder + outer com.oracle.truffle.api.bytecode.debug.PrintInstructionTracer +meth public com.oracle.truffle.api.bytecode.debug.PrintInstructionTracer build() +meth public com.oracle.truffle.api.bytecode.debug.PrintInstructionTracer$Builder filter(java.util.function.Predicate) +supr java.lang.Object +hfds filter,out + CLSS public abstract interface com.oracle.truffle.api.bytecode.serialization.BytecodeDeserializer anno 0 java.lang.FunctionalInterface() innr public abstract interface static DeserializerContext @@ -1004,8 +1126,3 @@ meth public abstract java.lang.annotation.ElementType[] value() CLSS public abstract interface java.lang.constant.Constable meth public abstract java.util.Optional describeConstable() -CLSS public abstract interface !annotation jdk.internal.vm.annotation.AOTSafeClassInitializer - anno 0 java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy value=RUNTIME) - anno 0 java.lang.annotation.Target(java.lang.annotation.ElementType[] value=[TYPE]) -intf java.lang.annotation.Annotation - diff --git a/truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/BytecodeAccessor.java b/truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/BytecodeAccessor.java index 2058f4daf2a6..cdad61c240d5 100644 --- a/truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/BytecodeAccessor.java +++ b/truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/BytecodeAccessor.java @@ -40,6 +40,9 @@ */ package com.oracle.truffle.api.bytecode; +import java.util.List; +import java.util.function.Function; + import com.oracle.truffle.api.impl.Accessor; final class BytecodeAccessor extends Accessor { @@ -48,8 +51,26 @@ final class BytecodeAccessor extends Accessor { static final MemorySupport MEMORY = ACCESSOR.memorySupport(); static final RuntimeSupport RUNTIME = ACCESSOR.runtimeSupport(); + static final EngineSupport ENGINE = ACCESSOR.engineSupport(); + static final LanguageSupport LANGUAGE = ACCESSOR.languageSupport(); private BytecodeAccessor() { } + static class BytecodeSupportImpl extends BytecodeSupport { + + @SuppressWarnings("unchecked") + @Override + public void registerInstructionTracerFactory(Object hostLanguage, Function tracerFactory) { + BytecodeEngineData.get(hostLanguage).addEngineTracerFactory((Function, InstructionTracer>) tracerFactory); + } + + @SuppressWarnings("unchecked") + @Override + public List getEngineInstructionTracers(Object hostLanguage, Function tracerFactory) { + return (List) BytecodeEngineData.get(hostLanguage).getEngineInstructionTracers((Function, InstructionTracer>) tracerFactory); + } + + } + } diff --git a/truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/BytecodeConfig.java b/truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/BytecodeConfig.java index 51aa40388514..26f8b995e0e4 100644 --- a/truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/BytecodeConfig.java +++ b/truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/BytecodeConfig.java @@ -108,7 +108,7 @@ public static Builder newBuilder(BytecodeConfigEncoder encoder) { * * @since 24.2 */ - public static class Builder { + public static final class Builder { private final BytecodeConfigEncoder encoder; private long encoding; @@ -150,12 +150,27 @@ public Builder addTag(Class tag) { public Builder addInstrumentation(Class instrumentation) { CompilerAsserts.neverPartOfCompilation(); Objects.requireNonNull(instrumentation); + if (instrumentation == InstructionTracer.class) { + throw new IllegalArgumentException( + "Enabling the instruction tracer with addInstrumentation is not supported. Add an instruction tracer instead using BytecodeRootNodes.addInstructionTracer(...)."); + } long encodedTag = encoder.encodeInstrumentation(instrumentation); assert encodedTag != SOURCE_ENCODING && Long.bitCount(encodedTag) == 1 : "generated code invariant violated"; this.encoding |= encodedTag; return this; } + /** + * Internal way to attach instruction tracer instrumentation. Encoding the instrumentation + * tag does nothing if tracing is enabled for this interpreter. + */ + Builder addInstructionTracing() { + CompilerAsserts.neverPartOfCompilation(); + long encodedTag = encoder.encodeInstrumentation(InstructionTracer.class); + this.encoding |= encodedTag; + return this; + } + /** * Builds the config. * diff --git a/truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/BytecodeDescriptor.java b/truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/BytecodeDescriptor.java new file mode 100644 index 000000000000..875760522d0d --- /dev/null +++ b/truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/BytecodeDescriptor.java @@ -0,0 +1,506 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.oracle.truffle.api.bytecode; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.util.IntSummaryStatistics; +import java.util.List; +import java.util.Objects; +import java.util.function.Function; +import java.util.function.Supplier; + +import com.oracle.truffle.api.CallTarget; +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; +import com.oracle.truffle.api.RootCallTarget; +import com.oracle.truffle.api.TruffleLanguage; +import com.oracle.truffle.api.bytecode.BytecodeEngineData.DescriptorData; +import com.oracle.truffle.api.bytecode.serialization.BytecodeDeserializer; +import com.oracle.truffle.api.bytecode.serialization.BytecodeSerializer; +import com.oracle.truffle.api.nodes.RootNode; + +/** + * Describes the bytecode interpreter generated for a specific {@link BytecodeRootNode} class and + * provides the management API for that interpreter. + *

+ * A generated bytecode interpreter exposes a public static {@code BYTECODE} field of the generated + * {@code *Gen.Bytecode} class. It is recommended to not refer to the {@link BytecodeDescriptor} + * directly and instead use the generated {@code *Gen.Bytecode} class to avoid the rather verbose + * generic type. That field is a concrete subclass of {@link BytecodeDescriptor}, and acts as the + * central entry point for: + * + *

    + *
  • Creating executable bytecode: + * {@link #create(TruffleLanguage, BytecodeConfig, BytecodeParser)} builds one or more + * {@link BytecodeRootNode} instances by driving a {@link BytecodeBuilder}. + * + *
  • Serialization support: {@link #serialize} and {@link #deserialize} define + * the contract for turning parsed bytecode into a byte stream and restoring it. Generated + * interpreters that were built with {@code enableSerialization=true} implement these methods. The + * default implementation throws {@link UnsupportedOperationException}. + * + *
  • Describing the instruction set: {@link #getInstructionDescriptors()} and + * {@link #getInstructionDescriptor(int)} expose all instructions, including normal language + * operations and injected instrumentation instructions. The descriptor can also produce a human + * readable dump via {@link #dump()}. + * + *
  • Per-language runtime configuration: + * {@link #update(TruffleLanguage, BytecodeConfig)} applies configuration (for example enabling + * instrumentations) to all bytecode roots that were already created for the given language, and to + * any future roots.
  • + * + *
  • Instruction tracing: + * {@link #addInstructionTracer(TruffleLanguage, InstructionTracer)} and + * {@link #removeInstructionTracer(TruffleLanguage, InstructionTracer)} install and remove + * {@link InstructionTracer}s for all roots created for a given language. After a tracer is + * installed, the interpreter automatically injects tracing instructions (for example + * {@code trace.instruction}) and invokes the tracer callback on every executed instruction. + *
+ * + * The descriptor also exposes metadata about the interpreter itself: {@link #getGeneratedClass()} + * returns the generated root node class that implements execution, and + * {@link #getSpecificationClass()} identifies the original user-declared {@code @GenerateBytecode} + * root node (when available). {@link #getLanguageClass()} identifies the owning + * {@link TruffleLanguage}. + *

+ * In short, {@code BytecodeDescriptor} is both: (1) a static description of the generated + * bytecode interpreter (instruction set, generated classes, language binding), and (2) the + * runtime control surface for all bytecode roots created from that interpreter in a given language + * context (creation, tracing, instrumentation, serialization). + *

+ *

Usage example

+ * + *
{@code
+ * // Assume you have a language MyLanguage and a bytecode root node
+ * // declared as:
+ * //
+ * //   @GenerateBytecode(languageClass = MyLanguage.class)
+ * //   abstract class MyBytecodeRootNode extends RootNode implements BytecodeRootNode { ... }
+ * //
+ * // The annotation processor generates MyBytecodeRootNodeGen and a nested
+ * // MyBytecodeRootNodeGen.Bytecode class with a public static BYTECODE field:
+ * //
+ * //   public static final MyBytecodeRootNodeGen.Bytecode BYTECODE;
+ * //
+ * // The BYTECODE field is a concrete BytecodeDescriptor.
+ *
+ * MyLanguage language = ...; // instance of the Truffle language
+ *
+ * // Create a new bytecode root node by emitting bytecode through the builder.
+ * // The parser lambda drives the builder to generate the bytecode body.
+ * MyBytecodeRootNode root = MyBytecodeRootNodeGen.BYTECODE
+ *     .create(language, BytecodeConfig.DEFAULT, (builder) -> {
+ *         builder.beginRoot();
+ *         builder.beginReturn();
+ *         builder.emitLoadArgument(0);   // return the first argument
+ *         builder.endReturn();
+ *         builder.endRoot();
+ *     })
+ *     .getNode(0); // obtain the first created root node
+ *
+ * // Execute the generated root node through its call target.
+ * Object result = root.getCallTarget().call(42);
+ * }
+ * + * @param the {@link #getSpecificationClass() specification class}, which is both a + * {@link RootNode} and a {@link BytecodeRootNode} + * @param the Truffle language type + * @param the builder type used to emit bytecode for this interpreter + * @since 25.1 + */ +public abstract class BytecodeDescriptor, B extends BytecodeBuilder> { + + private static volatile boolean engineDescriptorLookupEnabled; + private volatile boolean descriptorLookupEnabled; + + protected BytecodeDescriptor(Object token) { + BytecodeRootNodes.checkToken(token); + } + + /** + * Returns the specification class for this descriptor. This is the user defined + * {@link RootNode} subclass that was annotated to generate this bytecode interpreter. + * + * @return the specification class, never null. + * @since 25.1 + */ + public abstract Class getSpecificationClass(); + + /** + * Returns the generated root node class that implements the bytecode interpreter. This is the + * concrete {@link RootNode} subclass created by the bytecode generator. + * + * @return the generated root node class, never null + * @since 25.1 + */ + public abstract Class getGeneratedClass(); + + /** + * Returns the {@link TruffleLanguage} class that this descriptor is associated with, never + * null. + * + * @see GenerateBytecode#languageClass() + * @since 25.1 + */ + public abstract Class getLanguageClass(); + + /** + * Returns the {@link InstructionDescriptor} for the given operation code, or null + * if the code is not defined by this interpreter. + * + * @param operationCode the numeric operation code of the instruction + * @see InstructionDescriptor#getOperationCode() + * @see Instruction#getOperationCode() + * @since 25.1 + */ + public abstract InstructionDescriptor getInstructionDescriptor(int operationCode); + + /** + * Returns an immutable list of all instruction descriptors known to this interpreter. The list + * describes every instruction kind that can be executed by nodes generated from this + * interpreter. + * + * @since 25.1 + */ + public abstract List getInstructionDescriptors(); + + /** + * Casts a {@link RootNode} to the specification root node type {@code R}. This also handles + * {@link ContinuationRootNode} if yield is enabled. Returns null if the given root + * is not compatible with this descriptor. + * + * @param rootNode a root node, must not be null + * @since 25.1 + */ + public abstract R cast(RootNode rootNode); + + /** + * Casts a {@link CallTarget} to the generated root node type {@code R}. Returns + * null if the given root is not compatible with this descriptor. + * + * @param target a call target, must not be null + * @since 25.1 + */ + public final R cast(CallTarget target) { + Objects.requireNonNull(target); + if (target instanceof RootCallTarget r) { + return cast(r.getRootNode()); + } + return null; + } + + /** + * Creates a new {@link BytecodeConfig.Builder} initialized with defaults that are valid for + * this descriptor. + * + * @return a new mutable configuration builder + * @since 25.1 + */ + public abstract BytecodeConfig.Builder newConfigBuilder(); + + /** + * Creates one or more bytecode root nodes. This is the entrypoint for creating new {@link R} + * instances. + *

+ * The {@code parser} is invoked to emit builder calls on a {@link BytecodeBuilder} which is + * then materialized into executable bytecode. The returned {@link BytecodeRootNodes} contains + * the created root nodes, for example entry points and helper nodes. + * + * @param language the language instance + * @param config the bytecode configuration, for example whether to include source information + * @param parser the parser that drives the {@link BytecodeBuilder} + * @return the created bytecode root nodes + * @since 25.1 + */ + public abstract BytecodeRootNodes create(L language, BytecodeConfig config, BytecodeParser parser); + + /** + * Serializes the bytecode nodes parsed by the {@code parser}. All metadata (e.g., source info) + * is serialized (even if it has not yet been parsed). + *

+ * Unlike the {@link BytecodeRootNodes#serialize} instance method, which replays builder calls + * that were already validated during the original bytecode parse, this method does + * not validate the builder calls performed by the {@code parser}. Validation + * will happen (as usual) when the bytes are deserialized. + *

+ * Additionally, this method cannot serialize field values that get set outside of the parser, + * unlike the {@link BytecodeRootNodes#serialize} instance method, which has access to the + * instances being serialized. + * + * @param buffer the buffer to write the byte output to. + * @param callback the language-specific serializer for constants in the bytecode. + * @param parser the parser. + * @since 25.1 + */ + @SuppressWarnings("unused") + @TruffleBoundary + public void serialize(DataOutput buffer, BytecodeSerializer callback, BytecodeParser parser) throws IOException { + throw new UnsupportedOperationException("The generated bytecode interpreter does not support serialization. Enable serialization using @GenerateBytecode(enableSerialization=true)."); + } + + /** + * Deserializes a byte sequence to bytecode nodes. The bytes must have been produced by a + * previous call to {@link #serialize}. + * + * @param language the language instance. + * @param config indicates whether to deserialize metadata (e.g., source information). + * @param input A function that supplies the bytes to deserialize. This supplier must produce a + * new {@link DataInput} each time, since the bytes may be processed multiple times + * for reparsing. + * @param callback The language-specific deserializer for constants in the bytecode. This + * callback must perform the inverse of the callback that was used to + * {@link #serialize} the nodes to bytes. + * @since 25.1 + */ + @SuppressWarnings("unused") + @TruffleBoundary + public BytecodeRootNodes deserialize(L language, BytecodeConfig config, Supplier input, BytecodeDeserializer callback) throws IOException { + throw new UnsupportedOperationException("The generated bytecode interpreter does not support serialization. Enable serialization using @GenerateBytecode(enableSerialization=true)."); + } + + /** + * Registers an {@link InstructionTracer} for all bytecode root nodes created from this + * descriptor in the given language. + *

+ * After a tracer is installed, all existing and future {@link BytecodeRootNode} instances + * associated with this descriptor and {@code language} will start invoking the tracer's + * callback before executing each instruction. + *

+ * Installing a tracer is intentionally invasive and may be expensive: + *

    + *
  • All affected bytecode roots may need to be invalidated and re-parsed so that tracing + * hooks (for example {@code trace.instruction}) can be inserted.
  • + *
  • The change applies process-wide to that language instance, not just to a single root + * node.
  • + *
+ * Tracing itself runs on the language execution thread and may be called at very high + * frequency. + *

+ * If this bytecode interpreter was generated without instruction tracing support (for example + * using {@code @GenerateBytecode(enableInstructionTracing = false)}), this method throws + * {@link UnsupportedOperationException}. + * + * @param language the language instance whose bytecode roots should start reporting + * @param tracer the tracer to install + * @throws IllegalArgumentException if {@code tracer} is exclusive to a different + * {@link BytecodeDescriptor} + * @throws UnsupportedOperationException if instruction tracing is not enabled for this + * descriptor + * @since 25.1 + */ + public void addInstructionTracer(L language, InstructionTracer tracer) { + Objects.requireNonNull(language); + Objects.requireNonNull(tracer); + getDescriptorData(language).addInstructionTracer(tracer); + } + + /** + * Unregisters a previously installed {@link InstructionTracer} for the given language. + *

+ * After removal, bytecode roots created from this descriptor for {@code language} will stop + * invoking the tracer's callback. Future roots created for the same language will also not + * attach the tracer. The {@code tracer} argument must be the same tracer instance that was + * passed to {@link #addInstructionTracer(TruffleLanguage, InstructionTracer)}. + *

+ * Note that currently when all instruction tracers were removed, the trace instructions remain + * in the bytecode. This may be improved in the future. + *

+ * If this bytecode interpreter was generated without instruction tracing support (for example + * using {@code @GenerateBytecode(enableInstructionTracing = false)}), this method throws + * {@link UnsupportedOperationException}. + * + * @param language the language instance whose bytecode roots should stop reporting + * @param tracer the tracer to remove + * @throws IllegalArgumentException if {@code tracer} is exclusive to a different + * {@link BytecodeDescriptor} + * @throws UnsupportedOperationException if instruction tracing is not enabled for this + * descriptor + * @since 25.1 + */ + public void removeInstructionTracer(L language, InstructionTracer tracer) { + Objects.requireNonNull(language); + Objects.requireNonNull(tracer); + getDescriptorData(language).removeInstructionTracer(tracer); + } + + /** + * Updates the bytecode configuration for all bytecode root nodes that were created by this + * descriptor for the given language instance. + *

+ * This can be used to enable features such as source information or instrumentation globally. + * Options are applied cumulatively and cannot be reverted for that language instance. + * + * @param language the language instance + * @param config the configuration to apply + * @since 25.1 + */ + public void update(L language, BytecodeConfig config) { + BytecodeEngineData.get(language).getDescriptor(this).updateConfig(config); + } + + /** + * Returns a short string representation for debugging. + * + * @return a concise descriptor string + * @since 25.1 + */ + @Override + public String toString() { + return "BytecodeDescriptor[" + getGeneratedClass().getSimpleName() + "]"; + } + + /** + * Returns a human readable dump of this bytecode descriptor. + *

+ * The dump includes the instruction set, statistics such as encoded size, and formatted + * instruction listings. This is intended for diagnostics and test assertions, not for + * programmatic parsing. + * + * @return a multi line string describing the bytecode descriptor + * @since 25.1 + */ + public String dump() { + List descriptors = getInstructionDescriptors(); + int maxLabelSize = Math.min(80, descriptors.stream().mapToInt((i) -> InstructionDescriptor.formatLabel(i).length()).max().orElse(0)); + String instructionsDump = formatList(descriptors, + (i) -> InstructionDescriptor.format(descriptors.indexOf(i), i, maxLabelSize)); + + IntSummaryStatistics stats = descriptors.stream().mapToInt((i) -> i.getLength()).summaryStatistics(); + String header = String.format("%s(root=%s)[", + getClass().getSimpleName(), + getSpecificationClass() == null ? "null" : getSpecificationClass().getSimpleName()); + + StringBuilder b = new StringBuilder(); + b.append(header); + b.append("\n instructions(" + formatStatistics(stats) + ") = " + instructionsDump); + b.append("\n]"); + return b.toString(); + } + + private static String formatStatistics(IntSummaryStatistics stats) { + return String.format( + "count=%d, sum=%db, min=%db, average=%fb, max=%db", + stats.getCount(), + stats.getSum(), + stats.getMin(), + stats.getAverage(), + stats.getMax()); + } + + private static String formatList(List list, Function toString) { + if (list == null) { + return "Not Available"; + } else if (list.isEmpty()) { + return "Empty"; + } + StringBuilder b = new StringBuilder(); + for (T o : list) { + b.append("\n "); + String v; + try { + v = toString.apply(o); + } catch (Throwable t) { + v = t.toString(); + } + b.append(v); + } + return b.toString(); + } + + /** + * Internal method called by generated code on root node load. + * + * @since 25.1 + */ + protected final void onPrepareForLoad(TruffleLanguage language, R rootNode) { + if (isDescriptorLookupEnabled(language)) { + /* + * Fast-path optimization. We want to avoid looking up the engine data if not needed. + * Typically only needed for tracing and other debug features. + */ + getDescriptorData(language).onPrepareForCall(rootNode); + } + } + + /** + * Internal method called by generated code whenever the a {@link BytecodeConfig} is used. + * + * @since 25.1 + */ + protected final long withGlobalConfig(TruffleLanguage language, long config) { + if (isDescriptorLookupEnabled(language)) { + /* + * Fast-path optimization. We want to avoid looking up the engine data if not needed. + * Typically only needed for tracing and other debug features. + */ + return getDescriptorData(language).updateBytecodeConfig(config); + } + return config; + } + + private DescriptorData getDescriptorData(TruffleLanguage language) { + return BytecodeEngineData.get(language).getDescriptor(this); + } + + private boolean isDescriptorLookupEnabled(TruffleLanguage language) { + return language != null && (descriptorLookupEnabled || engineDescriptorLookupEnabled); + } + + /** + * We only create BytecodeEngineData if any tracer factories are installed. So we need to enable + * it here such that the BytecodeEngineData gets created so engine instruction tracers are + * actually created. + */ + static final void enableEngineDescriptorLookup() { + engineDescriptorLookupEnabled = true; + } + + /** + * The descriptor lookup may be enabled the first time instruction tracers are added to the + * language using {@link #addInstructionTracer(TruffleLanguage, InstructionTracer)} or global + * configuration updates are performed with {@link #update(TruffleLanguage, BytecodeConfig)}. + */ + final void enableDescriptorLookup() { + descriptorLookupEnabled = true; + } + +} diff --git a/truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/BytecodeEngineData.java b/truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/BytecodeEngineData.java new file mode 100644 index 000000000000..3c9269e358ec --- /dev/null +++ b/truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/BytecodeEngineData.java @@ -0,0 +1,273 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.oracle.truffle.api.bytecode; + +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Consumer; +import java.util.function.Function; + +import com.oracle.truffle.api.TruffleLanguage; + +/** + * Sharing layer specific data for bytecode DSL interpreters. + *

+ * Note we do not create the engine data per sharing layer and not per polyglot engine in order to + * support features like {@link BytecodeDescriptor#update(TruffleLanguage, BytecodeConfig)} in the + * language. Bytecode updates must never occur across multiple sharing layers. It is a bit awkward + * for engine data like engineTracers which may print data for each sharing layer. + */ +final class BytecodeEngineData { + + private final Object sharingLayer; // PolyglotSharingLayer + private final ConcurrentHashMap, DescriptorData> descriptorData = new ConcurrentHashMap<>(); + private volatile Map, InstructionTracer>, Map, InstructionTracer>> engineTracerFactories; + + BytecodeEngineData(Object sharingLayer) { + this.sharingLayer = sharingLayer; + } + + DescriptorData getDescriptor(BytecodeDescriptor descriptor) { + DescriptorData d = descriptorData.get(descriptor); + if (d == null) { + d = initializeDescriptor(descriptor); + } + return d; + } + + private synchronized DescriptorData initializeDescriptor(BytecodeDescriptor descriptor) { + DescriptorData d = descriptorData.get(descriptor); + if (d != null) { + return d; + } + + DescriptorData newData = new DescriptorData(this, descriptor); + + DescriptorData prev = descriptorData.putIfAbsent(descriptor, newData); + if (prev != null) { + return prev; + } + + if (engineTracerFactories != null) { + for (var entry : engineTracerFactories.entrySet()) { + InstructionTracer tracer = entry.getKey().apply(descriptor); + newData.addInstructionTracer(tracer); + entry.getValue().put(descriptor, tracer); + } + } + + return newData; + } + + synchronized List getEngineInstructionTracers(Function, InstructionTracer> tracerFactory) { + Map, InstructionTracer> tracers = engineTracerFactories.get(tracerFactory); + if (tracers == null) { + return List.of(); + } + return List.copyOf(tracers.values()); + } + + synchronized void addEngineTracerFactory(Function, InstructionTracer> tracerFactory) { + if (engineTracerFactories == null) { + engineTracerFactories = new LinkedHashMap<>(); + } + Map, InstructionTracer> tracers = new LinkedHashMap<>(); + var prev = engineTracerFactories.putIfAbsent(tracerFactory, tracers); + if (prev != null) { + throw new IllegalArgumentException("tracer factory already registered"); + } + for (var entry : descriptorData.entrySet()) { + BytecodeDescriptor descriptor = entry.getKey(); + DescriptorData data = entry.getValue(); + InstructionTracer tracer = tracerFactory.apply(descriptor); + data.addInstructionTracer(tracer); + tracers.put(descriptor, tracer); + } + BytecodeDescriptor.enableEngineDescriptorLookup(); + } + + static BytecodeEngineData get(TruffleLanguage language) { + Object languageInstance = BytecodeAccessor.LANGUAGE.getPolyglotLanguageInstance(language); + Object sharingLayer = BytecodeAccessor.ENGINE.getSharingLayer(languageInstance); + return get(sharingLayer); + } + + static BytecodeEngineData get(Object sharingLayer) { + BytecodeEngineData data = BytecodeAccessor.ENGINE.getOrCreateBytecodeData(sharingLayer, BytecodeEngineData::new); + if (data == null) { + throw new IllegalStateException("Could not load the bytecode engine data. Context not yet initialized?"); + } + return data; + } + + /** + * Sharing layer specific data for an individual bytecode interpreter specification. + */ + static final class DescriptorData { + + private static final InstructionTracer[] EMPTY = new InstructionTracer[0]; + + private final BytecodeEngineData bytecodeData; + private final BytecodeDescriptor descriptor; + private final long traceInstructionEncoding; + private final BytecodeConfigEncoder configEncoder; + + /** + * Stores the global config encoding for all bytecode interpreters of this language. + */ + private volatile long descriptorConfig; + + /** + * Stores all installed global tracers. + */ + private volatile InstructionTracer[] descriptorTracers = EMPTY; + + DescriptorData(BytecodeEngineData bytecodeData, BytecodeDescriptor descriptor) { + this.bytecodeData = bytecodeData; + this.descriptor = descriptor; + BytecodeConfig c = descriptor.newConfigBuilder().addInstructionTracing().build(); + this.traceInstructionEncoding = c.encoding; + this.configEncoder = c.encoder; + } + + void addInstructionTracer(InstructionTracer tracer) { + Objects.requireNonNull(tracer); + InstructionTracer[] current = descriptorTracers; + for (InstructionTracer t : current) { + if (t == tracer) { + return; + } + } + InstructionTracer[] updated = Arrays.copyOf(current, current.length + 1); + updated[current.length] = tracer; + updateTracers(updated); + } + + void removeInstructionTracer(InstructionTracer tracer) { + List tracers = List.of(descriptorTracers); + if (!tracers.contains(tracer)) { + return; + } + InstructionTracer[] newArray = new InstructionTracer[tracers.size() - 1]; + int index = 0; + for (InstructionTracer t : tracers) { + if (!t.equals(tracer)) { + newArray[index++] = t; + } + } + assert index == newArray.length : "tracer is stored exactly once"; + updateTracers(newArray); + } + + private synchronized void updateTracers(InstructionTracer[] update) { + InstructionTracer[] tracers = this.descriptorTracers; + if (Arrays.equals(tracers, update)) { + // other update already did the work + return; + } + this.descriptorTracers = update; + this.descriptorConfig |= traceInstructionEncoding; + this.descriptor.enableDescriptorLookup(); + /* + * For each already loaded bytecode root node we need to explicitly update. + */ + forEachLoadedBytecodeRoot((r) -> { + updateRootNode(r); + }); + } + + private void forEachLoadedBytecodeRoot(Consumer action) { + BytecodeAccessor.ENGINE.forEachLoadedRootNode(bytecodeData.sharingLayer, (rootNode) -> { + BytecodeRootNode resolved = descriptor.cast(rootNode); + if (resolved != null) { + action.accept(resolved); + } + }); + } + + long updateBytecodeConfig(long config) { + return config | this.descriptorConfig; + } + + void onPrepareForCall(BytecodeRootNode rootNode) { + updateRootNode(rootNode); + } + + private void updateRootNode(BytecodeRootNode rootNode) { + BytecodeRootNodes r = rootNode.getRootNodes(); + long encoding = this.descriptorConfig; + if (encoding != 0) { + r.updateImpl(this.configEncoder, encoding); + } + if ((encoding & this.traceInstructionEncoding) != 0) { + r.updateGlobalInstructionTracers(this.descriptorTracers); + } + } + + void updateConfig(BytecodeConfig config) { + long newEncoding = config.encoding; + long oldEncoding = this.descriptorConfig; + if ((oldEncoding | newEncoding) == oldEncoding) { + // double checked locking + return; + } + updateConfigImpl(newEncoding); + } + + private synchronized void updateConfigImpl(long newEncoding) { + long oldEncoding = this.descriptorConfig; + if ((oldEncoding | newEncoding) == oldEncoding) { + return; + } + this.descriptorConfig |= newEncoding; + this.descriptor.enableDescriptorLookup(); + forEachLoadedBytecodeRoot((e) -> { + onPrepareForCall(e); + }); + } + + } + +} diff --git a/truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/BytecodeLocation.java b/truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/BytecodeLocation.java index 22c6c886b0b9..4472985a735e 100644 --- a/truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/BytecodeLocation.java +++ b/truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/BytecodeLocation.java @@ -131,7 +131,7 @@ public boolean equals(Object obj) { */ @Override public String toString() { - return String.format("BytecodeLocation [bytecode=%s, bci=%d]", bytecodes, bytecodeIndex); + return String.format("BytecodeLocation[bytecode=%s, bci=%d]@%s", bytecodes, bytecodeIndex, getInstruction().toString()); } /** diff --git a/truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/BytecodeRootNodes.java b/truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/BytecodeRootNodes.java index 9ef25a618a59..33929139354d 100644 --- a/truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/BytecodeRootNodes.java +++ b/truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/BytecodeRootNodes.java @@ -207,6 +207,76 @@ public final boolean ensureComplete() { return updateImpl(null, BytecodeConfig.COMPLETE.encoding); } + /** + * Registers an {@link InstructionTracer} for this {@code BytecodeRootNodes} instance. + *

+ * This is a local variant of + * {@link BytecodeDescriptor#addInstructionTracer(TruffleLanguage, InstructionTracer)}: it only + * affects the root nodes contained in this {@code BytecodeRootNodes} object, not all bytecode + * roots for the entire language. + *

+ * Tracer invocation happens on the language execution thread, at instruction granularity, and + * may run at very high frequency. Implementations should avoid unnecessary allocation or + * blocking. + *

+ * The same tracer instance can only be attached once. If the tracer is already installed for + * this {@code BytecodeRootNodes} instance, then this method has no effect. The order in which + * multiple tracers are invoked is unspecified. + *

+ * If this bytecode interpreter was generated without instruction tracing support (for example + * using {@code @GenerateBytecode(enableInstructionTracing = false)}), this method throws + * {@link UnsupportedOperationException}. + * + * @param tracer the tracer to install + * @throws IllegalArgumentException if {@code tracer} is exclusive to a different + * {@link BytecodeDescriptor} (see + * {@link InstructionTracer#getExclusiveBytecodeDescriptor()}) + * @throws UnsupportedOperationException if instruction tracing is not enabled for this + * interpreter + * @since 25.1 + */ + public void addInstructionTracer(InstructionTracer tracer) { + throw failUnsupported(); + } + + /** + * Unregisters a previously installed {@link InstructionTracer} from this + * {@code BytecodeRootNodes} instance. + *

+ * Note that currently when all instruction tracers were removed, the trace instructions remain + * in the bytecode. This may be improved in the future. + *

+ * If this bytecode interpreter was generated without instruction tracing support (for example + * using {@code @GenerateBytecode(enableInstructionTracing = false)}), this method throws + * {@link UnsupportedOperationException}. + * + * @param tracer the tracer to remove + * @throws IllegalArgumentException if {@code tracer} is exclusive to a different + * {@link BytecodeDescriptor} (see + * {@link InstructionTracer#getExclusiveBytecodeDescriptor()}) + * @throws UnsupportedOperationException if instruction tracing is not enabled for this + * interpreter + * @since 25.1 + */ + public void removeInstructionTracer(InstructionTracer tracer) { + throw failUnsupported(); + } + + private static UnsupportedOperationException failUnsupported() { + return new UnsupportedOperationException( + "Instruction tracing is not enabled for this bytecode root node. Enable with @GenerateBytecode(enableInstructionTracing=true) to use instruction tracing."); + } + + /** + * Internal callback to the generated code to notify when global instruction tracers change. + * + * @since 25.1 + */ + @SuppressWarnings("unused") + protected void updateGlobalInstructionTracers(InstructionTracer[] tracers) { + // nothing to do if not supported + } + /** * Returns a string representation of a {@link BytecodeRootNodes}. * diff --git a/truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/BytecodeSupport.java b/truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/BytecodeSupport.java index d3c86bc239b0..354dd790c970 100644 --- a/truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/BytecodeSupport.java +++ b/truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/BytecodeSupport.java @@ -225,10 +225,9 @@ public int addNull() { if (index >= consts.length) { this.constants = Arrays.copyOf(consts, consts.length * 2); } - if (index == HASH_THRESHOLD) { + if (index == HASH_THRESHOLD - 1) { fillMapFromConsts(); } - return index; } diff --git a/truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/GenerateBytecode.java b/truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/GenerateBytecode.java index 695d3a19d4eb..0cf8b9ffed01 100644 --- a/truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/GenerateBytecode.java +++ b/truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/GenerateBytecode.java @@ -568,8 +568,24 @@ * no effect. * * @see HostCompilerDirectives#markThreadedSwitch(int) - * @since 26.0 + * @since 25.1 */ boolean enableThreadedSwitch() default true; + /** + * Enables instruction tracing support for the generated bytecode interpreter. + *

+ * If {@code true}, the interpreter is generated with tracing hooks so that + * {@link InstructionTracer InstructionTracer}s can be attached at run time (via + * {@link BytecodeRootNodes#addInstructionTracer BytecodeRootNodes.addInstructionTracer} or + * {@link BytecodeDescriptor#addInstructionTracer BytecodeDescriptor.addInstructionTracer}). + * Attaching a tracer causes all executed instructions to invoke the tracer before execution. + *

+ * If {@code false}, no tracing hooks are generated and any attempt to attach a tracer will + * throw {@link UnsupportedOperationException}. + * + * @since 25.1 + */ + boolean enableInstructionTracing() default true; + } diff --git a/truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/Instruction.java b/truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/Instruction.java index 5b6d7493f07f..09b14e2112c0 100644 --- a/truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/Instruction.java +++ b/truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/Instruction.java @@ -40,17 +40,20 @@ */ package com.oracle.truffle.api.bytecode; +import java.lang.reflect.Array; import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; import java.util.Objects; +import com.oracle.truffle.api.bytecode.InstructionDescriptor.ArgumentDescriptor; import com.oracle.truffle.api.dsl.Bind.DefaultExpression; import com.oracle.truffle.api.dsl.Introspection; import com.oracle.truffle.api.dsl.Introspection.SpecializationInfo; import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.source.SourceSection; +import com.oracle.truffle.api.strings.TruffleString; /** * Represents metadata for an instruction in a bytecode node. @@ -97,12 +100,30 @@ protected Instruction(Object token) { */ public abstract int getBytecodeIndex(); + /** + * Returns the descriptor object for this instruction. + *

+ * The descriptor provides static metadata about the instruction executed at this bytecode + * index, such as its name, operation code, length in bytes, argument layout, and whether it is + * considered instrumentation. + * + * @see BytecodeDescriptor#getInstructionDescriptors() + * @since 25.1 + */ + public InstructionDescriptor getDescriptor() { + return null; + } + /** * Returns the length of this instruction in bytes. * + * @see #getDescriptor() + * @see InstructionDescriptor#getLength() * @since 24.2 */ - public abstract int getLength(); + public int getLength() { + return getDescriptor().getLength(); + } /** * Converts this instruction pointer into a bytecode location. It is recommended to use @@ -124,9 +145,13 @@ public final BytecodeLocation getLocation() { * and other optimizations. * * @see #getOperationCode() + * @see #getDescriptor() + * @see InstructionDescriptor#getName() * @since 24.2 */ - public abstract String getName(); + public String getName() { + return getDescriptor().getName(); + } /** * Returns the operation code of this instruction. The operation code of the instruction is @@ -138,9 +163,13 @@ public final BytecodeLocation getLocation() { * to quickening and other optimizations. * * @see #getName() + * @see #getDescriptor() + * @see InstructionDescriptor#getOperationCode() * @since 24.2 */ - public abstract int getOperationCode(); + public int getOperationCode() { + return getDescriptor().getOperationCode(); + } /** * Returns an immutable list of immediate arguments for this instructions. The number and kinds @@ -159,7 +188,9 @@ public final BytecodeLocation getLocation() { * * @since 24.2 */ - public abstract boolean isInstrumentation(); + public boolean isInstrumentation() { + return getDescriptor().isInstrumentation(); + } /** * Returns the most concrete source section associated with this instruction. If no source @@ -255,7 +286,7 @@ public final boolean equals(Object obj) { */ @Override public final String toString() { - return formatInstruction(null, -1, this, 40, 60); + return formatInstruction(null, -1, this, 40, 120); } static String formatInstruction(List errors, int index, Instruction instruction, int maxLabelWidth, int maxArgumentWidth) { @@ -268,10 +299,10 @@ static String formatInstruction(List errors, int index, Instruction i appendSpaces(sb, maxLabelWidth - label.length()); String arguments = formatArguments(instruction); sb.append(arguments); - appendSpaces(sb, maxArgumentWidth - arguments.length()); try { SourceSection s = instruction.getSourceSection(); if (s != null) { + appendSpaces(sb, maxArgumentWidth - arguments.length()); sb.append(" | "); sb.append(SourceInformation.formatSourceSection(s, 60)); } @@ -325,6 +356,19 @@ protected Argument(Object token) { BytecodeRootNodes.checkToken(token); } + /** + * Returns the descriptor object for this argument. + *

+ * The descriptor provides static metadata about the argument position and interpretation + * for this instruction, including its {@link Kind}, printable name, and encoded width in + * bytes. + * + * @since 25.1 + */ + public ArgumentDescriptor getDescriptor() { + return null; + } + /** * Returns the {@link Kind} of this argument. The kind determines which {@code asX} method * can be called on a given argument. @@ -335,7 +379,9 @@ protected Argument(Object token) { * * @since 24.2 */ - public abstract Kind getKind(); + public Kind getKind() { + return getDescriptor().getKind(); + } /** * Returns a human readable name for this argument. This could be for example @@ -346,7 +392,9 @@ protected Argument(Object token) { * * @since 24.2 */ - public abstract String getName(); + public String getName() { + return getDescriptor().getName(); + } /** * Converts this argument to an int value. This method is only supported if the @@ -521,11 +569,11 @@ private static String printTagProfile(TagTreeNode o) { } private String printNodeProfile(Object o) { - StringBuilder sb = new StringBuilder(); if (o == null) { - return "null"; + return "Disabled"; } - sb.append(o.getClass().getSimpleName()); + StringBuilder sb = new StringBuilder(); + sb.append("Enabled"); List info = getSpecializationInfo(); if (info != null) { sb.append("("); @@ -555,35 +603,97 @@ private static String printConstant(Object value) { if (value == null) { return "null"; } - String typeString = value.getClass().getSimpleName(); - String valueString = value.getClass().isArray() ? printArray(value) : value.toString(); - if (valueString.length() > 100) { - valueString = valueString.substring(0, 97) + "..."; + if (value instanceof LocalAccessor || value instanceof LocalRangeAccessor) { + // trusted toString to contain the type name + return value.toString(); + } else if (value instanceof String || value instanceof TruffleString) { + return "\"" + truncateString(value.toString()) + "\""; + } else if (value instanceof Number n) { + return formatLiteral(n); + } else { + String typeString = value.getClass().getSimpleName(); + String valueString = value.getClass().isArray() ? printArray(value, 16) : value.toString(); + return String.format("%s %s", typeString, truncateString(valueString)); + } + } + + private static String formatLiteral(Number n) { + String s = n.toString(); + String suffix = ""; + if (n instanceof Float) { + suffix = "F"; + } else if (n instanceof Long) { + suffix = "L"; + } else if (n instanceof Double) { + suffix = "D"; + } + return s + suffix; + } + + private static String truncateString(String s) { + String replaced = s.replace("\n", "\\n"); + if (replaced.length() > 30) { + replaced = replaced.substring(0, 27) + "..."; } - return String.format("%s %s", typeString, valueString); - } - - private static String printArray(Object array) { - if (array instanceof Object[] objArr) { - return Arrays.toString(objArr); - } else if (array instanceof long[] longArr) { - return Arrays.toString(longArr); - } else if (array instanceof int[] intArr) { - return Arrays.toString(intArr); - } else if (array instanceof short[] shortArr) { - return Arrays.toString(shortArr); - } else if (array instanceof char[] charArr) { - return Arrays.toString(charArr); - } else if (array instanceof byte[] byteArr) { - return Arrays.toString(byteArr); - } else if (array instanceof double[] doubleArr) { - return Arrays.toString(doubleArr); - } else if (array instanceof float[] floatArr) { - return Arrays.toString(floatArr); - } else if (array instanceof boolean[] boolArr) { - return Arrays.toString(boolArr); + return replaced; + } + + private static String printArray(Object array, int limit) { + Object a = array; + int length = Array.getLength(array); + if (length > limit) { + a = truncateArray(array, limit); + } + String s; + if (a instanceof Object[] objArr) { + s = Arrays.toString(objArr); + } else if (a instanceof long[] longArr) { + s = Arrays.toString(longArr); + } else if (a instanceof int[] intArr) { + s = Arrays.toString(intArr); + } else if (a instanceof short[] shortArr) { + s = Arrays.toString(shortArr); + } else if (a instanceof char[] charArr) { + s = Arrays.toString(charArr); + } else if (a instanceof byte[] byteArr) { + s = Arrays.toString(byteArr); + } else if (a instanceof double[] doubleArr) { + s = Arrays.toString(doubleArr); + } else if (a instanceof float[] floatArr) { + s = Arrays.toString(floatArr); + } else if (a instanceof boolean[] boolArr) { + s = Arrays.toString(boolArr); + } else { + throw new AssertionError(String.format("Unhandled array type %s", array)); + } + if (length > limit) { + return "[length:" + length + "]" + s; + } + return s; + + } + + private static Object truncateArray(Object array, int limit) { + if (array instanceof Object[] arr) { + return Arrays.copyOf(arr, limit); + } else if (array instanceof long[] arr) { + return Arrays.copyOf(arr, limit); + } else if (array instanceof int[] arr) { + return Arrays.copyOf(arr, limit); + } else if (array instanceof short[] arr) { + return Arrays.copyOf(arr, limit); + } else if (array instanceof char[] arr) { + return Arrays.copyOf(arr, limit); + } else if (array instanceof byte[] arr) { + return Arrays.copyOf(arr, limit); + } else if (array instanceof double[] arr) { + return Arrays.copyOf(arr, limit); + } else if (array instanceof float[] arr) { + return Arrays.copyOf(arr, limit); + } else if (array instanceof boolean[] arr) { + return Arrays.copyOf(arr, limit); } - throw new AssertionError(String.format("Unhandled array type %s", array)); + return array; } /** diff --git a/truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/InstructionDescriptor.java b/truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/InstructionDescriptor.java new file mode 100644 index 000000000000..724e534419f0 --- /dev/null +++ b/truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/InstructionDescriptor.java @@ -0,0 +1,225 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.oracle.truffle.api.bytecode; + +import java.util.List; +import java.util.Objects; + +import com.oracle.truffle.api.bytecode.Instruction.Argument; +import com.oracle.truffle.api.bytecode.Instruction.Argument.Kind; + +/** + * Descriptor for a concrete bytecode instruction. Instances describe static properties of an + * instruction, for example name, length and arguments. + * + * @since 25.1 + */ +public abstract class InstructionDescriptor { + + /** + * Internal constructor for generated code. Do not use. + * + * @since 25.1 + */ + protected InstructionDescriptor(Object token) { + BytecodeRootNodes.checkToken(token); + } + + /** + * Returns the encoded size of this instruction in bytes. + * + * @since 25.1 + */ + public abstract int getLength(); + + /** + * Returns a human readable instruction name. Two descriptors with the same name also have the + * same {@link #getOperationCode() operation code}. + * + * @see #getOperationCode() + * @since 25.1 + */ + public abstract String getName(); + + /** + * Returns the operation code for this instruction. The value is only intended for debugging or + * tooling. Two descriptors with the same operation code also have the same {@link #getName() + * name}. + * + * @see #getName() + * @since 25.1 + */ + public abstract int getOperationCode(); + + /** + * Returns an immutable list describing the immediate arguments of this instruction. The list + * size and the descriptor kinds are stable, the runtime values of the arguments may change. + * + * @since 25.1 + */ + public abstract List getArgumentDescriptors(); + + /** + * Returns true if this instruction represents a bytecode or tag instrumentation + * instruction, else false. Instrumentation instructions may get inserted + * dynamically during execution, e.g., if a tag is materialized or an {@link Instrumentation} is + * {@link BytecodeRootNodes#update(BytecodeConfig) configured}. + * + * @since 25.1 + */ + public abstract boolean isInstrumentation(); + + /** + * {@inheritDoc} + * + * @since 25.1 + */ + @Override + public int hashCode() { + return Objects.hash(getClass(), getOperationCode()); + } + + /** + * {@inheritDoc} + * + * @since 25.1 + */ + @Override + public boolean equals(Object obj) { + return this == obj; + } + + /** + * {@inheritDoc} + * + * @since 25.1 + */ + @Override + public String toString() { + return format(-1, this, 40); + } + + static String format(int index, InstructionDescriptor instruction, int maxLabelWidth) { + StringBuilder sb = new StringBuilder(); + if (index != -1) { + sb.append(String.format("%3d ", index)); + } + String label = formatLabel(instruction); + sb.append(label); + appendSpaces(sb, maxLabelWidth - label.length()); + String arguments = formatArguments(instruction); + sb.append(arguments); + return sb.toString(); + } + + private static void appendSpaces(StringBuilder sb, int spaces) { + for (int i = 0; i < spaces; i++) { + sb.append(' '); + } + } + + static String formatLabel(InstructionDescriptor instruction) { + return String.format("%03x %5s %s", instruction.getOperationCode(), String.format("[%db]", instruction.getLength()), instruction.getName()); + } + + static String formatArguments(InstructionDescriptor instruction) { + StringBuilder b = new StringBuilder(" "); + for (ArgumentDescriptor a : instruction.getArgumentDescriptors()) { + b.append(' ').append(a.toString()); + } + return b.toString(); + } + + /** + * Descriptor for a single immediate argument of an instruction. Captures kind, name and encoded + * width of the argument. + * + * @since 25.1 + */ + public abstract static class ArgumentDescriptor { + + /** + * Internal constructor for generated code. Do not use. + * + * @since 25.1 + */ + protected ArgumentDescriptor(Object token) { + BytecodeRootNodes.checkToken(token); + } + + /** + * Returns the {@link Kind} of this argument descriptor. + * + * @since 25.1 + */ + public abstract Argument.Kind getKind(); + + /** + * Returns a human readable name for this argument. This could be for example + * "localOffset" for a local variable access instruction. Arguments with the + * same {@link #getKind()} may have different {@link #getName() names}. A name is typically + * more descriptive than just the kind and should be preferred over the kind for debug + * output. + * + * @since 25.1 + */ + public abstract String getName(); + + /** + * Returns width in bytes. + * + * @since 25.1 + */ + public abstract int getLength(); + + /** + * {@inheritDoc} + * + * @since 25.1 + */ + @Override + public String toString() { + return String.format("[%db]%s(%s)", getLength(), getName(), getKind()); + } + + } + +} diff --git a/truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/InstructionTracer.java b/truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/InstructionTracer.java new file mode 100644 index 000000000000..91a48d9f2083 --- /dev/null +++ b/truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/InstructionTracer.java @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.oracle.truffle.api.bytecode; + +import com.oracle.truffle.api.TruffleLanguage; +import com.oracle.truffle.api.bytecode.debug.HistogramInstructionTracer; +import com.oracle.truffle.api.bytecode.debug.PrintInstructionTracer; +import com.oracle.truffle.api.frame.Frame; + +/** + * A low-overhead callback interface to observe instruction execution in Bytecode DSL interpreters. + * A tracer receives a notification immediately before the interpreter executes the instruction at a + * given bytecode index. The callback runs on the language thread that executes the bytecode, on the + * hot path, and may be invoked at very high frequency. + *

+ * Tracers for a {@link BytecodeRootNodes} instance may be attached via the + * {@link BytecodeRootNodes#addInstructionTracer(InstructionTracer)} method. Attaching tracers to + * all root node instances of a {@link BytecodeDescriptor} is possible using + * {@link BytecodeDescriptor#addInstructionTracer(TruffleLanguage, InstructionTracer)}. Note that + * attaching a tracer requires all affected root nodes to be invalidated and is therefore a very + * expensive operation. It also potentially requires all root nodes to be re-parsed. As a trade-off, + * not using an instruction tracer is basically free, except for reserving instructions in the + * bytecode interpreter, similar to how the {@link Instrumentation @Instrumentation} annotation is + * implemented. + *

+ * Typical uses include printing a trace, maintaining per-opcode counters, or driving custom + * debugging or testing utilities. For convenience, the + * {@code com.oracle.truffle.api.bytecode.debug} package provides reference implementations such as + * a {@link PrintInstructionTracer line printer} and a {@link HistogramInstructionTracer histogram + * tracer}. + *

+ * Performance considerations. + *

    + *
  • Instruction tracers are subject to partial evaluation, with the tracer reference being a + * partial-evaluation constant, allowing the tracer to be optimized. + *
  • Prefer {@link InstructionAccess#getTracedOperationCode(BytecodeNode, int)} for fast-path + * logic. In generated interpreters this typically avoids allocating an {@link Instruction} object. + *
  • {@link InstructionAccess#getTracedInstruction(BytecodeNode, int)} materializes an + * {@link Instruction} object and is therefore more expensive. Use it only when metadata is actually + * needed, for example to print the instruction name or retrieve source sections. + *
+ * + * @see BytecodeRootNodes#addInstructionTracer(InstructionTracer) + * @see BytecodeDescriptor#addInstructionTracer(TruffleLanguage, InstructionTracer) + * @since 25.1 + */ +public interface InstructionTracer { + + /** + * Called immediately before executing the instruction at {@code bytecodeIndex} of the given + * {@code bytecode} node. + * + * @param access accessor to query information about the current instruction, valid only during + * this call + * @param bytecode the {@link BytecodeNode} currently being interpreted + * @param bytecodeIndex the bytecode index (BCI) of the instruction to be executed + * @param frame the current frame for the root being interpreted + * @since 25.1 + */ + void onInstructionEnter(InstructionAccess access, BytecodeNode bytecode, int bytecodeIndex, Frame frame); + + /** + * Returns the {@link BytecodeDescriptor} that this tracer is exclusive for, or + * null if this instruction tracer may be used with any bytecode descriptor. + * + * @since 25.1 + */ + default BytecodeDescriptor getExclusiveBytecodeDescriptor() { + return null; + } + + /** + * Accessor for properties of the instruction about to execute. + *

+ * Instances of this class are provided by the generated interpreter and are scoped to a single + * {@link InstructionTracer#onInstructionEnter} invocation. They must not be stored, shared + * across threads, or used after the callback returns. + * + * @since 25.1 + */ + abstract class InstructionAccess { + + /** + * Internal constructor for generated code. + * + * @since 25.1 + */ + protected InstructionAccess(Object token) { + BytecodeRootNodes.checkToken(token); + } + + /** + * Materializes the {@link Instruction} object for the instruction at the given bytecode + * index. + * + * @param bytecode the {@link BytecodeNode} currently being interpreted + * @param bytecodeIndex the bytecode index (BCI) of the instruction + * @return the materialized {@link Instruction} representing the instruction at {@code BCI} + * @since 25.1 + */ + public abstract Instruction getTracedInstruction(BytecodeNode bytecode, int bytecodeIndex); + + /** + * Returns the operation code for the instruction at the given bytecode index, without + * allocating an {@link Instruction} object. + *

+ * The returned value corresponds to {@link Instruction#getOperationCode()} for the same + * instruction. + * + * @param bytecode the {@link BytecodeNode} currently being interpreted + * @param bytecodeIndex the bytecode index (BCI) of the instruction + * @return the integer operation code of the instruction at {@code BCI} + * @see Instruction#getOperationCode() + * @since 25.1 + */ + public abstract int getTracedOperationCode(BytecodeNode bytecode, int bytecodeIndex); + + } + +} diff --git a/truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/debug/HistogramInstructionTracer.java b/truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/debug/HistogramInstructionTracer.java new file mode 100644 index 000000000000..a87c55870007 --- /dev/null +++ b/truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/debug/HistogramInstructionTracer.java @@ -0,0 +1,689 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.oracle.truffle.api.bytecode.debug; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.LongSummaryStatistics; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import com.oracle.truffle.api.CompilerDirectives; +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; +import com.oracle.truffle.api.bytecode.BytecodeDescriptor; +import com.oracle.truffle.api.bytecode.BytecodeNode; +import com.oracle.truffle.api.bytecode.InstructionDescriptor; +import com.oracle.truffle.api.bytecode.InstructionTracer; +import com.oracle.truffle.api.frame.Frame; + +/** + * Instruction tracer that records per-opcode execution counts and optionally aggregates them into + * hierarchical histograms by user defined group clauses. + *

+ * The tracer is optimized for the no-filter, no-group case. In that mode the hot path avoids + * boundary calls and can be partially evaluated. When grouping or filtering is enabled, a small LRU + * cache avoids repeated evaluation of the grouping and filtering functions for the same + * {@link BytecodeNode}, current thread, and compilation tier. + *

+ * Thread safety: increments are performed via atomic counters and are safe under concurrent + * execution. Histogram creation is a snapshot that can be performed concurrently with counting. For + * interval semantics, prefer {@link #getHistogramAndReset()} over mixing {@link #getHistogram()} + * with {@link #reset()}. + *

+ *

Basic usage

The example below shows how to attach a {@link HistogramInstructionTracer} + * directly to a generated bytecode root, execute code, then collect and print a histogram snapshot. + *

+ * + *
+ * var root = MyRootNodeGen.BYTECODE.create(language, BytecodeConfig.DEFAULT, b -> {
+ *     b.beginRoot();
+ *     b.beginReturn();
+ *     b.emitLoadArgument(0);
+ *     b.endReturn();
+ *     b.endRoot();
+ * }).getNode(0);
+ *
+ * // Create and attach the histogram tracer to the root.
+ * var tracer = HistogramInstructionTracer.newBuilder().build(MyRootNodeGen.BYTECODE);
+ * root.getRootNodes().addInstructionTracer(tracer);
+ *
+ * // Execute your program as usual.
+ * Object result = root.getCallTarget().call(42);
+ *
+ * // Take a consistent snapshot and reset counters for the next interval.
+ * var histogram = tracer.getHistogramAndReset();
+ *
+ * // Inspect or print the histogram.
+ * long total = histogram.getInstructionsExecuted();
+ * histogram.print(System.out);
+ *
+ * // Detach the tracer when done.
+ * root.getRootNodes().removeInstructionTracer(tracer);
+ * 
+ * + * @since 25.1 + */ +public final class HistogramInstructionTracer implements InstructionTracer { + + private final BytecodeDescriptor descriptor; + private final Predicate filterClause; + private final GroupClause[] groupClauses; + + /* + * Least recently used cache for avoiding repeated invocations of the bytecode node filter and + * group clause. This will perform quite poor for heavily concurrent workloads. For those its + * better to not use any filters or groups. + */ + private volatile LastTraceCache cache; + private final Counters counters; + private final AtomicLong[] rootCounters; // one less indirection + + HistogramInstructionTracer(BytecodeDescriptor descriptor, + Predicate bytecodeFilter, + GroupClause[] groupClauses) { + Objects.requireNonNull(descriptor); + this.descriptor = descriptor; + this.filterClause = bytecodeFilter; + this.groupClauses = groupClauses; + this.counters = new Counters(operationCodeTableSize(descriptor), groupClauses == null); + this.rootCounters = counters.data; + } + + private static int operationCodeTableSize(BytecodeDescriptor descriptor) { + int maxCode = 0; + for (InstructionDescriptor instructionDescriptor : descriptor.getInstructionDescriptors()) { + maxCode = Math.max(instructionDescriptor.getOperationCode(), maxCode); + } + // if this assertion fails, there is likely a bug or change in the descriptor encoding + // currently we assume we can fit it all descriptors densely in an opcode table. + assert maxCode <= descriptor.getInstructionDescriptors().size() + 128 : "descriptor density too sparse"; + return maxCode + 1; + } + + /** + * Records the execution of an instruction. This method is not intended to be called directly, + * but by the bytecode DSL framework as part of instruction tracing. + * + * @since 25.1 + */ + public void onInstructionEnter(InstructionAccess access, BytecodeNode bytecode, int bytecodeIndex, Frame frame) { + assert descriptor.getGeneratedClass() == bytecode.getRootNode().getClass() : "Statistics listener attached to the wrong bytecode descriptor."; + /* + * This is carefully crafted to make sure it partially evaluates cleanly if no filter and no + * grouping are applied. Without filtering and grouping we can avoid boundary calls here, + * which improves the peak performance of this tracer. This might be needed when tracing big + * long-running applications. + */ + boolean filter = this.filterClause != null; // pe-constant + boolean group = this.groupClauses != null; // pe-constant + LastTraceCache c; + AtomicLong[] counterArray; + if (!filter && !group) { + // fast-path implementation + c = null; + counterArray = this.rootCounters; // pe-constant + } else { + c = this.cache; + int compiledTier = getCompiledTier(); + if ((c == null || c.bytecodeNode != bytecode || c.compiledTier != compiledTier || c.thread != Thread.currentThread())) { + c = updateCache(bytecode, compiledTier); + this.cache = c; + } + if (group) { // pe-constant + counterArray = c.counters; + } else { + counterArray = this.rootCounters; // pe-constant + } + if (filter && !c.included) { + return; + } + } + counterArray[access.getTracedOperationCode(bytecode, bytecodeIndex)].incrementAndGet(); + } + + private static int getCompiledTier() { + if (CompilerDirectives.inInterpreter()) { + return 0; + } else if (CompilerDirectives.hasNextTier()) { + return 1; + } else { + return 2; + } + } + + @TruffleBoundary + private LastTraceCache updateCache(BytecodeNode bytecode, int compiledTier) { + Counters c = this.counters; + if (groupClauses != null) { + Thread t = Thread.currentThread(); + c = this.counters; + int length = groupClauses.length; + for (int i = 0; i < length; i++) { + c = c.getOrCreateGroup(groupClauses[i].group(bytecode, t, compiledTier), i == length - 1); + } + } + boolean included = filterClause != null ? filterClause.test(bytecode) : true; + assert c.data != null; // must be leaf + return new LastTraceCache(bytecode, c.data, included, compiledTier, Thread.currentThread()); + } + + /** + * Returns the {@link BytecodeDescriptor} that this tracer is attached to. + * + * @return the descriptor used to resolve instruction metadata and opcodes + */ + @Override + public BytecodeDescriptor getExclusiveBytecodeDescriptor() { + return descriptor; + } + + /** + * Creates a hierarchical histogram snapshot of the recorded counters without resetting them. + *

+ * Use this to inspect cumulative counts. If you need interval semantics, prefer + * {@link #getHistogramAndReset()}. + * + * @return a {@link Histogram} view of the current counters, possibly grouped + * @since 25.1 + */ + public Histogram getHistogram() { + return new Histogram(descriptor, counters, false); + } + + /** + * Creates a hierarchical histogram snapshot of the recorded counters and atomically resets the + * underlying counters to zero. + *

+ * Use this to obtain interval counts in long running applications. The reset is performed + * atomically per-counter, increments that occur after the swap will be visible in the next + * interval. + * + * @return a {@link Histogram} view of the counts since the previous reset + * @since 25.1 + */ + public Histogram getHistogramAndReset() { + return new Histogram(descriptor, counters, true); + } + + /** + * Renders the current histogram snapshot into a string using + * {@link #printHistogram(PrintStream)}. + * + * @return a human readable histogram table as UTF-8 text + * @see Histogram#dump() + * @since 25.1 + */ + public String dumpHistogram() { + try (ByteArrayOutputStream w = new ByteArrayOutputStream()) { + printHistogram(new PrintStream(w)); + return w.toString(); + } catch (IOException e) { + // IOException unexpected here + throw new AssertionError(e); + } + } + + /** + * Prints the current histogram snapshot to the given {@link PrintStream}. + *

+ * The output is a single table with aligned numeric columns. Groups, if present, are printed as + * indented pseudo rows followed by their leaves. + * + * @param out the stream to print to + * @see Histogram#print(PrintStream) + * @since 25.1 + */ + public void printHistogram(PrintStream out) { + Objects.requireNonNull(out); + getHistogram().print(out); + } + + /** + * Resets all counters to zero and discards previously recorded data. + *

+ * Do not mix this with {@link #getHistogram()} if you need interval correctness, use + * {@link #getHistogramAndReset()} instead. + * + * @since 25.1 + */ + public void reset() { + this.counters.reset(); + } + + /** + * Returns a builder for {@link HistogramInstructionTracer} that allows configuring a filter and + * one or more grouping clauses. + * + * @return a new builder instance + * @since 25.1 + */ + public static Builder newBuilder() { + return new Builder(); + } + + /** + * A grouping function that maps a {@link BytecodeNode}, the current {@link Thread}, and the + * compilation tier to an arbitrary grouping key. Keys are used to form hierarchical histograms. + * + * @since 25.1 + */ + @FunctionalInterface + public interface GroupClause { + + /** + * Computes the grouping key for the given execution context. + * + * @param bytecodeNode the bytecode node executing + * @param thread the current thread + * @param compilationTier 0 for interpreter, 1 for compiled with a next tier, 2 for final + * compiled + * @return a non null grouping key, for example a string, an enum, or a small record + * @since 25.1 + */ + Object group(BytecodeNode bytecodeNode, Thread thread, int compilationTier); + + } + + /** + * Builder for {@link HistogramInstructionTracer}. + *

+ * The builder supports an optional filter predicate and an ordered list of group clauses. Group + * clauses are applied in the order they are added, producing a hierarchy of groups. + * + * @since 25.1 + */ + public static final class Builder { + + private Predicate filter; + private List groups = new ArrayList<>(); + + private Builder() { + } + + /** + * Sets an optional filter predicate. If present, only bytecode nodes that satisfy the + * predicate contribute to counters. + * + * @param filterClause the predicate to apply, may be {@code null} + * @return this builder + * + * @since 25.1 + */ + public Builder filter(Predicate filterClause) { + this.filter = filterClause; + return this; + } + + /** + * Adds a grouping clause to the histogram. Multiple clauses create a hierarchy, the first + * clause forms the top level, the last clause forms the leaves. + * + * @param clause the grouping clause to add, must not be {@code null} + * @return this builder + * @throws NullPointerException if {@code clause} is {@code null} + * @since 25.1 + */ + public Builder groupBy(GroupClause clause) { + Objects.requireNonNull(clause); + groups.add(clause); + return this; + } + + /** + * Builds a {@link HistogramInstructionTracer} for the given {@link BytecodeDescriptor}. + * + * @param descriptor the descriptor to attach to + * @return a new tracer configured with the previously set filter and groups + * @since 25.1 + */ + @SuppressWarnings("unchecked") + public HistogramInstructionTracer build(BytecodeDescriptor descriptor) { + return new HistogramInstructionTracer(descriptor, filter, groups.isEmpty() ? null : groups.toArray(GroupClause[]::new)); + } + } + + /** + * Represents a hierarchical histogram that can group instruction counts by arbitrary + * attributes. A histogram is an immutable snapshot of the counters at the time it was created. + * + * @since 25.1 + */ + public static final class Histogram { + + private final BytecodeDescriptor descriptor; + private final Map subGroups; + private final Map data; + private final long instructionsExecuted; + + private Histogram(BytecodeDescriptor descriptor, Counters counters, boolean reset) { + this.descriptor = descriptor; + Map map = new LinkedHashMap<>(); + long sum = 0; + if (counters.subgroups != null) { + Map groups = new LinkedHashMap<>(); + for (var entry : counters.subgroups.entrySet()) { + Histogram histogram = new Histogram(descriptor, entry.getValue(), reset); + groups.put(entry.getKey(), histogram); + sum += sumChild(map, histogram.data); + } + this.subGroups = Collections.unmodifiableMap(groups); + } else { + this.subGroups = null; + sum += sumCounters(map, counters.data, reset); + } + this.data = Collections.unmodifiableMap(map); + this.instructionsExecuted = sum; + } + + /** + * Returns the per-instruction counts for this histogram level when it is a leaf. + *

+ * If this histogram has subgroups, this map may be empty, since counts then live in the + * children. + * + * @return an unmodifiable map from {@link InstructionDescriptor} to count + * @since 25.1 + */ + public Map getCounters() { + return data; + } + + /** + * Returns aggregated statistics across this histogram level. + *

+ * For leaf histograms, this wraps each count in a {@link LongSummaryStatistics}. For parent + * histograms, statistics are combined across all children. + * + * @return a map from {@link InstructionDescriptor} to aggregated statistics + * @since 25.1 + */ + public Map getStatistics() { + if (subGroups == null) { + return data.entrySet().stream().collect(Collectors.toMap( + Map.Entry::getKey, + e -> { + LongSummaryStatistics s = new LongSummaryStatistics(); + s.accept(e.getValue()); + return s; + })); + } else { + Map aggregated = new LinkedHashMap<>(); + for (Histogram child : subGroups.values()) { + Map childStats = child.getStatistics(); + for (var entry : childStats.entrySet()) { + aggregated.computeIfAbsent(entry.getKey(), k -> new LongSummaryStatistics()).combine(entry.getValue()); + } + } + return aggregated; + } + } + + /** + * Returns the subgroups of this histogram, keyed by the grouping values produced by the + * configured {@link GroupClause}s. + * + * @return an unmodifiable map of subgroup keys to child histograms, or {@code null} if + * there are no groups + * @since 25.1 + */ + public Map getGroups() { + return subGroups; + } + + /** + * Renders this histogram to a string using {@link #print(PrintStream)}. + * + * @return a human readable histogram table + * @since 25.1 + */ + public String dump() { + try (ByteArrayOutputStream w = new ByteArrayOutputStream()) { + print(new PrintStream(w)); + return w.toString(); + } catch (IOException e) { + // IOException unexpected here + throw new AssertionError(e); + } + } + + /** + * Prints this histogram as a single indented table. + *

+ * Numeric columns are aligned. When groups are present, each group is shown as a row that + * displays the total for the group, followed by the indented rows of its children. + * + * @param out the stream to print to + * @since 25.1 + */ + public void print(PrintStream out) { + long executed = instructionsExecuted; + out.println("Instruction histogram for: " + descriptor.getSpecificationClass().getName()); + if (executed == 0) { + out.println(" (no events)"); + return; + } + + final String countLabel = "Count"; + final String percentLabel = "Percent"; + final String nameLabel = "Group / Instruction"; + + int countWidth = Math.max(countLabel.length(), String.valueOf(executed).length()); + int percentWidth = Math.max(percentLabel.length(), 7); // fits "100.0%" + + String header = String.format(" %" + countWidth + "s | %" + percentWidth + "s | %s", + countLabel, percentLabel, nameLabel); + String formatString = " %" + countWidth + "d | %" + percentWidth + "s | %s%s%n"; + String ruler = " " + repeat('-', header.length()); + + out.println(ruler); + out.println(header); + out.println(ruler); + + printRecursive(out, 0, executed, formatString); + + out.println(ruler); + out.printf(" Total executed instructions: %d%n", executed); + } + + /** + * Returns the total number of executed instructions represented by this histogram. + *

+ * For grouped histograms, this is the sum across all children. For leaves, this is the sum + * of the per-instruction counts. + * + * @return the total count represented by this histogram + * @since 25.1 + */ + public long getInstructionsExecuted() { + return instructionsExecuted; + } + + private void printRecursive(PrintStream out, int depth, + long globalTotal, String formatString) { + // Compute total count for this histogram + long total = this.getInstructionsExecuted(); + if (total == 0) { + return; + } + + if (this.subGroups != null) { + assert !this.subGroups.isEmpty(); + // Sort groups for deterministic output + List> entries = new ArrayList<>(this.subGroups.entrySet()); + entries.sort(Map.Entry.comparingByValue(Comparator.comparingLong(Histogram::getInstructionsExecuted).reversed())); + + for (var entry : entries) { + Histogram child = entry.getValue(); + long childTotal = child.getInstructionsExecuted(); + double percentGlobal = (childTotal * 100.0) / globalTotal; + String indent = " ".repeat(depth) + "\u25B6 "; + out.printf(formatString, childTotal, String.format("%.1f", percentGlobal), indent, entry.getKey()); + child.printRecursive(out, depth + 1, globalTotal, formatString); + } + } else { + // Leaf node: print per-instruction rows + List> rows = new ArrayList<>(this.data.entrySet()); + rows.sort((a, b) -> Long.compare(b.getValue(), a.getValue())); + + for (var e : rows) { + InstructionDescriptor d = e.getKey(); + long count = e.getValue(); + double percent = (count * 100.0) / globalTotal; + String indent = " ".repeat(depth); + out.printf(formatString, count, String.format("%.1f", percent), indent, formatLabel(d)); + } + } + } + + static String formatLabel(InstructionDescriptor instruction) { + return String.format("%03x %s", instruction.getOperationCode(), instruction.getName()); + } + + private static String repeat(char ch, int n) { + char[] c = new char[n]; + Arrays.fill(c, ch); + return new String(c); + } + + private static long sumChild(Map parent, + Map child) { + long sum = 0; + for (var entry : child.entrySet()) { + Long counter = entry.getValue(); + if (counter == 0) { + continue; + } + sum += counter; + parent.compute(entry.getKey(), (key, old) -> old == null ? counter : old + counter); + } + return sum; + } + + private long sumCounters(Map statistics, AtomicLong[] values, boolean reset) { + int opcode = -1; + long sum = 0; + for (AtomicLong l : values) { + opcode++; + long counter = reset ? l.getAndSet(0L) : l.get(); + if (counter == 0L) { + continue; + } + InstructionDescriptor d = descriptor.getInstructionDescriptor(opcode); + if (d == null) { + assert d != null : "No InstructionDescriptor for opcode=" + opcode + ", counter=" + counter + ", values.length=" + values.length + ", descriptor=" + descriptor; + continue; + } + sum += counter; + statistics.put(d, counter); + } + return sum; + } + + } + + private static final class Counters { + + final int size; + final AtomicLong[] data; + final ConcurrentHashMap subgroups; + + Counters(int tableSize, boolean leaf) { + this.size = tableSize; + if (leaf) { + this.data = createCounters(tableSize); + this.subgroups = null; + } else { + this.data = null; + this.subgroups = new ConcurrentHashMap<>(); + } + } + + private static AtomicLong[] createCounters(int size) { + /* + * We are using AtomicLong as counters instead of VarHandle + long[] in order to make + * sure the counter increment can be safely partially evaluated and does not cause any + * additional overhead in the interpreter. + */ + AtomicLong[] c = new AtomicLong[size]; + for (int i = 0; i < c.length; i++) { + c[i] = new AtomicLong(); + } + return c; + } + + public Counters getOrCreateGroup(Object childGroup, boolean leaf) { + Objects.requireNonNull(childGroup, "Group key must not be null."); + return subgroups.computeIfAbsent(childGroup, (k) -> new Counters(size, leaf)); + } + + public void reset() { + if (data != null) { + resetCounters(data); + } + if (subgroups != null) { + for (Counters child : subgroups.values()) { + child.reset(); + } + } + } + + private static void resetCounters(AtomicLong[] counters) { + for (AtomicLong counter : counters) { + counter.getAndSet(0); + } + } + } + + private record LastTraceCache(BytecodeNode bytecodeNode, AtomicLong[] counters, boolean included, int compiledTier, Thread thread) { + } + +} diff --git a/truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/debug/PrintInstructionTracer.java b/truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/debug/PrintInstructionTracer.java new file mode 100644 index 000000000000..b21febc10aec --- /dev/null +++ b/truffle/src/com.oracle.truffle.api.bytecode/src/com/oracle/truffle/api/bytecode/debug/PrintInstructionTracer.java @@ -0,0 +1,234 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.oracle.truffle.api.bytecode.debug; + +import java.io.PrintStream; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Consumer; +import java.util.function.Predicate; + +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; +import com.oracle.truffle.api.bytecode.BytecodeNode; +import com.oracle.truffle.api.bytecode.InstructionTracer; +import com.oracle.truffle.api.frame.Frame; +import com.oracle.truffle.api.nodes.RootNode; + +/** + * An {@link InstructionTracer} implementation that prints a textual trace of executed bytecode + * instructions. + *

+ * Each time an instruction is about to execute, this tracer formats a single line containing: + *

    + *
  • a monotonically increasing instruction counter, + *
  • the qualified root name of the {@link BytecodeNode} being interpreted, and + *
  • a human readable representation of the instruction at the current bytecode index. + *
+ * + * Output is delivered to a user-supplied {@link Consumer Consumer}<String> (for example, + * {@code System.out::println}) or to a {@link java.io.PrintStream} via the + * {@link #newBuilder(PrintStream)} convenience factory. + *

+ * A tracer may optionally be restricted to a subset of bytecode roots by installing a filter + * predicate. If a {@link BytecodeNode} does not match the filter, its instructions are skipped. + *

+ * This tracer is intended for debugging and profiling during development. It runs on the language + * execution thread and executes on the hot path before every instruction; it should not be left + * enabled in production code. + * + * @since 25.1 + */ +public final class PrintInstructionTracer implements InstructionTracer { + + private final Consumer out; + private final Predicate filter; + private final AtomicLong executedInstructions = new AtomicLong(); + private volatile LastTraceCache cache; + + PrintInstructionTracer(Consumer out, Predicate filter) { + this.out = out; + this.filter = filter; + } + + /** + * {@inheritDoc} + *

+ * This implementation formats and emits a single trace line for the current instruction, unless + * the {@link BytecodeNode} is excluded by the active filter. + * + * @since 25.1 + */ + public void onInstructionEnter(InstructionAccess access, BytecodeNode bytecode, int bytecodeIndex, Frame frame) { + traceInstruction(access, bytecode, bytecodeIndex); + } + + /** + * Resets the internal instruction counter back to zero. + *

+ * This only affects the counter included in the printed trace lines. It does not clear any + * output sink or filter configuration. + * + * @since 25.1 + */ + public void reset() { + executedInstructions.set(0); + } + + /** + * Creates a new {@link Builder} that will emit trace lines to the given consumer. + *

+ * Each generated line is already formatted. The consumer is typically a logger or + * {@code System.out::println}, but any {@link Consumer} is accepted. + * + * @param out sink for formatted trace lines + * @return a new builder + * @since 25.1 + */ + public static Builder newBuilder(Consumer out) { + return new Builder(out); + } + + /** + * Convenience overload of {@link #newBuilder(Consumer)} that writes to a {@link PrintStream}. + *

+ * The resulting tracer will call {@link PrintStream#println(String)} for every traced + * instruction. + * + * @param out a print stream (for example {@code System.out}) + * @return a new builder + * @since 25.1 + */ + public static Builder newBuilder(PrintStream out) { + return newBuilder((s) -> out.println(s)); + } + + private LastTraceCache updateCache(BytecodeNode bytecode) { + RootNode rootNode = bytecode.getRootNode(); + String name = rootNode.getQualifiedName(); + if (name == null) { + name = ""; + } + boolean included = filter != null ? filter.test(bytecode) : true; + return new LastTraceCache(bytecode, name, included); + } + + @TruffleBoundary + private void traceInstruction(InstructionAccess access, BytecodeNode bytecode, int bytecodeIndex) { + LastTraceCache c = this.cache; + if (c == null || c.bytecodeNode != bytecode) { + c = updateCache(bytecode); + this.cache = c; + } + if (!c.included) { + return; + } + StringBuilder b = new StringBuilder(); + String counter = String.valueOf(executedInstructions.incrementAndGet()); + spaces(b, 6 - counter.length()); + b.append(counter); + b.append(":"); + b.append(c.rootName); + b.append(":"); + b.append(access.getTracedInstruction(bytecode, bytecodeIndex).toString()); + out.accept(b.toString()); + } + + private static void spaces(StringBuilder b, int count) { + for (int i = 0; i < count; i++) { + b.append(' '); + } + } + + /** + * Builder for {@link PrintInstructionTracer} instances. + *

+ * Call {@link #filter(Predicate)} to restrict tracing to selected {@link BytecodeNode}s, then + * {@link #build()} to create the tracer. + * + * @since 25.1 + */ + public static final class Builder { + + private final Consumer out; + private Predicate filter; + + private Builder(Consumer out) { + Objects.requireNonNull(out); + this.out = out; + } + + /** + * Sets an optional filter that decides which {@link BytecodeNode}s should be traced. + *

+ * If the predicate returns {@code false} for a given node, instructions executed in that + * node are not printed. + * + * @param filterClause predicate that returns true if tracing should be enabled for the + * given node + * @return this builder + * @since 25.1 + */ + public Builder filter(Predicate filterClause) { + this.filter = filterClause; + return this; + } + + /** + * Creates a new {@link PrintInstructionTracer} instance using the configured output sink + * and optional filter. + * + * @return a new tracer + * @since 25.1 + */ + public PrintInstructionTracer build() { + return new PrintInstructionTracer(out, filter); + } + } + + /** + * Small cache record holding metadata for the most recently traced {@link BytecodeNode}. + *

+ * This avoids recomputing node name and filter status for every single instruction of the same + * node. + */ + private record LastTraceCache(BytecodeNode bytecodeNode, String rootName, boolean included) { + } +} diff --git a/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/impl/Accessor.java b/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/impl/Accessor.java index da3acb94599e..876c2b19226b 100644 --- a/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/impl/Accessor.java +++ b/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/impl/Accessor.java @@ -830,8 +830,14 @@ public abstract Iterator mergeHostGuestFrames(Object polyglotEngine, S public abstract DispatchOutputStream getEngineOut(Object engine); + public abstract T getOrCreateBytecodeData(Object languageInstance, Function create); + public abstract InputStream getEngineIn(Object engine); + public abstract void forEachLoadedRootNode(Object sharingLayer, Consumer rootNodeUpdater); + + public abstract Object getSharingLayer(Object languageInstance); + } public abstract static class LanguageSupport extends Support { @@ -973,6 +979,7 @@ public abstract Env createEnv(Object polyglotLanguageContext, TruffleLanguage public abstract OptionDescriptors createOptionDescriptorsUnion(OptionDescriptors... descriptors); public abstract InternalResource.Env createInternalResourceEnv(InternalResource resource, BooleanSupplier contextPreinitializationCheck); + } public abstract static class InstrumentSupport extends Support { @@ -1469,6 +1476,19 @@ protected MemorySupport() { } + public abstract static class BytecodeSupport extends Support { + + static final String IMPL_CLASS_NAME = "com.oracle.truffle.api.bytecode.BytecodeAccessor$BytecodeSupportImpl"; + + protected BytecodeSupport() { + super(IMPL_CLASS_NAME); + } + + public abstract void registerInstructionTracerFactory(Object hostLanguage, Function tracerFactory); + + public abstract List getEngineInstructionTracers(Object hostLanguage, Function tracerFactory); + } + public final void transferOSRFrameStaticSlot(FrameWithoutBoxing sourceFrame, FrameWithoutBoxing targetFrame, int slot) { sourceFrame.transferOSRStaticSlot(targetFrame, slot); } @@ -1496,6 +1516,7 @@ private static final class Constants { private static final Accessor.LanguageProviderSupport LANGUAGE_PROVIDER; private static final Accessor.InstrumentProviderSupport INSTRUMENT_PROVIDER; private static final Accessor.MemorySupport MEMORY_SUPPORT; + private static final Accessor.BytecodeSupport BYTECODE; static { // Eager load all accessors so the above fields are all set and all methods are @@ -1515,6 +1536,7 @@ private static final class Constants { INSTRUMENT_PROVIDER = loadSupport(InstrumentProviderSupport.IMPL_CLASS_NAME); MEMORY_SUPPORT = loadSupport(MemorySupport.IMPL_CLASS_NAME); STRINGS = loadSupport(StringsSupport.IMPL_CLASS_NAME); + BYTECODE = loadSupport(BytecodeSupport.IMPL_CLASS_NAME); } @SuppressWarnings("unchecked") @@ -1611,6 +1633,10 @@ public final HostSupport hostSupport() { return Constants.HOST; } + public final BytecodeSupport bytecodeSupport() { + return Constants.BYTECODE; + } + public final IOSupport ioSupport() { return Constants.IO; } diff --git a/truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/TruffleTypes.java b/truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/TruffleTypes.java index 4f29ba5dd148..154460dc2da7 100644 --- a/truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/TruffleTypes.java +++ b/truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/TruffleTypes.java @@ -400,6 +400,8 @@ public class TruffleTypes { public static final String ShortCircuitOperation_Name = "com.oracle.truffle.api.bytecode.ShortCircuitOperation"; public static final String Variadic_Name = "com.oracle.truffle.api.bytecode.Variadic"; public static final String Instrumentation_Name = "com.oracle.truffle.api.bytecode.Instrumentation"; + public static final String InstructionTracer_Name = "com.oracle.truffle.api.bytecode.InstructionTracer"; + public static final String InstructionTracer_InstructionAccess_Name = "com.oracle.truffle.api.bytecode.InstructionTracer.InstructionAccess"; public static final String Yield_Name = "com.oracle.truffle.api.bytecode.Yield"; public static final String Instruction_Argument_Kind_Name = "com.oracle.truffle.api.bytecode.Instruction.Argument.Kind"; @@ -407,6 +409,9 @@ public class TruffleTypes { public static final String Instruction_Argument_BranchProfile_Name = "com.oracle.truffle.api.bytecode.Instruction.Argument.BranchProfile"; public static final String BytecodeIntrospection_Name = "com.oracle.truffle.api.bytecode.BytecodeIntrospection"; public static final String Instruction_Name = "com.oracle.truffle.api.bytecode.Instruction"; + public static final String InstructionDescriptor_Name = "com.oracle.truffle.api.bytecode.InstructionDescriptor"; + public static final String InstructionDescriptor_ArgumentDescriptor_Name = "com.oracle.truffle.api.bytecode.InstructionDescriptor.ArgumentDescriptor"; + public static final String BytecodeDescriptor_Name = "com.oracle.truffle.api.bytecode.BytecodeDescriptor"; public static final String SourceInformation_Name = "com.oracle.truffle.api.bytecode.SourceInformation"; public static final String SourceInformationTree_Name = "com.oracle.truffle.api.bytecode.SourceInformationTree"; public static final String LocalVariable_Name = "com.oracle.truffle.api.bytecode.LocalVariable"; @@ -466,6 +471,8 @@ public class TruffleTypes { public final DeclaredType ShortCircuitOperation = c.getDeclaredTypeOptional(ShortCircuitOperation_Name); public final DeclaredType Variadic = c.getDeclaredTypeOptional(Variadic_Name); public final DeclaredType Instrumentation = c.getDeclaredTypeOptional(Instrumentation_Name); + public final DeclaredType InstructionTracer = c.getDeclaredTypeOptional(InstructionTracer_Name); + public final DeclaredType InstructionTracer_InstructionAccess = c.getDeclaredTypeOptional(InstructionTracer_InstructionAccess_Name); public final DeclaredType Yield = c.getDeclaredTypeOptional(Yield_Name); public final DeclaredType Instruction_Argument = c.getDeclaredTypeOptional(Instruction_Argument_Name); @@ -473,6 +480,9 @@ public class TruffleTypes { public final DeclaredType Instruction_Argument_Kind = c.getDeclaredTypeOptional(Instruction_Argument_Kind_Name); public final DeclaredType BytecodeIntrospection = c.getDeclaredTypeOptional(BytecodeIntrospection_Name); public final DeclaredType Instruction = c.getDeclaredTypeOptional(Instruction_Name); + public final DeclaredType InstructionDescriptor = c.getDeclaredTypeOptional(InstructionDescriptor_Name); + public final DeclaredType InstructionDescriptor_ArgumentDescriptor = c.getDeclaredTypeOptional(InstructionDescriptor_ArgumentDescriptor_Name); + public final DeclaredType BytecodeDescriptor = c.getDeclaredTypeOptional(BytecodeDescriptor_Name); public final DeclaredType SourceInformation = c.getDeclaredTypeOptional(SourceInformation_Name); public final DeclaredType SourceInformationTree = c.getDeclaredTypeOptional(SourceInformationTree_Name); public final DeclaredType LocalVariable = c.getDeclaredTypeOptional(LocalVariable_Name); diff --git a/truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/bytecode/generator/BytecodeDSLCodeGenerator.java b/truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/bytecode/generator/BytecodeDSLCodeGenerator.java index d1658bae5f0f..9ea9b619dae4 100644 --- a/truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/bytecode/generator/BytecodeDSLCodeGenerator.java +++ b/truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/bytecode/generator/BytecodeDSLCodeGenerator.java @@ -41,16 +41,14 @@ package com.oracle.truffle.dsl.processor.bytecode.generator; import static com.oracle.truffle.dsl.processor.bytecode.generator.ElementHelpers.generic; +import static javax.lang.model.element.Modifier.PUBLIC; +import static javax.lang.model.element.Modifier.STATIC; -import java.io.DataInput; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; -import java.util.function.Supplier; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; @@ -58,7 +56,6 @@ import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; -import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.ElementFilter; @@ -71,11 +68,10 @@ import com.oracle.truffle.dsl.processor.generator.GeneratorUtils; import com.oracle.truffle.dsl.processor.java.ElementUtils; import com.oracle.truffle.dsl.processor.java.model.CodeExecutableElement; -import com.oracle.truffle.dsl.processor.java.model.CodeNames; import com.oracle.truffle.dsl.processor.java.model.CodeTreeBuilder; import com.oracle.truffle.dsl.processor.java.model.CodeTypeElement; -import com.oracle.truffle.dsl.processor.java.model.CodeTypeParameterElement; import com.oracle.truffle.dsl.processor.java.model.CodeVariableElement; +import com.oracle.truffle.dsl.processor.java.model.GeneratedTypeMirror; public class BytecodeDSLCodeGenerator extends CodeTypeElementFactory { @@ -89,11 +85,19 @@ public List create(ProcessorContext context, AnnotationProcesso return results; } + TypeMirror abstractBuilder; + CodeTypeElement abstractBuilderType = null; + if (modelList.getModels().size() > 1) { + abstractBuilderType = createAbstractBuilderType(modelList.getModels().getFirst(), modelList.getModels().getFirst().getTemplateType()); + abstractBuilder = abstractBuilderType.asType(); + } else { + abstractBuilder = null; + } for (BytecodeDSLModel model : modelList.getModels()) { if (modelList.hasErrors()) { - results.add(new BytecodeRootNodeErrorElement(model)); + results.add(new BytecodeRootNodeErrorElement(model, abstractBuilder)); } else { - results.add(new BytecodeRootNodeElement(model)); + results.add(new BytecodeRootNodeElement(model, abstractBuilder)); } } @@ -106,14 +110,6 @@ public List create(ProcessorContext context, AnnotationProcesso * defining the public interface for the Builders. Test code writes parsers using this * abstract builder's interface so that the parser can be used to test each variant. */ - CodeTypeElement abstractBuilderType = (CodeTypeElement) ElementUtils.castTypeElement(modelList.getModels().getFirst().abstractBuilderType); - - for (BytecodeDSLModel model : modelList.getModels()) { - if (abstractBuilderType != ElementUtils.castTypeElement(model.abstractBuilderType)) { - throw new AssertionError("Invalid builder type."); - } - } - Iterator builders = results.stream().map(result -> (CodeTypeElement) ElementUtils.findTypeElement(result, "Builder")).iterator(); /** @@ -173,8 +169,23 @@ public List create(ProcessorContext context, AnnotationProcesso } } - // Add helper methods to reflectively invoke static methods. - abstractBuilderType.addAll(createReflectiveHelpers(modelList, abstractBuilderType.asType())); + TypeMirror descriptorType = findBytecodeVariantType(abstractBuilderType.asType()); + TypeMirror returnType = generic(context.getType(List.class), descriptorType); + + CodeExecutableElement ex = new CodeExecutableElement(Set.of(Modifier.PUBLIC, Modifier.STATIC), returnType, "variants"); + CodeTreeBuilder b = ex.createBuilder(); + b.startReturn().startStaticCall(context.getType(List.class), "of"); + for (BytecodeDSLModel model : modelList.getModels()) { + b.startGroup(); + b.newLine(); + b.startIndention(); + b.staticReference(new GeneratedTypeMirror(ElementUtils.getPackageName(model.getTemplateType()), + model.getName()), "BYTECODE"); + b.end(); // indention + b.end(); // group + } + b.end().end(); + abstractBuilderType.add(ex); results.add(abstractBuilderType); @@ -182,7 +193,7 @@ public List create(ProcessorContext context, AnnotationProcesso } - public static CodeTypeElement createAbstractBuilderType(TypeElement templateType) { + public static CodeTypeElement createAbstractBuilderType(BytecodeDSLModel model, TypeElement templateType) { String abstractBuilderName = templateType.getSimpleName() + "Builder"; CodeTypeElement abstractBuilderType = new CodeTypeElement(Set.of(Modifier.PUBLIC, Modifier.ABSTRACT), ElementKind.CLASS, ElementUtils.findPackageElement(templateType), abstractBuilderName); abstractBuilderType.setSuperClass(ProcessorContext.types().BytecodeBuilder); @@ -192,9 +203,29 @@ public static CodeTypeElement createAbstractBuilderType(TypeElement templateType constructor.addParameter(new CodeVariableElement(ProcessorContext.getInstance().getType(Object.class), "token")); constructor.createBuilder().statement("super(token)"); abstractBuilderType.add(constructor); + + abstractBuilderType.add(new BytecodeVariantElement(templateType.asType(), model.languageClass, abstractBuilderType.asType())); + return abstractBuilderType; } + public static TypeMirror findBytecodeVariantType(TypeMirror builderType) { + TypeElement e = findInnerClass(ElementUtils.castTypeElement(builderType), "BytecodeVariant"); + if (e == null) { + return null; + } + return e.asType(); + } + + static TypeElement findInnerClass(TypeElement type, String name) { + for (TypeElement field : ElementFilter.typesIn(type.getEnclosedElements())) { + if (field.getSimpleName().toString().equals(name)) { + return field; + } + } + return null; + } + private boolean hasExpectErrors(Element element) { if (!ExpectError.getExpectedErrors(element).isEmpty()) { return true; @@ -217,78 +248,12 @@ private boolean hasExpectErrors(Element element) { return false; } - private List createReflectiveHelpers(BytecodeDSLModels modelList, TypeMirror abstractBuilderType) { - List result = new ArrayList<>(); - ProcessorContext ctx = ProcessorContext.getInstance(); - - TypeMirror templateType = modelList.getTemplateType().asType(); - TypeMirror languageClass = modelList.getModels().getFirst().languageClass; - - CodeTypeParameterElement tExtendsBasicInterpreter = new CodeTypeParameterElement(CodeNames.of("T"), templateType); - result.add(createReflectiveHelper("newConfigBuilder", templateType, types.BytecodeConfig_Builder, null)); - result.add(createReflectiveHelper("create", templateType, generic(types.BytecodeRootNodes, tExtendsBasicInterpreter.asType()), tExtendsBasicInterpreter, - new CodeVariableElement(languageClass, "language"), - new CodeVariableElement(types.BytecodeConfig, "config"), - new CodeVariableElement(generic(types.BytecodeParser, ElementHelpers.wildcard(abstractBuilderType, null)), "builder"))); - result.add(createReflectiveHelper("deserialize", templateType, generic(types.BytecodeRootNodes, tExtendsBasicInterpreter.asType()), tExtendsBasicInterpreter, - new CodeVariableElement(languageClass, "language"), - new CodeVariableElement(types.BytecodeConfig, "config"), - new CodeVariableElement(generic(ctx.getDeclaredType(Supplier.class), ctx.getDeclaredType(DataInput.class)), "input"), - new CodeVariableElement(types.BytecodeDeserializer, "callback"))); - - return result; - } - - private static CodeExecutableElement createReflectiveHelper(String name, TypeMirror templateType, DeclaredType returnType, CodeTypeParameterElement typeParameter, CodeVariableElement... params) { - String helperName = "invoke" + Character.toUpperCase(name.charAt(0)) + name.substring(1); - CodeExecutableElement ex = new CodeExecutableElement(Set.of(Modifier.PUBLIC, Modifier.STATIC), returnType, helperName); - - if (!returnType.getTypeArguments().isEmpty()) { - GeneratorUtils.mergeSuppressWarnings(ex, "unchecked"); - } + static final class BytecodeVariantElement extends CodeTypeElement { + BytecodeVariantElement(TypeMirror rootType, TypeMirror languageClass, TypeMirror builderType) { + super(Set.of(PUBLIC, STATIC, Modifier.ABSTRACT), ElementKind.CLASS, null, "BytecodeVariant"); + setSuperClass(generic(ProcessorContext.types().BytecodeDescriptor, rootType, languageClass, builderType)); - if (typeParameter != null) { - ex.getTypeParameters().add(typeParameter); + add(GeneratorUtils.createConstructorUsingFields(Set.of(Modifier.PROTECTED), this)); } - - ex.addParameter(new CodeVariableElement(generic(Class.class, ElementHelpers.wildcard(templateType, null)), "interpreterClass")); - for (CodeVariableElement param : params) { - ex.addParameter(param); - } - - CodeTreeBuilder b = ex.createBuilder(); - ProcessorContext ctx = ProcessorContext.getInstance(); - - b.startTryBlock(); - b.startDeclaration(ctx.getDeclaredType(Method.class), "method"); - b.startCall("interpreterClass.getMethod"); - b.doubleQuote(name); - for (CodeVariableElement param : params) { - b.typeLiteral(param.asType()); - } - b.end(); - b.end(); - - b.startReturn().cast(returnType); - b.startCall("method.invoke"); - b.string("null"); // static method - for (CodeVariableElement param : params) { - b.variable(param); - } - b.end(); - b.end(); - - b.end().startCatchBlock(ctx.getDeclaredType(InvocationTargetException.class), "e"); - b.startIf().string("e.getCause() instanceof RuntimeException err").end().startBlock(); - b.startThrow().string("err").end(); - b.end().startElseBlock(); - b.startThrow().startNew(ctx.getDeclaredType(AssertionError.class)).string("e.getCause()").end(2); - b.end(); - b.end().startCatchBlock(ctx.getDeclaredType(Exception.class), "e"); - b.startThrow().startNew(ctx.getDeclaredType(AssertionError.class)).string("e").end(2); - b.end(); - - return ex; } - } diff --git a/truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/bytecode/generator/BytecodeRootNodeElement.java b/truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/bytecode/generator/BytecodeRootNodeElement.java index 48d871ed317e..d17570b1496c 100644 --- a/truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/bytecode/generator/BytecodeRootNodeElement.java +++ b/truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/bytecode/generator/BytecodeRootNodeElement.java @@ -143,6 +143,7 @@ import com.oracle.truffle.dsl.processor.java.model.CodeTree; import com.oracle.truffle.dsl.processor.java.model.CodeTreeBuilder; import com.oracle.truffle.dsl.processor.java.model.CodeTypeElement; +import com.oracle.truffle.dsl.processor.java.model.CodeTypeMirror; import com.oracle.truffle.dsl.processor.java.model.CodeTypeMirror.ArrayCodeTypeMirror; import com.oracle.truffle.dsl.processor.java.model.CodeTypeMirror.WildcardTypeMirror; import com.oracle.truffle.dsl.processor.java.model.CodeTypeParameterElement; @@ -177,6 +178,10 @@ final class BytecodeRootNodeElement extends CodeTypeElement { // Limit from HotSpot to be classified as a huge method and therefore not be JIT compiled private static final int JAVA_JIT_BYTECODE_LIMIT = 8000; + // The abstract builder and descriptor class (different from builderType if + // GenerateBytecodeTestVariants used) + private final TypeMirror abstractBuilderType; + private final ProcessorContext context = ProcessorContext.getInstance(); private final TruffleTypes types = context.getTypes(); private final BytecodeDSLModel model; @@ -210,7 +215,8 @@ final class BytecodeRootNodeElement extends CodeTypeElement { private final BytecodeRootNodesImplElement bytecodeRootNodesImpl = new BytecodeRootNodesImplElement(); // Helper classes that map instructions/operations/tags to constant integral values. - private final InstructionConstantsElement instructionsElement = new InstructionConstantsElement(); + private final InstructionsElement instructionsElement = new InstructionsElement(); + private final BytecodeDescriptorElement bytecodeDescriptorElement = new BytecodeDescriptorElement(); private final OperationConstantsElement operationsElement = new OperationConstantsElement(); private final FrameTagConstantsElement frameTagsElement; @@ -224,19 +230,25 @@ final class BytecodeRootNodeElement extends CodeTypeElement { private AbstractBytecodeNodeElement abstractBytecodeNode; private TagNodeElement tagNode; private TagRootNodeElement tagRootNode; + private InstructionTracerAccessImplElement instructionTracerAccessImplElement; + private InstructionDescriptorImplElement instructionDescriptorImpl; + private InstructionDescriptorListElement instructionDescriptorList; private InstructionImplElement instructionImpl; private SerializationRootNodeElement serializationRootNode; private Map expectMethods = new HashMap<>(); - BytecodeRootNodeElement(BytecodeDSLModel model) { + BytecodeRootNodeElement(BytecodeDSLModel model, TypeMirror abstractBuilderType) { super(Set.of(PUBLIC, FINAL), ElementKind.CLASS, ElementUtils.findPackageElement(model.getTemplateType()), model.getName()); if (model.hasErrors()) { throw new IllegalArgumentException("Models with errors are not supported."); } + this.model = model; + this.abstractBuilderType = abstractBuilderType == null ? types.BytecodeBuilder : abstractBuilderType; this.bytecodeBuilderType = builder.asType(); - this.parserType = generic(types.BytecodeParser, bytecodeBuilderType); + + this.parserType = generic(types.BytecodeParser, abstractBuilderType == null ? bytecodeBuilderType : abstractBuilderType); setSuperClass(model.getTemplateType().asType()); addField(this, Set.of(PRIVATE, STATIC, FINAL), int[].class, EMPTY_INT_ARRAY, "new int[0]"); @@ -267,13 +279,21 @@ final class BytecodeRootNodeElement extends CodeTypeElement { frameTagsElement = null; } - this.instructionImpl = this.add(new InstructionImplElement()); + if (model.enableInstructionTracing) { + this.instructionTracerAccessImplElement = new InstructionTracerAccessImplElement(); + } + this.instructionDescriptorImpl = new InstructionDescriptorImplElement(); + this.instructionDescriptorList = new InstructionDescriptorListElement(); + + this.instructionImpl = new InstructionImplElement(); if (model.enableTagInstrumentation) { - this.tagNode = this.add(new TagNodeElement()); - this.tagRootNode = this.add(new TagRootNodeElement()); + this.tagNode = new TagNodeElement(); + this.tagRootNode = new TagRootNodeElement(); } + this.add(bytecodeDescriptorElement); + this.abstractBytecodeNode = this.add(new AbstractBytecodeNodeElement()); if (model.enableTagInstrumentation) { tagNode.lazyInit(); @@ -316,12 +336,25 @@ final class BytecodeRootNodeElement extends CodeTypeElement { builder.lazyInit(); this.add(builder); + if (model.enableInstructionTracing) { + this.add(instructionTracerAccessImplElement); + instructionTracerAccessImplElement.lazyInit(); + } + this.add(instructionDescriptorImpl); + this.add(instructionDescriptorList); + this.add(instructionImpl); + + if (model.enableTagInstrumentation) { + this.add(tagNode); + this.add(tagRootNode); + } + + instructionDescriptorImpl.lazyInit(); instructionImpl.lazyInit(); configEncoder = this.add(createBytecodeConfigEncoderClass()); - CodeExecutableElement newConfigBuilder = this.add(new CodeExecutableElement(Set.of(PUBLIC, STATIC), types.BytecodeConfig_Builder, "newConfigBuilder")); - newConfigBuilder.createBuilder().startReturn().startStaticCall(types.BytecodeConfig, "newBuilder").staticReference(configEncoder.asType(), "INSTANCE").end().end(); + this.add(createNewConfigBuilder()); // Define implementations for the public classes that Truffle interpreters interact // with. @@ -332,6 +365,12 @@ final class BytecodeRootNodeElement extends CodeTypeElement { instructionsElement.lazyInit(); this.add(instructionsElement); + bytecodeDescriptorElement.lazyInit(); + + CodeVariableElement descriptor = new CodeVariableElement(Set.of(PUBLIC, STATIC, FINAL), bytecodeDescriptorElement.asType(), "BYTECODE"); + descriptor.createInitBuilder().startNew(bytecodeDescriptorElement.asType()).end(); + this.getEnclosedElements().add(0, descriptor); + operationsElement.lazyInit(); this.add(operationsElement); @@ -417,7 +456,6 @@ final class BytecodeRootNodeElement extends CodeTypeElement { // Define serialization methods and helper fields. if (model.enableSerialization) { this.add(createSerialize()); - this.add(createDoSerialize()); this.add(createDeserialize()); } @@ -459,6 +497,8 @@ final class BytecodeRootNodeElement extends CodeTypeElement { this.add(createGetBytecodeNodeImpl()); this.add(createGetBytecodeRootNode()); + this.add(createInvalidate()); + this.add(createGetRootNodes()); this.addOptional(createCountTowardsStackTraceLimit()); this.add(createGetSourceSection()); @@ -507,6 +547,12 @@ final class BytecodeRootNodeElement extends CodeTypeElement { } } + private CodeExecutableElement createNewConfigBuilder() { + CodeExecutableElement method = new CodeExecutableElement(Set.of(PUBLIC, STATIC), types.BytecodeConfig_Builder, "newConfigBuilder"); + method.createBuilder().startReturn().startStaticCall(types.BytecodeConfig, "newBuilder").staticReference(configEncoder.asType(), "INSTANCE").end().end(); + return method; + } + private CodeExecutableElement createValidateVariadicStackLimit() { CodeExecutableElement validateVariadicStackLimit = new CodeExecutableElement(Set.of(PRIVATE, STATIC), type(int.class), "validateVariadicStackLimit", new CodeVariableElement(type(int.class), "limit")); @@ -1200,22 +1246,7 @@ private CodeExecutableElement createCreate() { """, model.getName())); CodeTreeBuilder b = ex.getBuilder(); - - b.declaration("BytecodeRootNodesImpl", "nodes", "new BytecodeRootNodesImpl(parser, config)"); - b.startAssign("Builder builder").startNew(builder.getSimpleName().toString()); - b.string("language"); - b.string("nodes"); - b.string("config"); - b.end(2); - - b.startStatement().startCall("parser", "parse"); - b.string("builder"); - b.end(2); - - b.startStatement().startCall("builder", "finish").end(2); - - b.startReturn().string("nodes").end(); - + b.statement("return BYTECODE.create(language, config, parser)"); return ex; } @@ -1281,67 +1312,19 @@ Validation will happen (as usual) when the bytes are deserialized. @param parser the parser. """); - CodeTreeBuilder init = CodeTreeBuilder.createBuilder(); - init.startNew("Builder"); - init.string("null"); // language not needed for serialization - init.startGroup(); - init.startNew(bytecodeRootNodesImpl.asType()); - init.string("parser"); - init.staticReference(types.BytecodeConfig, "COMPLETE"); - init.end(2); - init.staticReference(types.BytecodeConfig, "COMPLETE"); - init.end(); - - CodeTreeBuilder b = method.createBuilder(); - - b.declaration("Builder", "builder", init.build()); - - b.startStatement(); - b.startCall("doSerialize"); - b.string("buffer"); - b.string("callback"); - b.string("builder"); - b.string("null"); // existingNodes - b.end(2); - - return withTruffleBoundary(method); - } - - private CodeExecutableElement createDoSerialize() { - CodeExecutableElement method = new CodeExecutableElement(Set.of(PRIVATE, STATIC), type(void.class), "doSerialize"); - method.addParameter(new CodeVariableElement(type(DataOutput.class), "buffer")); - method.addParameter(new CodeVariableElement(types.BytecodeSerializer, "callback")); - method.addParameter(new CodeVariableElement(bytecodeBuilderType, "builder")); - method.addParameter(new CodeVariableElement(generic(List.class, model.getTemplateType().asType()), "existingNodes")); - method.addThrownType(type(IOException.class)); - CodeTreeBuilder b = method.createBuilder(); - - b.startTryBlock(); - - b.startStatement().startCall("builder", "serialize"); - b.string("buffer"); - b.string("callback"); - b.string("existingNodes"); - b.end().end(); - - b.end().startCatchBlock(type(IOError.class), "e"); - b.startThrow().cast(type(IOException.class), "e.getCause()").end(); - b.end(); - - return withTruffleBoundary(method); + b.statement("BYTECODE.serialize(buffer, callback, parser)"); + return method; } private CodeExecutableElement createDeserialize() { CodeExecutableElement method = new CodeExecutableElement(Set.of(PUBLIC, STATIC), generic(types.BytecodeRootNodes, model.getTemplateType().asType()), "deserialize"); - method.addParameter(new CodeVariableElement(model.languageClass, "language")); method.addParameter(new CodeVariableElement(types.BytecodeConfig, "config")); method.addParameter(new CodeVariableElement(generic(Supplier.class, DataInput.class), "input")); method.addParameter(new CodeVariableElement(types.BytecodeDeserializer, "callback")); method.addThrownType(type(IOException.class)); - addJavadoc(method, """ Deserializes a byte sequence to bytecode nodes. The bytes must have been produced by a previous call to {@link #serialize}.").newLine() @@ -1353,15 +1336,8 @@ private CodeExecutableElement createDeserialize() { """); CodeTreeBuilder b = method.createBuilder(); - - b.startTryBlock(); - - b.statement("return create(language, config, (b) -> b.deserialize(input, callback, null))"); - b.end().startCatchBlock(type(IOError.class), "e"); - b.startThrow().cast(type(IOException.class), "e.getCause()").end(); - b.end(); - - return withTruffleBoundary(method); + b.statement("return BYTECODE.deserialize(language, config, input, callback)"); + return method; } private CodeExecutableElement createGetBytecodeNode() { @@ -1383,6 +1359,14 @@ private CodeExecutableElement createGetBytecodeNodeImpl() { return ex; } + private CodeExecutableElement createInvalidate() { + CodeExecutableElement ex = new CodeExecutableElement(Set.of(PRIVATE), type(void.class), "invalidate"); + ex.addParameter(new CodeVariableElement(type(String.class), "reason")); + CodeTreeBuilder b = ex.createBuilder(); + b.statement("this.reportReplace(this, this, reason)"); + return ex; + } + private CodeExecutableElement createGetBytecodeRootNode() { CodeExecutableElement ex = new CodeExecutableElement(Set.of(PRIVATE), this.asType(), "getBytecodeRootNodeImpl"); ex.addParameter(new CodeVariableElement(type(int.class), "index")); @@ -1482,17 +1466,30 @@ private CodeExecutableElement createUpdateBytecode() { b.end(); if (model.hasYieldOperation()) { + if (model.enableInstructionTracing) { + b.declaration(type(int.class), "oldConstantOffset", "oldBytecode.isInstructionTracingEnabled() ? 1 : 0"); + b.declaration(type(int.class), "newConstantOffset", "newBytecode.isInstructionTracingEnabled() ? 1 : 0"); + } + // We need to patch the BytecodeNodes for continuations. b.startFor().string("int i = 0; i < continuationsIndex; i = i + CONTINUATION_LENGTH").end().startBlock(); b.declaration(type(int.class), "constantPoolIndex", "continuations[i + CONTINUATION_OFFSET_CPI]"); b.declaration(type(int.class), "continuationBci", "continuations[i + CONTINUATION_OFFSET_BCI]"); + if (model.enableInstructionTracing) { + b.lineComment("The constant offset is 1 with instruction tracing enabled. See INSTRUCTION_TRACER_CONSTANT_INDEX."); + b.lineComment("We need to align constant indices for the continuation root node updates."); + b.declaration(type(int.class), "oldConstantPoolIndex", "constantPoolIndex - newConstantOffset + oldConstantOffset"); + } else { + b.declaration(type(int.class), "oldConstantPoolIndex", "constantPoolIndex"); + } + b.startDeclaration(continuationRootNodeImpl.asType(), "continuationRootNode"); b.cast(continuationRootNodeImpl.asType()); - b.string("oldBytecode.constants[constantPoolIndex]"); + b.string("oldBytecode.constants[oldConstantPoolIndex]"); b.end(); - b.startAssert().string("oldBytecode.constants[constantPoolIndex] == newBytecode.constants[constantPoolIndex]").end(); + b.startAssert().string("oldBytecode.constants[oldConstantPoolIndex] == newBytecode.constants[constantPoolIndex]").end(); b.lineComment("locations may become null if they are no longer reachable."); b.declaration(types.BytecodeLocation, "newLocation", "continuationBci == -1 ? null : newBytecode.getBytecodeLocation(continuationBci)"); @@ -1505,7 +1502,7 @@ private CodeExecutableElement createUpdateBytecode() { b.end(2); b.end(); - b.end(); + b.end(); // for } } @@ -1608,13 +1605,17 @@ private CodeExecutableElement createDecode2(CodeTypeElement type) { b.end(); long mask = 1L; - if (model.getInstrumentations().size() > MAX_INSTRUMENTATIONS) { + if (model.getInstrumentationsCount() > MAX_INSTRUMENTATIONS) { throw new AssertionError("Unsupported instrumentation size."); } if (model.getProvidedTags().size() > MAX_TAGS) { throw new AssertionError("Unsupported instrumentation size."); } + if (model.traceInstructionInstrumentationIndex != -1) { + mask |= 1L << (INSTRUMENTATION_OFFSET + model.traceInstructionInstrumentationIndex); + } + for (int i = 0; i < model.getInstrumentations().size(); i++) { mask |= 1L << (INSTRUMENTATION_OFFSET + i); } @@ -1631,9 +1632,17 @@ private CodeExecutableElement createEncodeInstrumentation() { CodeExecutableElement encodeInstrumentation = GeneratorUtils.override(types.BytecodeConfigEncoder, "encodeInstrumentation", new String[]{"c"}); CodeTreeBuilder b = encodeInstrumentation.createBuilder(); - if (!model.getInstrumentations().isEmpty()) { + if (model.hasInstrumentations()) { b.declaration("long", "encoding", "0L"); - boolean elseIf = false; + boolean elseIf = b.startIf(false); + b.string("c == ").typeLiteral(types.InstructionTracer); + b.end().startBlock(); + if (model.enableInstructionTracing) { + b.statement("encoding |= 0x" + Integer.toHexString(1 << model.traceInstructionInstrumentationIndex)); + } else { + b.lineComment("Instruction tracing disabled"); + } + b.end(); for (CustomOperationModel customOperation : model.getInstrumentations()) { elseIf = b.startIf(elseIf); b.string("c == ").typeLiteral(customOperation.operation.instruction.nodeType.asType()); @@ -1646,7 +1655,7 @@ private CodeExecutableElement createEncodeInstrumentation() { b.startThrow().startNew(type(IllegalArgumentException.class)).startCall("String.format").doubleQuote( "Invalid instrumentation specified. Instrumentation '%s' does not exist or is not an instrumentation for '" + ElementUtils.getQualifiedName(model.templateType) + "'. " + "Instrumentations can be specified using the @Instrumentation annotation.").string("c.getName()").end().end().end(); - if (!model.getInstrumentations().isEmpty()) { + if (model.hasInstrumentations()) { b.end(); // else b.startReturn().string("encoding << 1").end(); } @@ -1670,6 +1679,13 @@ private CodeExecutableElement createPrepareForCall() { b.startIf().string("!this.nodes.isParsed()").end().startBlock(); emitThrowIllegalStateException(ex, b, "A call target cannot be created until bytecode parsing completes. Request a call target after the parse is complete instead."); b.end(); + + b.startStatement(); + b.startCall("BYTECODE.prepareForCall"); + b.startCall("getLanguage").typeLiteral(model.languageClass).end(); + b.string("this"); + b.end(); + b.end(); return ex; } @@ -2645,6 +2661,19 @@ private static String childString(int numChildren) { return numChildren + ((numChildren == 1) ? " child" : " children"); } + private void emitValidateInstructionTracer(CodeTreeBuilder b) { + b.startStatement().startStaticCall(type(Objects.class), "requireNonNull").string("tracer").end().end(); + b.declaration("var", "exclusiveDescriptor", "tracer.getExclusiveBytecodeDescriptor()"); + b.startIf().string("exclusiveDescriptor != null && exclusiveDescriptor != BYTECODE").end().startBlock(); + b.startThrow().startNew(type(IllegalArgumentException.class)); + b.startStaticCall(type(String.class), "format"); + b.doubleQuote("The passed instruction tracer is exclusive to %s, but it was installed for %s."); + b.string("exclusiveDescriptor").string("BYTECODE"); + b.end(); // static call + b.end().end(); // throw, new + b.end(); // if block + } + /** * Whether we store the bci at the beginning of an instruction execute, e.g. if all * specializations require it or some other condition requires it. @@ -2806,7 +2835,7 @@ void lazyInit() { addJavadoc(this, """ Builder class to generate bytecode. An interpreter can invoke this class with its {@link com.oracle.truffle.api.bytecode.BytecodeParser} to generate bytecode. """); - this.setSuperClass(model.abstractBuilderType); + this.setSuperClass(abstractBuilderType); this.setEnclosingElement(BytecodeRootNodeElement.this); this.rootStackElement.lazyInit(); @@ -2915,6 +2944,11 @@ void lazyInit() { this.add(createDeserialize()); } + if (model.enableInstructionTracing) { + this.add(createFindOrCreateInstructionTracer()); + this.add(new CodeVariableElement(Set.of(PRIVATE, STATIC, FINAL), type(int.class), "INSTRUCTION_TRACER_CONSTANT_INDEX")).createInitBuilder().string("0"); + } + this.add(createToString()); this.add(createFailState()); this.add(createFailArgument()); @@ -2951,6 +2985,8 @@ private CodeExecutableElement createReparseConstructor() { b.statement("this.builtNodes = new ArrayList<>()"); b.startAssign("this.state").startStaticCall(rootStackElement.asType(), "acquire").end().end(); + b.statement("this.state.instrumentations = instrumentations"); + return ctor; } @@ -2958,7 +2994,7 @@ private CodeExecutableElement createParseConstructor() { CodeExecutableElement ctor = new CodeExecutableElement(Set.of(PRIVATE), null, "Builder"); ctor.addParameter(new CodeVariableElement(model.languageClass, "language")); ctor.addParameter(new CodeVariableElement(bytecodeRootNodesImpl.asType(), "nodes")); - ctor.addParameter(new CodeVariableElement(types.BytecodeConfig, "config")); + ctor.addParameter(new CodeVariableElement(type(long.class), "configEncoding")); CodeTreeBuilder javadoc = ctor.createDocBuilder(); javadoc.startJavadoc(); @@ -2971,7 +3007,7 @@ private CodeExecutableElement createParseConstructor() { b.statement("this.language = language"); b.statement("this.nodes = nodes"); b.statement("this.reparseReason = null"); - b.statement("long encoding = BytecodeConfigEncoderImpl.decode(config)"); + b.statement("long encoding = configEncoding"); b.statement("this.tags = (int)((encoding >> " + TAG_OFFSET + ") & 0xFFFF_FFFF)"); b.statement("this.instrumentations = (int)((encoding >> " + INSTRUMENTATION_OFFSET + ") & 0x7FFF_FFFF)"); b.statement("this.parseSources = (encoding & 0x1) != 0"); @@ -2980,6 +3016,7 @@ private CodeExecutableElement createParseConstructor() { b.statement("this.builtNodes = new ArrayList<>()"); b.startAssign("this.state").startStaticCall(rootStackElement.asType(), "acquire").end().end(); + b.statement("this.state.instrumentations = instrumentations"); return ctor; } @@ -3321,6 +3358,31 @@ private CodeExecutableElement createSerializeFinallyGenerator() { return method; } + private CodeExecutableElement createFindOrCreateInstructionTracer() { + CodeExecutableElement method = new CodeExecutableElement(Set.of(PRIVATE), + instructionTracerAccessImplElement.asType(), "findOrCreateInstructionTracer"); + CodeTreeBuilder b = method.createBuilder(); + + b.declaration(instructionTracerAccessImplElement.asType(), "tracer", "null"); + b.startIf().string("reparseReason != null").end().startBlock(); + b.startDeclaration(arrayOf(type(Object.class)), "previousConstants"); + b.string("((").type(abstractBytecodeNode.asType()).string(") nodes.getNode(numRoots - 1).getBytecodeNode()).constants"); + b.end(); + b.startIf().string("INSTRUCTION_TRACER_CONSTANT_INDEX < previousConstants.length && previousConstants[INSTRUCTION_TRACER_CONSTANT_INDEX] instanceof ").type( + instructionTracerAccessImplElement.asType()).string( + " t").end().startBlock(); + b.statement("tracer = t"); + b.end(); // if constant + b.end(); // if reparse + + b.startIf().string("tracer == null").end().startBlock(); + b.startAssign("tracer").startNew(instructionTracerAccessImplElement.asType()).string("language").end().end(); + b.end(); + + b.statement("return tracer"); + return method; + } + private CodeExecutableElement createDeserialize() { CodeExecutableElement method = new CodeExecutableElement(Set.of(PRIVATE), type(void.class), "deserialize"); @@ -4144,7 +4206,7 @@ private CodeExecutableElement createBegin(OperationModel operation) { } break; case TAG: - buildEmitInstruction(b, model.tagEnterInstruction, "nodeId"); + buildEmitInstruction(b, null, model.tagEnterInstruction, "nodeId"); break; case WHILE: case RETURN: @@ -4267,8 +4329,8 @@ private CodeExecutableElement createBeginRoot(OperationModel rootOperation) { b.startIf().string("state.rootOperationSp != -1").end().startBlock(); // { b.statement("state = state.getNext()"); b.end(); // } - b.statement("state.rootOperationSp = state.operationSp"); + b.statement("state.instrumentations = instrumentations"); Map initValues = new HashMap<>(); initValues.put(operationFields.index, safeCastShort("numRoots++")); @@ -4276,8 +4338,16 @@ private CodeExecutableElement createBeginRoot(OperationModel rootOperation) { b.tree(createOperationConstant(rootOperation)); b.end(2); b.tree(operationStack.createInitialize(rootOperation, "operation", initValues)); - b.end(); + + if (model.enableInstructionTracing) { + int mask = 1 << model.traceInstructionInstrumentationIndex; + b.startIf().string("(instrumentations & ").string("0x", Integer.toHexString(mask)).string(") != 0").end().startBlock(); + b.statement("int constantIndex = state.addConstant(findOrCreateInstructionTracer())"); + b.statement("assert constantIndex == INSTRUCTION_TRACER_CONSTANT_INDEX"); + b.end(); + } + b.startIf().string("reparseReason == null").end().startBlock(); b.statement("builtNodes.add(null)"); b.startIf().string("builtNodes.size() > Short.MAX_VALUE").end().startBlock(); @@ -4748,7 +4818,7 @@ private CodeExecutableElement createEnd(OperationModel operation) { b.statement("markReachable(", operationStack.read(operation, operationFields.thenReachable), " || ", operationStack.read(operation, operationFields.elseReachable), ")"); if (model.usesBoxingElimination()) { - buildEmitInstruction(b, operation.instruction, emitMergeConditionalArguments(operation.instruction)); + buildEmitInstruction(b, null, operation.instruction, emitMergeConditionalArguments(operation.instruction)); } break; case TRY_CATCH: @@ -4830,12 +4900,12 @@ private CodeExecutableElement createEnd(OperationModel operation) { * return at any point and we need a point where we can continue. */ b.statement("markReachable(true)"); - buildEmitInstruction(b, model.tagLeaveValueInstruction, args); + buildEmitInstruction(b, null, model.tagLeaveValueInstruction, args); b.statement("state.doCreateExceptionHandler(", operationStack.read(operation, operationFields.handlerStartBci), ", state.bci, HANDLER_TAG_EXCEPTIONAL, ", operationStack.read(operation, operationFields.nodeId), ", ", operationStack.read(operation, operationFields.startStackHeight), ")"); b.end().startElseBlock(); - buildEmitInstruction(b, model.tagLeaveValueInstruction, args); + buildEmitInstruction(b, null, model.tagLeaveValueInstruction, args); b.end(); emitCallAfterChild(b, operation, "true", "state.bci - " + model.tagLeaveValueInstruction.getInstructionLength()); @@ -4848,12 +4918,12 @@ private CodeExecutableElement createEnd(OperationModel operation) { * return at any point and we need a point where we can continue. */ b.statement("markReachable(true)"); - buildEmitInstruction(b, model.tagLeaveVoidInstruction, operationStack.read(operation, operationFields.nodeId)); + buildEmitInstruction(b, null, model.tagLeaveVoidInstruction, operationStack.read(operation, operationFields.nodeId)); b.statement("state.doCreateExceptionHandler(", operationStack.read(operation, operationFields.handlerStartBci), ", state.bci, HANDLER_TAG_EXCEPTIONAL, ", operationStack.read(operation, operationFields.nodeId), ", ", operationStack.read(operation, operationFields.startStackHeight), ")"); b.end().startElseBlock(); - buildEmitInstruction(b, model.tagLeaveVoidInstruction, operationStack.read(operation, operationFields.nodeId)); + buildEmitInstruction(b, null, model.tagLeaveVoidInstruction, operationStack.read(operation, operationFields.nodeId)); b.end(); emitCallAfterChild(b, operation, "false", "-1"); @@ -5022,7 +5092,7 @@ private void createEndLocalsBlock(CodeTreeBuilder b, OperationModel operation) { b.statement("state.locals[", operationStack.read(operation, operationFields.locals), "[index] + LOCALS_OFFSET_END_BCI] = state.bci"); if (operation.kind == OperationKind.BLOCK) { - buildEmitInstruction(b, model.clearLocalInstruction, + buildEmitInstruction(b, null, model.clearLocalInstruction, safeCastShort("state.locals[" + operationStack.read(operation, operationFields.locals) + "[index] + LOCALS_OFFSET_FRAME_INDEX]")); } b.end(); // for @@ -5200,21 +5270,40 @@ private CodeExecutableElement createEndRoot(OperationModel rootOperation) { b.declaration(abstractBytecodeNode.asType(), "oldBytecodeNode", "result.bytecode"); b.statement("assert result.maxLocals == " + maxLocals()); b.statement("assert result.nodes == this.nodes"); - b.statement("assert constants_.length == oldBytecodeNode.constants.length"); b.startAssert(); b.string("result.getFrameDescriptor().getNumberOfSlots() == "); buildFrameSize(b); b.end(); if (model.hasYieldOperation()) { + + if (model.enableInstructionTracing) { + b.declaration(type(int.class), "oldConstantOffset", "oldBytecodeNode.isInstructionTracingEnabled() ? 1 : 0"); + b.startDeclaration(type(int.class), "newConstantOffset"); + int mask = 1 << model.traceInstructionInstrumentationIndex; + b.string("(this.instrumentations & ").string("0x", Integer.toHexString(mask)).string(") != 0 ? 1 : 0"); + b.end(); // delcaration + b.statement("assert constants_.length - newConstantOffset == oldBytecodeNode.constants.length - oldConstantOffset"); + } else { + b.statement("assert constants_.length == oldBytecodeNode.constants.length"); + } + /** * Copy ContinuationRootNodes into new constant array *before* we update the new * bytecode, otherwise a racy thread may read it as null */ b.startFor().string("int i = 0; i < continuationsIndex; i = i + CONTINUATION_LENGTH").end().startBlock(); b.declaration(type(int.class), "constantPoolIndex", "continuations[i + CONTINUATION_OFFSET_CPI]"); + if (model.enableInstructionTracing) { + b.lineComment("The constant offset is 1 with instruction tracing enabled. See INSTRUCTION_TRACER_CONSTANT_INDEX."); + b.lineComment("We need to align constant indices for the continuation root node updates."); + b.declaration(type(int.class), "oldConstantPoolIndex", "constantPoolIndex - newConstantOffset + oldConstantOffset"); + } else { + b.declaration(type(int.class), "oldConstantPoolIndex", "constantPoolIndex"); + } b.startDeclaration(continuationRootNodeImpl.asType(), "continuationRootNode"); - b.cast(continuationRootNodeImpl.asType()).string("oldBytecodeNode.constants[constantPoolIndex]"); + b.cast(continuationRootNodeImpl.asType()).string("oldBytecodeNode.constants[oldConstantPoolIndex]"); + b.end(); b.startStatement().startCall("ACCESS.writeObject"); @@ -5476,8 +5565,8 @@ private void emitFinallyHandlersAfterTry(CodeTreeBuilder b, OperationModel op) { b.tree(operationStack.write(op, operationFields.tryReachable, operationStack.read(op, operationFields.tryReachable) + " && state.reachable")); b.startIf().string("state.reachable").end().startBlock(); - b.tree(operationStack.write(op, operationFields.endBranchFixupBci, "state.bci + " + model.branchInstruction.findImmediate(ImmediateKind.BYTECODE_INDEX, "branch_target").offset())); - buildEmitInstruction(b, model.branchInstruction, new String[]{UNINIT}); + buildEmitInstruction(b, "branchTargetBci", model.branchInstruction, new String[]{UNINIT}); + b.tree(operationStack.write(op, operationFields.endBranchFixupBci, "branchTargetBci + " + model.branchInstruction.findImmediate(ImmediateKind.BYTECODE_INDEX, "branch_target").offset())); b.end(); b.startIf().string(operationStack.read(op, operationFields.operationReachable)).end().startBlock(); @@ -5502,7 +5591,7 @@ private void emitFinallyHandlersAfterTry(CodeTreeBuilder b, OperationModel op) { b.lineComment("emit handler for exceptional case"); b.statement("state.currentStackHeight = handlerSp"); b.statement("doEmitFinallyHandler(operation, state.operationSp - 1)"); - buildEmitInstruction(b, model.throwInstruction); + buildEmitInstruction(b, null, model.throwInstruction); } } @@ -5566,42 +5655,30 @@ private void buildEmitOperationInstruction(CodeTreeBuilder b, OperationModel ope case LOAD_ARGUMENT -> new String[]{safeCastShort(operation.getOperationBeginArgumentName(0))}; case LOAD_CONSTANT -> new String[]{"state.addConstant(" + operation.getOperationBeginArgumentName(0) + ")"}; case YIELD -> { - String constantPoolIndex = buildEmitYieldInitializer(b, operation); - yield new String[]{constantPoolIndex}; + b.declaration(type(short.class), "constantPoolIndex", "state.allocateContinuationConstant()"); + yield new String[]{"constantPoolIndex"}; } case CUSTOM, CUSTOM_YIELD, CUSTOM_INSTRUMENTATION -> buildCustomInitializer(b, operation, operation.instruction, customChildBci, constantOperandValues); case CUSTOM_SHORT_CIRCUIT -> throw new AssertionError("Tried to emit a short circuit instruction directly. These operations should only be emitted implicitly."); default -> throw new AssertionError("Reached an operation " + operation.name + " that cannot be initialized. This is a bug in the Bytecode DSL processor."); }; - buildEmitInstruction(b, operation.instruction, args); - } - - private String buildEmitYieldInitializer(CodeTreeBuilder b, OperationModel operation) { - b.declaration(type(short.class), "constantPoolIndex", "state.allocateContinuationConstant()"); - - b.declaration(type(int.class), "continuationBci"); - b.startIf().string("state.reachable").end().startBlock(); - b.statement("continuationBci = state.bci + " + operation.instruction.getInstructionLength()); - b.end().startElseBlock(); - b.statement("continuationBci = -1"); - b.end(); - - b.startStatement().startCall("state.doEmitContinuation"); - b.string("constantPoolIndex").string("continuationBci"); - int stackEffect = getStackEffect(operation.instruction); - b.startGroup().string("state.currentStackHeight"); - if (stackEffect > 0) { - b.string(" + " + stackEffect); - } else if (stackEffect < 0) { - b.string(" - " + (-stackEffect)); + switch (operation.kind) { + case CUSTOM_YIELD: + case YIELD: + buildEmitInstruction(b, "continuationBci", operation.instruction, args); + b.startStatement().startCall("state.doEmitContinuation"); + b.string("constantPoolIndex").string("continuationBci != -1 ? continuationBci + " + operation.instruction.getInstructionLength() + " : -1"); + b.string("state.currentStackHeight"); + b.end(); + b.end(2); // statement + call + b.end(); + break; + default: + buildEmitInstruction(b, null, operation.instruction, args); + break; } - b.end(); - - b.end(2); // statement + call - b.end(); - return "constantPoolIndex"; } private void buildEmitLabel(CodeTreeBuilder b, OperationModel operation) { @@ -5673,23 +5750,23 @@ private void buildEmitBranch(CodeTreeBuilder b, OperationModel operation) { b.lineComment("Pop any extra values off the stack before branching."); b.declaration(type(int.class), "stackHeightBeforeBranch", "state.currentStackHeight"); b.startWhile().string("targetStackHeight != state.currentStackHeight").end().startBlock(); - buildEmitInstruction(b, model.popInstruction, emitPopArguments("-1")); + buildEmitInstruction(b, null, model.popInstruction, emitPopArguments("-1")); b.end(); b.lineComment("If the branch is not taken (e.g., control branches over it) the values are still on the stack."); b.statement("state.currentStackHeight = stackHeightBeforeBranch"); b.startIf().string("state.reachable").end().startBlock(); + + buildEmitInstruction(b, "branchTargetBci", model.branchInstruction, UNINIT); /** * Mark the branch target as uninitialized. Add this location to a work list to be * processed once the label is defined. */ b.startStatement().startCall("state.registerUnresolvedLabel"); b.string("labelImpl"); - b.string("state.bci + " + model.branchInstruction.getImmediate(ImmediateKind.BYTECODE_INDEX).offset()); + b.string("branchTargetBci + " + model.branchInstruction.getImmediate(ImmediateKind.BYTECODE_INDEX).offset()); b.end(2); b.end(); // if reachable - - buildEmitInstruction(b, model.branchInstruction, UNINIT); } private void buildEmitLoadException(CodeTreeBuilder b, OperationModel operation) { @@ -5722,7 +5799,7 @@ private void buildEmitLoadException(CodeTreeBuilder b, OperationModel operation) b.startThrow().startCall("failState").doubleQuote("LoadException can only be used in the catch operation of a TryCatch/TryCatchOtherwise operation in the current root.").end().end(); b.end(); - buildEmitInstruction(b, operation.instruction, "safeCastShort(exceptionStackHeight)"); + buildEmitInstruction(b, null, operation.instruction, "safeCastShort(exceptionStackHeight)"); } private CodeExecutableElement createValidateRootOperationBegin() { @@ -6019,7 +6096,8 @@ private String[] buildCustomInitializer(CodeTreeBuilder b, OperationModel operat yield constantOperandValues.get(constantIndex++); } else if (operation.kind == OperationKind.CUSTOM_YIELD) { // The continuation root is the last constant, after constant operands. - yield buildEmitYieldInitializer(b, operation); + b.declaration(type(short.class), "constantPoolIndex", "state.allocateContinuationConstant()"); + yield "constantPoolIndex"; } else { throw new AssertionError("Operation has more constant immediates than constant operands: " + operation); } @@ -6081,7 +6159,7 @@ private CodeExecutableElement createBeforeChild() { childBci = operationStack.read(op, operationFields.childBci); } - buildEmitInstruction(b, model.popInstruction, emitPopArguments(childBci)); + buildEmitInstruction(b, null, model.popInstruction, emitPopArguments(childBci)); b.end(); b.statement("break"); } else if (op.kind == OperationKind.CUSTOM_SHORT_CIRCUIT) { @@ -6093,13 +6171,20 @@ private CodeExecutableElement createBeforeChild() { // If this operation has a converter, convert the value. if (shortCircuitModel.convertsOperands()) { if (shortCircuitModel.duplicatesOperandOnStack()) { - buildEmitInstruction(b, model.dupInstruction); + buildEmitInstruction(b, null, model.dupInstruction); } buildEmitBooleanConverterInstruction(b, op.instruction); } + if (model.usesBoxingElimination()) { + b.tree(operationStack.write(op, operationFields.shortCircuitBci, "state.bci")); + } + + // Emit the boolean check. + buildEmitInstruction(b, "shortCircuitBci", op.instruction, emitShortCircuitArguments(op.instruction)); + // Remember the short circuit instruction's bci so we can patch the branch bci. - b.startIf().string("state.reachable").end().startBlock(); + b.startIf().string("shortCircuitBci != -1").end().startBlock(); b.declaration(type(int[].class), "branchFixupBcis", operationStack.read(op, operationFields.branchFixupBcis)); b.declaration(type(int.class), "numBranchFixupBcis", operationStack.read(op, operationFields.numBranchFixupBcis)); @@ -6107,18 +6192,11 @@ private CodeExecutableElement createBeforeChild() { b.startAssign("branchFixupBcis").startStaticCall(type(Arrays.class), "copyOf").string("branchFixupBcis").string("branchFixupBcis.length * 2").end().end(); b.tree(operationStack.write(op, operationFields.branchFixupBcis, "branchFixupBcis")); b.end(); - b.statement("branchFixupBcis[numBranchFixupBcis] = state.bci + " + op.instruction.getImmediate("branch_target").offset()); + b.statement("branchFixupBcis[numBranchFixupBcis] = shortCircuitBci + " + op.instruction.getImmediate("branch_target").offset()); b.tree(operationStack.write(op, operationFields.numBranchFixupBcis, "numBranchFixupBcis + 1")); b.end(); // reachable - if (model.usesBoxingElimination()) { - b.tree(operationStack.write(op, operationFields.shortCircuitBci, "state.bci")); - } - - // Emit the boolean check. - buildEmitInstruction(b, op.instruction, emitShortCircuitArguments(op.instruction)); - b.end(); // childIndex != 0 b.statement("break"); @@ -6227,7 +6305,7 @@ private void buildEmitBooleanConverterInstruction(CodeTreeBuilder b, Instruction default -> throw new AssertionError(String.format("Boolean converter instruction had unexpected encoding: %s", immediates)); }; } - buildEmitInstruction(b, booleanConverter, args); + buildEmitInstruction(b, null, booleanConverter, args); } private CodeExecutableElement createAfterChild() { @@ -6271,7 +6349,7 @@ private CodeExecutableElement createAfterChild() { } else if (valueChildren.isEmpty()) { // Simplification: each child should not be value producing. b.startIf().string("producedValue").end().startBlock(); - buildEmitInstruction(b, model.popInstruction, emitPopArguments("childBci")); + buildEmitInstruction(b, null, model.popInstruction, emitPopArguments("childBci")); b.end(); } else { // Otherwise, partition by value/not value producing. @@ -6310,7 +6388,7 @@ private CodeExecutableElement createAfterChild() { } b.string(") && producedValue"); b.end().startBlock(); - buildEmitInstruction(b, model.popInstruction, emitPopArguments("childBci")); + buildEmitInstruction(b, null, model.popInstruction, emitPopArguments("childBci")); b.end(); } } @@ -6330,12 +6408,13 @@ private CodeExecutableElement createAfterChild() { break; case IF_THEN: b.startIf().string("childIndex == 0").end().startBlock(); - b.startIf().string("state.reachable").end().startBlock(); + buildEmitInstruction(b, "branchFalseBci", model.branchFalseInstruction, emitBranchFalseArguments(model.branchFalseInstruction)); + b.startIf().string("branchFalseBci != -1").end().startBlock(); b.tree(operationStack.write(op, operationFields.falseBranchFixupBci, - "state.bci + " + model.branchFalseInstruction.findImmediate(ImmediateKind.BYTECODE_INDEX, "branch_target").offset())); + "branchFalseBci + " + model.branchFalseInstruction.findImmediate(ImmediateKind.BYTECODE_INDEX, "branch_target").offset())); b.end(); - buildEmitInstruction(b, model.branchFalseInstruction, emitBranchFalseArguments(model.branchFalseInstruction)); + b.end().startElseBlock(); b.statement("int toUpdate = ", operationStack.read(op, operationFields.falseBranchFixupBci)); b.startIf().string("toUpdate != ", UNINIT).end().startBlock(); @@ -6345,16 +6424,16 @@ private CodeExecutableElement createAfterChild() { break; case IF_THEN_ELSE: b.startIf().string("childIndex == 0").end().startBlock(); - b.startIf().string("state.reachable").end().startBlock(); + buildEmitInstruction(b, "branchFalseBci", model.branchFalseInstruction, emitBranchFalseArguments(model.branchFalseInstruction)); + b.startIf().string("branchFalseBci != -1").end().startBlock(); b.tree(operationStack.write(op, operationFields.falseBranchFixupBci, - "state.bci + " + model.branchFalseInstruction.findImmediate(ImmediateKind.BYTECODE_INDEX, "branch_target").offset())); + "branchFalseBci + " + model.branchFalseInstruction.findImmediate(ImmediateKind.BYTECODE_INDEX, "branch_target").offset())); b.end(); - buildEmitInstruction(b, model.branchFalseInstruction, emitBranchFalseArguments(model.branchFalseInstruction)); b.end().startElseIf().string("childIndex == 1").end().startBlock(); - b.startIf().string("state.reachable").end().startBlock(); - b.tree(operationStack.write(op, operationFields.endBranchFixupBci, "state.bci + " + model.branchInstruction.getImmediate(ImmediateKind.BYTECODE_INDEX).offset())); + buildEmitInstruction(b, "branchBci", model.branchInstruction, new String[]{UNINIT}); + b.startIf().string("branchBci != -1").end().startBlock(); + b.tree(operationStack.write(op, operationFields.endBranchFixupBci, "branchBci + " + model.branchInstruction.getImmediate(ImmediateKind.BYTECODE_INDEX).offset())); b.end(); - buildEmitInstruction(b, model.branchInstruction, new String[]{UNINIT}); b.statement("int toUpdate = ", operationStack.read(op, operationFields.falseBranchFixupBci)); b.startIf().string("toUpdate != ", UNINIT).end().startBlock(); b.statement(writeInt("state.bc", "toUpdate", "state.bci")); @@ -6369,21 +6448,21 @@ private CodeExecutableElement createAfterChild() { case CONDITIONAL: b.startIf().string("childIndex == 0").end().startBlock(); if (model.usesBoxingElimination()) { - buildEmitInstruction(b, model.dupInstruction); + buildEmitInstruction(b, null, model.dupInstruction); } - b.startIf().string("state.reachable").end().startBlock(); + buildEmitInstruction(b, "branchFalseBci", model.branchFalseInstruction, emitBranchFalseArguments(model.branchFalseInstruction)); + b.startIf().string("branchFalseBci != -1").end().startBlock(); b.tree(operationStack.write(op, operationFields.falseBranchFixupBci, - "state.bci + " + model.branchFalseInstruction.findImmediate(ImmediateKind.BYTECODE_INDEX, "branch_target").offset())); + "branchFalseBci + " + model.branchFalseInstruction.findImmediate(ImmediateKind.BYTECODE_INDEX, "branch_target").offset())); b.end(); - buildEmitInstruction(b, model.branchFalseInstruction, emitBranchFalseArguments(model.branchFalseInstruction)); b.end().startElseIf().string("childIndex == 1").end().startBlock(); if (model.usesBoxingElimination()) { b.tree(operationStack.write(op, operationFields.getChildBci(0, false), "childBci")); } b.startIf().string("state.reachable").end().startBlock(); - b.tree(operationStack.write(op, operationFields.endBranchFixupBci, "state.bci + " + model.branchInstruction.getImmediate(ImmediateKind.BYTECODE_INDEX).offset())); - buildEmitInstruction(b, model.branchInstruction, new String[]{UNINIT}); + buildEmitInstruction(b, "branchBci", model.branchInstruction, new String[]{UNINIT}); + b.tree(operationStack.write(op, operationFields.endBranchFixupBci, "branchBci + " + model.branchInstruction.getImmediate(ImmediateKind.BYTECODE_INDEX).offset())); // we have to adjust the stack for the third child b.end(); b.statement("state.currentStackHeight -= 1"); @@ -6405,10 +6484,10 @@ private CodeExecutableElement createAfterChild() { case WHILE: InstructionImmediate branchTarget = model.branchFalseInstruction.findImmediate(ImmediateKind.BYTECODE_INDEX, "branch_target"); b.startIf().string("childIndex == 0").end().startBlock(); - b.startIf().string("state.reachable").end().startBlock(); - b.tree(operationStack.write(op, operationFields.endBranchFixupBci, "state.bci + " + branchTarget.offset())); + buildEmitInstruction(b, "branchFalseBci", model.branchFalseInstruction, emitBranchFalseArguments(model.branchFalseInstruction)); + b.startIf().string("branchFalseBci != -1").end().startBlock(); + b.tree(operationStack.write(op, operationFields.endBranchFixupBci, "branchFalseBci + " + branchTarget.offset())); b.end(); - buildEmitInstruction(b, model.branchFalseInstruction, emitBranchFalseArguments(model.branchFalseInstruction)); b.end().startElseBlock(); b.statement("int toUpdate = ", operationStack.read(op, operationFields.endBranchFixupBci)); b.startIf().string("toUpdate != ", UNINIT).end().startBlock(); @@ -6428,7 +6507,7 @@ private CodeExecutableElement createAfterChild() { throw new AssertionError("branch profile width changed"); } String readBranchProfile = readInt("state.bc", "toUpdate + " + offset + " /* loop branch profile */"); - buildEmitInstruction(b, model.branchBackwardInstruction, new String[]{operationStack.read(op, operationFields.whileStartBci), readBranchProfile}); + buildEmitInstruction(b, null, model.branchBackwardInstruction, new String[]{operationStack.read(op, operationFields.whileStartBci), readBranchProfile}); b.statement(writeInt("state.bc", "toUpdate", "state.bci")); b.end(); @@ -6440,8 +6519,8 @@ private CodeExecutableElement createAfterChild() { b.declaration(type(int.class), "tryEndBci", "state.bci"); b.startIf().string(operationStack.read(op, operationFields.tryReachable)).end().startBlock(); - b.tree(operationStack.write(op, operationFields.endBranchFixupBci, "state.bci + " + model.branchInstruction.getImmediate(ImmediateKind.BYTECODE_INDEX).offset())); - buildEmitInstruction(b, model.branchInstruction, new String[]{UNINIT}); + buildEmitInstruction(b, "branchBci", model.branchInstruction, new String[]{UNINIT}); + b.tree(operationStack.write(op, operationFields.endBranchFixupBci, "branchBci + " + model.branchInstruction.getImmediate(ImmediateKind.BYTECODE_INDEX).offset())); b.end(); // if tryReachable b.declaration(type(int.class), "handlerSp", "state.currentStackHeight + 1"); @@ -6459,7 +6538,7 @@ private CodeExecutableElement createAfterChild() { b.startElseIf().string("childIndex == 1").end().startBlock(); b.lineComment("pop the exception"); - buildEmitInstruction(b, model.popInstruction, emitPopArguments("-1")); + buildEmitInstruction(b, null, model.popInstruction, emitPopArguments("-1")); emitFixFinallyBranchBci(b, op); b.end(); break; @@ -6468,7 +6547,7 @@ private CodeExecutableElement createAfterChild() { emitFinallyHandlersAfterTry(b, op); b.end().startElseBlock(); b.lineComment("pop the exception"); - buildEmitInstruction(b, model.popInstruction, emitPopArguments("-1")); + buildEmitInstruction(b, null, model.popInstruction, emitPopArguments("-1")); emitFixFinallyBranchBci(b, op); b.end(); @@ -6703,7 +6782,7 @@ private CodeExecutableElement createDoEmitVariadicEnd() { if (model.hasVariadicReturn) { b.statement("assert dynamicArgumentsLength == 0"); } - buildEmitInstruction(b, model.emptyVariadicInstruction); + buildEmitInstruction(b, null, model.emptyVariadicInstruction); b.end().startElseIf().string("count <= VARIADIC_STACK_LIMIT").end().startBlock(); @@ -6714,7 +6793,7 @@ private CodeExecutableElement createDoEmitVariadicEnd() { b.end(); } - buildEmitInstructionStackEffect(b, model.createVariadicInstruction, "-count + 1", createCreateVariadicArguments("offset", "(short)count", "(short)mergeCount")); + buildEmitInstructionStackEffect(b, null, model.createVariadicInstruction, "-count + 1", createCreateVariadicArguments("offset", "(short)count", "(short)mergeCount")); b.end().startElseBlock(); @@ -6730,7 +6809,8 @@ private CodeExecutableElement createDoEmitVariadicEnd() { b.end(); } - buildEmitInstructionStackEffect(b, model.loadVariadicInstruction, "-stackCount", createLoadVariadicArguments("offset + count - stackCount", "(short)(stackCount)", "(short)mergeCount")); + buildEmitInstructionStackEffect(b, null, model.loadVariadicInstruction, "-stackCount", + createLoadVariadicArguments("offset + count - stackCount", "(short)(stackCount)", "(short)mergeCount")); if (model.hasVariadicReturn) { b.end(); @@ -6753,7 +6833,7 @@ private CodeExecutableElement createDoEmitVariadicEnd() { b.statement("length++"); b.end().startElseBlock(); b.lineComment("range not continuous"); - buildEmitInstructionStackEffect(b, model.splatVariadicInstruction, "0", "offset + prev", "length"); + buildEmitInstructionStackEffect(b, null, model.splatVariadicInstruction, "0", "offset + prev", "length"); b.statement("length = 1"); b.end(); b.statement("prev = index"); @@ -6762,7 +6842,7 @@ private CodeExecutableElement createDoEmitVariadicEnd() { b.startIf().string("length > 0").end().startBlock(); b.lineComment("emit last range"); b.statement("assert prev != -1"); - buildEmitInstructionStackEffect(b, model.splatVariadicInstruction, "0", "offset + prev", "length"); + buildEmitInstructionStackEffect(b, null, model.splatVariadicInstruction, "0", "offset + prev", "length"); b.end(); b.end(); // dynamicArguments != null @@ -6791,9 +6871,9 @@ private CodeExecutableElement createDoEmitVariadicBeforeChild() { b.end(); b.startIf().string("count <= VARIADIC_STACK_LIMIT").end().startBlock(); - b.declaration(type(int.class), "variadicSizePatchOffset", "state.bci + " + model.createVariadicInstruction.findImmediate(ImmediateKind.INTEGER, "count").offset()); - buildEmitInstructionStackEffect(b, model.createVariadicInstruction, "-VARIADIC_STACK_LIMIT + 1", createCreateVariadicArguments("offset", "VARIADIC_STACK_LIMIT", "(short) 0")); - b.startReturn().string("variadicSizePatchOffset").end(); + buildEmitInstructionStackEffect(b, "createVariadicOffset", model.createVariadicInstruction, "-VARIADIC_STACK_LIMIT + 1", + createCreateVariadicArguments("offset", "VARIADIC_STACK_LIMIT", "(short) 0")); + b.startReturn().string("createVariadicOffset + " + model.createVariadicInstruction.findImmediate(ImmediateKind.INTEGER, "count").offset()).end(); b.end().startElseBlock(); String offset; if (model.maximumVariadicOffset > 0) { @@ -6801,7 +6881,7 @@ private CodeExecutableElement createDoEmitVariadicBeforeChild() { } else { offset = "count - VARIADIC_STACK_LIMIT"; } - buildEmitInstructionStackEffect(b, model.loadVariadicInstruction, "-VARIADIC_STACK_LIMIT", createLoadVariadicArguments(offset, "(short)VARIADIC_STACK_LIMIT", "(short) 0")); + buildEmitInstructionStackEffect(b, null, model.loadVariadicInstruction, "-VARIADIC_STACK_LIMIT", createLoadVariadicArguments(offset, "(short)VARIADIC_STACK_LIMIT", "(short) 0")); b.startReturn().string("-1").end(); b.end(); @@ -6861,7 +6941,7 @@ private String[] createLoadVariadicArguments(String offset, String count, String return createArguments; } - private void buildEmitInstruction(CodeTreeBuilder b, InstructionModel instr, String... arguments) { + private void buildEmitInstruction(CodeTreeBuilder b, String localName, InstructionModel instr, String... arguments) { int stackEffect = switch (instr.kind) { case BRANCH, BRANCH_BACKWARD, // TAG_ENTER, TAG_LEAVE, TAG_LEAVE_VOID, TAG_RESUME, TAG_YIELD, TAG_YIELD_NULL, // @@ -6905,12 +6985,17 @@ private void buildEmitInstruction(CodeTreeBuilder b, InstructionModel instr, Str default -> throw new UnsupportedOperationException(); }; - buildEmitInstructionStackEffect(b, instr, String.valueOf(stackEffect), arguments); + buildEmitInstructionStackEffect(b, localName, instr, String.valueOf(stackEffect), arguments); } - private void buildEmitInstructionStackEffect(CodeTreeBuilder b, InstructionModel instr, String stackEffect, String... arguments) throws AssertionError { + private void buildEmitInstructionStackEffect(CodeTreeBuilder b, String localName, InstructionModel instr, String stackEffect, String... arguments) throws AssertionError { CodeExecutableElement doEmitInstruction = rootStackElement.ensureDoEmitInstructionCreated(instr); - b.startStatement().startCall("state", doEmitInstruction.getSimpleName().toString()); + if (localName != null) { + b.startDeclaration(type(int.class), localName); + } else { + b.startStatement(); + } + b.startCall("state", doEmitInstruction.getSimpleName().toString()); b.tree(createInstructionConstant(instr)); b.string(stackEffect); int argumentsLength = arguments != null ? arguments.length : 0; @@ -7180,7 +7265,7 @@ private CodeExecutableElement createDoEmitTagYield(InstructionModel instr) { OperationModel op = model.findOperation(OperationKind.TAG); b.startCase().tree(createOperationConstant(op)).end(); b.startBlock(); - buildEmitInstruction(b, instr, operationStack.read(op, operationFields.nodeId)); + buildEmitInstruction(b, null, instr, operationStack.read(op, operationFields.nodeId)); b.statement("break"); b.end(); // case tag @@ -7213,7 +7298,7 @@ private CodeExecutableElement createDoEmitTagResume() { b.startCase().tree(createOperationConstant(op)).end(); b.startBlock(); b.startAssign("tagResumeBci").string("state.bci").end(); - buildEmitInstruction(b, model.tagResumeInstruction, operationStack.read(op, operationFields.nodeId)); + buildEmitInstruction(b, null, model.tagResumeInstruction, operationStack.read(op, operationFields.nodeId)); b.statement("break"); b.end(); // case tag @@ -7260,13 +7345,13 @@ private void emitUnwindBeforeEarlyExit(CodeTreeBuilder b, OperationKind operatio b.startBlock(); b.startIf().string("state.reachable").end().startBlock(); if (operationKind == OperationKind.RETURN) { - buildEmitInstruction(b, model.tagLeaveValueInstruction, buildTagLeaveArguments(model.tagLeaveValueInstruction)); + buildEmitInstruction(b, null, model.tagLeaveValueInstruction, buildTagLeaveArguments(model.tagLeaveValueInstruction)); b.statement("childBci = state.bci - " + model.tagLeaveValueInstruction.getInstructionLength()); } else { if (operationKind != OperationKind.BRANCH) { throw new AssertionError("unexpected operation kind used for unwind code generation."); } - buildEmitInstruction(b, model.tagLeaveVoidInstruction, operationStack.read(model.tagOperation, operationFields.nodeId)); + buildEmitInstruction(b, null, model.tagLeaveVoidInstruction, operationStack.read(model.tagOperation, operationFields.nodeId)); } b.statement("state.doCreateExceptionHandler(", operationStack.read(model.tagOperation, operationFields.handlerStartBci), @@ -7347,7 +7432,7 @@ private void emitUnwindBeforeEarlyExit(CodeTreeBuilder b, OperationKind operatio b.statement("state.locals[", operationStack.read(model.blockOperation, operationFields.locals), "[j] + LOCALS_OFFSET_END_BCI] = state.bci"); if (operationKind == OperationKind.BRANCH) { - buildEmitInstruction(b, model.clearLocalInstruction, + buildEmitInstruction(b, null, model.clearLocalInstruction, safeCastShort("state.locals[" + operationStack.read(model.blockOperation, operationFields.locals) + "[j] + LOCALS_OFFSET_FRAME_INDEX]")); } b.statement("needsRewind = true"); @@ -7515,6 +7600,7 @@ void lazyInit() { this.add(new CodeVariableElement(Set.of(PRIVATE), arrayOf(type(int.class)), "locals")); this.add(new CodeVariableElement(Set.of(PRIVATE), type(int.class), "localsTableIndex")); this.add(new CodeVariableElement(Set.of(PRIVATE, FINAL), types.BytecodeSupport_ConstantsBuffer, "constants")); + this.add(new CodeVariableElement(Set.of(PRIVATE), type(int.class), "instrumentations")); if (model.hasYieldOperation()) { /** @@ -7576,6 +7662,7 @@ void lazyInit() { } b.statement("this.sourceInfo = new int[16 * SOURCE_INFO_LENGTH]"); b.statement("this.sourceInfoIndex = 0"); + b.statement("this.instrumentations = 0"); this.add(createReset()); this.add(createPushOperation()); @@ -7604,6 +7691,10 @@ void lazyInit() { this.add(createDoEmitSourceInfo()); this.add(createRegisterUnresolvedLabel()); this.add(createResolveUnresolvedLabel()); + + if (model.enableInstructionTracing) { + this.add(createDoEmitTraceInstruction()); + } } void lateInit() { @@ -7644,6 +7735,7 @@ private CodeExecutableElement createReset() { b.statement("this.continuationsIndex = 0"); } b.statement("this.sourceInfoIndex = 0"); + b.statement("this.instrumentations = 0"); b.statement("this.needsClean = true"); return ex; @@ -8180,6 +8272,22 @@ private CodeExecutableElement ensureDoEmitInstructionCreated(InstructionModel in return doEmitInstructionMethods.computeIfAbsent(instruction.getInstructionEncoding(), (e) -> createDoEmitInstruction(e)); } + private CodeExecutableElement createDoEmitTraceInstruction() { + CodeExecutableElement ex = new CodeExecutableElement(Set.of(PRIVATE), type(void.class), "doEmitTraceInstruction"); + + CodeTreeBuilder b = ex.createBuilder(); + b.declaration(type(int.class), "newBci", "checkBci(this.bci + 2)"); + b.startIf().string("newBci > this.bc.length").end().startBlock(); + b.statement("this.ensureBytecodeCapacity(newBci)"); + b.end(); + + b.statement(writeInstruction("this.bc", "this.bci", createInstructionConstant(model.traceInstruction))); + b.statement("this.bci = newBci"); + + return ex; + + } + private CodeExecutableElement createDoEmitInstruction(InstructionEncoding encoding) { // Give each method a unique name so that we don't accidentally use the wrong // overload. @@ -8188,7 +8296,7 @@ private CodeExecutableElement createDoEmitInstruction(InstructionEncoding encodi methodName.append(immediate.width().toEncodedName()); } - CodeExecutableElement ex = new CodeExecutableElement(Set.of(PRIVATE), type(boolean.class), methodName.toString()); + CodeExecutableElement ex = new CodeExecutableElement(Set.of(PRIVATE), type(int.class), methodName.toString()); ex.addParameter(new CodeVariableElement(type(short.class), "instruction")); ex.addParameter(new CodeVariableElement(type(int.class), "stackEffect")); @@ -8209,10 +8317,18 @@ private CodeExecutableElement createDoEmitInstruction(InstructionEncoding encodi b.end(); b.startIf().string("!this.reachable").end().startBlock(); - b.statement("return false"); + b.statement("return -1"); b.end(); - b.declaration(type(int.class), "newBci", "checkBci(this.bci + " + encoding.length() + ")"); + if (model.enableInstructionTracing) { + int mask = 1 << model.traceInstructionInstrumentationIndex; + b.startIf().string("(this.instrumentations & ").string("0x", Integer.toHexString(mask)).string(") != 0").end().startBlock(); + b.statement("doEmitTraceInstruction()"); + b.end(); + } + + b.declaration(type(int.class), "instructionBci", "this.bci"); + b.declaration(type(int.class), "newBci", "checkBci(instructionBci + " + encoding.length() + ")"); b.startIf().string("newBci > this.bc.length").end().startBlock(); b.statement("this.ensureBytecodeCapacity(newBci)"); b.end(); @@ -8234,7 +8350,7 @@ private CodeExecutableElement createDoEmitInstruction(InstructionEncoding encodi } b.statement("this.bci = newBci"); - b.statement("return true"); + b.statement("return instructionBci"); return ex; } @@ -8326,7 +8442,7 @@ private CodeExecutableElement createToString() { b.startStatement().startCall("b.append").startNew(instructionImpl.asType()).string("null").string("currentBci").string("opcode").string("tempBytecodes").string( "tempConstants").end().end().end(); b.startStatement().startCall("b.append").string("System.lineSeparator()").end().end(); - b.startAssign("currentBci").string("currentBci + ").startStaticCall(instructionsElement.asType(), "getLength").string("opcode").end().end(); + b.startAssign("currentBci").string("currentBci + ").tree(instructionsElement.call("getLength", "opcode")).end(); b.end(); // for @@ -9793,6 +9909,13 @@ void lazyInit() { this.add(createGetLanguage()); this.add(createIsParsed()); + if (model.enableInstructionTracing) { + this.add(createAddInstructionTracer()); + this.add(createRemoveInstructionTracer()); + this.add(createFindInstructionTracerAccess()); + this.add(createUpdateGlobalInstructionTracers()); + } + if (model.enableSerialization) { this.add(createSerialize()); } @@ -9878,6 +10001,66 @@ private CodeExecutableElement createConstructor() { return ctor; } + private CodeExecutableElement createAddInstructionTracer() { + CodeExecutableElement ex = GeneratorUtils.override(types.BytecodeRootNodes, "addInstructionTracer", new String[]{"tracer"}); + CodeTreeBuilder b = ex.createBuilder(); + emitValidateInstructionTracer(b); + + b.startDeclaration(configEncoder.asType(), "encoder").staticReference(configEncoder.asType(), "INSTANCE").end(); + b.startStatement(); + b.string("updateImpl(encoder, encoder.encodeInstrumentation(").typeLiteral(types.InstructionTracer).string("))"); + b.end(); + + b.startFor().type(model.getTemplateType().asType()).string(" root : nodes").end().startBlock(); + b.startDeclaration(BytecodeRootNodeElement.this.asType(), "castRoot").cast(BytecodeRootNodeElement.this.asType(), "root").end(); + b.statement("findInstructionTracerAccess(castRoot).addLocalTracer(tracer)"); + b.statement("castRoot.invalidate(\"Local instruction tracer added.\")"); + b.end(); + return ex; + } + + private CodeExecutableElement createRemoveInstructionTracer() { + CodeExecutableElement ex = GeneratorUtils.override(types.BytecodeRootNodes, "removeInstructionTracer", new String[]{"tracer"}); + CodeTreeBuilder b = ex.createBuilder(); + emitValidateInstructionTracer(b); + b.startDeclaration(configEncoder.asType(), "encoder").staticReference(configEncoder.asType(), "INSTANCE").end(); + b.startStatement(); + b.string("updateImpl(encoder, encoder.encodeInstrumentation(").typeLiteral(types.InstructionTracer).string("))"); + b.end(); + + b.startFor().type(model.getTemplateType().asType()).string(" root : nodes").end().startBlock(); + b.startDeclaration(BytecodeRootNodeElement.this.asType(), "castRoot").cast(BytecodeRootNodeElement.this.asType(), "root").end(); + b.statement("findInstructionTracerAccess(castRoot).removeLocalTracer(tracer)"); + b.statement("castRoot.invalidate(\"Local instruction tracer removed.\")"); + b.end(); + return ex; + } + + private CodeExecutableElement createUpdateGlobalInstructionTracers() { + CodeExecutableElement ex = GeneratorUtils.override(types.BytecodeRootNodes, "updateGlobalInstructionTracers", new String[]{"tracers"}); + CodeTreeBuilder b = ex.createBuilder(); + b.startDeclaration(configEncoder.asType(), "encoder").staticReference(configEncoder.asType(), "INSTANCE").end(); + b.startStatement(); + b.string("updateImpl(encoder, encoder.encodeInstrumentation(").typeLiteral(types.InstructionTracer).string("))"); + b.end(); + b.startFor().type(model.getTemplateType().asType()).string(" root : nodes").end().startBlock(); + b.startDeclaration(BytecodeRootNodeElement.this.asType(), "castRoot").cast(BytecodeRootNodeElement.this.asType(), "root").end(); + b.statement("findInstructionTracerAccess(castRoot).updateGlobalTracers(tracers)"); + b.statement("castRoot.invalidate(\"Global instruction tracers updated.\")"); + b.end(); + return ex; + } + + private CodeExecutableElement createFindInstructionTracerAccess() { + CodeExecutableElement ex = new CodeExecutableElement(Set.of(PRIVATE), instructionTracerAccessImplElement.asType(), "findInstructionTracerAccess"); + ex.addParameter(new CodeVariableElement(BytecodeRootNodeElement.this.asType(), "root")); + CodeTreeBuilder b = ex.createBuilder(); + b.startReturn(); + b.cast(instructionTracerAccessImplElement.asType()).string("root.getBytecodeNodeImpl().constants[Builder.INSTRUCTION_TRACER_CONSTANT_INDEX]"); + b.end(); + return ex; + } + private CodeExecutableElement createReparseImpl() { CodeExecutableElement ex = GeneratorUtils.override(types.BytecodeRootNodes, "updateImpl", new String[]{"encoder", "encoding"}); mergeSuppressWarnings(ex, "hiding"); @@ -10047,21 +10230,27 @@ All metadata (e.g., source info) is serialized (even if it has not yet been pars b.end(2); b.end(); - b.startStatement(); - b.startStaticCall(BytecodeRootNodeElement.this.asType(), "doSerialize"); - b.string("buffer"); - b.string("callback"); - - // Create a new Builder with this BytecodeRootNodes instance. + b.startDeclaration(bytecodeBuilderType, "builder"); b.startNew("Builder"); b.string("getLanguage()"); b.string("this"); + b.startCall("BytecodeConfigEncoderImpl.decode"); b.staticReference(types.BytecodeConfig, "COMPLETE"); b.end(); + b.end(); + b.end(); + + b.startTryBlock(); + b.startStatement().startCall("builder", "serialize"); + b.string("buffer"); + b.string("callback"); b.string("existingNodes"); + b.end().end(); - b.end(2); + b.end().startCatchBlock(type(IOError.class), "e"); + b.startThrow().cast(type(IOException.class), "e.getCause()").end(); + b.end(); return ex; } @@ -10117,35 +10306,298 @@ private VariableElement getIllegal() { } } -// Generates an Instructions class with constants for each instruction. - final class InstructionConstantsElement extends CodeTypeElement { - InstructionConstantsElement() { - super(Set.of(PRIVATE, STATIC, FINAL), ElementKind.CLASS, null, "Instructions"); + final class InstructionDescriptorListElement extends CodeTypeElement { + + InstructionDescriptorListElement() { + super(Set.of(PRIVATE, STATIC, FINAL), + ElementKind.CLASS, null, "InstructionDescriptorList"); + this.setSuperClass(generic(type(AbstractList.class), types.InstructionDescriptor)); + this.add(new CodeVariableElement(Set.of(PRIVATE, STATIC, FINAL), this.asType(), "INSTANCE")).createInitBuilder().startNew(asType()).end(); + this.add(createGet()); + this.add(createSize()); } - private CodeTypeElement abstractArgument; + private CodeExecutableElement createGet() { + CodeExecutableElement ex = GeneratorUtils.override(declaredType(List.class), "get", new String[]{"index"}, new TypeMirror[]{type(int.class)}); + ex.setReturnType(types.InstructionDescriptor); + CodeTreeBuilder b = ex.createBuilder(); + b.startIf().string("index < 0 || index >= size()").end().startBlock(); + b.startThrow().startNew(type(IndexOutOfBoundsException.class)).string("String.valueOf(index)").end().end(); + b.end(); + b.startReturn(); + b.staticReference(instructionDescriptorImpl.asType(), "DESCRIPTORS[").string("index + " + model.getInstructionStartIndex()).string("]"); + b.end(); + return ex; + } + + private CodeExecutableElement createSize() { + CodeExecutableElement ex = GeneratorUtils.override(declaredType(List.class), "size"); + CodeTreeBuilder b = ex.createBuilder(); + b.startReturn().string(model.getInstructions().size()).end(); + return ex; + } + + } + + final class BytecodeDescriptorElement extends CodeTypeElement { + BytecodeDescriptorElement() { + super(Set.of(PUBLIC, STATIC, FINAL), ElementKind.CLASS, null, "Bytecode"); + } void lazyInit() { - for (InstructionModel instruction : BytecodeRootNodeElement.this.model.getInstructions()) { - CodeVariableElement fld = new CodeVariableElement(Set.of(PRIVATE, STATIC, FINAL), type(short.class), instruction.getConstantName()); - fld.createInitBuilder().string(instruction.getId()).end(); - fld.createDocBuilder().startDoc().lines(instruction.pp()).end(2); - this.add(fld); + TypeMirror superType = BytecodeDSLCodeGenerator.findBytecodeVariantType(abstractBuilderType); + if (superType == null) { + // regular case + superType = generic(types.BytecodeDescriptor, model.getTemplateType().asType(), model.languageClass, builder.asType()); } + setSuperClass(superType); - abstractArgument = this.add(new AbstractArgumentElement()); + CodeExecutableElement c = this.add(new CodeExecutableElement(Set.of(PRIVATE), null, this.getSimpleName().toString())); + CodeTreeBuilder b = c.createBuilder(); + b.startStatement().startSuperCall().staticReference(bytecodeRootNodesImpl.asType(), "VISIBLE_TOKEN").end().end(); - Set generated = new HashSet<>(); - for (ImmediateKind kind : ImmediateKind.values()) { - if (kind == ImmediateKind.LOCAL_INDEX && !model.localAccessesNeedLocalIndex() && !model.materializedLocalAccessesNeedLocalIndex()) { - // Only generate immediate class for LocalIndex when needed. - continue; - } + this.add(createGetRootNodeClass()); + this.add(createGetGeneratedClass()); + this.add(createCast()); + this.add(createGetLanguageClass()); + this.add(createGetInstructionDescriptors()); + this.add(createGetInstructionDescriptor()); + this.add(createCreate()); + this.add(createNewConfigBuilder()); - String className = getImmediateClassName(kind); - if (generated.contains(className)) { - continue; - } + this.add(createPrepareForCall()); + + this.add(createAddInstructionTracer()); + this.add(createRemoveInstructionTracer()); + + if (model.enableSerialization) { + this.add(createSerialize()); + this.add(createDeserialize()); + } + } + + private CodeExecutableElement createPrepareForCall() { + CodeExecutableElement ex = new CodeExecutableElement(Set.of(PRIVATE), type(void.class), "prepareForCall"); + ex.addParameter(new CodeVariableElement(model.languageClass, "language")); + ex.addParameter(new CodeVariableElement(BytecodeRootNodeElement.this.asType(), "rootNode")); + CodeTreeBuilder b = ex.createBuilder(); + b.statement("onPrepareForLoad(language, rootNode)"); + return ex; + } + + private CodeExecutableElement createGetRootNodeClass() { + CodeExecutableElement ex = GeneratorUtils.override(types.BytecodeDescriptor, "getSpecificationClass"); + ex.setReturnType(generic(type(Class.class), model.getTemplateType().asType())); + CodeTreeBuilder b = ex.createBuilder(); + b.startReturn().typeLiteral(model.getTemplateType().asType()).end(); + return ex; + } + + private CodeExecutableElement createGetGeneratedClass() { + CodeExecutableElement ex = GeneratorUtils.override(types.BytecodeDescriptor, "getGeneratedClass"); + ex.setReturnType(generic(type(Class.class), new CodeTypeMirror.WildcardTypeMirror(model.getTemplateType().asType(), null))); + CodeTreeBuilder b = ex.createBuilder(); + b.startReturn().typeLiteral(BytecodeRootNodeElement.this.asType()).end(); + return ex; + } + + private CodeExecutableElement createCast() { + CodeExecutableElement ex = GeneratorUtils.override(types.BytecodeDescriptor, "cast", new String[]{"root"}, new TypeMirror[]{types.RootNode}); + ex.setReturnType(model.getTemplateType().asType()); + CodeTreeBuilder b = ex.createBuilder(); + b.startIf().string("root.getClass() == ").typeLiteral(BytecodeRootNodeElement.this.asType()).end().startBlock(); + b.startReturn().cast(BytecodeRootNodeElement.this.asType(), "root").end(); + b.end(); + if (model.enableYield) { + b.startElseIf().string("root instanceof ").type(continuationRootNodeImpl.asType()).string(" c").end().startBlock(); + b.statement("return c.root"); + b.end(); + } + b.startElseBlock(); + b.statement("return null"); + b.end(); + return ex; + } + + private CodeExecutableElement createGetLanguageClass() { + CodeExecutableElement ex = GeneratorUtils.override(types.BytecodeDescriptor, "getLanguageClass"); + ex.setReturnType(generic(type(Class.class), model.languageClass)); + CodeTreeBuilder b = ex.createBuilder(); + b.startReturn().typeLiteral(model.languageClass).end(); + return ex; + } + + private CodeExecutableElement createAddInstructionTracer() { + CodeExecutableElement ex = GeneratorUtils.override(types.BytecodeDescriptor, "addInstructionTracer", new String[]{"language", "tracer"}); + ex.changeTypes(model.languageClass, types.InstructionTracer); + CodeTreeBuilder b = ex.createBuilder(); + + if (model.enableInstructionTracing) { + emitValidateInstructionTracer(b); + b.statement("super.addInstructionTracer(language, tracer)"); + } else { + b.startThrow().startNew(type(UnsupportedOperationException.class)); + b.doubleQuote("Instruction tracing is not enabled for this bytecode root node. Enable with @GenerateBytecode(enableInstructionTracing=true) to use instruction tracing."); + b.end().end(); + } + + return ex; + } + + private CodeExecutableElement createRemoveInstructionTracer() { + CodeExecutableElement ex = GeneratorUtils.override(types.BytecodeDescriptor, "removeInstructionTracer", new String[]{"language", "tracer"}); + ex.changeTypes(model.languageClass, types.InstructionTracer); + CodeTreeBuilder b = ex.createBuilder(); + if (model.enableInstructionTracing) { + emitValidateInstructionTracer(b); + b.statement("super.removeInstructionTracer(language, tracer)"); + } else { + b.startThrow().startNew(type(UnsupportedOperationException.class)); + b.doubleQuote("Instruction tracing is not enabled for this bytecode root node. Enable with @GenerateBytecode(enableInstructionTracing=true) to use instruction tracing."); + b.end().end(); + } + return ex; + } + + private CodeExecutableElement createGetInstructionDescriptors() { + CodeExecutableElement ex = GeneratorUtils.override(types.BytecodeDescriptor, "getInstructionDescriptors"); + CodeTreeBuilder b = ex.createBuilder(); + b.startReturn().staticReference(instructionDescriptorList.asType(), "INSTANCE").end(); + return ex; + } + + private CodeExecutableElement createGetInstructionDescriptor() { + CodeExecutableElement ex = GeneratorUtils.override(types.BytecodeDescriptor, "getInstructionDescriptor", new String[]{"operationCode"}); + CodeTreeBuilder b = ex.createBuilder(); + b.startIf().string("operationCode < 1 || operationCode >= ").staticReference(instructionDescriptorImpl.asType(), "DESCRIPTORS.length").end().startBlock(); + b.returnNull(); + b.end(); + b.startReturn().staticReference(instructionDescriptorImpl.asType(), "DESCRIPTORS").string("[operationCode]").end(); + return ex; + } + + private CodeExecutableElement createCreate() { + CodeExecutableElement ex = GeneratorUtils.override(types.BytecodeDescriptor, "create", new String[]{"language", "config", "parser"}); + ex.setReturnType(generic(types.BytecodeRootNodes, model.templateType.asType())); + ex.changeTypes(model.languageClass, types.BytecodeConfig, parserType); + CodeTreeBuilder b = ex.getBuilder(); + + b.declaration("BytecodeRootNodesImpl", "nodes", "new BytecodeRootNodesImpl(parser, config)"); + b.startAssign("Builder builder").startNew(builder.getSimpleName().toString()); + b.string("language"); + b.string("nodes"); + b.string("withGlobalConfig(language, BytecodeConfigEncoderImpl.decode(config))"); + b.end(2); + + b.startStatement().startCall("parser", "parse"); + b.string("builder"); + b.end(2); + + b.startStatement().startCall("builder", "finish").end(2); + + b.startReturn().string("nodes").end(); + + return ex; + } + + private CodeExecutableElement createNewConfigBuilder() { + CodeExecutableElement ex = GeneratorUtils.override(types.BytecodeDescriptor, "newConfigBuilder"); + CodeTreeBuilder b = ex.getBuilder(); + b.startReturn(); + b.startStaticCall(BytecodeRootNodeElement.this.asType(), "newConfigBuilder").end(); + b.end(); + return ex; + } + + private CodeExecutableElement createSerialize() { + CodeExecutableElement ex = GeneratorUtils.override(types.BytecodeDescriptor, "serialize", new String[]{"buffer", "callback", "parser"}); + ex.changeTypes(type(DataOutput.class), types.BytecodeSerializer, parserType); + + CodeTreeBuilder b = ex.createBuilder(); + + b.startDeclaration(bytecodeRootNodesImpl.asType(), "rootNodes"); + b.startNew(bytecodeRootNodesImpl.asType()); + b.string("parser"); + b.staticReference(types.BytecodeConfig, "COMPLETE"); + b.end(); // new + b.end(); // declaration + + b.startDeclaration(bytecodeBuilderType, "builder"); + b.startNew("Builder"); + b.string("null"); // language not needed for serialization + b.string("rootNodes"); // language not needed for serialization + b.string("rootNodes.encoding"); // language not needed for serialization + b.end(); // new + b.end(); // declaration + + b.startTryBlock(); + + b.startStatement().startCall("builder", "serialize"); + b.string("buffer"); + b.string("callback"); + b.string("null"); // existingNodes + b.end().end(); + + b.end().startCatchBlock(type(IOError.class), "e"); + b.startThrow().cast(type(IOException.class), "e.getCause()").end(); + b.end(); + return ex; + } + + private CodeExecutableElement createDeserialize() { + CodeExecutableElement ex = GeneratorUtils.override(types.BytecodeDescriptor, "deserialize", new String[]{"language", "config", "input", "callback"}); + ex.setReturnType(generic(types.BytecodeRootNodes, model.getTemplateType().asType())); + ex.changeTypes(model.languageClass, types.BytecodeConfig, generic(Supplier.class, DataInput.class), types.BytecodeDeserializer); + CodeTreeBuilder b = ex.createBuilder(); + + b.startTryBlock(); + + if (ElementUtils.typeEquals(abstractBuilderType, types.BytecodeBuilder)) { + b.statement("return create(language, config, (b) -> b.deserialize(input, callback, null))"); + } else { + b.statement("return create(language, config, (b) -> ((Builder) b).deserialize(input, callback, null))"); + } + + b.end().startCatchBlock(type(IOError.class), "e"); + b.startThrow().cast(type(IOException.class), "e.getCause()").end(); + b.end(); + + return ex; + } + + } + + final class InstructionsElement extends CodeTypeElement { + InstructionsElement() { + super(Set.of(PRIVATE, STATIC, FINAL), ElementKind.CLASS, null, "Instructions"); + } + + private ArgumentDescriptorElement argumentDescriptorImpl; + private CodeTypeElement abstractArgument; + + void lazyInit() { + + for (InstructionModel instruction : BytecodeRootNodeElement.this.model.getInstructions()) { + CodeVariableElement fld = new CodeVariableElement(Set.of(PRIVATE, STATIC, FINAL), type(short.class), instruction.getConstantName()); + fld.createInitBuilder().string(instruction.getId()).end(); + fld.createDocBuilder().startDoc().lines(instruction.pp()).end(2); + this.add(fld); + } + + argumentDescriptorImpl = this.add(new ArgumentDescriptorElement()); + abstractArgument = this.add(new AbstractArgumentElement()); + + Set generated = new HashSet<>(); + for (ImmediateKind kind : ImmediateKind.values()) { + if (kind == ImmediateKind.LOCAL_INDEX && !model.localAccessesNeedLocalIndex() && !model.materializedLocalAccessesNeedLocalIndex()) { + // Only generate immediate class for LocalIndex when needed. + continue; + } + + String className = getImmediateClassName(kind); + if (generated.contains(className)) { + continue; + } if (kind == ImmediateKind.TAG_NODE && !model.enableTagInstrumentation) { continue; } @@ -10154,13 +10606,20 @@ void lazyInit() { generated.add(className); } - this.add(createGetLength()); + this.add(createGetInstructionLength()); this.add(createGetName()); this.add(createIsInstrumentation()); this.add(createGetArguments()); + this.add(createGetArgumentDescriptors()); } - private CodeExecutableElement createGetLength() { + public CodeTree call(String methodName, String argumentName) { + CodeTreeBuilder b = new CodeTreeBuilder(null); + b.startStaticCall(instructionsElement.asType(), methodName).string(argumentName).end(); + return b.build(); + } + + private CodeExecutableElement createGetInstructionLength() { CodeExecutableElement ex = new CodeExecutableElement(Set.of(PRIVATE, STATIC), type(int.class), "getLength"); ex.addParameter(new CodeVariableElement(type(int.class), "opcode")); CodeTreeBuilder b = ex.createBuilder(); @@ -10225,8 +10684,54 @@ private CodeExecutableElement createIsInstrumentation() { return ex; } + private CodeExecutableElement createGetArgumentDescriptors() { + TypeMirror returnType = generic(List.class, types.InstructionDescriptor_ArgumentDescriptor); + CodeExecutableElement ex = new CodeExecutableElement(Set.of(PRIVATE, STATIC), returnType, "getArgumentDescriptors"); + ex.addParameter(new CodeVariableElement(type(int.class), "opcode")); + + CodeTreeBuilder b = ex.createBuilder(); + + Map> caseGrouping = EqualityCodeTree.group(b, model.getInstructions(), (InstructionModel instruction, CodeTreeBuilder group) -> { + group.startCaseBlock(); + group.startReturn().startStaticCall(type(List.class), "of"); + + for (InstructionImmediate immediate : resolveImmediates(instruction)) { + group.tree(this.argumentDescriptorImpl.readArgumentConstant(immediate)); + } + + group.end().end(); // return + group.end(); // case block + }); + + b.startSwitch().string("opcode").end().startBlock(); + for (var group : caseGrouping.entrySet()) { + EqualityCodeTree key = group.getKey(); + for (InstructionModel instruction : group.getValue()) { + b.startCase().tree(createInstructionConstant(instruction)).end(); + } + b.startCaseBlock(); + b.tree(key.getTree()); + b.end(); + } + b.end(); // switch + b.tree(GeneratorUtils.createShouldNotReachHere("Invalid opcode")); + return ex; + } + + private List resolveImmediates(InstructionModel instruction) { + + if (instruction.nodeData != null && instruction.canUseNodeSingleton()) { + List immediates = new ArrayList<>(instruction.immediates); + immediates.add(new InstructionImmediate(ImmediateKind.NODE_PROFILE, "node", InstructionImmediateEncoding.NONE)); + return immediates; + } else { + return instruction.immediates; + } + } + private CodeExecutableElement createGetArguments() { - CodeExecutableElement ex = new CodeExecutableElement(Set.of(PRIVATE, STATIC), generic(List.class, types.Instruction_Argument), "getArguments"); + TypeMirror returnType = generic(List.class, types.Instruction_Argument); + CodeExecutableElement ex = new CodeExecutableElement(Set.of(PRIVATE, STATIC), returnType, "getArguments"); ex.addParameter(new CodeVariableElement(type(int.class), "opcode")); ex.addParameter(new CodeVariableElement(type(int.class), "bci")); ex.addParameter(new CodeVariableElement(abstractBytecodeNode.asType(), "bytecode")); @@ -10238,14 +10743,9 @@ private CodeExecutableElement createGetArguments() { Map> caseGrouping = EqualityCodeTree.group(b, model.getInstructions(), (InstructionModel instruction, CodeTreeBuilder group) -> { group.startCaseBlock(); group.startReturn().startStaticCall(type(List.class), "of"); - for (InstructionImmediate immediate : instruction.getImmediates()) { + for (InstructionImmediate immediate : resolveImmediates(instruction)) { emitCreateArgument(group, instruction, immediate); } - - if (instruction.nodeData != null && instruction.canUseNodeSingleton()) { - emitCreateArgument(group, instruction, new InstructionImmediate(ImmediateKind.NODE_PROFILE, "node", InstructionImmediateEncoding.NONE)); - } - group.end().end(); // return group.end(); // case block }); @@ -10270,16 +10770,12 @@ private void emitCreateArgument(CodeTreeBuilder b, InstructionModel instruction, b.newLine(); b.startIndention(); b.startNew(getImmediateClassName(immediate.kind())); - - b.doubleQuote(getIntrospectionArgumentName(immediate)); + b.tree(argumentDescriptorImpl.readArgumentConstant(immediate)); b.string("bci + " + immediate.offset()); for (CodeVariableElement var : createImmediateArguments(immediate.kind())) { String name = var.getName(); switch (name) { - case "width": - b.string(Integer.toString(immediate.kind().width.byteSize)); - break; case "bytecodeIndex": b.string("bci"); break; @@ -10301,6 +10797,19 @@ private void emitCreateArgument(CodeTreeBuilder b, InstructionModel instruction, b.end(); // group } + private static String getArgumentDescriptorKind(ImmediateKind kind) { + return switch (kind) { + case BRANCH_PROFILE -> "BRANCH_PROFILE"; + case BYTECODE_INDEX -> "BYTECODE_INDEX"; + case CONSTANT, CONSTANT_LONG, CONSTANT_DOUBLE, CONSTANT_INT, CONSTANT_FLOAT, CONSTANT_SHORT, CONSTANT_CHAR, CONSTANT_BYTE, CONSTANT_BOOL -> "CONSTANT"; + case FRAME_INDEX -> "LOCAL_OFFSET"; + case LOCAL_INDEX -> "LOCAL_INDEX"; + case SHORT, INTEGER, LOCAL_ROOT, STACK_POINTER, STATE_PROFILE -> "INTEGER"; + case NODE_PROFILE -> "NODE_PROFILE"; + case TAG_NODE -> "TAG_NODE"; + }; + } + private static String getImmediateClassName(ImmediateKind kind) { switch (kind) { case BRANCH_PROFILE: @@ -10343,14 +10852,6 @@ private static String getImmediateClassName(ImmediateKind kind) { throw new AssertionError("invalid kind"); } - private String getIntrospectionArgumentName(InstructionImmediate immediate) { - if (immediate.kind() == ImmediateKind.FRAME_INDEX) { - // We expose the frame_index as a local offset, so don't use the immediate name. - return "local_offset"; - } - return immediate.name(); - } - private List createImmediateArguments(ImmediateKind immediateKind) { List args = new ArrayList<>(); switch (immediateKind) { @@ -10372,7 +10873,6 @@ private List createImmediateArguments(ImmediateKind immedia case INTEGER: case STATE_PROFILE: args.add(new CodeVariableElement(Set.of(FINAL), type(byte[].class), "bytecodes")); - args.add(new CodeVariableElement(Set.of(PRIVATE, FINAL), type(int.class), "width")); break; case BYTECODE_INDEX: case FRAME_INDEX: @@ -10392,13 +10892,94 @@ private List createImmediateArguments(ImmediateKind immedia return args; } + final class ArgumentDescriptorElement extends CodeTypeElement { + + private final Map descriptors = new HashMap<>(); + + ArgumentDescriptorElement() { + super(Set.of(PRIVATE, STATIC, FINAL), + ElementKind.CLASS, null, "ArgumentDescriptorImpl"); + this.setSuperClass(types.InstructionDescriptor_ArgumentDescriptor); + this.add(new CodeVariableElement(Set.of(PRIVATE, FINAL), type(String.class), "name")); + this.add(new CodeVariableElement(Set.of(PRIVATE, FINAL), types.Instruction_Argument_Kind, "kind")); + this.add(new CodeVariableElement(Set.of(PRIVATE, FINAL), type(int.class), "length")); + CodeExecutableElement constructor = this.add(createConstructorUsingFields(Set.of(), this, null)); + CodeTree tree = constructor.getBodyTree(); + CodeTreeBuilder b = constructor.createBuilder(); + b.startStatement().startSuperCall().staticReference(bytecodeRootNodesImpl.asType(), "VISIBLE_TOKEN").end().end(); + b.tree(tree); + + this.add(createGetName()); + this.add(createGetLength()); + this.add(createGetKind()); + } + + record DescriptorData(String name, String descriptorKind, ImmediateWidth width) { + DescriptorData(InstructionImmediate immediate) { + this(getIntrospectionArgumentName(immediate), getArgumentDescriptorKind(immediate.kind()), immediate.encoding().width()); + } + + int byteSize() { + return width == null ? 0 : width.byteSize; + } + + String constantName() { + return ElementUtils.createConstantName(name) + "_" + descriptorKind + "_" + byteSize(); + } + } + + private static String getIntrospectionArgumentName(InstructionImmediate immediate) { + if (immediate.kind() == ImmediateKind.FRAME_INDEX) { + // We expose the frame_index as a local offset, so don't use the immediate name. + return "local_offset"; + } + return immediate.name(); + } + + private CodeTree readArgumentConstant(InstructionImmediate immediate) { + CodeVariableElement constant = descriptors.computeIfAbsent(new DescriptorData(immediate), (d) -> { + return createArgumentConstant(d); + }); + return CodeTreeBuilder.createBuilder().staticReference(asType(), constant.getName()).build(); + } + + private CodeVariableElement createArgumentConstant(DescriptorData data) { + CodeVariableElement constant = new CodeVariableElement(Set.of(PRIVATE, STATIC, FINAL), asType(), data.constantName()); + constant.createInitBuilder().startNew(asType()).doubleQuote(data.name).staticReference(types.Instruction_Argument_Kind, data.descriptorKind).string(data.byteSize()).end(); + this.add(constant); + return constant; + } + + private CodeExecutableElement createGetName() { + CodeExecutableElement ex = GeneratorUtils.override(types.InstructionDescriptor_ArgumentDescriptor, "getName"); + CodeTreeBuilder b = ex.createBuilder(); + b.statement("return this.name"); + return ex; + } + + private CodeExecutableElement createGetLength() { + CodeExecutableElement ex = GeneratorUtils.override(types.InstructionDescriptor_ArgumentDescriptor, "getLength"); + CodeTreeBuilder b = ex.createBuilder(); + b.statement("return this.length"); + return ex; + } + + private CodeExecutableElement createGetKind() { + CodeExecutableElement ex = GeneratorUtils.override(types.InstructionDescriptor_ArgumentDescriptor, "getKind"); + CodeTreeBuilder b = ex.createBuilder(); + b.statement("return this.kind"); + return ex; + } + + } + final class AbstractArgumentElement extends CodeTypeElement { AbstractArgumentElement() { super(Set.of(PRIVATE, SEALED, STATIC, ABSTRACT), ElementKind.CLASS, null, "AbstractArgument"); this.setSuperClass(types.Instruction_Argument); - this.add(new CodeVariableElement(Set.of(FINAL), type(String.class), "name")); + this.add(new CodeVariableElement(Set.of(FINAL), argumentDescriptorImpl.asType(), "descriptor")); this.add(new CodeVariableElement(Set.of(FINAL), type(int.class), "bci")); CodeExecutableElement constructor = this.add(createConstructorUsingFields(Set.of(), this, null)); CodeTree tree = constructor.getBodyTree(); @@ -10410,14 +10991,14 @@ final class AbstractArgumentElement extends CodeTypeElement { .createInitBuilder().tree(createFastAccessFieldInitializer(false)); this.add(new CodeVariableElement(Set.of(PROTECTED, STATIC, FINAL), types.ByteArraySupport, "SAFE_BYTES")) // .createInitBuilder().startCall("SAFE_ACCESS.getByteArraySupport").end(); - this.add(createGetName()); + this.add(createGetDescriptor()); } - private CodeExecutableElement createGetName() { - CodeExecutableElement ex = GeneratorUtils.override(types.Instruction_Argument, "getName"); + private CodeExecutableElement createGetDescriptor() { + CodeExecutableElement ex = GeneratorUtils.override(types.Instruction_Argument, "getDescriptor"); ex.getModifiers().add(Modifier.FINAL); CodeTreeBuilder b = ex.createBuilder(); - b.statement("return name"); + b.statement("return this.descriptor"); return ex; } @@ -10425,15 +11006,11 @@ private CodeExecutableElement createGetName() { final class ArgumentElement extends CodeTypeElement { - private ImmediateKind immediateKind; - ArgumentElement(ImmediateKind immediateKind) { super(Set.of(PRIVATE, STATIC, FINAL), ElementKind.CLASS, null, getImmediateClassName(immediateKind)); - this.immediateKind = immediateKind; this.setSuperClass(abstractArgument.asType()); this.addAll(createImmediateArguments(immediateKind)); this.add(createConstructorUsingFields(Set.of(), this)); - this.add(createGetKind()); switch (immediateKind) { case BYTECODE_INDEX: @@ -10514,6 +11091,7 @@ private CodeExecutableElement createAsInteger() { CodeExecutableElement ex = GeneratorUtils.override(types.Instruction_Argument, "asInteger"); ex.getModifiers().add(Modifier.FINAL); CodeTreeBuilder b = ex.createBuilder(); + b.declaration(type(int.class), "width", "this.getDescriptor().getLength()"); b.declaration(type(byte[].class), "bc", "this.bytecodes"); b.startSwitch().string("width").end().startBlock(); b.startCase().string("1").end(); @@ -10694,27 +11272,6 @@ private CodeExecutableElement createAsBranchProfile() { return ex; } - private CodeExecutableElement createGetKind() { - CodeExecutableElement ex = GeneratorUtils.override(types.Instruction_Argument, "getKind"); - ex.getModifiers().add(Modifier.FINAL); - CodeTreeBuilder b = ex.createBuilder(); - b.startReturn(); - String name = switch (immediateKind) { - case BRANCH_PROFILE -> "BRANCH_PROFILE"; - case BYTECODE_INDEX -> "BYTECODE_INDEX"; - case CONSTANT, CONSTANT_LONG, CONSTANT_DOUBLE, CONSTANT_INT, CONSTANT_FLOAT, CONSTANT_SHORT, CONSTANT_CHAR, CONSTANT_BYTE, CONSTANT_BOOL -> "CONSTANT"; - case FRAME_INDEX -> "LOCAL_OFFSET"; - case LOCAL_INDEX -> "LOCAL_INDEX"; - case SHORT, INTEGER, LOCAL_ROOT, STACK_POINTER -> "INTEGER"; - case NODE_PROFILE -> "NODE_PROFILE"; - case TAG_NODE -> "TAG_NODE"; - case STATE_PROFILE -> "STATE_PROFILE"; - }; - b.staticReference(types.Instruction_Argument_Kind, name); - b.end(); - return ex; - } - } } @@ -11249,14 +11806,263 @@ private CodeExecutableElement createSize() { } + final class InstructionTracerAccessImplElement extends CodeTypeElement { + + InstructionTracerAccessImplElement() { + super(Set.of(PRIVATE, STATIC, FINAL), ElementKind.CLASS, null, "InstructionTracerAccessImpl"); + this.setSuperClass(types.InstructionTracer_InstructionAccess); + } + + void lazyInit() { + // private static final InstructionTracer[] EMPTY = new InstructionTracer[0]; + CodeVariableElement emptyArr = new CodeVariableElement(Set.of(PRIVATE, STATIC, FINAL), arrayOf(types.InstructionTracer), "EMPTY"); + emptyArr.createInitBuilder().startNewArray(arrayOf(types.InstructionTracer), CodeTreeBuilder.singleString("0")).end(); + this.add(emptyArr); + + CodeExecutableElement c = this.add(createConstructorUsingFields(Set.of(), this, null)); + c.addParameter(new CodeVariableElement(model.languageClass, "language")); + CodeTree tree = c.getBodyTree(); + CodeTreeBuilder b = c.createBuilder(); + b.startStatement().startSuperCall().staticReference(bytecodeRootNodesImpl.asType(), "VISIBLE_TOKEN").end().end(); + b.tree(tree); + + CodeVariableElement localTracers = this.add(new CodeVariableElement(Set.of(VOLATILE), arrayOf(types.InstructionTracer), "localTracers")); + CodeVariableElement globalTracers = this.add(new CodeVariableElement(Set.of(VOLATILE), arrayOf(types.InstructionTracer), "globalTracers")); + + GeneratorUtils.addCompilationFinal(localTracers, 1); + GeneratorUtils.addCompilationFinal(globalTracers, 1); + + localTracers.createInitBuilder().string("EMPTY"); + globalTracers.createInitBuilder().string("EMPTY"); + + this.add(createOnInstructionEnter()); + + this.add(createAddLocalTracer()); + this.add(createRemoveLocalTracer()); + + this.add(createUpdateGlobalTracers()); + + this.add(createGetTracedInstruction()); + this.add(createGetTracedOperationCode()); + } + + private CodeExecutableElement createOnInstructionEnter() { + CodeExecutableElement ex = new CodeExecutableElement(Set.of(), type(void.class), "onInstructionEnter", + new CodeVariableElement(types.BytecodeNode, "bytecode"), + new CodeVariableElement(type(int.class), "bytecodeIndex"), + new CodeVariableElement(types.Frame, "frame")); + ex.getAnnotationMirrors().add(new CodeAnnotationMirror(types.HostCompilerDirectives_InliningCutoff)); + ex.getAnnotationMirrors().add(new CodeAnnotationMirror(types.ExplodeLoop)); + + CodeTreeBuilder b = ex.createBuilder(); + + b.startFor().type(types.InstructionTracer).string(" t : localTracers").end().startBlock(); + b.statement("t.onInstructionEnter(this, bytecode, bytecodeIndex, frame)"); + b.end(); + + b.startFor().type(types.InstructionTracer).string(" t : globalTracers").end().startBlock(); + b.statement("t.onInstructionEnter(this, bytecode, bytecodeIndex, frame)"); + b.end(); + return ex; + } + + private CodeExecutableElement createUpdateGlobalTracers() { + CodeExecutableElement ex = new CodeExecutableElement(Set.of(), type(void.class), "updateGlobalTracers", + new CodeVariableElement(arrayOf(types.InstructionTracer), "tracers")); + + CodeTreeBuilder b = ex.createBuilder(); + b.statement("this.globalTracers = tracers"); + return ex; + } + + private CodeExecutableElement createAddLocalTracer() { + CodeExecutableElement ex = new CodeExecutableElement( + Set.of(Modifier.SYNCHRONIZED), + type(void.class), + "addLocalTracer", + new CodeVariableElement(types.InstructionTracer, "tracer")); + CodeTreeBuilder b = ex.createBuilder(); + + b.startStatement().startStaticCall(type(Objects.class), "requireNonNull").string("tracer").end().end(); + + b.startDeclaration(arrayOf(types.InstructionTracer), "newTracers"); + b.startStaticCall(type(Arrays.class), "copyOf"); + b.string("localTracers").string("localTracers.length + 1"); + b.end(); + b.end(); // declaration + + b.startFor().type(types.InstructionTracer).string(" search : newTracers").end().startBlock(); + b.startIf().string("tracer == search").end().startBlock(); + b.statement("return"); + b.end(); // block + b.end(); + + b.statement("newTracers[newTracers.length - 1] = tracer"); + b.statement("this.localTracers = newTracers"); + return ex; + } + + private CodeExecutableElement createRemoveLocalTracer() { + CodeExecutableElement ex = new CodeExecutableElement( + Set.of(Modifier.SYNCHRONIZED), + type(void.class), + "removeLocalTracer", + new CodeVariableElement(types.InstructionTracer, "tracer")); + CodeTreeBuilder b = ex.createBuilder(); + + b.startStatement().startStaticCall(type(Objects.class), "requireNonNull").string("tracer").end().end(); + b.declaration(arrayOf(types.InstructionTracer), "tracers", "this.localTracers"); + b.declaration(type(boolean.class), "found", "false"); + + b.startFor().type(types.InstructionTracer).string(" t : tracers").end().startBlock(); + b.startIf().string("t == tracer").end().startBlock(); + b.statement("found = true"); + b.statement("break"); + b.end(); // if + b.end(); // for + + b.startIf().string("found").end().startBlock(); + + b.startDeclaration(arrayOf(types.InstructionTracer), "newTracers"); + b.startNewArray(arrayOf(types.InstructionTracer), CodeTreeBuilder.singleString("tracers.length - 1")); + b.end(); + b.end(); // declaration + + b.statement("int index = 0"); + b.startFor().string("int i = 0; i < tracers.length; i++").end().startBlock(); + b.declaration(types.InstructionTracer, "t", "tracers[i]"); + b.startIf().string("t != tracer").end().startBlock(); + b.statement("newTracers[index++] = t"); + b.end(); // if + b.end(); // for + + b.statement("this.localTracers = newTracers"); + + b.end(); // if (found) + + return ex; + } + + private CodeExecutableElement createGetTracedInstruction() { + CodeExecutableElement ex = GeneratorUtils.override(types.InstructionTracer_InstructionAccess, "getTracedInstruction", new String[]{"bytecode", "bytecodeIndex"}); + CodeTreeBuilder b = ex.createBuilder(); + b.startDeclaration(abstractBytecodeNode.asType(), "castBytecode").cast(abstractBytecodeNode.asType()).string("bytecode").end(); + int length = model.traceInstruction.getInstructionEncoding().length(); + b.declaration("int", "nextBytecodeIndex", "bytecodeIndex + " + length); + b.startReturn(); + emitParseInstruction(b, "castBytecode", "nextBytecodeIndex", CodeTreeBuilder.singleString("castBytecode.readValidBytecode(castBytecode.bytecodes, nextBytecodeIndex)")); + b.end(); + return ex; + } + + private CodeExecutableElement createGetTracedOperationCode() { + CodeExecutableElement ex = GeneratorUtils.override(types.InstructionTracer_InstructionAccess, "getTracedOperationCode", new String[]{"bytecode", "bytecodeIndex"}); + CodeTreeBuilder b = ex.createBuilder(); + b.startDeclaration(abstractBytecodeNode.asType(), "castBytecode").cast(abstractBytecodeNode.asType()).string("bytecode").end(); + int length = model.traceInstruction.getInstructionEncoding().length(); + b.declaration("int", "nextBytecodeIndex", "bytecodeIndex + " + length); + b.startReturn(); + b.string("castBytecode.readValidBytecode(castBytecode.bytecodes, ", "bytecodeIndex + " + length, ")"); + b.end(); + return ex; + } + + } + + final class InstructionDescriptorImplElement extends CodeTypeElement { + + InstructionDescriptorImplElement() { + super(Set.of(PRIVATE, STATIC, FINAL), ElementKind.CLASS, null, "InstructionDescriptorImpl"); + this.setSuperClass(types.InstructionDescriptor); + } + + void lazyInit() { + this.add(new CodeVariableElement(Set.of(FINAL), type(int.class), "opcode")); + + CodeExecutableElement c = this.add(createConstructorUsingFields(Set.of(), this, null)); + CodeTree tree = c.getBodyTree(); + CodeTreeBuilder b = c.createBuilder(); + b.startStatement().startSuperCall().staticReference(bytecodeRootNodesImpl.asType(), "VISIBLE_TOKEN").end().end(); + b.tree(tree); + + this.add(createGetLength()); + this.add(createGetName()); + this.add(createIsInstrumentation()); + this.add(createGetOperationCode()); + this.add(createGetArgumentDescriptors()); + + CodeVariableElement descriptors = this.add(new CodeVariableElement(Set.of(STATIC, FINAL), arrayOf(asType()), "DESCRIPTORS")); + descriptors.createInitBuilder().string("createDescriptors()"); + GeneratorUtils.addCompilationFinal(descriptors, 1); + this.add(createCreateDescriptors()); + } + + private CodeExecutableElement createCreateDescriptors() { + CodeExecutableElement ex = new CodeExecutableElement(Set.of(PRIVATE, STATIC), arrayOf(asType()), "createDescriptors"); + CodeTreeBuilder b = ex.createBuilder(); + int endIndex = model.getInstructionStartIndex() + model.getInstructions().size(); + b.startDeclaration(arrayOf(asType()), "array").startNewArray(arrayOf(asType()), CodeTreeBuilder.singleString(String.valueOf(endIndex))).end().end(); + b.startFor().string("int opcode = " + model.getInstructionStartIndex() + "; opcode < array.length; opcode++").end().startBlock(); + b.startStatement(); + b.string("array[opcode] = ").startNew(asType()).string("opcode").end(); + b.end(); + b.end(); + + b.statement("return array"); + return ex; + } + + private CodeExecutableElement createGetName() { + CodeExecutableElement ex = GeneratorUtils.override(types.InstructionDescriptor, "getName"); + ex.getModifiers().add(FINAL); + CodeTreeBuilder b = ex.createBuilder(); + b.startReturn().tree(instructionsElement.call("getName", "opcode")).end(); + return ex; + } + + private CodeExecutableElement createIsInstrumentation() { + CodeExecutableElement ex = GeneratorUtils.override(types.InstructionDescriptor, "isInstrumentation"); + ex.getModifiers().add(FINAL); + CodeTreeBuilder b = ex.createBuilder(); + b.startReturn().tree(instructionsElement.call("isInstrumentation", "opcode")).end(); + return ex; + } + + private CodeExecutableElement createGetLength() { + CodeExecutableElement ex = GeneratorUtils.override(types.InstructionDescriptor, "getLength"); + ex.getModifiers().add(FINAL); + CodeTreeBuilder b = ex.createBuilder(); + b.startReturn().tree(instructionsElement.call("getLength", "opcode")).end(); + return ex; + } + + private CodeExecutableElement createGetOperationCode() { + CodeExecutableElement ex = GeneratorUtils.override(types.InstructionDescriptor, "getOperationCode"); + ex.getModifiers().add(FINAL); + CodeTreeBuilder b = ex.createBuilder(); + b.startReturn().string("opcode").end(); + return ex; + } + + private CodeExecutableElement createGetArgumentDescriptors() { + CodeExecutableElement ex = GeneratorUtils.override(types.InstructionDescriptor, "getArgumentDescriptors"); + CodeTreeBuilder b = ex.createBuilder(); + b.startReturn(); + b.startStaticCall(instructionsElement.asType(), "getArgumentDescriptors").string("opcode").end(); + b.end(); + return ex; + } + } + final class InstructionImplElement extends CodeTypeElement { InstructionImplElement() { super(Set.of(PRIVATE, STATIC, FINAL), ElementKind.CLASS, null, "InstructionImpl"); - this.setSuperClass(types.Instruction); } void lazyInit() { + this.setSuperClass(types.Instruction); + this.add(new CodeVariableElement(Set.of(FINAL), abstractBytecodeNode.asType(), "bytecode")); this.add(new CodeVariableElement(Set.of(FINAL), type(int.class), "bci")); this.add(new CodeVariableElement(Set.of(FINAL), type(int.class), "opcode")); @@ -11279,13 +12085,18 @@ void lazyInit() { this.add(createGetBytecodeIndex()); this.add(createGetBytecodeNode()); - this.add(createGetOperationCode()); - this.add(createGetLength()); - this.add(createGetName()); this.add(createGetArguments()); - this.add(createIsInstrumentation()); + this.add(createGetDescriptor()); this.add(createNext()); + } + private CodeExecutableElement createGetDescriptor() { + CodeExecutableElement ex = GeneratorUtils.override(types.Instruction, "getDescriptor"); + CodeTreeBuilder b = ex.createBuilder(); + b.startReturn(); + b.staticReference(instructionDescriptorImpl.asType(), "DESCRIPTORS[opcode]"); + b.end(); + return ex; } private CodeExecutableElement createGetBytecodeIndex() { @@ -11313,15 +12124,6 @@ private CodeExecutableElement createGetBytecodeNode() { return ex; } - private CodeExecutableElement createGetName() { - CodeExecutableElement ex = GeneratorUtils.override(types.Instruction, "getName"); - CodeTreeBuilder b = ex.createBuilder(); - b.startReturn(); - b.startStaticCall(instructionsElement.asType(), "getName").string("opcode").end(); - b.end(); - return ex; - } - private CodeExecutableElement createGetArguments() { CodeExecutableElement ex = GeneratorUtils.override(types.Instruction, "getArguments"); CodeTreeBuilder b = ex.createBuilder(); @@ -11331,31 +12133,6 @@ private CodeExecutableElement createGetArguments() { return ex; } - private CodeExecutableElement createIsInstrumentation() { - CodeExecutableElement ex = GeneratorUtils.override(types.Instruction, "isInstrumentation"); - CodeTreeBuilder b = ex.createBuilder(); - b.startReturn(); - b.startStaticCall(instructionsElement.asType(), "isInstrumentation").string("opcode").end(); - b.end(); - return ex; - } - - private CodeExecutableElement createGetLength() { - CodeExecutableElement ex = GeneratorUtils.override(types.Instruction, "getLength"); - CodeTreeBuilder b = ex.createBuilder(); - b.startReturn(); - b.startStaticCall(instructionsElement.asType(), "getLength").string("opcode").end(); - b.end(); - return ex; - } - - private CodeExecutableElement createGetOperationCode() { - CodeExecutableElement ex = GeneratorUtils.override(types.Instruction, "getOperationCode"); - CodeTreeBuilder b = ex.createBuilder(); - b.startReturn().string("opcode").end(); - return ex; - } - } final class TagNodeElement extends CodeTypeElement { @@ -11785,6 +12562,10 @@ final class AbstractBytecodeNodeElement extends CodeTypeElement { if (model.isBytecodeUpdatable() || model.hasYieldOperation()) { this.add(createTransition()); } + if (model.hasYieldOperation() && model.enableInstructionTracing) { + this.add(createIsInstructionTracingEnabled()); + } + if (model.isBytecodeUpdatable()) { this.add(createToStableBytecodeIndex()); this.add(createFromStableBytecodeIndex()); @@ -11794,6 +12575,15 @@ final class AbstractBytecodeNodeElement extends CodeTypeElement { this.add(createAdoptNodesAfterUpdate()); } + private CodeExecutableElement createIsInstructionTracingEnabled() { + CodeExecutableElement ex = new CodeExecutableElement(Set.of(PRIVATE, FINAL), type(boolean.class), "isInstructionTracingEnabled"); + CodeTreeBuilder b = ex.createBuilder(); + b.startReturn(); + b.string("readValidBytecode(this.bytecodes, 0) == ").tree(createInstructionConstant(model.traceInstruction)); + b.end(); + return ex; + } + private CodeExecutableElement createGetLocalCount() { CodeExecutableElement ex = GeneratorUtils.override(types.BytecodeNode, "getLocalCount", new String[]{"bci"}, new TypeMirror[]{type(int.class)}); ex.getModifiers().add(FINAL); @@ -12178,7 +12968,8 @@ private CodeExecutableElement createGetLocals() { record InstructionValidationGroup(List immediates, int instructionLength, boolean allowNegativeChildBci, boolean localVar, boolean localVarMat) { InstructionValidationGroup(BytecodeDSLModel model, InstructionModel instruction) { - this(instruction.getImmediates(), instruction.getInstructionLength(), acceptsInvalidChildBci(model, instruction), + this(instruction.getImmediates(), instruction.getInstructionLength(), + acceptsInvalidChildBci(model, instruction), instruction.kind.isLocalVariableAccess(), instruction.kind.isLocalVariableMaterializedAccess()); } @@ -15645,6 +16436,14 @@ private void buildInstructionCaseBlock(CodeTreeBuilder b, InstructionModel instr emitCustomStackEffect(b, getStackEffect(instr)); } break; + case TRACE_INSTRUCTION: + b.startStatement(); + b.startCall(lookupTraceInstruction(instr).getSimpleName().toString()); + b.string("frame").string("bci"); + b.end(); + b.end(); + break; + case SUPERINSTRUCTION: // not implemented yet break; @@ -17190,6 +17989,28 @@ private CodeExecutableElement lookupDoMergeConditional(InstructionModel instr) { } + private CodeExecutableElement lookupTraceInstruction(InstructionModel instr) { + CodeExecutableElement method = doInstructionMethods.get(instr); + if (method != null) { + return method; + } + method = new CodeExecutableElement( + Set.of(PRIVATE), + type(void.class), instructionMethodName(instr), + new CodeVariableElement(types.Frame, "frame"), + new CodeVariableElement(type(int.class), "bci")); + + CodeTreeBuilder b = method.createBuilder(); + b.startStatement(); + b.tree(readConstFastPath(CodeTreeBuilder.singleString("Builder.INSTRUCTION_TRACER_CONSTANT_INDEX"), "this.constants", instructionTracerAccessImplElement.asType())); + b.startCall(".onInstructionEnter").string("this").string("bci").string("frame").end(); + b.end(); // statement + + doInstructionMethods.put(instr, method); + return method; + + } + private CodeExecutableElement lookupDoSpecializeMergeConditional(InstructionModel instr) { CodeExecutableElement method = doInstructionMethods.get(instr); if (method != null) { @@ -18281,7 +19102,7 @@ private CodeExecutableElement createToString() { b.startStaticCall(type(String.class), "format"); b.doubleQuote("%s(resume_bci=%s)"); b.string("root"); - b.string("location.getBytecodeIndex()"); + b.string("location == null ? \"unreachable\" : location.getBytecodeIndex()"); b.end(2); return ex; } diff --git a/truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/bytecode/generator/BytecodeRootNodeErrorElement.java b/truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/bytecode/generator/BytecodeRootNodeErrorElement.java index 7d239fb4c478..412b0b22a11a 100644 --- a/truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/bytecode/generator/BytecodeRootNodeErrorElement.java +++ b/truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/bytecode/generator/BytecodeRootNodeErrorElement.java @@ -42,6 +42,7 @@ import static com.oracle.truffle.dsl.processor.bytecode.generator.ElementHelpers.generic; import static com.oracle.truffle.dsl.processor.generator.GeneratorUtils.mergeSuppressWarnings; +import static javax.lang.model.element.Modifier.ABSTRACT; import static javax.lang.model.element.Modifier.FINAL; import static javax.lang.model.element.Modifier.PRIVATE; import static javax.lang.model.element.Modifier.PUBLIC; @@ -82,17 +83,24 @@ final class BytecodeRootNodeErrorElement extends CodeTypeElement { private final DeclaredType languageClass; private final BuilderElement builder; private final DeclaredType builderType; + private final BytecodeDescriptorElement bytecodeDescriptorElement; private final TypeMirror parserType; + private final TypeMirror abstractBuilderType; - BytecodeRootNodeErrorElement(BytecodeDSLModel model) { + BytecodeRootNodeErrorElement(BytecodeDSLModel model, TypeMirror abstractBuilderType) { super(Set.of(PUBLIC, FINAL), ElementKind.CLASS, ElementUtils.findPackageElement(model.getTemplateType()), model.getName()); this.model = model; this.languageClass = model.languageClass == null ? generic(types.TruffleLanguage) : model.languageClass; + this.abstractBuilderType = abstractBuilderType; this.setSuperClass(model.templateType.asType()); GeneratorUtils.addGeneratedBy(context, this, model.templateType); this.builder = this.add(new BuilderElement()); this.builderType = new GeneratedTypeMirror("", builder.getSimpleName().toString(), builder.asType()); this.parserType = generic(types.BytecodeParser, builderType); + this.bytecodeDescriptorElement = this.add(new BytecodeDescriptorElement()); + CodeVariableElement descriptor = new CodeVariableElement(Set.of(PUBLIC, STATIC, FINAL), bytecodeDescriptorElement.asType(), "BYTECODE"); + descriptor.createInitBuilder().string("null").end(); + this.getEnclosedElements().add(0, descriptor); this.add(createExecute()); this.add(createConstructor()); @@ -180,6 +188,19 @@ TypeMirror type(Class c) { return context.getType(c); } + final class BytecodeDescriptorElement extends CodeTypeElement { + BytecodeDescriptorElement() { + super(Set.of(PUBLIC, STATIC, ABSTRACT), ElementKind.CLASS, null, "Bytecode"); + + TypeMirror superType = abstractBuilderType == null ? null : BytecodeDSLCodeGenerator.findBytecodeVariantType(abstractBuilderType); + if (superType == null) { + // regular case + superType = generic(types.BytecodeDescriptor, model.getTemplateType().asType(), model.languageClass, builder.asType()); + } + setSuperClass(superType); + } + } + private final class BuilderElement extends CodeTypeElement { BuilderElement() { super(Set.of(PUBLIC, STATIC, FINAL), ElementKind.CLASS, null, "Builder"); diff --git a/truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/bytecode/model/BytecodeDSLBuiltins.java b/truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/bytecode/model/BytecodeDSLBuiltins.java index 02b984b02a2e..78516d8df43e 100644 --- a/truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/bytecode/model/BytecodeDSLBuiltins.java +++ b/truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/bytecode/model/BytecodeDSLBuiltins.java @@ -396,6 +396,11 @@ public static void addBuiltinsOnFinalize(BytecodeDSLModel m) { m.tagResumeInstruction.addImmediate(ImmediateKind.TAG_NODE, "tag"); } + if (m.enableInstructionTracing) { + m.traceInstruction = m.instruction(InstructionKind.TRACE_INSTRUCTION, "trace.instruction", m.signature(void.class)); + m.traceInstructionInstrumentationIndex = m.getInstrumentations().size(); + } + // invalidate instructions should be the last instructions to add as it they depend on the // length of all other instructions if (m.isBytecodeUpdatable()) { diff --git a/truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/bytecode/model/BytecodeDSLModel.java b/truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/bytecode/model/BytecodeDSLModel.java index d8a04fda39c6..4e721c130d27 100644 --- a/truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/bytecode/model/BytecodeDSLModel.java +++ b/truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/bytecode/model/BytecodeDSLModel.java @@ -80,16 +80,12 @@ public class BytecodeDSLModel extends Template implements PrettyPrintable { public final TypeElement templateType; // The generated class. public final String modelName; - // The abstract builder class (different from builderType if GenerateBytecodeTestVariants used) - public final TypeMirror abstractBuilderType; - public BytecodeDSLModel(ProcessorContext context, TypeElement templateType, AnnotationMirror mirror, String name, - TypeMirror abstractBuilderType) { + public BytecodeDSLModel(ProcessorContext context, TypeElement templateType, AnnotationMirror mirror, String name) { super(context, templateType, mirror); this.context = context; this.templateType = templateType; this.modelName = name; - this.abstractBuilderType = abstractBuilderType; } private int operationId = 1; @@ -136,6 +132,7 @@ public BytecodeDSLModel(ProcessorContext context, TypeElement templateType, Anno public String variadicStackLimit; public DSLExpression variadicStackLimitExpression; + public boolean enableInstructionTracing; public ExecutableElement fdConstructor; public ExecutableElement fdBuilderConstructor; public ExecutableElement interceptControlFlowException; @@ -194,6 +191,8 @@ public BytecodeDSLModel(ProcessorContext context, TypeElement templateType, Anno public InstructionModel tagYieldNullInstruction; public InstructionModel tagResumeInstruction; public InstructionModel clearLocalInstruction; + public InstructionModel traceInstruction; + public int traceInstructionInstrumentationIndex = -1; public ExportsData tagTreeNodeLibrary; @@ -271,7 +270,7 @@ public TypeMirror getProvidedRootBodyTag() { } public boolean isBytecodeUpdatable() { - return !getInstrumentations().isEmpty() || (enableTagInstrumentation && !getProvidedTags().isEmpty()); + return hasInstrumentations() || (enableTagInstrumentation && !getProvidedTags().isEmpty()); } public boolean hasYieldOperation() { @@ -305,10 +304,18 @@ public OperationModel operation(OperationKind kind, String name, String javadoc, return op; } + public boolean hasInstrumentations() { + return !instrumentations.isEmpty() || enableInstructionTracing; + } + public List getInstrumentations() { return instrumentations; } + public int getInstrumentationsCount() { + return instrumentations.size() + (enableInstructionTracing ? 1 : 0); + } + public CustomOperationModel customRegularOperation(OperationKind kind, String name, String javadoc, TypeElement typeElement, AnnotationMirror mirror) { OperationModel op = operation(kind, name, javadoc); if (op == null) { @@ -466,7 +473,7 @@ public void finalizeInstructions() { } } - short currentId = 1; + short currentId = getInstructionStartIndex(); for (InstructionModel m : newInstructions.values()) { m.setId(currentId++); m.validateAlignment(); @@ -487,6 +494,10 @@ public void finalizeInstructions() { this.instructions = newInstructions; } + public short getInstructionStartIndex() { + return 1; + } + @Override protected List findChildContainers() { ArrayList result = new ArrayList<>(customRegularOperations.values()); diff --git a/truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/bytecode/model/InstructionModel.java b/truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/bytecode/model/InstructionModel.java index 61d993b28aac..e94ea16367b8 100644 --- a/truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/bytecode/model/InstructionModel.java +++ b/truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/bytecode/model/InstructionModel.java @@ -100,6 +100,7 @@ public enum InstructionKind { TAG_YIELD, TAG_YIELD_NULL, TAG_RESUME, + TRACE_INSTRUCTION, INVALIDATE; public boolean isLocalVariableAccess() { @@ -531,8 +532,14 @@ public boolean isTagInstrumentation() { } } + public boolean isTraceInstrumentation() { + return kind == InstructionKind.TRACE_INSTRUCTION; + } + public boolean isInstrumentation() { - if (isTagInstrumentation()) { + if (isTraceInstrumentation()) { + return true; + } else if (isTagInstrumentation()) { return true; } else if (kind == InstructionKind.CUSTOM) { return operation.kind == OperationKind.CUSTOM_INSTRUMENTATION; diff --git a/truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/bytecode/parser/BytecodeDSLParser.java b/truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/bytecode/parser/BytecodeDSLParser.java index 17be0b8f77e4..f98d3f006fb9 100644 --- a/truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/bytecode/parser/BytecodeDSLParser.java +++ b/truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/bytecode/parser/BytecodeDSLParser.java @@ -71,7 +71,6 @@ import com.oracle.truffle.dsl.processor.ProcessorContext; import com.oracle.truffle.dsl.processor.TruffleProcessorOptions; import com.oracle.truffle.dsl.processor.TruffleTypes; -import com.oracle.truffle.dsl.processor.bytecode.generator.BytecodeDSLCodeGenerator; import com.oracle.truffle.dsl.processor.bytecode.model.BytecodeDSLBuiltins; import com.oracle.truffle.dsl.processor.bytecode.model.BytecodeDSLModel; import com.oracle.truffle.dsl.processor.bytecode.model.BytecodeDSLModels; @@ -137,7 +136,7 @@ protected BytecodeDSLModels parse(Element element, List mirror } topLevelAnnotationMirror = generateBytecodeMirror; - models = List.of(createBytecodeDSLModel(typeElement, generateBytecodeMirror, "Gen", types.BytecodeBuilder)); + models = List.of(createBytecodeDSLModel(typeElement, generateBytecodeMirror, "Gen")); } BytecodeDSLModels modelList = new BytecodeDSLModels(context, typeElement, topLevelAnnotationMirror, models); @@ -167,8 +166,6 @@ private List parseGenerateBytecodeTestVariants(TypeElement typ boolean enableMaterializedLocalAccessors = false; boolean enableTagInstrumentation = false; - TypeMirror abstractBuilderType = BytecodeDSLCodeGenerator.createAbstractBuilderType(typeElement).asType(); - List result = new ArrayList<>(); for (AnnotationMirror variant : variants) { @@ -178,7 +175,7 @@ private List parseGenerateBytecodeTestVariants(TypeElement typ AnnotationValue generateBytecodeMirrorValue = ElementUtils.getAnnotationValue(variant, "configuration"); AnnotationMirror generateBytecodeMirror = ElementUtils.resolveAnnotationValue(AnnotationMirror.class, generateBytecodeMirrorValue); - BytecodeDSLModel model = createBytecodeDSLModel(typeElement, generateBytecodeMirror, suffix, abstractBuilderType); + BytecodeDSLModel model = createBytecodeDSLModel(typeElement, generateBytecodeMirror, suffix); if (!first && suffixes.contains(suffix)) { model.addError(variant, suffixValue, "A variant with suffix \"%s\" already exists. Each variant must have a unique suffix.", suffix); @@ -225,8 +222,9 @@ private List parseGenerateBytecodeTestVariants(TypeElement typ return result; } - private BytecodeDSLModel createBytecodeDSLModel(TypeElement typeElement, AnnotationMirror generateBytecodeMirror, String suffix, TypeMirror abstractBuilderType) { - return new BytecodeDSLModel(context, typeElement, generateBytecodeMirror, typeElement.getSimpleName() + suffix, abstractBuilderType); + private BytecodeDSLModel createBytecodeDSLModel(TypeElement typeElement, AnnotationMirror generateBytecodeMirror, String suffix) { + return new BytecodeDSLModel(context, typeElement, generateBytecodeMirror, + typeElement.getSimpleName() + suffix); } @SuppressWarnings("unchecked") @@ -252,6 +250,7 @@ private void parseBytecodeDSLModel(TypeElement typeElement, BytecodeDSLModel mod model.additionalAssertions = TruffleProcessorOptions.additionalAssertions(processingEnv) || ElementUtils.getAnnotationValue(Boolean.class, generateBytecodeMirror, "additionalAssertions", true); model.enableThreadedSwitch = ElementUtils.getAnnotationValue(Boolean.class, generateBytecodeMirror, "enableThreadedSwitch"); + model.enableInstructionTracing = ElementUtils.getAnnotationValue(Boolean.class, generateBytecodeMirror, "enableInstructionTracing"); BytecodeDSLBuiltins.addBuiltins(model, types, context); @@ -547,11 +546,11 @@ private void parseBytecodeDSLModel(TypeElement typeElement, BytecodeDSLModel mod CustomOperationParser.forCodeGeneration(model, types.Operation).parseCustomRegularOperation(mir, te, null); } - if (model.getInstrumentations().size() > MAX_INSTRUMENTATIONS) { - model.addError("Too many @Instrumentation annotated operations specified. The number of instrumentations is " + model.getInstrumentations().size() + + if (model.getInstrumentationsCount() > MAX_INSTRUMENTATIONS) { + model.addError("Too many @Instrumentation annotated operations specified. The number of instrumentations is " + model.getInstrumentationsCount() + ". The maximum number of instrumentations is " + MAX_INSTRUMENTATIONS + "."); - } else if (model.getInstrumentations().size() + model.getProvidedTags().size() > MAX_TAGS_AND_INSTRUMENTATIONS) { - model.addError("Too many @Instrumentation and provided tags specified. The number of instrumentrations is " + model.getInstrumentations().size() + " and provided tags is " + + } else if (model.getInstrumentationsCount() + model.getProvidedTags().size() > MAX_TAGS_AND_INSTRUMENTATIONS) { + model.addError("Too many @Instrumentation and provided tags specified. The number of instrumentrations is " + model.getInstrumentationsCount() + " and provided tags is " + model.getProvidedTags().size() + ". The maximum number of instrumentations and provided tags is " + MAX_TAGS_AND_INSTRUMENTATIONS + "."); } diff --git a/truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/bytecode/parser/CustomOperationParser.java b/truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/bytecode/parser/CustomOperationParser.java index ef61d5f17242..0d367c79856c 100644 --- a/truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/bytecode/parser/CustomOperationParser.java +++ b/truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/bytecode/parser/CustomOperationParser.java @@ -126,7 +126,7 @@ public static CustomOperationParser forProxyValidation() { dummyBytecodeClass.setEnclosingElement(new GeneratedPackageElement("dummy")); return new CustomOperationParser( context, - new BytecodeDSLModel(context, dummyBytecodeClass, null, null, null), + new BytecodeDSLModel(context, dummyBytecodeClass, null, null), context.getTypes().OperationProxy_Proxyable, true); } diff --git a/truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/generator/GeneratorUtils.java b/truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/generator/GeneratorUtils.java index 492e6da966d4..c9d23d9e11d7 100644 --- a/truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/generator/GeneratorUtils.java +++ b/truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/generator/GeneratorUtils.java @@ -141,6 +141,14 @@ public static CodeExecutableElement createConstructorUsingFields(Set m return createConstructorUsingFields(modifiers, clazz, superConstructor); } + public static void addCompilationFinal(CodeVariableElement v, int dimensions) { + CodeAnnotationMirror mirror = new CodeAnnotationMirror(ProcessorContext.types().CompilerDirectives_CompilationFinal); + v.getAnnotationMirrors().add(mirror); + if (dimensions >= 0) { + mirror.setElementValue("dimensions", new CodeAnnotationValue(dimensions)); + } + } + public static void addBoundaryOrTransferToInterpreter(CodeExecutableElement method, CodeTreeBuilder builder) { if (builder != null && method != builder.findMethod()) { throw new AssertionError("Expected " + method + " but was " + builder.findMethod()); diff --git a/truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/java/model/CodeTypeElement.java b/truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/java/model/CodeTypeElement.java index 209cc31580a5..c56b9e16b071 100644 --- a/truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/java/model/CodeTypeElement.java +++ b/truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/java/model/CodeTypeElement.java @@ -131,6 +131,15 @@ public VariableElement findField(String name) { return null; } + public TypeElement findInnerClass(String name) { + for (TypeElement field : getInnerClasses()) { + if (field.getSimpleName().toString().equals(name)) { + return field; + } + } + return null; + } + @Override public NestingKind getNestingKind() { return isTopLevelClass() ? NestingKind.TOP_LEVEL : NestingKind.LOCAL; diff --git a/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/EngineAccessor.java b/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/EngineAccessor.java index bfb154d34893..e6a5e78cf184 100644 --- a/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/EngineAccessor.java +++ b/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/EngineAccessor.java @@ -96,6 +96,7 @@ import com.oracle.truffle.api.ContextThreadLocal; import com.oracle.truffle.api.InstrumentInfo; import com.oracle.truffle.api.InternalResource; +import com.oracle.truffle.api.RootCallTarget; import com.oracle.truffle.api.ThreadLocalAction; import com.oracle.truffle.api.Truffle; import com.oracle.truffle.api.TruffleContext; @@ -156,6 +157,7 @@ final class EngineAccessor extends Accessor { static final ExceptionSupport EXCEPTION = ACCESSOR.exceptionSupport(); static final RuntimeSupport RUNTIME = ACCESSOR.runtimeSupport(); static final HostSupport HOST = ACCESSOR.hostSupport(); + static final BytecodeSupport BYTECODE = ACCESSOR.bytecodeSupport(); static final LanguageProviderSupport LANGUAGE_PROVIDER = ACCESSOR.languageProviderSupport(); static final InstrumentProviderSupport INSTRUMENT_PROVIDER = ACCESSOR.instrumentProviderSupport(); @@ -2302,6 +2304,35 @@ public DispatchOutputStream getEngineOut(Object engine) { return ((PolyglotEngineImpl) engine).out; } + @Override + public T getOrCreateBytecodeData(Object languageInstance, Function create) { + PolyglotSharingLayer layer = (PolyglotSharingLayer) languageInstance; + if (layer == null) { + layer = (PolyglotSharingLayer) getCurrentSharingLayer(); + if (layer == null) { + // we can't support engine instruction tracers for disconnected code + return null; + } + } + return layer.getOrCreateBytecodeData(create); + } + + @Override + public void forEachLoadedRootNode(Object sharingLayer, Consumer rootNodeUpdater) { + PolyglotSharingLayer layer = (PolyglotSharingLayer) sharingLayer; + for (CallTarget target : EngineAccessor.INSTRUMENT.getLoadedCallTargets(layer.engine.instrumentationHandler)) { + rootNodeUpdater.accept(((RootCallTarget) target).getRootNode()); + } + } + + @Override + public Object getSharingLayer(Object languageInstance) { + if (languageInstance == null) { + return null; + } + return ((PolyglotLanguageInstance) languageInstance).sharing; + } + } private static class GuardedExecutableNode extends ExecutableNode { diff --git a/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotContextImpl.java b/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotContextImpl.java index ada1572fe5db..548636e1110c 100644 --- a/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotContextImpl.java +++ b/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotContextImpl.java @@ -3782,6 +3782,7 @@ boolean patch(PolyglotContextConfig newConfig) { PolyglotSharingLayer.Shared s = layer.shared; if (s != null) { s.sourceCache.patch(TracingSourceCacheListener.createOrNull(engine), engine.sourceCacheStatisticsListener); + layer.initializeInstructionTracers(s); } return true; } diff --git a/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotEngineImpl.java b/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotEngineImpl.java index 03e80789c2ac..cf24c1e0471f 100644 --- a/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotEngineImpl.java +++ b/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotEngineImpl.java @@ -480,6 +480,7 @@ void freeSharingLayer(PolyglotSharingLayer layer, PolyglotContextImpl context) { switch (layer.getContextPolicy()) { case EXCLUSIVE: + layer.close(); break; case REUSE: sharedLayers.add(layer); @@ -1399,6 +1400,9 @@ void ensureClosed(boolean force, boolean initiatedByContext) { } getEngineLogger().log(Level.INFO, String.format("Specialization histogram: %n%s", logMessage.toString())); } + for (PolyglotSharingLayer layer : sharedLayers) { + layer.close(); + } RUNTIME.onEngineClosed(this.runtimeData); if (sourceCacheStatisticsListener != null) { diff --git a/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotEngineOptions.java b/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotEngineOptions.java index 0e340eedc4c6..cd22056dcee0 100644 --- a/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotEngineOptions.java +++ b/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotEngineOptions.java @@ -40,6 +40,11 @@ */ package com.oracle.truffle.polyglot; +import java.time.Duration; +import java.time.format.DateTimeParseException; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.List; import java.util.function.Function; import org.graalvm.options.OptionCategory; @@ -161,6 +166,43 @@ final class PolyglotEngineOptions { @Option(category = OptionCategory.EXPERT, stability = OptionStability.STABLE, help = "Print source cache statistics for an engine when the engine is closed. With the details enabled, statistics for all individual sources are printed.") // public static final OptionKey SourceCacheStatisticDetails = new OptionKey<>(false); + @Option(category = OptionCategory.EXPERT, stability = OptionStability.STABLE, help = "" + // + "Trace every executed bytecode instruction. " + // + "Very high overhead, use only for debugging, never in production. " + // + "Supported only by Bytecode DSL interpreters. " + // + "Combine with engine.BytecodeMethodFilter and engine.BytecodeLanguageFilter to limit output.")// + public static final OptionKey TraceBytecode = new OptionKey<>(false); + + @Option(category = OptionCategory.EXPERT, usageSyntax = "true|false|[,...]", stability = OptionStability.STABLE, help = "" + // + "Collect and print a histogram of executed bytecode opcodes. " + // + "Set to 'true' to enable basic mode or use a comma separated list to configure grouping (e.g. source,root)." + // + "Available groupings are root, tier, source, language, thread." + // + "This feature adds high overhead, use for profiling in non-production runs only. " + // + "Supported only by Bytecode DSL interpreters. " + // + "Prints when the engine is closed by default, or periodically if BytecodeHistogramInterval > 0.") // + public static final OptionKey> BytecodeHistogram = new OptionKey<>(null, createBytecodeHistogramType()); + + @Option(category = OptionCategory.EXPERT, stability = OptionStability.STABLE, help = "" + // + "Print and reset the opcode histogram at a fixed interval while BytecodeHistogram is enabled. " + // + "Use 0 to disable periodic printing and print only once at shutdown. " + // + "Examples: 250ms, 2s, 1m.") // + public static final OptionKey BytecodeHistogramInterval = new OptionKey<>(Duration.ofMillis(0), createDurationType()); + + @Option(category = OptionCategory.EXPERT, stability = OptionStability.STABLE, help = "" + // + "Limit tracing and statistics to selected methods. " + // + "Provide a comma-separated list of includes, or excludes prefixed with '~'. " + // + "Empty means no restriction. " + // + "Whitespace around commas is ignored. " + // + "Applies to engine.TraceBytecode and engine.BytecodeHistogram.") // + public static final OptionKey BytecodeMethodFilter = new OptionKey<>(""); + + @Option(category = OptionCategory.EXPERT, stability = OptionStability.STABLE, help = "" + // + "Limit tracing and statistics to specific language IDs. " + // + "Provide a comma-separated list of language IDs, for example: js, python. " + // + "Empty means that all languages are included. " + // + "Applies to engine.TraceBytecode and engine.BytecodeHistogram.") // + public static final OptionKey BytecodeLanguageFilter = new OptionKey<>(""); + enum StaticObjectStorageStrategies { DEFAULT, ARRAY_BASED, @@ -202,4 +244,99 @@ enum CloseOnGCExceptionAction { " PrintAll: Log each failure.%n" + " Throw: Throw an exception instead of logging the failure."; } + + enum BytecodeHistogramGrouping { + root, + tier, + source, + language, + thread, + } + + private static OptionType> createBytecodeHistogramType() { + return new OptionType<>("histogram", new Function>() { + + private static final OptionType TYPE = OptionType.defaultType(BytecodeHistogramGrouping.class); + + @SuppressWarnings("ResultOfMethodCallIgnored") + @Override + public List apply(String t) { + if ("true".equals(t)) { + return List.of(); + } else if ("false".equals(t)) { + return null; + } else { + List result = new ArrayList<>(); + for (String s : t.split(",")) { + result.add(TYPE.convert(s)); + } + return List.copyOf(result); + } + } + + }); + } + + private static OptionType createDurationType() { + return new OptionType<>("time", new Function() { + + @SuppressWarnings("ResultOfMethodCallIgnored") + @Override + public Duration apply(String t) { + try { + ChronoUnit foundUnit = null; + String foundUnitName = null; + for (ChronoUnit unit : ChronoUnit.values()) { + String unitName = getUnitName(unit); + if (unitName == null) { + continue; + } + if (t.endsWith(unitName)) { + foundUnit = unit; + foundUnitName = unitName; + break; + } + } + if (foundUnit == null || foundUnitName == null) { + throw invalidValue(t); + } + String subString = t.substring(0, t.length() - foundUnitName.length()); + long value = Long.parseLong(subString); + if (value < 0) { + throw invalidValue(t); + } + return Duration.of(value, foundUnit); + } catch (NumberFormatException | ArithmeticException | DateTimeParseException e) { + throw invalidValue(t); + } + } + + private String getUnitName(ChronoUnit unit) { + switch (unit) { + case MILLIS: + return "ms"; + case SECONDS: + return "s"; + case MINUTES: + return "m"; + case HOURS: + return "h"; + case DAYS: + return "d"; + } + return null; + } + + private IllegalArgumentException invalidValue(String value) { + throw new IllegalArgumentException("Invalid duration '" + value + "' specified. " // + + "A valid duration consists of a positive integer value followed by a chronological time unit. " // + + "For example '15ms' or '6s'. Valid time units are " // + + "'ms' for milliseconds, " // + + "'s' for seconds, " // + + "'m' for minutes, " // + + "'h' for hours, and " // + + "'d' for days."); + } + }); + } } diff --git a/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotInstructionTracers.java b/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotInstructionTracers.java new file mode 100644 index 000000000000..a48bb30b399a --- /dev/null +++ b/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotInstructionTracers.java @@ -0,0 +1,353 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.oracle.truffle.polyglot; + +import static com.oracle.truffle.polyglot.TracingSourceCacheListener.MAX_SOURCE_NAME_LENGTH; +import static com.oracle.truffle.polyglot.TracingSourceCacheListener.truncateString; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; +import java.util.logging.Level; + +import org.graalvm.collections.Pair; +import org.graalvm.options.OptionValues; + +import com.oracle.truffle.api.TruffleLanguage; +import com.oracle.truffle.api.TruffleLogger; +import com.oracle.truffle.api.bytecode.BytecodeDescriptor; +import com.oracle.truffle.api.bytecode.debug.HistogramInstructionTracer; +import com.oracle.truffle.api.bytecode.debug.PrintInstructionTracer; +import com.oracle.truffle.api.nodes.LanguageInfo; +import com.oracle.truffle.api.nodes.RootNode; +import com.oracle.truffle.api.source.Source; +import com.oracle.truffle.api.source.SourceSection; +import com.oracle.truffle.polyglot.PolyglotEngineOptions.BytecodeHistogramGrouping; + +/** + * Encapsulates data and logic related to polyglot instruction tracers. + */ +final class PolyglotInstructionTracers { + + private final PolyglotSharingLayer layer; + private final Pair, List> languageFilter; + private final Pair, List> methodFilter; + private final Function, PrintInstructionTracer> tracingFactory; + private final Function, HistogramInstructionTracer> histogramFactory; + private final ScheduledExecutorService intervalExecutor; + + PolyglotInstructionTracers(PolyglotSharingLayer layer) { + this.layer = layer; + OptionValues options = layer.engine.getEngineOptionValues(); + + this.languageFilter = parseLanguageFilter(layer.engine, options.get(PolyglotEngineOptions.BytecodeLanguageFilter)); + this.methodFilter = parseStringFilter(options.get(PolyglotEngineOptions.BytecodeMethodFilter)); + + if (options.get(PolyglotEngineOptions.TraceBytecode)) { + TruffleLogger engineLogger = layer.engine.getEngineLogger(); + PrintInstructionTracer.Builder b = PrintInstructionTracer.newBuilder((s) -> { + if (engineLogger.isLoggable(Level.INFO)) { + engineLogger.info("[bc] " + s); + } + }); + + if (this.languageFilter != null || this.methodFilter != null) { + b.filter((bytecodeNode) -> filterRootNode(languageFilter, methodFilter, bytecodeNode.getRootNode())); + } + + this.tracingFactory = (descriptor) -> b.build(); + EngineAccessor.BYTECODE.registerInstructionTracerFactory(layer, tracingFactory); + } else { + this.tracingFactory = null; + } + + List groups = options.get(PolyglotEngineOptions.BytecodeHistogram); + if (groups != null) { + HistogramInstructionTracer.Builder b = HistogramInstructionTracer.newBuilder(); + if (this.languageFilter != null || this.methodFilter != null) { + b.filter((bytecodeNode) -> filterRootNode(languageFilter, methodFilter, bytecodeNode.getRootNode())); + } + for (BytecodeHistogramGrouping group : groups) { + switch (group) { + case tier: + b.groupBy((node, thread, compilationTier) -> { + switch (compilationTier) { + case 1: + return TierGroup.TIER_2_COMPILED_FIRST; + case 2: + return TierGroup.TIER_3_COMPILED_LAST; + default: + switch (node.getTier()) { + case CACHED: + return TierGroup.TIER_1_INTERPRETED_CACHED; + case UNCACHED: + return TierGroup.TIER_0_INTERPRETED_UNCACHED; + default: + throw new AssertionError(); + } + } + }); + break; + case root: + b.groupBy((node, thread, compiled) -> new RootNodeGroup(node.getRootNode())); + break; + case language: + b.groupBy((node, thread, compiled) -> node.getRootNode().getLanguageInfo().getId()); + break; + case source: + b.groupBy((node, thread, compiled) -> { + SourceSection sc = node.getRootNode().getSourceSection(); + if (sc == null) { + return ""; + } else { + return new SourceGroup(sc.getSource()); + } + }); + break; + case thread: + b.groupBy((node, thread, compiled) -> thread); + break; + default: + throw new AssertionError(); + } + } + + this.histogramFactory = (descriptor) -> b.build(descriptor); + + Duration histogramInterval = options.get(PolyglotEngineOptions.BytecodeHistogramInterval); + if (!histogramInterval.isZero()) { + this.intervalExecutor = Executors.newSingleThreadScheduledExecutor(); + this.intervalExecutor.scheduleAtFixedRate( + this::dumpAndReset, + histogramInterval.toMillis(), + histogramInterval.toMillis(), + TimeUnit.MILLISECONDS); + } else { + this.intervalExecutor = null; + } + + EngineAccessor.BYTECODE.registerInstructionTracerFactory(layer, histogramFactory); + } else { + this.histogramFactory = null; + this.intervalExecutor = null; + } + } + + void onLayerClose() { + dumpAndReset(); + + if (intervalExecutor != null) { + intervalExecutor.close(); + try { + intervalExecutor.awaitTermination(25, TimeUnit.SECONDS); + } catch (InterruptedException e) { + } + } + } + + private void dumpAndReset() { + List engineTracers = EngineAccessor.BYTECODE.getEngineInstructionTracers(layer, histogramFactory); + TruffleLogger logger = layer.engine.getEngineLogger(); + for (HistogramInstructionTracer t : engineTracers) { + logger.info("[bc] " + t.getHistogramAndReset().dump()); + } + } + + static PolyglotInstructionTracers install(PolyglotSharingLayer layer) { + OptionValues options = layer.engine.getEngineOptionValues(); + boolean traceBytecode = options.get(PolyglotEngineOptions.TraceBytecode); + List bytecodeStatistics = options.get(PolyglotEngineOptions.BytecodeHistogram); + if (!traceBytecode && bytecodeStatistics == null) { + // fast-path: not enabled + return null; + } + return new PolyglotInstructionTracers(layer); + } + + private static boolean filterRootNode(Pair, List> languageFilter, Pair, List> methodFilter, RootNode rootNode) { + if (languageFilter != null) { + if (!applyLanguageFilter(rootNode, languageFilter)) { + return false; + } + } + if (methodFilter != null) { + if (!applyMethodFilter(rootNode.getQualifiedName(), methodFilter)) { + return false; + } + } + return true; + } + + private static Pair, List> parseLanguageFilter(PolyglotEngineImpl engine, String filter) { + Pair, List> f = parseStringFilter(filter); + if (f == null) { + return null; + } + if (f.getLeft().isEmpty() && f.getRight().isEmpty()) { + return null; + } + return Pair.create(resolveLanguages(engine, f.getLeft()), resolveLanguages(engine, f.getRight())); + } + + private static List resolveLanguages(PolyglotEngineImpl engine, List f) { + // we deliberately keep null values. + return f.stream().map((s) -> engine.idToInternalLanguageInfo.get(s)).toList(); + } + + private static Pair, List> parseStringFilter(String filter) { + if (filter == null || filter.isBlank()) { + return null; + } + List includesList = new ArrayList<>(); + List excludesList = new ArrayList<>(); + String[] items = filter.split(","); + for (String item : items) { + String trimmedItem = item.trim(); + if (trimmedItem.startsWith("~")) { + excludesList.add(trimmedItem.substring(1)); + } else { + includesList.add(trimmedItem); + } + } + return Pair.create(includesList, excludesList); + } + + private static boolean applyLanguageFilter(RootNode rootNode, Pair, List> filter) { + TruffleLanguage truffleLanguage = EngineAccessor.NODES.getLanguage(rootNode); + LanguageInfo language; + if (truffleLanguage != null) { + PolyglotLanguageInstance li = (PolyglotLanguageInstance) EngineAccessor.LANGUAGE.getPolyglotLanguageInstance(truffleLanguage); + language = li.language.info; + } else { + language = null; + } + List includes = filter.getLeft(); + // Empty include list means match-all unless excluded below. + boolean included = includes.isEmpty(); + if (language != null) { + for (int i = 0; !included && i < includes.size(); i++) { + if (includes.get(i) != null && language.equals(includes.get(i))) { + included = true; + } + } + } + if (!included) { + return false; + } + if (language != null) { + List excludes = filter.getRight(); + for (LanguageInfo exclude : excludes) { + if (language.equals(exclude)) { + return false; + } + } + } + return included; + } + + private static boolean applyMethodFilter(String input, Pair, List> filter) { + if (filter == null) { + return true; + } + List includes = filter.getLeft(); + // Empty include list means match-all unless excluded below. + boolean included = includes.isEmpty(); + if (input != null) { + for (int i = 0; i < includes.size(); i++) { + if (input.contains(includes.get(i))) { + included = true; + break; + } + } + } + if (!included) { + return false; + } + if (input != null) { + for (String exclude : filter.getRight()) { + if (input.contains(exclude)) { + return false; + } + } + } + return true; + } + + private record RootNodeGroup(RootNode rootNode) { + @Override + public String toString() { + String name = rootNode.getQualifiedName(); + if (name == null) { + name = rootNode.toString(); + } + return "Root: " + name; + } + } + + private record SourceGroup(Source source) { + @Override + public String toString() { + return String.format("Source: 0x%08x %s", source.hashCode(), truncateString(source.getName(), MAX_SOURCE_NAME_LENGTH)); + } + } + + private enum TierGroup { + TIER_3_COMPILED_LAST, + TIER_2_COMPILED_FIRST, + TIER_1_INTERPRETED_CACHED, + TIER_0_INTERPRETED_UNCACHED; + + @Override + public String toString() { + return switch (this) { + case TIER_0_INTERPRETED_UNCACHED -> "Tier 0: Unprofiled Interpreter"; + case TIER_1_INTERPRETED_CACHED -> "Tier 1: Profiled Interpreter"; + case TIER_2_COMPILED_FIRST -> "Tier 2: First Tier Compiler"; + case TIER_3_COMPILED_LAST -> "Tier 3: Last Tier Compiler"; + }; + } + } + +} diff --git a/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotSharingLayer.java b/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotSharingLayer.java index f232c06957bb..4b495f9c7f1e 100644 --- a/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotSharingLayer.java +++ b/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotSharingLayer.java @@ -48,6 +48,7 @@ import java.util.Objects; import java.util.Set; import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Function; import com.oracle.truffle.api.CompilerAsserts; import com.oracle.truffle.api.CompilerDirectives; @@ -109,10 +110,14 @@ static final class Shared { */ volatile PreinitConfig preinitConfig; volatile PolyglotContextImpl preInitializedContext; + volatile Object bytecodeData; int claimedCount; + @CompilationFinal PolyglotInstructionTracers instructionTracers; - private Shared(PolyglotEngineImpl engine, ContextPolicy contextPolicy, Map previousLanguageOptions) { + private Shared(PolyglotEngineImpl engine, + ContextPolicy contextPolicy, + Map previousLanguageOptions) { this.sourceCache = new PolyglotSourceCache(engine.getDeadSourcesQueue(), TracingSourceCacheListener.createOrNull(engine), engine.sourceCacheStatisticsListener); this.contextPolicy = contextPolicy; this.instances = new PolyglotLanguageInstance[engine.languageCount]; @@ -259,6 +264,7 @@ public boolean claimLayerForContext(PolyglotSharingLayer sharableLayer, Polyglot } s.claimedCount++; + initializeInstructionTracers(s); if (engine.getEngineOptionValues().get(PolyglotEngineOptions.TraceCodeSharing)) { traceClaimLayer(true, s, context, requestingLanguages, previousLanguageOptions); @@ -266,6 +272,12 @@ public boolean claimLayerForContext(PolyglotSharingLayer sharableLayer, Polyglot return true; } + void initializeInstructionTracers(Shared s) { + if (s.instructionTracers == null) { // avoid initializing twice + s.instructionTracers = PolyglotInstructionTracers.install(this); + } + } + boolean isSingleContext() { Shared s = this.shared; return (s == null || s.contextPolicy == ContextPolicy.EXCLUSIVE) && !engine.isStoreEngine(); @@ -499,10 +511,40 @@ public ContextPolicy getContextPolicy() { return shared.contextPolicy; } + @SuppressWarnings("unchecked") + public T getOrCreateBytecodeData(Function create) { + Shared s = this.shared; + if (s == null) { + return null; + } + // double checked locking + Object v = s.bytecodeData; + if (v == null) { + synchronized (engine.lock) { + v = s.bytecodeData; + if (v == null) { + s.bytecodeData = v = create.apply(this); + } + } + } + return (T) v; + } + public boolean isClaimed() { return shared != null; } + public void close() { + Shared s = this.shared; + if (s == null) { + return; + } + PolyglotInstructionTracers tracers = s.instructionTracers; + if (tracers != null) { + tracers.onLayerClose(); + } + } + @Override public boolean equals(Object obj) { if (!(obj instanceof PolyglotSharingLayer)) { @@ -783,5 +825,4 @@ static final class SharingLazyInitializationError extends AbstractTruffleExcepti } } - } diff --git a/truffle/src/com.oracle.truffle.sl/src/com/oracle/truffle/sl/SLLanguage.java b/truffle/src/com.oracle.truffle.sl/src/com/oracle/truffle/sl/SLLanguage.java index b2867b0563a3..6092a99e405b 100644 --- a/truffle/src/com.oracle.truffle.sl/src/com/oracle/truffle/sl/SLLanguage.java +++ b/truffle/src/com.oracle.truffle.sl/src/com/oracle/truffle/sl/SLLanguage.java @@ -40,6 +40,8 @@ */ package com.oracle.truffle.sl; +import static com.oracle.truffle.sl.bytecode.SLBytecodeRootNodeGen.BYTECODE; + import java.io.PrintStream; import java.lang.invoke.MethodHandles; import java.util.ArrayList; @@ -95,7 +97,6 @@ import com.oracle.truffle.sl.builtins.SLReadlnBuiltin; import com.oracle.truffle.sl.builtins.SLStackTraceBuiltin; import com.oracle.truffle.sl.bytecode.SLBytecodeRootNode; -import com.oracle.truffle.sl.bytecode.SLBytecodeRootNodeGen; import com.oracle.truffle.sl.nodes.SLAstRootNode; import com.oracle.truffle.sl.nodes.SLBuiltinAstNode; import com.oracle.truffle.sl.nodes.SLBuiltinAstNodeGen; @@ -355,7 +356,7 @@ public RootCallTarget lookupBuiltin(NodeFactory factory } private SLBytecodeRootNode createBytecodeBuiltin(TruffleString name, int argumentCount, NodeFactory factory) { - SLBytecodeRootNode node = SLBytecodeRootNodeGen.create(this, BytecodeConfig.DEFAULT, (b) -> { + SLBytecodeRootNode node = BYTECODE.create(this, BytecodeConfig.DEFAULT, (b) -> { b.beginSource(BUILTIN_SOURCE); b.beginSourceSectionUnavailable(); b.beginRoot(); diff --git a/truffle/src/com.oracle.truffle.sl/src/com/oracle/truffle/sl/bytecode/SLBytecodeSerialization.java b/truffle/src/com.oracle.truffle.sl/src/com/oracle/truffle/sl/bytecode/SLBytecodeSerialization.java index f2504d28510b..0d9fbe710b17 100644 --- a/truffle/src/com.oracle.truffle.sl/src/com/oracle/truffle/sl/bytecode/SLBytecodeSerialization.java +++ b/truffle/src/com.oracle.truffle.sl/src/com/oracle/truffle/sl/bytecode/SLBytecodeSerialization.java @@ -40,6 +40,8 @@ */ package com.oracle.truffle.sl.bytecode; +import static com.oracle.truffle.sl.bytecode.SLBytecodeRootNodeGen.BYTECODE; + import java.io.ByteArrayOutputStream; import java.io.DataInput; import java.io.DataOutput; @@ -50,8 +52,8 @@ import java.util.function.Supplier; import com.oracle.truffle.api.bytecode.BytecodeConfig; -import com.oracle.truffle.api.bytecode.BytecodeRootNodes; import com.oracle.truffle.api.bytecode.BytecodeParser; +import com.oracle.truffle.api.bytecode.BytecodeRootNodes; import com.oracle.truffle.api.bytecode.serialization.SerializationUtils; import com.oracle.truffle.api.source.Source; import com.oracle.truffle.api.strings.TruffleString; @@ -123,7 +125,7 @@ private static void writeByteArray(DataOutput buffer, byte[] data) throws IOExce public static BytecodeRootNodes deserializeNodes(SLLanguage language, byte[] inputData) throws IOException { Supplier input = () -> SerializationUtils.createDataInput(ByteBuffer.wrap(inputData)); - return SLBytecodeRootNodeGen.deserialize(language, BytecodeConfig.DEFAULT, input, (context, buffer) -> { + return BYTECODE.deserialize(language, BytecodeConfig.DEFAULT, input, (context, buffer) -> { byte tag; switch (tag = buffer.readByte()) { case CODE_SL_NULL: diff --git a/truffle/src/com.oracle.truffle.sl/src/com/oracle/truffle/sl/parser/SLBytecodeParser.java b/truffle/src/com.oracle.truffle.sl/src/com/oracle/truffle/sl/parser/SLBytecodeParser.java index 5c6a3845bb4b..647a23cffe4e 100644 --- a/truffle/src/com.oracle.truffle.sl/src/com/oracle/truffle/sl/parser/SLBytecodeParser.java +++ b/truffle/src/com.oracle.truffle.sl/src/com/oracle/truffle/sl/parser/SLBytecodeParser.java @@ -40,6 +40,8 @@ */ package com.oracle.truffle.sl.parser; +import static com.oracle.truffle.sl.bytecode.SLBytecodeRootNodeGen.BYTECODE; + import java.io.IOException; import java.math.BigInteger; import java.util.ArrayDeque; @@ -133,9 +135,9 @@ public static void parseSL(SLLanguage language, Source source, Map createBytecodeDSLNodes(Class interpreterClass, - BenchmarkLanguage language, BytecodeParser builder) { - return BytecodeDSLBenchmarkRootNodeBuilder.invokeCreate(interpreterClass, language, BytecodeConfig.DEFAULT, builder); + public static BytecodeRootNodes createBytecodeDSLNodes(BytecodeDSLBenchmarkRootNodeBuilder.BytecodeVariant variant, + BenchmarkLanguage language, BytecodeParser parser) { + return variant.create(language, BytecodeConfig.DEFAULT, parser); } public static BaseBytecodeRootNode createBytecodeNodes(Class interpreterClass, diff --git a/truffle/src/org.graalvm.truffle.benchmark/src/org/graalvm/truffle/benchmark/bytecode_dsl/BytecodeBuilderBenchmark.java b/truffle/src/org.graalvm.truffle.benchmark/src/org/graalvm/truffle/benchmark/bytecode_dsl/BytecodeBuilderBenchmark.java index fd3434fb2cf5..c9191d86df42 100644 --- a/truffle/src/org.graalvm.truffle.benchmark/src/org/graalvm/truffle/benchmark/bytecode_dsl/BytecodeBuilderBenchmark.java +++ b/truffle/src/org.graalvm.truffle.benchmark/src/org/graalvm/truffle/benchmark/bytecode_dsl/BytecodeBuilderBenchmark.java @@ -43,7 +43,6 @@ import java.util.function.Consumer; import org.graalvm.truffle.benchmark.TruffleBenchmark; -import org.graalvm.truffle.benchmark.bytecode_dsl.BytecodeDSLBenchmarkRootNodeAllOpts.Builder; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.Measurement; import org.openjdk.jmh.annotations.OperationsPerInvocation; @@ -59,7 +58,7 @@ @Measurement(iterations = 5, time = 1) public class BytecodeBuilderBenchmark extends TruffleBenchmark { - private static void parse(Builder b, Consumer inner) { + private static void parse(BytecodeDSLBenchmarkRootNodeBuilder b, Consumer inner) { b.beginRoot(); BytecodeLocal iLoc = b.createLocal(); @@ -221,7 +220,7 @@ public BytecodeDSLBenchmarkRootNode buildRecursive() { }).getNode(0); } - private void decrementAndParse(Builder b, int count) { + private void decrementAndParse(BytecodeDSLBenchmarkRootNodeBuilder b, int count) { if (count == 0) { return; } diff --git a/truffle/src/org.graalvm.truffle.benchmark/src/org/graalvm/truffle/benchmark/bytecode_dsl/ConstantOperandBenchmark.java b/truffle/src/org.graalvm.truffle.benchmark/src/org/graalvm/truffle/benchmark/bytecode_dsl/ConstantOperandBenchmark.java index 2e5078d3a3a7..697280418cdd 100644 --- a/truffle/src/org.graalvm.truffle.benchmark/src/org/graalvm/truffle/benchmark/bytecode_dsl/ConstantOperandBenchmark.java +++ b/truffle/src/org.graalvm.truffle.benchmark/src/org/graalvm/truffle/benchmark/bytecode_dsl/ConstantOperandBenchmark.java @@ -41,6 +41,7 @@ package org.graalvm.truffle.benchmark.bytecode_dsl; import org.graalvm.truffle.benchmark.TruffleBenchmark; +import org.graalvm.truffle.benchmark.bytecode_dsl.ConstantOperandBenchmarkRootNodeBuilder.BytecodeVariant; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.Measurement; import org.openjdk.jmh.annotations.Param; @@ -71,17 +72,17 @@ public class ConstantOperandBenchmark extends TruffleBenchmark { @Param({"ConstantOperandBenchmarkRootNodeRegular", "ConstantOperandBenchmarkRootNodeInlined"}) private String implClassName; @SuppressWarnings("unchecked") - private Class getImplClass() { + private BytecodeVariant getVariant() { try { - return (Class) Class.forName(ConstantOperandBenchmark.class.getPackageName() + "." + implClassName); - } catch (ClassNotFoundException e) { + return (BytecodeVariant) Class.forName(ConstantOperandBenchmark.class.getPackageName() + "." + implClassName).getField("BYTECODE").get(null); + } catch (ReflectiveOperationException e) { throw new RuntimeException("Could not load bytecode class " + implClassName, e); } } @Setup public void parseRootNode() { - rootCallTarget = ConstantOperandBenchmarkRootNodeBuilder.invokeCreate(getImplClass(), null, BytecodeConfig.DEFAULT, b -> { + rootCallTarget = getVariant().create(null, BytecodeConfig.DEFAULT, b -> { b.beginRoot(); b.beginBlock(); diff --git a/truffle/src/org.graalvm.truffle.benchmark/src/org/graalvm/truffle/benchmark/bytecode_dsl/SimpleBytecodeBenchmark.java b/truffle/src/org.graalvm.truffle.benchmark/src/org/graalvm/truffle/benchmark/bytecode_dsl/SimpleBytecodeBenchmark.java index 3f20b9463c99..f428eede3d04 100644 --- a/truffle/src/org.graalvm.truffle.benchmark/src/org/graalvm/truffle/benchmark/bytecode_dsl/SimpleBytecodeBenchmark.java +++ b/truffle/src/org.graalvm.truffle.benchmark/src/org/graalvm/truffle/benchmark/bytecode_dsl/SimpleBytecodeBenchmark.java @@ -87,11 +87,12 @@ public void setup(BenchmarkParams params) { benchmarkSpec = getBenchmarkSpec(); String benchMethod = getBenchmarkMethod(params); callTarget = switch (benchMethod) { - case "bytecodeDSLNoOpts" -> createBytecodeDSLNodes(BytecodeDSLBenchmarkRootNodeNoOpts.class, null, benchmarkSpec::parseBytecodeDSL).getNodes().getLast().getCallTarget(); - case "bytecodeDSLAllOpts" -> createBytecodeDSLNodes(BytecodeDSLBenchmarkRootNodeAllOpts.class, null, benchmarkSpec::parseBytecodeDSL).getNodes().getLast().getCallTarget(); - case "bytecodeDSLThreadedAllOpts" -> createBytecodeDSLNodes(BytecodeDSLBenchmarkRootNodeThreadedAllOpts.class, null, benchmarkSpec::parseBytecodeDSL).getNodes().getLast().getCallTarget(); + case "bytecodeDSLNoOpts" -> createBytecodeDSLNodes(BytecodeDSLBenchmarkRootNodeNoOpts.BYTECODE, null, benchmarkSpec::parseBytecodeDSL).getNodes().getLast().getCallTarget(); + case "bytecodeDSLAllOpts" -> createBytecodeDSLNodes(BytecodeDSLBenchmarkRootNodeAllOpts.BYTECODE, null, benchmarkSpec::parseBytecodeDSL).getNodes().getLast().getCallTarget(); + case "bytecodeDSLThreadedAllOpts" -> createBytecodeDSLNodes(BytecodeDSLBenchmarkRootNodeThreadedAllOpts.BYTECODE, null, + benchmarkSpec::parseBytecodeDSL).getNodes().getLast().getCallTarget(); case "bytecodeDSLUncached" -> { - var node = createBytecodeDSLNodes(BytecodeDSLBenchmarkRootNodeUncached.class, null, benchmarkSpec::parseBytecodeDSL).getNodes().getLast(); + var node = createBytecodeDSLNodes(BytecodeDSLBenchmarkRootNodeUncached.BYTECODE, null, benchmarkSpec::parseBytecodeDSL).getNodes().getLast(); node.getBytecodeNode().setUncachedThreshold(Integer.MIN_VALUE); yield node.getCallTarget(); }