diff --git a/substratevm/mx.substratevm/suite.py b/substratevm/mx.substratevm/suite.py index c89b3d826f07..c3bdd2f6c638 100644 --- a/substratevm/mx.substratevm/suite.py +++ b/substratevm/mx.substratevm/suite.py @@ -1620,6 +1620,10 @@ }, "checkstyle": "com.oracle.svm.hosted", "javaCompliance": "24+", + "annotationProcessors": [ + "compiler:GRAAL_PROCESSOR", + "substratevm:SVM_PROCESSOR", + ], "workingSets": "SVM", "jacoco": "exclude", }, diff --git a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/profile/InterpreterProfilingOptions.java b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/profile/InterpreterProfilingOptions.java new file mode 100644 index 000000000000..73b32a5407e9 --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/profile/InterpreterProfilingOptions.java @@ -0,0 +1,38 @@ +/* + * 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 com.oracle.svm.core.option.RuntimeOptionKey; + +import jdk.graal.compiler.options.Option; + +public final class InterpreterProfilingOptions { + + @Option(help = "Number of invocations before profiling considers a method profile to be mature.")// + public static final RuntimeOptionKey JITProfileMatureInvocationThreshold = new RuntimeOptionKey<>(1000); + + @Option(help = "Number of types to record per type profile.")// + public static final RuntimeOptionKey JITProfileTypeProfileWidth = new RuntimeOptionKey<>(4); +} 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 index cd4dff33e8c7..8ebc58ac8361 100644 --- 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 @@ -27,39 +27,55 @@ import static jdk.graal.compiler.bytecode.Bytecodes.END; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import com.oracle.svm.core.hub.DynamicHub; import com.oracle.svm.interpreter.metadata.Bytecodes; import jdk.graal.compiler.bytecode.BytecodeStream; +import jdk.graal.compiler.nodes.IfNode; +import jdk.internal.misc.Unsafe; +import jdk.vm.ci.meta.JavaTypeProfile; import jdk.vm.ci.meta.ProfilingInfo; import jdk.vm.ci.meta.ResolvedJavaMethod; +import jdk.vm.ci.meta.ResolvedJavaType; +import jdk.vm.ci.meta.TriState; /** + * * 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. + * 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. - */ + /** Artificial byte code index for the method entry profile. */ private static final int JVMCI_METHOD_ENTRY_BCI = -1; + /** + * All profiles for the current method. Includes branch profiles, type profiles, profiles for + * exceptions etc. + */ 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)}. + * set in {@link #getAtBCI(int, Class)}. This field may be written concurrently by multiple + * threads, yet we do not synchronize access to it for performance reasons. Its value is always + * compared to the length of {@code profiles[]} array and thus can never cause out of bounds + * reads. Also tearing cannot happen because it is a 32 bit field and such writes are atomic on + * all supported platforms. */ private int lastIndex; @@ -75,7 +91,6 @@ public MethodProfile(ResolvedJavaMethod 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)); @@ -83,16 +98,18 @@ private static InterpreterProfile[] buildProfiles(ResolvedJavaMethod method) { 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 + allProfiles.add(new TypeProfile(bci)); } // TODO GR-71799 - backedge / goto profiles stream.next(); } + return allProfiles.toArray(new InterpreterProfile[0]); } @@ -105,7 +122,7 @@ public ResolvedJavaMethod getMethod() { * 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 */ @@ -133,13 +150,30 @@ public void profileBranch(int bci, boolean taken) { } } + public JavaTypeProfile getTypeProfile(int bci) { + return ((TypeProfile) getAtBCI(bci, TypeProfile.class)).toTypeProfile(); + } + public double getBranchTakenProbability(int bci) { return ((BranchProfile) getAtBCI(bci, BranchProfile.class)).takenProfile(); } + public void profileType(int bci, ResolvedJavaType type) { + ((TypeProfile) getAtBCI(bci, TypeProfile.class)).incrementTypeProfile(type); + } + + public void profileReceiver(int bci, Object receiver) { + if (receiver == null) { + // TODO GR-71949 - profile nullSeen + return; + } + ResolvedJavaType type = DynamicHub.fromClass(receiver.getClass()).getInterpreterType(); + profileType(bci, type); + } + /** * 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) { @@ -174,6 +208,10 @@ public static List profilesAtBCI(MethodProfile methodProfile } } + /** + * Abstract base class for all interpreter profiles. Every profile has at least a bytecode index + * (BCI) it is associated with. + */ public abstract static class InterpreterProfile { protected final int bci; @@ -186,6 +224,9 @@ public int getBci() { } } + /** + * Abstraction for counting profiles, i.e., profiles that record a frequency. + */ public static class CountingProfile extends InterpreterProfile { protected long counter; @@ -207,6 +248,10 @@ public String toString() { } } + /** + * Abstraction for a binary branch profile for bytecode if instructions. In a compiler graph + * normally represented by {@link IfNode}. + */ public static class BranchProfile extends CountingProfile { private long takenCounter; @@ -225,14 +270,14 @@ public void incrementNotTakenCounter() { public double takenProfile() { if (counter == 0) { - return -1; + return -1D; } return (double) takenCounter / (double) counter; } public double notTakenProfile() { if (counter == 0) { - return -1; + return -1D; } return 1D - takenProfile(); } @@ -243,4 +288,165 @@ public String toString() { } } + /** + * Abstraction for a type profile for bytecode instructions based on types: invokes, type + * checks, etc. + *

+ * Abstraction is generic to record any set of types together with their count and a + * {@link JavaTypeProfile#getNotRecordedProbability() not recorded probability}. In a compiler + * graph normally represented as a {@link JavaTypeProfile} attached to a + * {@link jdk.graal.compiler.nodes.java.MethodCallTargetNode}. + */ + public static class TypeProfile extends CountingProfile { + + private static final Unsafe UNSAFE = Unsafe.getUnsafe(); + + private static final long TYPE_ARRAY_BASE = UNSAFE.arrayBaseOffset(ResolvedJavaType[].class); + + private static final long TYPE_ARRAY_SHIFT = UNSAFE.arrayIndexScale(ResolvedJavaType[].class); + + /** + * List of profiled types. Initially allocated to a fixed size and filled lazily during + * profiling. + */ + private final ResolvedJavaType[] types; + + /** + * All counts per type - each [index] represents the number of times {@code types[i]} was + * seen during profiling. + */ + private final long[] counts; + + public TypeProfile(int bci) { + super(bci); + final int typeProfileWidth = InterpreterProfilingOptions.JITProfileTypeProfileWidth.getValue(); + types = new ResolvedJavaType[typeProfileWidth]; + counts = new long[typeProfileWidth]; + } + + /** + * See {@link JavaTypeProfile#getNotRecordedProbability()}. + * + * Do not use from performance sensitive code, computes the notRecordedProbability via + * {@link #toTypeProfile()} . + */ + public double notRecordedProbability() { + return toTypeProfile().getNotRecordedProbability(); + } + + /** + * Return the "saturation" of this type profile, i.e., the number of types already recorded. + */ + private int getProfiledTypeCount() { + for (int i = 0; i < types.length; i++) { + if (types[i] == null) { + return i; + } + } + return types.length; + } + + /** + * Returns the probability of a given type in this profile. + */ + public double getProbability(ResolvedJavaType type) { + if (counter == 0L) { + // no type profiled yet + return -1D; + } + for (int i = 0; i < getProfiledTypeCount(); i++) { + ResolvedJavaType t = types[i]; + if (t.equals(type)) { + return (double) counts[i] / (double) counter; + } + } + return -1; + } + + /** + * Tries to increment the profile count for the given {@code type}. If the profile is + * saturated ({@code getProfiledTypeCount() == types.length}) only + * {@link CountingProfile#counter} is incremented (which results in the + * notRecordedProbability to be increased). + *

+ * If {@code type} cannot be found in the profile tries to add it to the profile array. If + * that fails, because another thread concurrently added a type (sequentialized via + * {@link Unsafe#compareAndSetReference(Object, long, Object, Object)}) tries the next slot + * until the profile is saturated by other threads or the current thread added the type. + */ + public void incrementTypeProfile(ResolvedJavaType type) { + for (int i = 0; i < types.length; i++) { + ResolvedJavaType slotType = types[i]; + if (slotType == null) { + /* Try to "claim" this slot for the current type. */ + long offset = TYPE_ARRAY_BASE + i * TYPE_ARRAY_SHIFT; + slotType = (ResolvedJavaType) UNSAFE.compareAndExchangeReference(types, offset, null, type); + if (slotType == null) { + /* CAS succeeded. */ + slotType = type; + } + } + + if (slotType.equals(type)) { + /* Either the CAS succeeded or another thread wrote the same type already. */ + counts[i]++; + break; + } + } + + /* Always update the total count, even if recording the type failed. */ + counter++; + } + + public JavaTypeProfile toTypeProfile() { + final int profiledTypeCount = getProfiledTypeCount(); + if (profiledTypeCount == 0 || counter == 0L) { + // nothing recorded + return null; + } + // taken from HotSpotMethodData.java#createTypeProfile - sync any bug fixes there + JavaTypeProfile.ProfiledType[] ptypes = new JavaTypeProfile.ProfiledType[profiledTypeCount]; + double totalProbability = 0.0; + for (int i = 0; i < profiledTypeCount; i++) { + double p = counts[i]; + p = p / counter; + totalProbability += p; + ptypes[i] = new JavaTypeProfile.ProfiledType(types[i], p); + } + Arrays.sort(ptypes); + double notRecordedTypeProbability = profiledTypeCount < types.length ? 0.0 : Math.min(1.0, Math.max(0.0, 1.0 - totalProbability)); + assert notRecordedTypeProbability == 0 || profiledTypeCount == types.length; + // TODO GR-71949 - null seen + return new JavaTypeProfile(TriState.UNKNOWN, notRecordedTypeProbability, ptypes); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(128); + sb.append("{TypeProfile:bci=").append(bci).append(", counter=").append(counter); + int limit = Math.min(getProfiledTypeCount(), types.length); + sb.append(", types=["); + for (int i = 0; i < limit; i++) { + if (i > 0) { + sb.append(", "); + } + ResolvedJavaType t = types[i]; + long c = counts[i]; + if (t != null) { + sb.append(t.toClassName()).append(':').append(c); + } else { + sb.append("null:").append(c); + } + } + sb.append(']'); + if (limit >= types.length) { + sb.append(", saturated=true"); + } else { + sb.append(", freeSlots=").append(types.length - limit); + } + sb.append(", notRecorded=").append(notRecordedProbability()); + sb.append('}'); + return sb.toString(); + } + } } 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 29ed36c21712..3718c728dc30 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 @@ -746,7 +746,7 @@ private static Object executeBodyFromBCI(InterpreterFrame frame, InterpreterReso case BALOAD: // fall through case CALOAD: // fall through case SALOAD: // fall through - case AALOAD: arrayLoad(frame, top, curOpcode); break; + case AALOAD: arrayLoad(frame, methodProfile, curBCI, top, curOpcode); break; case ISTORE: setLocalInt(frame, BytecodeStream.readLocalIndex1(code, curBCI), popInt(frame, top - 1)); break; case LSTORE: setLocalLong(frame, BytecodeStream.readLocalIndex1(code, curBCI), popLong(frame, top - 1)); break; @@ -786,7 +786,7 @@ private static Object executeBodyFromBCI(InterpreterFrame frame, InterpreterReso case AASTORE: // fall through case BASTORE: // fall through case CASTORE: // fall through - case SASTORE: arrayStore(frame, top, curOpcode); break; + case SASTORE: arrayStore(frame, methodProfile, curBCI, top, curOpcode); break; case POP2: clear(frame, top - 1); @@ -887,9 +887,7 @@ private static Object executeBodyFromBCI(InterpreterFrame frame, InterpreterReso case IFGT: // fall through case IFLE: final boolean branchTaken1 = takeBranchPrimitive1(popInt(frame, top - 1), curOpcode); - if (methodProfile != null) { - methodProfile.profileBranch(curBCI, branchTaken1); - } + profileBranch(methodProfile, curBCI, branchTaken1); if (branchTaken1) { top += ConstantBytecodes.stackEffectOf(IFLE); curBCI = beforeJumpChecks(frame, curBCI, BytecodeStream.readBranchDest2(code, curBCI), top); @@ -904,9 +902,7 @@ private static Object executeBodyFromBCI(InterpreterFrame frame, InterpreterReso case IF_ICMPGT: // fall through case IF_ICMPLE: final boolean branchTaken2 = takeBranchPrimitive2(popInt(frame, top - 1), popInt(frame, top - 2), curOpcode); - if (methodProfile != null) { - methodProfile.profileBranch(curBCI, branchTaken2); - } + profileBranch(methodProfile, curBCI, branchTaken2); if (branchTaken2) { top += ConstantBytecodes.stackEffectOf(IF_ICMPLE); curBCI = beforeJumpChecks(frame, curBCI, BytecodeStream.readBranchDest2(code, curBCI), top); @@ -917,9 +913,7 @@ private static Object executeBodyFromBCI(InterpreterFrame frame, InterpreterReso case IF_ACMPEQ: // fall through case IF_ACMPNE: final boolean branchTakenRef2 = takeBranchRef2(popObject(frame, top - 1), popObject(frame, top - 2), curOpcode); - if (methodProfile != null) { - methodProfile.profileBranch(curBCI, branchTakenRef2); - } + profileBranch(methodProfile, curBCI, branchTakenRef2); if (branchTakenRef2) { top += ConstantBytecodes.stackEffectOf(IF_ACMPNE); curBCI = beforeJumpChecks(frame, curBCI, BytecodeStream.readBranchDest2(code, curBCI), top); @@ -930,9 +924,7 @@ private static Object executeBodyFromBCI(InterpreterFrame frame, InterpreterReso case IFNULL: // fall through case IFNONNULL: final boolean branchTakenRef1 = takeBranchRef1(popObject(frame, top - 1), curOpcode); - if (methodProfile != null) { - methodProfile.profileBranch(curBCI, branchTakenRef1); - } + profileBranch(methodProfile, curBCI, branchTakenRef1); if (branchTakenRef1) { top += ConstantBytecodes.stackEffectOf(IFNONNULL); curBCI = beforeJumpChecks(frame, curBCI, BytecodeStream.readBranchDest2(code, curBCI), top); @@ -1069,7 +1061,7 @@ private static Object executeBodyFromBCI(InterpreterFrame frame, InterpreterReso } try { - top += invoke(frame, method, code, top, curBCI, curOpcode, forceStayInInterpreter, preferStayInInterpreter); + top += invoke(frame, methodProfile, method, code, top, curBCI, curOpcode, forceStayInInterpreter, preferStayInInterpreter); } finally { SteppingControl newSteppingControl = DebuggerEvents.singleton().getSteppingControl(currentThread); if (newSteppingControl != null) { @@ -1094,6 +1086,7 @@ private static Object executeBodyFromBCI(InterpreterFrame frame, InterpreterReso case CHECKCAST : { Object receiver = peekObject(frame, top - 1); + profileType(methodProfile, curBCI, receiver); // Resolve type iff receiver != null. if (receiver != null) { InterpreterToVM.checkCast(receiver, resolveType(method, CHECKCAST, BytecodeStream.readCPI2(code, curBCI))); @@ -1102,6 +1095,7 @@ private static Object executeBodyFromBCI(InterpreterFrame frame, InterpreterReso } case INSTANCEOF : { Object receiver = popObject(frame, top - 1); + profileType(methodProfile, curBCI, receiver); // Resolve type iff receiver != null. putInt(frame, top - 1, (receiver != null && InterpreterToVM.instanceOf(receiver, resolveType(method, INSTANCEOF, BytecodeStream.readCPI2(code, curBCI)))) ? 1 : 0); break; @@ -1180,6 +1174,18 @@ private static Object executeBodyFromBCI(InterpreterFrame frame, InterpreterReso } } + private static void profileType(MethodProfile methodProfile, int bci, Object o) { + if (methodProfile != null) { + methodProfile.profileReceiver(bci, o); + } + } + + private static void profileBranch(MethodProfile methodProfile, int curBCI, boolean branchTaken1) { + if (methodProfile != null) { + methodProfile.profileBranch(curBCI, branchTaken1); + } + } + @SuppressWarnings("unchecked") private static RuntimeException uncheckedThrow(Throwable e) throws T { throw (T) e; @@ -1255,7 +1261,7 @@ private static boolean takeBranchRef2(Object operand1, Object operand2, int opco }; } - private static void arrayLoad(InterpreterFrame frame, int top, int loadOpcode) { + private static void arrayLoad(InterpreterFrame frame, MethodProfile methodProfile, int bci, int top, int loadOpcode) { assert IALOAD <= loadOpcode && loadOpcode <= SALOAD; int index = popInt(frame, top - 1); Object array = nullCheck(popObject(frame, top - 2)); @@ -1267,12 +1273,16 @@ private static void arrayLoad(InterpreterFrame frame, int top, int loadOpcode) { case FALOAD -> putFloat(frame, top - 2, InterpreterToVM.getArrayFloat(index, (float[]) array)); case LALOAD -> putLong(frame, top - 2, InterpreterToVM.getArrayLong(index, (long[]) array)); case DALOAD -> putDouble(frame, top - 2, InterpreterToVM.getArrayDouble(index, (double[]) array)); - case AALOAD -> putObject(frame, top - 2, InterpreterToVM.getArrayObject(index, (Object[]) array)); + case AALOAD -> { + Object o = InterpreterToVM.getArrayObject(index, (Object[]) array); + profileType(methodProfile, bci, o); + putObject(frame, top - 2, o); + } default -> throw VMError.shouldNotReachHereAtRuntime(); } } - private static void arrayStore(InterpreterFrame frame, int top, int storeOpcode) { + private static void arrayStore(InterpreterFrame frame, MethodProfile methodProfile, int bci, int top, int storeOpcode) { assert IASTORE <= storeOpcode && storeOpcode <= SASTORE; int offset = (storeOpcode == LASTORE || storeOpcode == DASTORE) ? 2 : 1; int index = popInt(frame, top - 1 - offset); @@ -1285,7 +1295,11 @@ private static void arrayStore(InterpreterFrame frame, int top, int storeOpcode) case FASTORE -> InterpreterToVM.setArrayFloat(popFloat(frame, top - 1), index, (float[]) array); case LASTORE -> InterpreterToVM.setArrayLong(popLong(frame, top - 1), index, (long[]) array); case DASTORE -> InterpreterToVM.setArrayDouble(popDouble(frame, top - 1), index, (double[]) array); - case AASTORE -> InterpreterToVM.setArrayObject(popObject(frame, top - 1), index, (Object[]) array); + case AASTORE -> { + Object o = popObject(frame, top - 1); + profileType(methodProfile, bci, o); + InterpreterToVM.setArrayObject(o, index, (Object[]) array); + } default -> throw VMError.shouldNotReachHereAtRuntime(); } } @@ -1398,7 +1412,8 @@ private static InterpreterConstantPool getConstantPool(InterpreterResolvedJavaMe return method.getConstantPool(); } - private static int invoke(InterpreterFrame callerFrame, InterpreterResolvedJavaMethod method, byte[] code, int top, int curBCI, int opcode, boolean forceStayInInterpreter, + private static int invoke(InterpreterFrame callerFrame, MethodProfile methodProfile, InterpreterResolvedJavaMethod method, byte[] code, int top, int curBCI, int opcode, + boolean forceStayInInterpreter, boolean preferStayInInterpreter) { int invokeTop = top; @@ -1479,8 +1494,11 @@ private static int invoke(InterpreterFrame callerFrame, InterpreterResolvedJavaM Object[] calleeArgs = EspressoFrame.popArguments(callerFrame, invokeTop, hasReceiver, seedSignature); if (!seedMethod.isStatic()) { - nullCheck(calleeArgs[0]); + final Object receiver = calleeArgs[0]; + profileType(methodProfile, curBCI, receiver); + nullCheck(receiver); } + Object retObj = InterpreterToVM.dispatchInvocation(seedMethod, calleeArgs, isVirtual, forceStayInInterpreter, preferStayInInterpreter, opcode == INVOKEINTERFACE, false); retStackEffect += EspressoFrame.putKind(callerFrame, resultAt, retObj, seedSignature.getReturnKind()); 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 65b35d9e139f..5069b6f20245 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,7 +46,4 @@ 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 07ab99b19407..28734b2bc005 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 @@ -141,6 +141,24 @@ public static CompilationResult compile(DebugContext debug, final SubstrateMetho return doCompile(debug, RuntimeCompilationSupport.getRuntimeConfig(), RuntimeCompilationSupport.getLIRSuites(), method); } + public static StructuredGraph parseOnly(SubstrateMethod method) { + if (method instanceof RistrettoMethod rMethod) { + final RuntimeConfiguration runtimeConfig = RuntimeCompilationSupport.getRuntimeConfig(); + final DebugContext debug = new DebugContext.Builder(RuntimeOptionValues.singleton(), new GraalDebugHandlersFactory(runtimeConfig.getProviders().getSnippetReflection())).build(); + final OptionValues options = debug.getOptions(); + final SpeculationLog speculationLog = new SubstrateSpeculationLog(); + final ProfileProvider profileProvider = new RistrettoProfileProvider(rMethod); + final StructuredGraph.AllowAssumptions allowAssumptions = StructuredGraph.AllowAssumptions.NO; + SubstrateCompilationIdentifier compilationId = new SubstrateCompilationIdentifier(method); + StructuredGraph graph = new StructuredGraph.Builder(options, debug, allowAssumptions).method(method).speculationLog(speculationLog) + .profileProvider(profileProvider).compilationId(compilationId).build(); + assert graph != null; + parseFromBytecode(graph, runtimeConfig); + return graph; + } + return null; + } + public static InstalledCode compileAndInstall(SubstrateMethod method) { return compileAndInstall(method, () -> new SubstrateInstalledCodeImpl(method)); } @@ -242,7 +260,7 @@ private static void parseFromBytecode(StructuredGraph graph, RuntimeConfiguratio Replacements runtimeReplacements = runtimeProviders.getReplacements(); GraphBuilderConfiguration.Plugins gbp = runtimeReplacements.getGraphBuilderPlugins(); GraphBuilderConfiguration gpc = GraphBuilderConfiguration.getDefault(gbp); - HighTierContext hc = new HighTierContext(runtimeConfig.getProviders(), null, OptimisticOptimizations.NONE); + HighTierContext hc = new HighTierContext(runtimeConfig.getProviders(), null, OptimisticOptimizations.ALL); RistrettoGraphBuilderPhase graphBuilderPhase = new RistrettoGraphBuilderPhase(gpc); graphBuilderPhase.apply(graph, hc); assert graph.getNodeCount() > 1 : "Must have nodes after parsing"; diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/ristretto/meta/RistrettoConstantPool.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/ristretto/meta/RistrettoConstantPool.java index 4b414db2c35c..20e05479da54 100644 --- a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/ristretto/meta/RistrettoConstantPool.java +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/ristretto/meta/RistrettoConstantPool.java @@ -28,6 +28,7 @@ import java.util.function.Function; import com.oracle.svm.interpreter.Interpreter; +import com.oracle.svm.interpreter.metadata.Bytecodes; import com.oracle.svm.interpreter.metadata.CremaResolvedJavaFieldImpl; import com.oracle.svm.interpreter.metadata.InterpreterConstantPool; import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaMethod; @@ -137,7 +138,14 @@ public Object lookupConstant(int cpi, boolean resolve) { @Override public JavaConstant lookupAppendix(int rawIndex, int opcode) { - return interpreterConstantPool.lookupAppendix(rawIndex, opcode); + if (opcode == Bytecodes.INVOKEDYNAMIC) { + return interpreterConstantPool.lookupAppendix(rawIndex, opcode); + } + /* + * TODO GR-71270 - The parser has support for special runtimes that rewrite invokes of + * methods handles to static adapters. Crema does not do that. + */ + return null; } @Override diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/ristretto/meta/RistrettoType.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/ristretto/meta/RistrettoType.java index 3d52538bbdc9..57db92797b03 100644 --- a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/ristretto/meta/RistrettoType.java +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/ristretto/meta/RistrettoType.java @@ -79,4 +79,13 @@ public ResolvedJavaType getComponentType() { public boolean isArray() { return interpreterType.isArray(); } + + @Override + public boolean isLinked() { + /* + * TODO GR-59739, GR-71851 - crema does not implement linking at the moment, so we assume + * all resolved (==loaded) types successfully linked as well + */ + return true; + } } 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 index 0d0536e5900a..cf3ee7b5fd0c 100644 --- 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 @@ -24,8 +24,8 @@ */ package com.oracle.svm.interpreter.ristretto.profile; +import com.oracle.svm.interpreter.metadata.profile.InterpreterProfilingOptions; 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; @@ -51,7 +51,15 @@ public int getCodeSize() { @Override public JavaTypeProfile getTypeProfile(int bci) { - return null; + final var typeProfile = methodProfile.getTypeProfile(bci); + if (Assertions.assertionsEnabled() && typeProfile != null) { + for (var entry : typeProfile.getTypes()) { + final double p = entry.getProbability(); + assert !Double.isNaN(p) && !Double.isInfinite(p) : Assertions.errorMessage("Invalid recorded type probability", p, entry.getType(), + methodProfile.getMethod(), MethodProfile.TestingBackdoor.profilesAtBCI(methodProfile, bci)); + } + } + return typeProfile; } @Override @@ -118,7 +126,7 @@ public boolean isMature() { /* * Either maturity was explicitly requested or we follow regular ergonomics. */ - return methodProfile.isMature() || methodProfile.getProfileEntryCount() > RistrettoRuntimeOptions.JITProfileMatureInvocationThreshold.getValue(); + return methodProfile.isMature() || methodProfile.getProfileEntryCount() > InterpreterProfilingOptions.JITProfileMatureInvocationThreshold.getValue(); } @Override