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 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, "; ") + ">"; + } +}