diff --git a/substratevm/src/com.oracle.svm.interpreter.metadata/OWNERS.toml b/substratevm/src/com.oracle.svm.interpreter.metadata/OWNERS.toml
index 67c7e4be1111..31d2b48d7cf3 100644
--- a/substratevm/src/com.oracle.svm.interpreter.metadata/OWNERS.toml
+++ b/substratevm/src/com.oracle.svm.interpreter.metadata/OWNERS.toml
@@ -3,4 +3,5 @@ files = "*"
all = [
"bernhard.urban-forster@oracle.com",
"gilles.m.duboscq@oracle.com",
+ "david.leopoldseder@oracle.com"
]
diff --git a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/Bytecodes.java b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/Bytecodes.java
index dcab28120e7a..62d0d0610b72 100644
--- a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/Bytecodes.java
+++ b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/Bytecodes.java
@@ -31,11 +31,13 @@
import static com.oracle.svm.interpreter.metadata.Bytecodes.Flags.FALL_THROUGH;
import static com.oracle.svm.interpreter.metadata.Bytecodes.Flags.FIELD_READ;
import static com.oracle.svm.interpreter.metadata.Bytecodes.Flags.FIELD_WRITE;
+import static com.oracle.svm.interpreter.metadata.Bytecodes.Flags.IF_BRANCH_PROFILED;
import static com.oracle.svm.interpreter.metadata.Bytecodes.Flags.INVOKE;
import static com.oracle.svm.interpreter.metadata.Bytecodes.Flags.LOAD;
import static com.oracle.svm.interpreter.metadata.Bytecodes.Flags.STOP;
import static com.oracle.svm.interpreter.metadata.Bytecodes.Flags.STORE;
import static com.oracle.svm.interpreter.metadata.Bytecodes.Flags.TRAP;
+import static com.oracle.svm.interpreter.metadata.Bytecodes.Flags.TYPE_PROFILED;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
@@ -325,6 +327,14 @@ static class Flags {
* Denotes the 4 INVOKE* instructions.
*/
static final int INVOKE = 0x00001000;
+ /**
+ * Denotes all binary branches that are subject to branch profiling.
+ */
+ static final int IF_BRANCH_PROFILED = 0x00002000;
+ /**
+ * Denotes all instructions that are subject to type profiling.
+ */
+ static final int TYPE_PROFILED = 0x00004000;
}
// Performs a sanity check that none of the flags overlap.
@@ -423,7 +433,7 @@ static class Flags {
def(LALOAD , "laload" , "b" , 0, TRAP);
def(FALOAD , "faload" , "b" , -1, TRAP);
def(DALOAD , "daload" , "b" , 0, TRAP);
- def(AALOAD , "aaload" , "b" , -1, TRAP);
+ def(AALOAD , "aaload" , "b" , -1, TRAP | TYPE_PROFILED);
def(BALOAD , "baload" , "b" , -1, TRAP);
def(CALOAD , "caload" , "b" , -1, TRAP);
def(SALOAD , "saload" , "b" , -1, TRAP);
@@ -456,7 +466,7 @@ static class Flags {
def(LASTORE , "lastore" , "b" , -4, TRAP);
def(FASTORE , "fastore" , "b" , -3, TRAP);
def(DASTORE , "dastore" , "b" , -4, TRAP);
- def(AASTORE , "aastore" , "b" , -3, TRAP);
+ def(AASTORE , "aastore" , "b" , -3, TRAP | TYPE_PROFILED);
def(BASTORE , "bastore" , "b" , -3, TRAP);
def(CASTORE , "castore" , "b" , -3, TRAP);
def(SASTORE , "sastore" , "b" , -3, TRAP);
@@ -526,20 +536,20 @@ static class Flags {
def(FCMPG , "fcmpg" , "b" , -1);
def(DCMPL , "dcmpl" , "b" , -3);
def(DCMPG , "dcmpg" , "b" , -3);
- def(IFEQ , "ifeq" , "boo" , -1, FALL_THROUGH | BRANCH);
- def(IFNE , "ifne" , "boo" , -1, FALL_THROUGH | BRANCH);
- def(IFLT , "iflt" , "boo" , -1, FALL_THROUGH | BRANCH);
- def(IFGE , "ifge" , "boo" , -1, FALL_THROUGH | BRANCH);
- def(IFGT , "ifgt" , "boo" , -1, FALL_THROUGH | BRANCH);
- def(IFLE , "ifle" , "boo" , -1, FALL_THROUGH | BRANCH);
- def(IF_ICMPEQ , "if_icmpeq" , "boo" , -2, COMMUTATIVE | FALL_THROUGH | BRANCH);
- def(IF_ICMPNE , "if_icmpne" , "boo" , -2, COMMUTATIVE | FALL_THROUGH | BRANCH);
- def(IF_ICMPLT , "if_icmplt" , "boo" , -2, FALL_THROUGH | BRANCH);
- def(IF_ICMPGE , "if_icmpge" , "boo" , -2, FALL_THROUGH | BRANCH);
- def(IF_ICMPGT , "if_icmpgt" , "boo" , -2, FALL_THROUGH | BRANCH);
- def(IF_ICMPLE , "if_icmple" , "boo" , -2, FALL_THROUGH | BRANCH);
- def(IF_ACMPEQ , "if_acmpeq" , "boo" , -2, COMMUTATIVE | FALL_THROUGH | BRANCH);
- def(IF_ACMPNE , "if_acmpne" , "boo" , -2, COMMUTATIVE | FALL_THROUGH | BRANCH);
+ def(IFEQ , "ifeq" , "boo" , -1, FALL_THROUGH | BRANCH | IF_BRANCH_PROFILED);
+ def(IFNE , "ifne" , "boo" , -1, FALL_THROUGH | BRANCH | IF_BRANCH_PROFILED);
+ def(IFLT , "iflt" , "boo" , -1, FALL_THROUGH | BRANCH | IF_BRANCH_PROFILED);
+ def(IFGE , "ifge" , "boo" , -1, FALL_THROUGH | BRANCH | IF_BRANCH_PROFILED);
+ def(IFGT , "ifgt" , "boo" , -1, FALL_THROUGH | BRANCH | IF_BRANCH_PROFILED);
+ def(IFLE , "ifle" , "boo" , -1, FALL_THROUGH | BRANCH | IF_BRANCH_PROFILED);
+ def(IF_ICMPEQ , "if_icmpeq" , "boo" , -2, COMMUTATIVE | FALL_THROUGH | BRANCH | IF_BRANCH_PROFILED);
+ def(IF_ICMPNE , "if_icmpne" , "boo" , -2, COMMUTATIVE | FALL_THROUGH | BRANCH | IF_BRANCH_PROFILED);
+ def(IF_ICMPLT , "if_icmplt" , "boo" , -2, FALL_THROUGH | BRANCH | IF_BRANCH_PROFILED);
+ def(IF_ICMPGE , "if_icmpge" , "boo" , -2, FALL_THROUGH | BRANCH | IF_BRANCH_PROFILED);
+ def(IF_ICMPGT , "if_icmpgt" , "boo" , -2, FALL_THROUGH | BRANCH | IF_BRANCH_PROFILED);
+ def(IF_ICMPLE , "if_icmple" , "boo" , -2, FALL_THROUGH | BRANCH | IF_BRANCH_PROFILED);
+ def(IF_ACMPEQ , "if_acmpeq" , "boo" , -2, COMMUTATIVE | FALL_THROUGH | BRANCH | IF_BRANCH_PROFILED);
+ def(IF_ACMPNE , "if_acmpne" , "boo" , -2, COMMUTATIVE | FALL_THROUGH | BRANCH | IF_BRANCH_PROFILED);
def(GOTO , "goto" , "boo" , 0, STOP | BRANCH);
def(JSR , "jsr" , "boo" , 0, STOP | BRANCH);
def(RET , "ret" , "bi" , 0, STOP);
@@ -555,24 +565,24 @@ static class Flags {
def(PUTSTATIC , "putstatic" , "bjj" , -1, TRAP | FIELD_WRITE);
def(GETFIELD , "getfield" , "bjj" , 0, TRAP | FIELD_READ);
def(PUTFIELD , "putfield" , "bjj" , -2, TRAP | FIELD_WRITE);
- def(INVOKEVIRTUAL , "invokevirtual" , "bjj" , -1, TRAP | INVOKE);
- def(INVOKESPECIAL , "invokespecial" , "bjj" , -1, TRAP | INVOKE);
- def(INVOKESTATIC , "invokestatic" , "bjj" , 0, TRAP | INVOKE);
- def(INVOKEINTERFACE , "invokeinterface" , "bjja_", -1, TRAP | INVOKE);
- def(INVOKEDYNAMIC , "invokedynamic" , "bjjjj", 0, TRAP | INVOKE);
+ def(INVOKEVIRTUAL , "invokevirtual" , "bjj" , -1, TRAP | INVOKE | TYPE_PROFILED);
+ def(INVOKESPECIAL , "invokespecial" , "bjj" , -1, TRAP | INVOKE | TYPE_PROFILED);
+ def(INVOKESTATIC , "invokestatic" , "bjj" , 0, TRAP | INVOKE | TYPE_PROFILED);
+ def(INVOKEINTERFACE , "invokeinterface" , "bjja_", -1, TRAP | INVOKE | TYPE_PROFILED);
+ def(INVOKEDYNAMIC , "invokedynamic" , "bjjjj", 0, TRAP | INVOKE | TYPE_PROFILED);
def(NEW , "new" , "bii" , 1, TRAP);
def(NEWARRAY , "newarray" , "bc" , 0, TRAP);
def(ANEWARRAY , "anewarray" , "bii" , 0, TRAP);
def(ARRAYLENGTH , "arraylength" , "b" , 0, TRAP);
def(ATHROW , "athrow" , "b" , -1, TRAP | STOP);
- def(CHECKCAST , "checkcast" , "bii" , 0, TRAP);
- def(INSTANCEOF , "instanceof" , "bii" , 0, TRAP);
+ def(CHECKCAST , "checkcast" , "bii" , 0, TRAP | TYPE_PROFILED);
+ def(INSTANCEOF , "instanceof" , "bii" , 0, TRAP | TYPE_PROFILED);
def(MONITORENTER , "monitorenter" , "b" , -1, TRAP);
def(MONITOREXIT , "monitorexit" , "b" , -1, TRAP);
def(WIDE , "wide" , "" , 0);
def(MULTIANEWARRAY , "multianewarray" , "biic" , 1, TRAP);
- def(IFNULL , "ifnull" , "boo" , -1, FALL_THROUGH | BRANCH);
- def(IFNONNULL , "ifnonnull" , "boo" , -1, FALL_THROUGH | BRANCH);
+ def(IFNULL , "ifnull" , "boo" , -1, FALL_THROUGH | BRANCH | IF_BRANCH_PROFILED);
+ def(IFNONNULL , "ifnonnull" , "boo" , -1, FALL_THROUGH | BRANCH | IF_BRANCH_PROFILED);
def(GOTO_W , "goto_w" , "boooo", 0, STOP | BRANCH);
def(JSR_W , "jsr_w" , "boooo", 0, STOP | BRANCH);
def(BREAKPOINT , "breakpoint" , "b" , 0, TRAP);
@@ -648,6 +658,31 @@ public static boolean isBranch(int opcode) {
return (flagsArray[opcode & 0xff] & BRANCH) != 0;
}
+ /**
+ * Determines if a given opcode is an instruction that is subject to binary branch profiling in
+ * the interpreter. Binary branches are instructions that have 2 successors - the taken and not
+ * taken successor.
+ *
+ * Note that {@link #GOTO} and {@link #JSR} are not considered a profiled if branch
+ *
+ * @param opcode an opcode to test
+ * @return {@code true} iff {@code opcode} is a binary branch instruction that is profiled
+ */
+ public static boolean isProfiledIfBranch(int opcode) {
+ return (flagsArray[opcode & 0xff] & IF_BRANCH_PROFILED) != 0;
+ }
+
+ /**
+ * Determines if a given opcode is an instruction that is subject to type profiling. This covers
+ * all instructions that deal with objects of a dynamic type.
+ *
+ * @param opcode an opcode to test
+ * @return {@code true} iff {@code opcode} is a type profiled branch instruction
+ */
+ public static boolean isTypeProfiled(int opcode) {
+ return (flagsArray[opcode & 0xff] & TYPE_PROFILED) != 0;
+ }
+
/**
* Determines if a given opcode denotes a conditional branch.
*
diff --git a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/profile/MethodProfile.java b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/profile/MethodProfile.java
new file mode 100644
index 000000000000..cd4dff33e8c7
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/profile/MethodProfile.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.oracle.svm.interpreter.metadata.profile;
+
+import static jdk.graal.compiler.bytecode.Bytecodes.END;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.oracle.svm.interpreter.metadata.Bytecodes;
+
+import jdk.graal.compiler.bytecode.BytecodeStream;
+import jdk.vm.ci.meta.ProfilingInfo;
+import jdk.vm.ci.meta.ResolvedJavaMethod;
+
+/**
+ * Stores interpreter profiling data collected during the execution of a single
+ * {@link ResolvedJavaMethod}.
+ *
+ * The data is written concurrently by multiple Crema interpreter threads during method execution.
+ * It is subsequently read by compilation consumers, typically wrapped in a {@link ProfilingInfo}
+ * object.
+ *
+ * Thread Safety and Mutability: Because multiple interpreter threads update the profiles
+ * concurrently, the data within this object is highly volatile. Any profile-related
+ * information returned by methods of this class can change significantly and rapidly over time.
+ * Consumers must be aware of this mutability when reading and acting upon the profiling data.
+ */
+public final class MethodProfile {
+
+ /**
+ * Artificial byte code index for the method entry profile.
+ */
+ private static final int JVMCI_METHOD_ENTRY_BCI = -1;
+
+ private final InterpreterProfile[] profiles;
+
+ /**
+ * Caches the index of the last returned profile for the next access. Initialized to 0, will be
+ * set in {@link #getAtBCI(int, Class)}.
+ */
+ private int lastIndex;
+
+ private final ResolvedJavaMethod method;
+
+ private boolean isMature;
+
+ public MethodProfile(ResolvedJavaMethod method) {
+ this.method = method;
+ this.profiles = buildProfiles(method);
+ }
+
+ private static InterpreterProfile[] buildProfiles(ResolvedJavaMethod method) {
+ BytecodeStream stream = new BytecodeStream(method.getCode());
+ stream.setBCI(0);
+
+ List allProfiles = new ArrayList<>();
+ // we always add a method entry counting profile
+ allProfiles.add(new CountingProfile(JVMCI_METHOD_ENTRY_BCI));
+
+ while (stream.currentBC() != END) {
+ int bci = stream.currentBCI();
+ int opcode = stream.currentBC();
+ // we can have multiple profiles for a single BCI: type, exception etc
+ if (Bytecodes.isProfiledIfBranch(opcode)) {
+ allProfiles.add(new BranchProfile(bci));
+ }
+ if (Bytecodes.isTypeProfiled(opcode)) {
+ // TODO GR-71567
+ }
+ // TODO GR-71799 - backedge / goto profiles
+ stream.next();
+ }
+ return allProfiles.toArray(new InterpreterProfile[0]);
+ }
+
+ public ResolvedJavaMethod getMethod() {
+ return method;
+ }
+
+ /**
+ * Similar semantics as {@link ProfilingInfo#isMature()} except this method does not perform an
+ * ergonomic decision. A profile is only mature if it was explicitly set with
+ * {@link #setMature(boolean)}. Normally this is done by test code for example. Users of this
+ * {@link MethodProfile} can combine this with real ergonomics.
+ *
+ * @return true if an explicit maturity override has been set on this profiling data; false
+ * otherwise
+ */
+ public boolean isMature() {
+ return isMature;
+ }
+
+ public void setMature(boolean mature) {
+ isMature = mature;
+ }
+
+ public long profileMethodEntry() {
+ return ((CountingProfile) getAtBCI(JVMCI_METHOD_ENTRY_BCI, CountingProfile.class)).counter++;
+ }
+
+ public long getProfileEntryCount() {
+ return ((CountingProfile) getAtBCI(JVMCI_METHOD_ENTRY_BCI, CountingProfile.class)).counter;
+ }
+
+ public void profileBranch(int bci, boolean taken) {
+ if (taken) {
+ ((BranchProfile) getAtBCI(bci, BranchProfile.class)).incrementTakenCounter();
+ } else {
+ ((BranchProfile) getAtBCI(bci, BranchProfile.class)).incrementNotTakenCounter();
+ }
+ }
+
+ public double getBranchTakenProbability(int bci) {
+ return ((BranchProfile) getAtBCI(bci, BranchProfile.class)).takenProfile();
+ }
+
+ /**
+ * Gets the profile for {@code bci} whose class is {@code clazz}.
+ *
+ * @return null if there's no profile
+ */
+ private InterpreterProfile getAtBCI(int bci, Class extends InterpreterProfile> clazz) {
+ int lastIndexLocal = lastIndex;
+ for (int i = lastIndexLocal; i < profiles.length; i++) {
+ InterpreterProfile profile = profiles[i];
+ if (profile.getBci() == bci && profile.getClass() == clazz) {
+ lastIndex = i;
+ return profile;
+ }
+ }
+ for (int i = 0; i < lastIndexLocal; i++) {
+ InterpreterProfile profile = profiles[i];
+ if (profile.getBci() == bci && profile.getClass() == clazz) {
+ lastIndex = i;
+ return profile;
+ }
+ }
+ return null;
+ }
+
+ public static class TestingBackdoor {
+ public static List profilesAtBCI(MethodProfile methodProfile, int bci) {
+ ArrayList profiles = new ArrayList<>();
+ for (int i = 0; i < methodProfile.profiles.length; i++) {
+ InterpreterProfile profile = methodProfile.profiles[i];
+ if (profile.getBci() == bci) {
+ profiles.add(profile);
+ }
+ }
+ return profiles;
+ }
+ }
+
+ public abstract static class InterpreterProfile {
+ protected final int bci;
+
+ protected InterpreterProfile(int bci) {
+ this.bci = bci;
+ }
+
+ public int getBci() {
+ return bci;
+ }
+ }
+
+ public static class CountingProfile extends InterpreterProfile {
+ protected long counter;
+
+ CountingProfile(int bci) {
+ super(bci);
+ }
+
+ public long getCounter() {
+ return counter;
+ }
+
+ public void incrementCounter() {
+ counter++;
+ }
+
+ @Override
+ public String toString() {
+ return "{Counting:bci=" + bci + ", counter=" + counter + "}";
+ }
+ }
+
+ public static class BranchProfile extends CountingProfile {
+ private long takenCounter;
+
+ public BranchProfile(int bci) {
+ super(bci);
+ }
+
+ public void incrementTakenCounter() {
+ takenCounter++;
+ counter++;
+ }
+
+ public void incrementNotTakenCounter() {
+ counter++;
+ }
+
+ public double takenProfile() {
+ if (counter == 0) {
+ return -1;
+ }
+ return (double) takenCounter / (double) counter;
+ }
+
+ public double notTakenProfile() {
+ if (counter == 0) {
+ return -1;
+ }
+ return 1D - takenProfile();
+ }
+
+ @Override
+ public String toString() {
+ return "{BranchProfile:bci=" + bci + ", takenCounter=" + takenCounter + ", counter=" + counter + "}";
+ }
+ }
+
+}
diff --git a/substratevm/src/com.oracle.svm.interpreter/OWNERS.toml b/substratevm/src/com.oracle.svm.interpreter/OWNERS.toml
index 67c7e4be1111..31d2b48d7cf3 100644
--- a/substratevm/src/com.oracle.svm.interpreter/OWNERS.toml
+++ b/substratevm/src/com.oracle.svm.interpreter/OWNERS.toml
@@ -3,4 +3,5 @@ files = "*"
all = [
"bernhard.urban-forster@oracle.com",
"gilles.m.duboscq@oracle.com",
+ "david.leopoldseder@oracle.com"
]
diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/Interpreter.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/Interpreter.java
index f0f43def4dab..29ed36c21712 100644
--- a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/Interpreter.java
+++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/Interpreter.java
@@ -291,6 +291,7 @@
import com.oracle.svm.interpreter.metadata.ReferenceConstant;
import com.oracle.svm.interpreter.metadata.TableSwitch;
import com.oracle.svm.interpreter.metadata.UnsupportedResolutionException;
+import com.oracle.svm.interpreter.metadata.profile.MethodProfile;
import com.oracle.svm.interpreter.ristretto.profile.RistrettoProfileSupport;
import jdk.graal.compiler.api.directives.GraalDirectives;
@@ -622,10 +623,7 @@ public static final class Root {
@NeverInline("needed far stack walking")
private static Object executeBodyFromBCI(InterpreterFrame frame, InterpreterResolvedJavaMethod method, int startBCI, int startTop,
boolean forceStayInInterpreter) {
-
- if (RistrettoProfileSupport.isEnabled()) {
- RistrettoProfileSupport.profileMethodCall(method);
- }
+ final MethodProfile methodProfile = RistrettoProfileSupport.profileMethodEntry(method);
int curBCI = startBCI;
int top = startTop;
@@ -888,7 +886,11 @@ private static Object executeBodyFromBCI(InterpreterFrame frame, InterpreterReso
case IFGE: // fall through
case IFGT: // fall through
case IFLE:
- if (takeBranchPrimitive1(popInt(frame, top - 1), curOpcode)) {
+ final boolean branchTaken1 = takeBranchPrimitive1(popInt(frame, top - 1), curOpcode);
+ if (methodProfile != null) {
+ methodProfile.profileBranch(curBCI, branchTaken1);
+ }
+ if (branchTaken1) {
top += ConstantBytecodes.stackEffectOf(IFLE);
curBCI = beforeJumpChecks(frame, curBCI, BytecodeStream.readBranchDest2(code, curBCI), top);
continue loop;
@@ -901,7 +903,11 @@ private static Object executeBodyFromBCI(InterpreterFrame frame, InterpreterReso
case IF_ICMPGE: // fall through
case IF_ICMPGT: // fall through
case IF_ICMPLE:
- if (takeBranchPrimitive2(popInt(frame, top - 1), popInt(frame, top - 2), curOpcode)) {
+ final boolean branchTaken2 = takeBranchPrimitive2(popInt(frame, top - 1), popInt(frame, top - 2), curOpcode);
+ if (methodProfile != null) {
+ methodProfile.profileBranch(curBCI, branchTaken2);
+ }
+ if (branchTaken2) {
top += ConstantBytecodes.stackEffectOf(IF_ICMPLE);
curBCI = beforeJumpChecks(frame, curBCI, BytecodeStream.readBranchDest2(code, curBCI), top);
continue loop;
@@ -910,7 +916,11 @@ private static Object executeBodyFromBCI(InterpreterFrame frame, InterpreterReso
case IF_ACMPEQ: // fall through
case IF_ACMPNE:
- if (takeBranchRef2(popObject(frame, top - 1), popObject(frame, top - 2), curOpcode)) {
+ final boolean branchTakenRef2 = takeBranchRef2(popObject(frame, top - 1), popObject(frame, top - 2), curOpcode);
+ if (methodProfile != null) {
+ methodProfile.profileBranch(curBCI, branchTakenRef2);
+ }
+ if (branchTakenRef2) {
top += ConstantBytecodes.stackEffectOf(IF_ACMPNE);
curBCI = beforeJumpChecks(frame, curBCI, BytecodeStream.readBranchDest2(code, curBCI), top);
continue loop;
@@ -919,7 +929,11 @@ private static Object executeBodyFromBCI(InterpreterFrame frame, InterpreterReso
case IFNULL: // fall through
case IFNONNULL:
- if (takeBranchRef1(popObject(frame, top - 1), curOpcode)) {
+ final boolean branchTakenRef1 = takeBranchRef1(popObject(frame, top - 1), curOpcode);
+ if (methodProfile != null) {
+ methodProfile.profileBranch(curBCI, branchTakenRef1);
+ }
+ if (branchTakenRef1) {
top += ConstantBytecodes.stackEffectOf(IFNONNULL);
curBCI = beforeJumpChecks(frame, curBCI, BytecodeStream.readBranchDest2(code, curBCI), top);
continue loop;
@@ -1280,6 +1294,7 @@ private static void arrayStore(InterpreterFrame frame, int top, int storeOpcode)
private static int beforeJumpChecks(InterpreterFrame frame, int curBCI, int targetBCI, int top) {
if (targetBCI <= curBCI) {
// GR-55055: Safepoint poll needed?
+ // TODO GR-71799 - add ristretto backedge profiles
}
return targetBCI;
}
diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/ristretto/RistrettoRuntimeOptions.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/ristretto/RistrettoRuntimeOptions.java
index 5069b6f20245..65b35d9e139f 100644
--- a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/ristretto/RistrettoRuntimeOptions.java
+++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/ristretto/RistrettoRuntimeOptions.java
@@ -46,4 +46,7 @@ public class RistrettoRuntimeOptions {
@Option(help = "Trace compilation events.")//
public static final RuntimeOptionKey JITTraceCompilation = new RuntimeOptionKey<>(false);
+
+ @Option(help = "Number of invocations before profiling considers a method profile to be mature.")//
+ public static final RuntimeOptionKey JITProfileMatureInvocationThreshold = new RuntimeOptionKey<>(1000);
}
diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/ristretto/RistrettoUtils.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/ristretto/RistrettoUtils.java
index 722d89473b90..07ab99b19407 100644
--- a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/ristretto/RistrettoUtils.java
+++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/ristretto/RistrettoUtils.java
@@ -25,6 +25,7 @@
package com.oracle.svm.interpreter.ristretto;
import java.io.PrintStream;
+import java.util.Optional;
import org.graalvm.collections.EconomicMap;
import org.graalvm.nativeimage.ImageSingletons;
@@ -47,6 +48,7 @@
import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaMethod;
import com.oracle.svm.interpreter.ristretto.compile.RistrettoGraphBuilderPhase;
import com.oracle.svm.interpreter.ristretto.meta.RistrettoMethod;
+import com.oracle.svm.interpreter.ristretto.profile.RistrettoProfileProvider;
import jdk.graal.compiler.code.CompilationResult;
import jdk.graal.compiler.core.CompilationWatchDog;
@@ -54,6 +56,7 @@
import jdk.graal.compiler.debug.DebugContext;
import jdk.graal.compiler.debug.GraalError;
import jdk.graal.compiler.lir.phases.LIRSuites;
+import jdk.graal.compiler.nodes.GraphState;
import jdk.graal.compiler.nodes.StructuredGraph;
import jdk.graal.compiler.nodes.graphbuilderconf.GraphBuilderConfiguration;
import jdk.graal.compiler.nodes.spi.ProfileProvider;
@@ -61,17 +64,18 @@
import jdk.graal.compiler.options.OptionKey;
import jdk.graal.compiler.options.OptionValues;
import jdk.graal.compiler.phases.OptimisticOptimizations;
+import jdk.graal.compiler.phases.Phase;
+import jdk.graal.compiler.phases.common.HighTierLoweringPhase;
+import jdk.graal.compiler.phases.common.LowTierLoweringPhase;
+import jdk.graal.compiler.phases.common.MidTierLoweringPhase;
import jdk.graal.compiler.phases.tiers.HighTierContext;
import jdk.graal.compiler.phases.tiers.Suites;
import jdk.graal.compiler.phases.util.Providers;
import jdk.graal.compiler.printer.GraalDebugHandlersFactory;
import jdk.vm.ci.code.InstalledCode;
-import jdk.vm.ci.meta.DefaultProfilingInfo;
-import jdk.vm.ci.meta.ProfilingInfo;
import jdk.vm.ci.meta.ResolvedJavaMethod;
import jdk.vm.ci.meta.ResolvedJavaType;
import jdk.vm.ci.meta.SpeculationLog;
-import jdk.vm.ci.meta.TriState;
public class RistrettoUtils {
@@ -157,20 +161,6 @@ public static InstalledCode compileAndInstall(SubstrateMethod method, SubstrateI
return (InstalledCode) installedCode;
}
- private static final ProfileProvider NO_PROFILE_PROVIDER = new ProfileProvider() {
-
- @Override
- public ProfilingInfo getProfilingInfo(ResolvedJavaMethod method) {
- return getProfilingInfo(method, true, true);
- }
-
- @Override
- public ProfilingInfo getProfilingInfo(ResolvedJavaMethod method, boolean includeNormal, boolean includeOSR) {
- return DefaultProfilingInfo.get(TriState.FALSE);
- }
-
- };
-
public static CompilationResult doCompile(DebugContext initialDebug, RuntimeConfiguration runtimeConfig, LIRSuites lirSuites, SubstrateMethod method) {
SubstrateGraalUtils.updateGraalArchitectureWithHostCPUFeatures(runtimeConfig.lookupBackend(method));
@@ -202,13 +192,11 @@ protected CompilationResult performCompilation(DebugContext debug) {
try (CompilationWatchDog _ = CompilationWatchDog.watch(compilationId, debug.getOptions(), false, SubstrateGraalUtils.COMPILATION_WATCH_DOG_EVENT_HANDLER, null)) {
StructuredGraph graph;
Suites suites;
- if (method instanceof RistrettoMethod) {
+ if (method instanceof RistrettoMethod rMethod) {
final OptionValues options = debug.getOptions();
// final int entryBCI = 0;
final SpeculationLog speculationLog = new SubstrateSpeculationLog();
- // TODO GR-71495 - add branch profiles and combine profile provider with
- // crema profiles
- final ProfileProvider profileProvider = NO_PROFILE_PROVIDER;
+ final ProfileProvider profileProvider = new RistrettoProfileProvider(rMethod);
final StructuredGraph.AllowAssumptions allowAssumptions = StructuredGraph.AllowAssumptions.NO;
// TODO GR-71494 - OSR support will require setting the entry BCI for
// parsing
@@ -217,6 +205,11 @@ protected CompilationResult performCompilation(DebugContext debug) {
assert graph != null;
suites = RuntimeCompilationSupport.getMatchingSuitesForGraph(graph);
parseFromBytecode(graph, runtimeConfig);
+ if (TestingBackdoor.shouldRememberGraph()) {
+ // override the suites with graph capturing phases
+ suites = suites.copy();
+ TestingBackdoor.installLastGraphThieves(suites, graph);
+ }
} else {
graph = RuntimeCompilationSupport.decodeGraph(debug, null, compilationId, method, null);
suites = RuntimeCompilationSupport.getMatchingSuitesForGraph(graph);
@@ -255,4 +248,60 @@ private static void parseFromBytecode(StructuredGraph graph, RuntimeConfiguratio
assert graph.getNodeCount() > 1 : "Must have nodes after parsing";
}
+ public static final class TestingBackdoor {
+ private TestingBackdoor() {
+ // this type should never be allocated
+ }
+
+ /**
+ * Export access to the last compiled graph of a ristretto compile to be able to white box
+ * test various properties.
+ */
+ public static final ThreadLocal LastCompiledGraph = new ThreadLocal<>();
+ public static final ThreadLocal LastCompiledGraphAfterHighTierLowering = new ThreadLocal<>();
+ public static final ThreadLocal LastCompiledGraphAfterMidTierLowering = new ThreadLocal<>();
+ public static final ThreadLocal LastCompiledGraphAfterLowTierLowering = new ThreadLocal<>();
+
+ public static boolean shouldRememberGraph() {
+ String prop = System.getProperty("com.oracle.svm.interpreter.ristretto.RistrettoUtils.PreserveLastCompiledGraph", "false");
+ return Boolean.parseBoolean(prop);
+ }
+
+ static void installLastGraphThieves(Suites suites, StructuredGraph rootGraph) {
+ TestingBackdoor.LastCompiledGraph.set(rootGraph);
+ suites.getHighTier().insertAfterPhase(HighTierLoweringPhase.class, new Phase() {
+ @Override
+ public Optional notApplicableTo(GraphState graphState) {
+ return ALWAYS_APPLICABLE;
+ }
+
+ @Override
+ protected void run(StructuredGraph graph) {
+ TestingBackdoor.LastCompiledGraphAfterHighTierLowering.set((StructuredGraph) graph.copy(graph.getDebug()));
+ }
+ });
+ suites.getMidTier().insertAfterPhase(MidTierLoweringPhase.class, new Phase() {
+ @Override
+ public Optional notApplicableTo(GraphState graphState) {
+ return ALWAYS_APPLICABLE;
+ }
+
+ @Override
+ protected void run(StructuredGraph graph) {
+ TestingBackdoor.LastCompiledGraphAfterMidTierLowering.set((StructuredGraph) graph.copy(graph.getDebug()));
+ }
+ });
+ suites.getLowTier().insertAfterPhase(LowTierLoweringPhase.class, new Phase() {
+ @Override
+ public Optional notApplicableTo(GraphState graphState) {
+ return ALWAYS_APPLICABLE;
+ }
+
+ @Override
+ protected void run(StructuredGraph graph) {
+ TestingBackdoor.LastCompiledGraphAfterLowTierLowering.set((StructuredGraph) graph.copy(graph.getDebug()));
+ }
+ });
+ }
+ }
}
diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/ristretto/meta/RistrettoMethod.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/ristretto/meta/RistrettoMethod.java
index 78f4b7852b7e..75894c1e33ca 100644
--- a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/ristretto/meta/RistrettoMethod.java
+++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/ristretto/meta/RistrettoMethod.java
@@ -29,9 +29,11 @@
import com.oracle.svm.graal.meta.SubstrateMethod;
import com.oracle.svm.graal.meta.SubstrateType;
import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaMethod;
+import com.oracle.svm.interpreter.metadata.profile.MethodProfile;
import com.oracle.svm.interpreter.ristretto.RistrettoConstants;
import com.oracle.svm.interpreter.ristretto.RistrettoUtils;
+import jdk.graal.compiler.nodes.extended.MembarNode;
import jdk.vm.ci.code.InstalledCode;
import jdk.vm.ci.meta.ConstantPool;
import jdk.vm.ci.meta.ExceptionHandler;
@@ -58,10 +60,11 @@ public final class RistrettoMethod extends SubstrateMethod {
// JIT COMPILER SUPPORT START
/**
- * Field exposed for profiling support for this method. May be written and read in a
- * multithreaded fashion.
+ * Field exposed for profiling support for this method. Initialized once upon first profiling
+ * under heavy synchronization. Never written again. If a ristretto method is GCed profile is
+ * lost.
*/
- public volatile Object profile;
+ private MethodProfile profile;
/**
* State-machine for compilation handling of this crema method. Every methods starts in a
* NEVER_COMPILED state and than can cycle through different states.
@@ -95,6 +98,31 @@ public static RistrettoMethod create(InterpreterResolvedJavaMethod interpreterMe
return (RistrettoMethod) interpreterMethod.getRistrettoMethod(RISTRETTO_METHOD_FUNCTION);
}
+ public MethodProfile getProfile() {
+ if (profile == null) {
+ initializeProfile();
+ }
+ return profile;
+ }
+
+ /**
+ * Allocate the profile once per method. Apart from test scenarios the profile is never set to
+ * null again. Thus, the heavy locking code below is normally not run in a fast path.
+ */
+ private synchronized void initializeProfile() {
+ if (profile == null) {
+ MethodProfile newProfile = new MethodProfile(this);
+ // ensure everything is allocated and initialized before we signal the barrier
+ // for the publishing write
+ MembarNode.memoryBarrier(MembarNode.FenceKind.STORE_STORE);
+ profile = newProfile;
+ }
+ }
+
+ public synchronized void resetProfile() {
+ profile = null;
+ }
+
@Override
public boolean canBeInlined() {
final boolean wasCompiledAOT = RistrettoUtils.wasAOTCompiled(this.getInterpreterMethod());
diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/ristretto/profile/RistrettoCompilationManager.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/ristretto/profile/RistrettoCompilationManager.java
index e89e7d57b97b..eb0d22f68d60 100644
--- a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/ristretto/profile/RistrettoCompilationManager.java
+++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/ristretto/profile/RistrettoCompilationManager.java
@@ -175,7 +175,7 @@ public static synchronized void reset() {
task.getRMethod().installedCode.invalidate();
}
task.getRMethod().installedCode = null;
- task.getRMethod().profile = null;
+ task.getRMethod().resetProfile();
}
m.performedCompilations.clear();
m.submittedRequests.set(0);
diff --git a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/profile/InterpreterProfile.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/ristretto/profile/RistrettoProfileProvider.java
similarity index 58%
rename from substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/profile/InterpreterProfile.java
rename to substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/ristretto/profile/RistrettoProfileProvider.java
index d61778ce21ed..9e969b0b9fab 100644
--- a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/profile/InterpreterProfile.java
+++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/ristretto/profile/RistrettoProfileProvider.java
@@ -22,15 +22,31 @@
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
-package com.oracle.svm.interpreter.metadata.profile;
+package com.oracle.svm.interpreter.ristretto.profile;
-/**
- * Abstract base representation of profile data for crema.
- */
-public abstract class InterpreterProfile {
+import com.oracle.svm.interpreter.ristretto.meta.RistrettoMethod;
+
+import jdk.graal.compiler.nodes.spi.ProfileProvider;
+import jdk.vm.ci.meta.ProfilingInfo;
+import jdk.vm.ci.meta.ResolvedJavaMethod;
+
+public final class RistrettoProfileProvider implements ProfileProvider {
+ private final RistrettoProfilingInfo info;
- public static class CountingProfile extends InterpreterProfile {
- public long counter;
+ public RistrettoProfileProvider(RistrettoMethod rMethod) {
+ this.info = new RistrettoProfilingInfo(rMethod.getProfile());
}
+ @Override
+ public ProfilingInfo getProfilingInfo(ResolvedJavaMethod method) {
+ return info;
+ }
+
+ @Override
+ public ProfilingInfo getProfilingInfo(ResolvedJavaMethod method, boolean includeNormal, boolean includeOSR) {
+ /*
+ * TODO GR-71494 - no OSR support for now
+ */
+ return getProfilingInfo(method);
+ }
}
diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/ristretto/profile/RistrettoProfileSupport.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/ristretto/profile/RistrettoProfileSupport.java
index 729f52f99c69..419092f6eb1c 100644
--- a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/ristretto/profile/RistrettoProfileSupport.java
+++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/ristretto/profile/RistrettoProfileSupport.java
@@ -31,16 +31,16 @@
import com.oracle.svm.core.log.Log;
import com.oracle.svm.core.option.RuntimeOptionKey;
+import com.oracle.svm.core.util.VMError;
import com.oracle.svm.interpreter.metadata.CremaResolvedJavaMethodImpl;
import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaMethod;
-import com.oracle.svm.interpreter.metadata.profile.InterpreterProfile;
+import com.oracle.svm.interpreter.metadata.profile.MethodProfile;
import com.oracle.svm.interpreter.ristretto.RistrettoConstants;
import com.oracle.svm.interpreter.ristretto.RistrettoFeature;
import com.oracle.svm.interpreter.ristretto.RistrettoRuntimeOptions;
import com.oracle.svm.interpreter.ristretto.meta.RistrettoMethod;
import jdk.graal.compiler.api.replacements.Fold;
-import jdk.graal.compiler.debug.GraalError;
import jdk.graal.compiler.nodes.PauseNode;
public class RistrettoProfileSupport {
@@ -90,9 +90,12 @@ public static void trace(RuntimeOptionKey optionKey, String msg, Object
* {@link CremaResolvedJavaMethodImpl}
* @throws AssertionError if iMethod is not a InterpreterResolvedJavaMethod instance
*/
- public static void profileMethodCall(InterpreterResolvedJavaMethod iMethod) {
+ public static MethodProfile profileMethodEntry(InterpreterResolvedJavaMethod iMethod) {
+ if (!RistrettoProfileSupport.isEnabled()) {
+ return null;
+ }
if (!RistrettoRuntimeOptions.JITEnableCompilation.getValue()) {
- return;
+ return null;
}
assert iMethod instanceof CremaResolvedJavaMethodImpl;
@@ -103,55 +106,64 @@ public static void profileMethodCall(InterpreterResolvedJavaMethod iMethod) {
// no need to keep profiling this code, we are done
trace(RistrettoRuntimeOptions.JITTraceCompilationQueuing, "[Ristretto Compile Queue]Should not enter profiling for method %s because of state %s%n", iMethod,
RistrettoCompileStateMachine.toString(oldState));
- return;
+ return null;
}
// this point is only reached for state=INIT_VAL|INITIALIZING|NEVER_COMPILED
- while (true) {
- oldState = COMPILATION_STATE_UPDATER.get(rMethod);
-
+ do {
/*
* TODO GR-71597 A note on interpreter performance. Code is abstracted in methods here
* to ensure better readability. However, for performance we should ensure inlining
* happens on most of the frequently executed cases here.
*/
-
switch (oldState) {
- // profile has not been initialized yet for this method, do so by switching to
- // INITIALIZING and then wait, if another thread went to initializing in the
- // meantime we are done
case RistrettoConstants.COMPILE_STATE_INIT_VAL: {
+ /*
+ * The profile has not been initialized yet for this method. Do so by switching
+ * to INITIALIZING. If another thread transitioned to initializing in the
+ * meantime we are done.
+ */
methodEntryInitCase(iMethod, rMethod);
break;
}
case RistrettoConstants.COMPILE_STATE_SUBMITTED:
case RistrettoConstants.COMPILE_STATE_COMPILED: {
- methodEntrySkipCase(iMethod, rMethod);
- return;
+ profileSkipCase(iMethod, rMethod);
+ return null;
}
case RistrettoConstants.COMPILE_STATE_NEVER_COMPILED: {
methodEntryNeverCompiledCase(iMethod, rMethod, oldState);
// we only increment (and submit if applicable) once, thus return now
- return;
+ MethodProfile profile = rMethod.getProfile();
+ assert profile != null;
+ return profile;
}
case RistrettoConstants.COMPILE_STATE_INITIALIZING: {
- // another thread is initializing the compilation data, do a few more spins
- // until that is done and then go on
+ /*
+ * TODO GR-71948 - investigate an early return here
+ *
+ * another thread is initializing the compilation data, do a few more spins
+ * until that is done and then go on
+ */
+ PauseNode.pause();
break;
}
default:
- throw GraalError.shouldNotReachHere("Unknown state " + oldState);
+ throw VMError.shouldNotReachHere("Unknown state " + oldState);
}
- }
+ oldState = COMPILATION_STATE_UPDATER.get(rMethod);
+ } while (true);
}
private static void methodEntryNeverCompiledCase(InterpreterResolvedJavaMethod iMethod, RistrettoMethod rMethod, int oldState) {
- InterpreterProfile.CountingProfile methodProfile = (InterpreterProfile.CountingProfile) rMethod.profile;
+ MethodProfile methodProfile = rMethod.getProfile();
trace(RistrettoRuntimeOptions.JITTraceProfilingIncrements, String.format("[Ristretto Compile Queue]Entering state %s for %s, counter=%s%n",
- RistrettoCompileStateMachine.toString(COMPILATION_STATE_UPDATER.get(rMethod)), iMethod, methodProfile.counter));
- // we write without any synchronization to the methodProfile.counter value at
- // the cost of lost updates
- if (++methodProfile.counter > RistrettoRuntimeOptions.JITCompilerInvocationThreshold.getValue()) {
+ RistrettoCompileStateMachine.toString(COMPILATION_STATE_UPDATER.get(rMethod)), iMethod, methodProfile.getProfileEntryCount()));
+ /*
+ * We write without any synchronization to the methodProfile.counter value at the cost of
+ * lost updates.
+ */
+ if (methodProfile.profileMethodEntry() > RistrettoRuntimeOptions.JITCompilerInvocationThreshold.getValue()) {
trace(RistrettoRuntimeOptions.JITTraceCompilationQueuing, "[Ristretto Compile Queue]Entering state %s for %s, profile overflown, trying to submit compile%n",
RistrettoCompileStateMachine.toString(oldState), iMethod);
while (!COMPILATION_STATE_UPDATER.compareAndSet(rMethod, RistrettoConstants.COMPILE_STATE_NEVER_COMPILED, RistrettoConstants.COMPILE_STATE_SUBMITTED)) {
@@ -165,7 +177,7 @@ private static void methodEntryNeverCompiledCase(InterpreterResolvedJavaMethod i
}
}
- private static void methodEntrySkipCase(InterpreterResolvedJavaMethod iMethod, RistrettoMethod rMethod) {
+ private static void profileSkipCase(InterpreterResolvedJavaMethod iMethod, RistrettoMethod rMethod) {
// in the meantime compilation happened already, we are done
trace(RistrettoRuntimeOptions.JITTraceCompilationQueuing, "[Ristretto Compile Queue]Entering state %s for %s, skipping any profiling or compilation%n",
RistrettoCompileStateMachine.toString(COMPILATION_STATE_UPDATER.get(rMethod)), iMethod);
@@ -178,12 +190,8 @@ private static void methodEntryInitCase(InterpreterResolvedJavaMethod iMethod, R
if (COMPILATION_STATE_UPDATER.compareAndSet(rMethod, RistrettoConstants.COMPILE_STATE_INIT_VAL, RistrettoConstants.COMPILE_STATE_INITIALIZING)) {
trace(RistrettoRuntimeOptions.JITTraceCompilationQueuing, "[Ristretto Compile Queue]Entering state %s for %s%n",
RistrettoCompileStateMachine.toString(COMPILATION_STATE_UPDATER.get(rMethod)), iMethod);
-
- // we are the one writing
- rMethod.profile = new InterpreterProfile.CountingProfile();
- while (!COMPILATION_STATE_UPDATER.compareAndSet(rMethod, RistrettoConstants.COMPILE_STATE_INITIALIZING, RistrettoConstants.COMPILE_STATE_NEVER_COMPILED)) {
- // spin until we are done writing
- PauseNode.pause();
+ if (!COMPILATION_STATE_UPDATER.compareAndSet(rMethod, RistrettoConstants.COMPILE_STATE_INITIALIZING, RistrettoConstants.COMPILE_STATE_NEVER_COMPILED)) {
+ throw VMError.shouldNotReachHere("We set transition to COMPILE_STATE_INITIALIZING, we must be allowed to set it to COMPILE_STATE_NEVER_COMPILED");
}
// continue to the NEVER_COMPILED state
trace(RistrettoRuntimeOptions.JITTraceCompilationQueuing, "[Ristretto Compile Queue]Finished setting state %s for %s%n",
diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/ristretto/profile/RistrettoProfilingInfo.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/ristretto/profile/RistrettoProfilingInfo.java
new file mode 100644
index 000000000000..0d0536e5900a
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/ristretto/profile/RistrettoProfilingInfo.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.oracle.svm.interpreter.ristretto.profile;
+
+import com.oracle.svm.interpreter.metadata.profile.MethodProfile;
+import com.oracle.svm.interpreter.ristretto.RistrettoRuntimeOptions;
+
+import jdk.graal.compiler.debug.Assertions;
+import jdk.vm.ci.meta.DeoptimizationReason;
+import jdk.vm.ci.meta.JavaMethodProfile;
+import jdk.vm.ci.meta.JavaTypeProfile;
+import jdk.vm.ci.meta.ProfilingInfo;
+import jdk.vm.ci.meta.TriState;
+
+public class RistrettoProfilingInfo implements ProfilingInfo {
+
+ private static final double PROB_DELTA = 1e-6;
+
+ private final MethodProfile methodProfile;
+
+ public RistrettoProfilingInfo(MethodProfile methodProfile) {
+ this.methodProfile = methodProfile;
+ }
+
+ @Override
+ public int getCodeSize() {
+ return 0;
+ }
+
+ @Override
+ public JavaTypeProfile getTypeProfile(int bci) {
+ return null;
+ }
+
+ @Override
+ public JavaMethodProfile getMethodProfile(int bci) {
+ return null;
+ }
+
+ @Override
+ public double getBranchTakenProbability(int bci) {
+ double recordedProbability = methodProfile.getBranchTakenProbability(bci);
+ if (recordedProbability == -1D) {
+ return -1D;
+ }
+
+ assert !Double.isNaN(recordedProbability) && !Double.isInfinite(recordedProbability) : Assertions.errorMessage("Invalid recorded branch probability", recordedProbability,
+ methodProfile.getMethod(), MethodProfile.TestingBackdoor.profilesAtBCI(methodProfile, bci));
+
+ // at runtime if assertions are disabled fall back to unknown probability
+ if (Double.isNaN(recordedProbability) || Double.isInfinite(recordedProbability)) {
+ recordedProbability = 0.5D;
+ }
+
+ // ensure profile is in an expected range (modulo a small delta)
+ assert recordedProbability >= 0D - PROB_DELTA && recordedProbability <= 1D + PROB_DELTA : Assertions.errorMessage("Must be within [0,1] bounds modulo a small delta but is ",
+ recordedProbability);
+
+ // Clamp to [0, 1]
+ double clamped = Math.max(0.0, Math.min(1.0, recordedProbability));
+
+ // Verify the invariant
+ assert clamped >= 0.0 && clamped <= 1.0 : Assertions.errorMessage("Branch probability out of [0,1]", clamped, methodProfile.getMethod(),
+ MethodProfile.TestingBackdoor.profilesAtBCI(methodProfile, bci));
+
+ return clamped;
+ }
+
+ @Override
+ public double[] getSwitchProbabilities(int bci) {
+ return null;
+ }
+
+ @Override
+ public TriState getExceptionSeen(int bci) {
+ return TriState.UNKNOWN;
+ }
+
+ @Override
+ public TriState getNullSeen(int bci) {
+ return TriState.UNKNOWN;
+ }
+
+ @Override
+ public int getExecutionCount(int bci) {
+ return -1;
+ }
+
+ @Override
+ public int getDeoptimizationCount(DeoptimizationReason reason) {
+ return 0;
+ }
+
+ @Override
+ public boolean isMature() {
+ /*
+ * Either maturity was explicitly requested or we follow regular ergonomics.
+ */
+ return methodProfile.isMature() || methodProfile.getProfileEntryCount() > RistrettoRuntimeOptions.JITProfileMatureInvocationThreshold.getValue();
+ }
+
+ @Override
+ public void setMature() {
+ methodProfile.setMature(true);
+ }
+
+ @Override
+ public boolean setCompilerIRSize(Class> irType, int nodeCount) {
+ return false;
+ }
+
+ @Override
+ public int getCompilerIRSize(Class> irType) {
+ return -1;
+ }
+
+ @Override
+ public String toString() {
+ return "RistrettoProfilingInfo<" + this.toString(null, "; ") + ">";
+ }
+}