From c83d609a39bf8e8b26c121911bce1dbf0be03c1e Mon Sep 17 00:00:00 2001 From: David Leopoldseder Date: Thu, 4 Dec 2025 12:20:09 +0100 Subject: [PATCH 01/19] type profile tmp --- substratevm/mx.substratevm/suite.py | 4 + .../profile/InterpreterProfilingOptions.java | 38 +++++ .../metadata/profile/MethodProfile.java | 150 +++++++++++++++++- .../oracle/svm/interpreter/Interpreter.java | 47 +++++- .../ristretto/RistrettoRuntimeOptions.java | 3 - .../interpreter/ristretto/RistrettoUtils.java | 18 +++ .../profile/RistrettoProfilingInfo.java | 6 +- 7 files changed, 251 insertions(+), 15 deletions(-) create mode 100644 substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/profile/InterpreterProfilingOptions.java 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..eef0e8f555d6 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 @@ -28,12 +28,18 @@ import java.util.ArrayList; import java.util.List; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; import com.oracle.svm.interpreter.metadata.Bytecodes; import jdk.graal.compiler.bytecode.BytecodeStream; +import jdk.graal.compiler.debug.GraalError; +import jdk.graal.compiler.nodes.PauseNode; +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 @@ -88,7 +94,7 @@ private static InterpreterProfile[] buildProfiles(ResolvedJavaMethod method) { 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(); @@ -133,10 +139,18 @@ 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)).incrementType(type); + } + /** * Gets the profile for {@code bci} whose class is {@code clazz}. * @@ -243,4 +257,138 @@ public String toString() { } } + public static class TypeProfile extends CountingProfile { + /** + * All types that are profiled per type. + */ + 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; + private long notRecordedCount; + private int nextFreeSlot; + + // value of state is 0 if the state is READING, else it holds the id of the currently + // writing thread + private volatile long state; + + private static final int READING_STATE = 0; + private static final int WRITING_STATE = 1; + + public static final AtomicLongFieldUpdater PROFILING_STATE_UPDATER = AtomicLongFieldUpdater.newUpdater(TypeProfile.class, "state"); + + public TypeProfile(int bci) { + super(bci); + final int typeProfileWidth = InterpreterProfilingOptions.JITProfileTypeProfileWidth.getValue(); + types = new ResolvedJavaType[typeProfileWidth]; + counts = new long[typeProfileWidth]; + } + + public double notRecordedProbability() { + return (double) notRecordedCount / (double) counter; + } + + public double getProbabilty(ResolvedJavaType type) { + for (int i = 0; i < types.length; i++) { + ResolvedJavaType t = types[i]; + System.out.printf("Checking if %s equals %s%n?", t, type); + if (t.equals(type)) { + return (double) counts[i] / (double) counter; + } + } + return -1; + } + + public void incrementType(ResolvedJavaType type) { + counter++; + // check if the type was already recorded, in which case we update the counts in a racy + // fashion + for (int i = 0; i < types.length && i < nextFreeSlot; i++) { + if (types[i].equals(type)) { + counts[i]++; + return; + } + } + if (nextFreeSlot >= types.length) { + // all types saturated, racy update to not recorded + notRecordedCount++; + return; + } + addTypeAndIncrement(type); + } + + private void addTypeAndIncrement(ResolvedJavaType type) { + final long currentThreadId = Thread.currentThread().threadId(); + // we are adding a new type to the profile, we have to perform this under a heavy lock + while (true) { + if (!PROFILING_STATE_UPDATER.compareAndSet(this, 0, currentThreadId)) { + // try to acquire the state lock, spin if this fails until we are allowed to + PauseNode.pause(); + } else { + // we got the "lock" to write the profile + break; + } + } + /* + * in the meantime its possible another thread already saturated the list of profiles, + * in this case we have to add to the remaining ones + */ + if (nextFreeSlot >= types.length) { + notRecordedCount++; + return; + } + types[nextFreeSlot] = type; + counts[nextFreeSlot]++; + nextFreeSlot++; + + if (PROFILING_STATE_UPDATER.compareAndSet(this, currentThreadId, 0)) { + return; + } else { + throw GraalError.shouldNotReachHere("Must always be able to set back threadID lock to 0 after profiel udpate"); + } + } + + private boolean hasFreeSlots() { + return nextFreeSlot < types.length; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(128); + sb.append("{TypeProfile:bci=").append(bci).append(", counter=").append(counter); + int limit = Math.min(nextFreeSlot, 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]; + sb.append(t.getName()).append(':').append(c); + } + sb.append(']'); + // Include notRecordedCount if saturated or if there were any drops + if (limit >= types.length) { + sb.append(", saturated=true"); + } else { + sb.append(", freeSlots=").append(types.length - limit); + } + if (notRecordedCount > 0) { + sb.append(", notRecorded=").append(notRecordedCount); + } + sb.append('}'); + return sb.toString(); + } + + public JavaTypeProfile toTypeProfile() { + JavaTypeProfile.ProfiledType[] types = new JavaTypeProfile.ProfiledType[this.types.length]; + for (int i = 0; i < types.length; i++) { + types[i] = new JavaTypeProfile.ProfiledType(this.types[i], getProbabilty(this.types[i])); + } + return new JavaTypeProfile(TriState.UNKNOWN, notRecordedProbability(), types); + } + } } 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..9c822da589d0 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 @@ -269,6 +269,7 @@ import java.util.Objects; import com.oracle.svm.core.NeverInline; +import com.oracle.svm.core.hub.DynamicHub; import com.oracle.svm.core.invoke.Target_java_lang_invoke_MemberName; import com.oracle.svm.core.jdk.InternalVMMethod; import com.oracle.svm.core.methodhandles.MethodHandleInterpreterUtils; @@ -746,7 +747,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 +787,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); @@ -1069,7 +1070,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) { @@ -1096,12 +1097,21 @@ private static Object executeBodyFromBCI(InterpreterFrame frame, InterpreterReso Object receiver = peekObject(frame, top - 1); // Resolve type iff receiver != null. if (receiver != null) { + if (methodProfile != null) { + ResolvedJavaType storedType = DynamicHub.fromClass(receiver.getClass()).getInterpreterType(); + methodProfile.profileType(curBCI, storedType); + } InterpreterToVM.checkCast(receiver, resolveType(method, CHECKCAST, BytecodeStream.readCPI2(code, curBCI))); } break; } case INSTANCEOF : { Object receiver = popObject(frame, top - 1); + if (methodProfile != null&&receiver!=null) { + // TODO profile nullSeen + ResolvedJavaType storedType = (InterpreterResolvedJavaType) DynamicHub.fromClass(receiver.getClass()).getInterpreterType(); + methodProfile.profileType(curBCI, storedType); + } // Resolve type iff receiver != null. putInt(frame, top - 1, (receiver != null && InterpreterToVM.instanceOf(receiver, resolveType(method, INSTANCEOF, BytecodeStream.readCPI2(code, curBCI)))) ? 1 : 0); break; @@ -1255,7 +1265,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 +1277,19 @@ 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); + if (methodProfile != null) { + ResolvedJavaType storedType = DynamicHub.fromClass(o.getClass()).getInterpreterType(); + methodProfile.profileType(bci, storedType); + } + 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 +1302,14 @@ 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); + if (methodProfile != null) { + ResolvedJavaType storedType = DynamicHub.fromClass(o.getClass()).getInterpreterType(); + methodProfile.profileType(bci, storedType); + } + InterpreterToVM.setArrayObject(o, index, (Object[]) array); + } default -> throw VMError.shouldNotReachHereAtRuntime(); } } @@ -1398,7 +1422,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; @@ -1481,6 +1506,12 @@ private static int invoke(InterpreterFrame callerFrame, InterpreterResolvedJavaM if (!seedMethod.isStatic()) { nullCheck(calleeArgs[0]); } + if (methodProfile != null) { + Object receiver = calleeArgs[0]; + ResolvedJavaType storedType = (InterpreterResolvedJavaType) DynamicHub.fromClass(receiver.getClass()).getInterpreterType(); + methodProfile.profileType(curBCI, storedType); + } + 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..4cd60f107f83 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)); } 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..f03751536d95 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,7 @@ public int getCodeSize() { @Override public JavaTypeProfile getTypeProfile(int bci) { - return null; + return methodProfile.getTypeProfile(bci); } @Override @@ -118,7 +118,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 From 6882f9662026bb69642116f4c05a61b59f4ccbf1 Mon Sep 17 00:00:00 2001 From: David Leopoldseder Date: Wed, 3 Dec 2025 14:56:18 +0100 Subject: [PATCH 02/19] ristretto: crema does not implement linking yet, for parsing we require it, assume that resolved types are linked --- .../svm/interpreter/ristretto/meta/RistrettoType.java | 9 +++++++++ 1 file changed, 9 insertions(+) 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..9d1c07b59cc4 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-71851 - crema does not implement linking at the moment, so we assume all resolved + * (==loaded) types successfully linked as well + */ + return true; + } } From 2565b2ec5406877fac8353732f805b41389768f3 Mon Sep 17 00:00:00 2001 From: David Leopoldseder Date: Wed, 3 Dec 2025 14:48:36 +0100 Subject: [PATCH 03/19] ristretto: constant pool can be reached with invokevirtual from the parser for special logic implemented by certain vms (hs), not applicable for crema --- .../interpreter/ristretto/meta/RistrettoConstantPool.java | 6 ++++++ 1 file changed, 6 insertions(+) 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..f4745751fad5 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,6 +138,11 @@ public Object lookupConstant(int cpi, boolean resolve) { @Override public JavaConstant lookupAppendix(int rawIndex, int opcode) { + if (opcode == Bytecodes.INVOKEVIRTUAL) { + // The parser has support for special runtimes that rewrite invokes of methods handles + // to static adapters. Crema does not do that. + return null; + } return interpreterConstantPool.lookupAppendix(rawIndex, opcode); } From f11365ee8d173f5d143de11d2529b4040212fde4 Mon Sep 17 00:00:00 2001 From: David Leopoldseder Date: Fri, 5 Dec 2025 16:44:42 +0100 Subject: [PATCH 04/19] ristretto: run with optimistic optimizations --- .../com/oracle/svm/interpreter/ristretto/RistrettoUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 4cd60f107f83..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 @@ -260,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"; From 498bbba2c2d8eb970bcb04bfb18ea9d912356252 Mon Sep 17 00:00:00 2001 From: David Leopoldseder Date: Tue, 9 Dec 2025 10:22:16 +0100 Subject: [PATCH 05/19] ristretto:replace profileType with profileReceiver --- .../metadata/profile/MethodProfile.java | 13 ++++++++++- .../oracle/svm/interpreter/Interpreter.java | 23 +++++++------------ 2 files changed, 20 insertions(+), 16 deletions(-) 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 eef0e8f555d6..9ad0f94c0961 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 @@ -30,6 +30,7 @@ import java.util.List; import java.util.concurrent.atomic.AtomicLongFieldUpdater; +import com.oracle.svm.core.hub.DynamicHub; import com.oracle.svm.interpreter.metadata.Bytecodes; import jdk.graal.compiler.bytecode.BytecodeStream; @@ -151,6 +152,17 @@ public void profileType(int bci, ResolvedJavaType type) { ((TypeProfile) getAtBCI(bci, TypeProfile.class)).incrementType(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}. * @@ -294,7 +306,6 @@ public double notRecordedProbability() { public double getProbabilty(ResolvedJavaType type) { for (int i = 0; i < types.length; i++) { ResolvedJavaType t = types[i]; - System.out.printf("Checking if %s equals %s%n?", t, type); if (t.equals(type)) { return (double) counts[i] / (double) counter; } 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 9c822da589d0..bca85e709a89 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 @@ -269,7 +269,6 @@ import java.util.Objects; import com.oracle.svm.core.NeverInline; -import com.oracle.svm.core.hub.DynamicHub; import com.oracle.svm.core.invoke.Target_java_lang_invoke_MemberName; import com.oracle.svm.core.jdk.InternalVMMethod; import com.oracle.svm.core.methodhandles.MethodHandleInterpreterUtils; @@ -1095,22 +1094,19 @@ private static Object executeBodyFromBCI(InterpreterFrame frame, InterpreterReso case CHECKCAST : { Object receiver = peekObject(frame, top - 1); + if (methodProfile != null) { + methodProfile.profileReceiver(curBCI, receiver); + } // Resolve type iff receiver != null. if (receiver != null) { - if (methodProfile != null) { - ResolvedJavaType storedType = DynamicHub.fromClass(receiver.getClass()).getInterpreterType(); - methodProfile.profileType(curBCI, storedType); - } InterpreterToVM.checkCast(receiver, resolveType(method, CHECKCAST, BytecodeStream.readCPI2(code, curBCI))); } break; } case INSTANCEOF : { Object receiver = popObject(frame, top - 1); - if (methodProfile != null&&receiver!=null) { - // TODO profile nullSeen - ResolvedJavaType storedType = (InterpreterResolvedJavaType) DynamicHub.fromClass(receiver.getClass()).getInterpreterType(); - methodProfile.profileType(curBCI, storedType); + if (methodProfile != null) { + methodProfile.profileReceiver(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); @@ -1280,8 +1276,7 @@ private static void arrayLoad(InterpreterFrame frame, MethodProfile methodProfil case AALOAD -> { Object o = InterpreterToVM.getArrayObject(index, (Object[]) array); if (methodProfile != null) { - ResolvedJavaType storedType = DynamicHub.fromClass(o.getClass()).getInterpreterType(); - methodProfile.profileType(bci, storedType); + methodProfile.profileReceiver(bci, o); } putObject(frame, top - 2, o); } @@ -1305,8 +1300,7 @@ private static void arrayStore(InterpreterFrame frame, MethodProfile methodProfi case AASTORE -> { Object o = popObject(frame, top - 1); if (methodProfile != null) { - ResolvedJavaType storedType = DynamicHub.fromClass(o.getClass()).getInterpreterType(); - methodProfile.profileType(bci, storedType); + methodProfile.profileReceiver(bci, o); } InterpreterToVM.setArrayObject(o, index, (Object[]) array); } @@ -1508,8 +1502,7 @@ private static int invoke(InterpreterFrame callerFrame, MethodProfile methodProf } if (methodProfile != null) { Object receiver = calleeArgs[0]; - ResolvedJavaType storedType = (InterpreterResolvedJavaType) DynamicHub.fromClass(receiver.getClass()).getInterpreterType(); - methodProfile.profileType(curBCI, storedType); + methodProfile.profileReceiver(curBCI, receiver); } Object retObj = InterpreterToVM.dispatchInvocation(seedMethod, calleeArgs, isVirtual, forceStayInInterpreter, preferStayInInterpreter, opcode == INVOKEINTERFACE, false); From 5b23c17e656fb06d3bc69b2283ad4bbdf4d04094 Mon Sep 17 00:00:00 2001 From: David Leopoldseder Date: Tue, 9 Dec 2025 12:58:54 +0100 Subject: [PATCH 06/19] ristretto: type profile updates --- .../metadata/profile/MethodProfile.java | 56 +++++++++---------- 1 file changed, 26 insertions(+), 30 deletions(-) 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 9ad0f94c0961..ef7b86b7b816 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 @@ -276,20 +276,17 @@ public static class TypeProfile extends CountingProfile { private final ResolvedJavaType[] types; /** - * All counts per type - each index represents the number of times {@code types[i]} was seen - * during profiling. + * All counts per type - each [index] represents the number of times {@code types[i]} was + * seen during profiling. */ private final long[] counts; private long notRecordedCount; - private int nextFreeSlot; + private volatile int nextFreeSlot; // value of state is 0 if the state is READING, else it holds the id of the currently // writing thread private volatile long state; - private static final int READING_STATE = 0; - private static final int WRITING_STATE = 1; - public static final AtomicLongFieldUpdater PROFILING_STATE_UPDATER = AtomicLongFieldUpdater.newUpdater(TypeProfile.class, "state"); public TypeProfile(int bci) { @@ -303,7 +300,7 @@ public double notRecordedProbability() { return (double) notRecordedCount / (double) counter; } - public double getProbabilty(ResolvedJavaType type) { + public double getProbability(ResolvedJavaType type) { for (int i = 0; i < types.length; i++) { ResolvedJavaType t = types[i]; if (t.equals(type)) { @@ -324,13 +321,15 @@ public void incrementType(ResolvedJavaType type) { } } if (nextFreeSlot >= types.length) { - // all types saturated, racy update to not recorded + // all types saturated, racy update to notRecorded notRecordedCount++; return; } + // type was not seen and we have space, "lock" and increment addTypeAndIncrement(type); } + @SuppressWarnings("finally") private void addTypeAndIncrement(ResolvedJavaType type) { final long currentThreadId = Thread.currentThread().threadId(); // we are adding a new type to the profile, we have to perform this under a heavy lock @@ -343,29 +342,27 @@ private void addTypeAndIncrement(ResolvedJavaType type) { break; } } - /* - * in the meantime its possible another thread already saturated the list of profiles, - * in this case we have to add to the remaining ones - */ - if (nextFreeSlot >= types.length) { - notRecordedCount++; - return; - } - types[nextFreeSlot] = type; - counts[nextFreeSlot]++; - nextFreeSlot++; - - if (PROFILING_STATE_UPDATER.compareAndSet(this, currentThreadId, 0)) { - return; - } else { - throw GraalError.shouldNotReachHere("Must always be able to set back threadID lock to 0 after profiel udpate"); + try { + /* + * in the meantime its possible another thread already saturated the list of + * profiles, in this case we have to add to the remaining ones + */ + if (nextFreeSlot >= types.length) { + notRecordedCount++; + return; + } + types[nextFreeSlot] = type; + counts[nextFreeSlot]++; + nextFreeSlot = nextFreeSlot + 1; + } finally { + if (PROFILING_STATE_UPDATER.compareAndSet(this, currentThreadId, 0)) { + return; + } else { + throw GraalError.shouldNotReachHere("Must always be able to set back threadID lock to 0 after profile update"); + } } } - private boolean hasFreeSlots() { - return nextFreeSlot < types.length; - } - @Override public String toString() { StringBuilder sb = new StringBuilder(128); @@ -381,7 +378,6 @@ public String toString() { sb.append(t.getName()).append(':').append(c); } sb.append(']'); - // Include notRecordedCount if saturated or if there were any drops if (limit >= types.length) { sb.append(", saturated=true"); } else { @@ -397,7 +393,7 @@ public String toString() { public JavaTypeProfile toTypeProfile() { JavaTypeProfile.ProfiledType[] types = new JavaTypeProfile.ProfiledType[this.types.length]; for (int i = 0; i < types.length; i++) { - types[i] = new JavaTypeProfile.ProfiledType(this.types[i], getProbabilty(this.types[i])); + types[i] = new JavaTypeProfile.ProfiledType(this.types[i], getProbability(this.types[i])); } return new JavaTypeProfile(TriState.UNKNOWN, notRecordedProbability(), types); } From 8f88aed394e225581adda98ab30a9d3e63297c6a Mon Sep 17 00:00:00 2001 From: David Leopoldseder Date: Tue, 9 Dec 2025 13:36:52 +0100 Subject: [PATCH 07/19] ristretto: refactorings in profiling --- .../metadata/profile/MethodProfile.java | 2 + .../oracle/svm/interpreter/Interpreter.java | 52 ++++++++----------- 2 files changed, 25 insertions(+), 29 deletions(-) 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 ef7b86b7b816..3004cce96f7b 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 @@ -34,6 +34,7 @@ import com.oracle.svm.interpreter.metadata.Bytecodes; import jdk.graal.compiler.bytecode.BytecodeStream; +import jdk.graal.compiler.debug.Assertions; import jdk.graal.compiler.debug.GraalError; import jdk.graal.compiler.nodes.PauseNode; import jdk.vm.ci.meta.JavaTypeProfile; @@ -332,6 +333,7 @@ public void incrementType(ResolvedJavaType type) { @SuppressWarnings("finally") private void addTypeAndIncrement(ResolvedJavaType type) { final long currentThreadId = Thread.currentThread().threadId(); + assert currentThreadId != 0L : Assertions.errorMessage("ThreadID must never be 0", currentThreadId); // we are adding a new type to the profile, we have to perform this under a heavy lock while (true) { if (!PROFILING_STATE_UPDATER.compareAndSet(this, 0, currentThreadId)) { 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 bca85e709a89..611487d7adb3 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 @@ -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); @@ -1094,9 +1086,7 @@ private static Object executeBodyFromBCI(InterpreterFrame frame, InterpreterReso case CHECKCAST : { Object receiver = peekObject(frame, top - 1); - if (methodProfile != null) { - methodProfile.profileReceiver(curBCI, receiver); - } + profileType(methodProfile, curBCI, receiver); // Resolve type iff receiver != null. if (receiver != null) { InterpreterToVM.checkCast(receiver, resolveType(method, CHECKCAST, BytecodeStream.readCPI2(code, curBCI))); @@ -1105,9 +1095,7 @@ private static Object executeBodyFromBCI(InterpreterFrame frame, InterpreterReso } case INSTANCEOF : { Object receiver = popObject(frame, top - 1); - if (methodProfile != null) { - methodProfile.profileReceiver(curBCI, receiver); - } + 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; @@ -1186,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; @@ -1275,9 +1275,7 @@ private static void arrayLoad(InterpreterFrame frame, MethodProfile methodProfil case DALOAD -> putDouble(frame, top - 2, InterpreterToVM.getArrayDouble(index, (double[]) array)); case AALOAD -> { Object o = InterpreterToVM.getArrayObject(index, (Object[]) array); - if (methodProfile != null) { - methodProfile.profileReceiver(bci, o); - } + profileType(methodProfile, bci, o); putObject(frame, top - 2, o); } default -> throw VMError.shouldNotReachHereAtRuntime(); @@ -1299,9 +1297,7 @@ private static void arrayStore(InterpreterFrame frame, MethodProfile methodProfi case DASTORE -> InterpreterToVM.setArrayDouble(popDouble(frame, top - 1), index, (double[]) array); case AASTORE -> { Object o = popObject(frame, top - 1); - if (methodProfile != null) { - methodProfile.profileReceiver(bci, o); - } + profileType(methodProfile, bci, o); InterpreterToVM.setArrayObject(o, index, (Object[]) array); } default -> throw VMError.shouldNotReachHereAtRuntime(); @@ -1498,11 +1494,9 @@ private static int invoke(InterpreterFrame callerFrame, MethodProfile methodProf Object[] calleeArgs = EspressoFrame.popArguments(callerFrame, invokeTop, hasReceiver, seedSignature); if (!seedMethod.isStatic()) { - nullCheck(calleeArgs[0]); - } - if (methodProfile != null) { - Object receiver = calleeArgs[0]; - methodProfile.profileReceiver(curBCI, receiver); + final Object receiver = calleeArgs[0]; + profileType(methodProfile, curBCI, receiver); + nullCheck(receiver); } Object retObj = InterpreterToVM.dispatchInvocation(seedMethod, calleeArgs, isVirtual, forceStayInInterpreter, preferStayInInterpreter, opcode == INVOKEINTERFACE, false); From 534d182debd88574659ab7d3b3abeed71c30420a Mon Sep 17 00:00:00 2001 From: David Leopoldseder Date: Tue, 9 Dec 2025 15:57:16 +0100 Subject: [PATCH 08/19] ristretto: checkstyle fixes --- .../svm/interpreter/metadata/profile/MethodProfile.java | 9 +++++---- .../src/com/oracle/svm/interpreter/Interpreter.java | 4 ++-- 2 files changed, 7 insertions(+), 6 deletions(-) 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 3004cce96f7b..4591cbe933e0 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 @@ -369,6 +369,7 @@ private void addTypeAndIncrement(ResolvedJavaType type) { public String toString() { StringBuilder sb = new StringBuilder(128); sb.append("{TypeProfile:bci=").append(bci).append(", counter=").append(counter); + sb.append(", state=").append(state); int limit = Math.min(nextFreeSlot, types.length); sb.append(", types=["); for (int i = 0; i < limit; i++) { @@ -393,11 +394,11 @@ public String toString() { } public JavaTypeProfile toTypeProfile() { - JavaTypeProfile.ProfiledType[] types = new JavaTypeProfile.ProfiledType[this.types.length]; - for (int i = 0; i < types.length; i++) { - types[i] = new JavaTypeProfile.ProfiledType(this.types[i], getProbability(this.types[i])); + JavaTypeProfile.ProfiledType[] jTypes = new JavaTypeProfile.ProfiledType[this.types.length]; + for (int i = 0; i < jTypes.length; i++) { + jTypes[i] = new JavaTypeProfile.ProfiledType(this.types[i], getProbability(this.types[i])); } - return new JavaTypeProfile(TriState.UNKNOWN, notRecordedProbability(), types); + return new JavaTypeProfile(TriState.UNKNOWN, notRecordedProbability(), jTypes); } } } 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 611487d7adb3..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,methodProfile,curBCI, 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,methodProfile,curBCI, top, curOpcode); break; + case SASTORE: arrayStore(frame, methodProfile, curBCI, top, curOpcode); break; case POP2: clear(frame, top - 1); From af4c0009259c1e0c816294cae7d78e9cf286fa51 Mon Sep 17 00:00:00 2001 From: David Leopoldseder Date: Tue, 9 Dec 2025 17:52:17 +0100 Subject: [PATCH 09/19] ristretto: type profile fixes --- .../metadata/profile/MethodProfile.java | 34 ++++++++++++++----- .../profile/RistrettoProfilingInfo.java | 10 +++++- 2 files changed, 35 insertions(+), 9 deletions(-) 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 4591cbe933e0..fcbc98aa816c 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,6 +27,7 @@ import static jdk.graal.compiler.bytecode.Bytecodes.END; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.concurrent.atomic.AtomicLongFieldUpdater; @@ -69,7 +70,7 @@ public final class MethodProfile { * 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 volatile int lastIndex; private final ResolvedJavaMethod method; @@ -252,14 +253,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(); } @@ -298,11 +299,18 @@ public TypeProfile(int bci) { } public double notRecordedProbability() { + if (counter == 0L) { + return -1D; + } return (double) notRecordedCount / (double) counter; } public double getProbability(ResolvedJavaType type) { - for (int i = 0; i < types.length; i++) { + if (counter == 0L) { + // no entry yet seen + return -1D; + } + for (int i = 0; i < nextFreeSlot; i++) { ResolvedJavaType t = types[i]; if (t.equals(type)) { return (double) counts[i] / (double) counter; @@ -394,11 +402,21 @@ public String toString() { } public JavaTypeProfile toTypeProfile() { - JavaTypeProfile.ProfiledType[] jTypes = new JavaTypeProfile.ProfiledType[this.types.length]; - for (int i = 0; i < jTypes.length; i++) { - jTypes[i] = new JavaTypeProfile.ProfiledType(this.types[i], getProbability(this.types[i])); + if (nextFreeSlot == 0L || counter == 0L) { + // nothing recorded + return null; + } + // taken from HotSpotMethodData.java#createTypeProfile - sync any bug fixes there + JavaTypeProfile.ProfiledType[] ptypes = new JavaTypeProfile.ProfiledType[nextFreeSlot]; + for (int i = 0; i < nextFreeSlot; i++) { + double p = counts[i]; + p = p / counter; + ptypes[i] = new JavaTypeProfile.ProfiledType(types[i], p); } - return new JavaTypeProfile(TriState.UNKNOWN, notRecordedProbability(), jTypes); + Arrays.sort(ptypes); + double notRecordedTypeProbability = nextFreeSlot < types.length ? 0.0 : Math.min(1.0, Math.max(0.0, notRecordedProbability())); + assert notRecordedTypeProbability == 0 || nextFreeSlot == types.length; + return new JavaTypeProfile(TriState.UNKNOWN, notRecordedTypeProbability, ptypes); } } } 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 f03751536d95..e4faa97400df 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 @@ -51,7 +51,15 @@ public int getCodeSize() { @Override public JavaTypeProfile getTypeProfile(int bci) { - return methodProfile.getTypeProfile(bci); + final var typeProfile = methodProfile.getTypeProfile(bci); + if (Assertions.assertionsEnabled()) { + 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 From 31c368f03e4a74776709dd7efff230ca57b74e45 Mon Sep 17 00:00:00 2001 From: David Leopoldseder Date: Tue, 9 Dec 2025 18:01:06 +0100 Subject: [PATCH 10/19] ristretto: style fixes --- .../svm/interpreter/metadata/profile/MethodProfile.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 fcbc98aa816c..4e27d1f94aa8 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 @@ -151,7 +151,7 @@ public double getBranchTakenProbability(int bci) { } public void profileType(int bci, ResolvedJavaType type) { - ((TypeProfile) getAtBCI(bci, TypeProfile.class)).incrementType(type); + ((TypeProfile) getAtBCI(bci, TypeProfile.class)).incrementTypeProfile(type); } public void profileReceiver(int bci, Object receiver) { @@ -273,7 +273,7 @@ public String toString() { public static class TypeProfile extends CountingProfile { /** - * All types that are profiled per type. + * All types profiled. */ private final ResolvedJavaType[] types; @@ -319,7 +319,7 @@ public double getProbability(ResolvedJavaType type) { return -1; } - public void incrementType(ResolvedJavaType type) { + public void incrementTypeProfile(ResolvedJavaType type) { counter++; // check if the type was already recorded, in which case we update the counts in a racy // fashion From d3728f11a309a09a27ec790d82dc568e41e26afc Mon Sep 17 00:00:00 2001 From: David Leopoldseder Date: Wed, 10 Dec 2025 12:04:06 +0100 Subject: [PATCH 11/19] ristretto: refactorings --- .../metadata/profile/MethodProfile.java | 127 +++++++++++++----- 1 file changed, 96 insertions(+), 31 deletions(-) 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 4e27d1f94aa8..f3244cd5356c 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 @@ -202,6 +202,23 @@ public static List profilesAtBCI(MethodProfile methodProfile } } + @Override + public String toString() { + StringBuilder sb = new StringBuilder(256); + sb.append("MethodProfile{method=").append(method.toString()); + sb.append(", mature=").append(isMature); + sb.append(", profileCount=").append(profiles.length); + sb.append(", profiles=["); + for (int i = 0; i < profiles.length; i++) { + if (i > 0) { + sb.append(", "); + } + sb.append(profiles[i]); + } + sb.append("]}"); + return sb.toString(); + } + public abstract static class InterpreterProfile { protected final int bci; @@ -235,6 +252,10 @@ public String toString() { } } + /** + * Abstraction for a binary branch profile for bytecode if instructions. In compiled code + * normally represented by {@link jdk.graal.compiler.nodes.IfNode}. + */ public static class BranchProfile extends CountingProfile { private long takenCounter; @@ -271,9 +292,20 @@ 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 compiled + * code 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 AtomicLongFieldUpdater PROFILING_STATE_UPDATER = AtomicLongFieldUpdater.newUpdater(TypeProfile.class, "state"); + /** - * All types profiled. + * List of profiled types. Initially allocated to a fixed size and filled lazily during + * profiling. */ private final ResolvedJavaType[] types; @@ -282,15 +314,31 @@ public static class TypeProfile extends CountingProfile { * seen during profiling. */ private final long[] counts; + + /** + * Number of times a type was seen that could not fit into {@link #types}. + */ private long notRecordedCount; + + /** + * Next free index to use when lazily filling {@link #types}. {@link #typesSaturated()} + * determines if there is space for additional types. + */ private volatile int nextFreeSlot; // value of state is 0 if the state is READING, else it holds the id of the currently // writing thread + /** + * Synchronization mechanism for lazily filling types. If {@link #typesSaturated()} returns + * {@code false} interpreter threads can add additional types to the profile. This + * potentially happens concurrently with multiple interpreter threads. In order to avoid + * over writing values and heavy synchronization we use CAS on the state field here to + * synchronize accesses. If {@code state==0} then no thread is concurrently updating the + * type list. If {@code state!=0} then it holds a threadID of the thread currently adding a + * type to the profile. + */ private volatile long state; - public static final AtomicLongFieldUpdater PROFILING_STATE_UPDATER = AtomicLongFieldUpdater.newUpdater(TypeProfile.class, "state"); - public TypeProfile(int bci) { super(bci); final int typeProfileWidth = InterpreterProfilingOptions.JITProfileTypeProfileWidth.getValue(); @@ -298,6 +346,9 @@ public TypeProfile(int bci) { counts = new long[typeProfileWidth]; } + /** + * See {@link JavaTypeProfile#getNotRecordedProbability()}. + */ public double notRecordedProbability() { if (counter == 0L) { return -1D; @@ -305,9 +356,12 @@ public double notRecordedProbability() { return (double) notRecordedCount / (double) counter; } + /** + * Returns the probability of a given type in this profile. + */ public double getProbability(ResolvedJavaType type) { if (counter == 0L) { - // no entry yet seen + // no type profiled yet return -1D; } for (int i = 0; i < nextFreeSlot; i++) { @@ -329,20 +383,28 @@ public void incrementTypeProfile(ResolvedJavaType type) { return; } } - if (nextFreeSlot >= types.length) { + if (typesSaturated()) { // all types saturated, racy update to notRecorded notRecordedCount++; return; } - // type was not seen and we have space, "lock" and increment + // type was not seen, and we have space, "lock" and increment addTypeAndIncrement(type); } + /** + * Determine if there is still space to add more types to this profile. + */ + private boolean typesSaturated() { + return nextFreeSlot >= types.length; + } + @SuppressWarnings("finally") private void addTypeAndIncrement(ResolvedJavaType type) { final long currentThreadId = Thread.currentThread().threadId(); assert currentThreadId != 0L : Assertions.errorMessage("ThreadID must never be 0", currentThreadId); - // we are adding a new type to the profile, we have to perform this under a heavy lock + // we are adding a new type to the profile, we have to perform this under some kind of + // lock, we cas on a volatile field while (true) { if (!PROFILING_STATE_UPDATER.compareAndSet(this, 0, currentThreadId)) { // try to acquire the state lock, spin if this fails until we are allowed to @@ -354,16 +416,19 @@ private void addTypeAndIncrement(ResolvedJavaType type) { } try { /* - * in the meantime its possible another thread already saturated the list of - * profiles, in this case we have to add to the remaining ones + * in the meantime (while waiting in the spin loop above) its possible another + * thread already saturated the list of profiles, in this case we have to add to the + * notRecordedCount */ - if (nextFreeSlot >= types.length) { + if (typesSaturated()) { notRecordedCount++; return; } - types[nextFreeSlot] = type; - counts[nextFreeSlot]++; - nextFreeSlot = nextFreeSlot + 1; + // free space, add the new type + final int profileSlot = nextFreeSlot; + types[profileSlot] = type; + counts[profileSlot]++; + nextFreeSlot = profileSlot + 1; } finally { if (PROFILING_STATE_UPDATER.compareAndSet(this, currentThreadId, 0)) { return; @@ -373,6 +438,24 @@ private void addTypeAndIncrement(ResolvedJavaType type) { } } + public JavaTypeProfile toTypeProfile() { + if (nextFreeSlot == 0L || counter == 0L) { + // nothing recorded + return null; + } + // taken from HotSpotMethodData.java#createTypeProfile - sync any bug fixes there + JavaTypeProfile.ProfiledType[] ptypes = new JavaTypeProfile.ProfiledType[nextFreeSlot]; + for (int i = 0; i < nextFreeSlot; i++) { + double p = counts[i]; + p = p / counter; + ptypes[i] = new JavaTypeProfile.ProfiledType(types[i], p); + } + Arrays.sort(ptypes); + double notRecordedTypeProbability = nextFreeSlot < types.length ? 0.0 : Math.min(1.0, Math.max(0.0, notRecordedProbability())); + assert notRecordedTypeProbability == 0 || nextFreeSlot == types.length; + return new JavaTypeProfile(TriState.UNKNOWN, notRecordedTypeProbability, ptypes); + } + @Override public String toString() { StringBuilder sb = new StringBuilder(128); @@ -400,23 +483,5 @@ public String toString() { sb.append('}'); return sb.toString(); } - - public JavaTypeProfile toTypeProfile() { - if (nextFreeSlot == 0L || counter == 0L) { - // nothing recorded - return null; - } - // taken from HotSpotMethodData.java#createTypeProfile - sync any bug fixes there - JavaTypeProfile.ProfiledType[] ptypes = new JavaTypeProfile.ProfiledType[nextFreeSlot]; - for (int i = 0; i < nextFreeSlot; i++) { - double p = counts[i]; - p = p / counter; - ptypes[i] = new JavaTypeProfile.ProfiledType(types[i], p); - } - Arrays.sort(ptypes); - double notRecordedTypeProbability = nextFreeSlot < types.length ? 0.0 : Math.min(1.0, Math.max(0.0, notRecordedProbability())); - assert notRecordedTypeProbability == 0 || nextFreeSlot == types.length; - return new JavaTypeProfile(TriState.UNKNOWN, notRecordedTypeProbability, ptypes); - } } } From 4f33f41c079dc3f794b5d4eb2f492a356c65cfde Mon Sep 17 00:00:00 2001 From: David Leopoldseder Date: Wed, 10 Dec 2025 12:05:30 +0100 Subject: [PATCH 12/19] ristretto: use vmerror --- .../svm/interpreter/metadata/profile/MethodProfile.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 f3244cd5356c..2e9e04c42dc6 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 @@ -32,11 +32,11 @@ import java.util.concurrent.atomic.AtomicLongFieldUpdater; import com.oracle.svm.core.hub.DynamicHub; +import com.oracle.svm.core.util.VMError; import com.oracle.svm.interpreter.metadata.Bytecodes; import jdk.graal.compiler.bytecode.BytecodeStream; import jdk.graal.compiler.debug.Assertions; -import jdk.graal.compiler.debug.GraalError; import jdk.graal.compiler.nodes.PauseNode; import jdk.vm.ci.meta.JavaTypeProfile; import jdk.vm.ci.meta.ProfilingInfo; @@ -433,7 +433,7 @@ private void addTypeAndIncrement(ResolvedJavaType type) { if (PROFILING_STATE_UPDATER.compareAndSet(this, currentThreadId, 0)) { return; } else { - throw GraalError.shouldNotReachHere("Must always be able to set back threadID lock to 0 after profile update"); + throw VMError.shouldNotReachHere("Must always be able to set back threadID lock to 0 after profile update"); } } } From 4d56e43930f7c1fa6a3f2c4b06b405fcc04a041e Mon Sep 17 00:00:00 2001 From: David Leopoldseder Date: Wed, 10 Dec 2025 13:48:17 +0100 Subject: [PATCH 13/19] ristretto: type profiles concurrency fixes --- .../metadata/profile/MethodProfile.java | 151 +++++++++++------- 1 file changed, 94 insertions(+), 57 deletions(-) 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 2e9e04c42dc6..14a054c426e2 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 @@ -45,25 +45,29 @@ 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. */ + private static final int JVMCI_METHOD_ENTRY_BCI = -1; /** - * Artificial byte code index for the method entry profile. + * All profiles for the current method. Includes branch profiles, type profiles, profiles for + * exceptions etc. */ - private static final int JVMCI_METHOD_ENTRY_BCI = -1; - private final InterpreterProfile[] profiles; /** @@ -84,7 +88,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)); @@ -92,6 +95,7 @@ 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)); @@ -102,6 +106,7 @@ private static InterpreterProfile[] buildProfiles(ResolvedJavaMethod method) { // TODO GR-71799 - backedge / goto profiles stream.next(); } + return allProfiles.toArray(new InterpreterProfile[0]); } @@ -114,7 +119,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 */ @@ -156,9 +161,7 @@ public void profileType(int bci, ResolvedJavaType type) { public void profileReceiver(int bci, Object receiver) { if (receiver == null) { - /* - * TODO GR-71949 - profile nullSeen - */ + // TODO GR-71949 - profile nullSeen return; } ResolvedJavaType type = DynamicHub.fromClass(receiver.getClass()).getInterpreterType(); @@ -167,7 +170,7 @@ public void profileReceiver(int bci, Object receiver) { /** * 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) { @@ -202,23 +205,10 @@ public static List profilesAtBCI(MethodProfile methodProfile } } - @Override - public String toString() { - StringBuilder sb = new StringBuilder(256); - sb.append("MethodProfile{method=").append(method.toString()); - sb.append(", mature=").append(isMature); - sb.append(", profileCount=").append(profiles.length); - sb.append(", profiles=["); - for (int i = 0; i < profiles.length; i++) { - if (i > 0) { - sb.append(", "); - } - sb.append(profiles[i]); - } - sb.append("]}"); - return sb.toString(); - } - + /** + * 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; @@ -231,6 +221,9 @@ public int getBci() { } } + /** + * Abstraction for counting profiles, i.e., profiles that record a frequency. + */ public static class CountingProfile extends InterpreterProfile { protected long counter; @@ -294,8 +287,10 @@ 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 compiled + * checks, etc. + *

+ * Abstraction is generic to record any set of types together with their count and a + * {@link JavaTypeProfile#getNotRecordedProbability() not recorded probability}. In compiled * code normally represented as a {@link JavaTypeProfile} attached to a * {@link jdk.graal.compiler.nodes.java.MethodCallTargetNode}. */ @@ -326,16 +321,33 @@ public static class TypeProfile extends CountingProfile { */ private volatile int nextFreeSlot; - // value of state is 0 if the state is READING, else it holds the id of the currently - // writing thread /** - * Synchronization mechanism for lazily filling types. If {@link #typesSaturated()} returns - * {@code false} interpreter threads can add additional types to the profile. This - * potentially happens concurrently with multiple interpreter threads. In order to avoid - * over writing values and heavy synchronization we use CAS on the state field here to - * synchronize accesses. If {@code state==0} then no thread is concurrently updating the - * type list. If {@code state!=0} then it holds a threadID of the thread currently adding a - * type to the profile. + * Synchronization mechanism for lazily populating the types array. + * + *

+ * When {@link #typesSaturated()} returns false, interpreter threads may add + * new types concurrently. To avoid heavyweight locking and accidental overwrites, updates + * are coordinated via a CAS on the state field. + * + *

+ * Semantics: + * + *

    + *
  • state == 0: no thread is currently writing (READING state).
  • + *
  • state != 0: holds the thread ID of the writer currently updating the + * profile (WRITING state).
  • + *
+ *

+ * Protocol: + * + *

    + *
  • Writers spin until they successfully CAS state from 0 to + * their thread ID.
  • + *
  • Once acquired, a writer may check capacity, insert a new type, and update + * counters/indexes safely.
  • + *
  • Writers must always release by CAS-ing state back to 0, + * allowing subsequent writers to proceed.
  • + *
*/ private volatile long state; @@ -375,13 +387,12 @@ public double getProbability(ResolvedJavaType type) { public void incrementTypeProfile(ResolvedJavaType type) { counter++; - // check if the type was already recorded, in which case we update the counts in a racy - // fashion - for (int i = 0; i < types.length && i < nextFreeSlot; i++) { - if (types[i].equals(type)) { - counts[i]++; - return; - } + /* + * check if the type was already recorded, in which case we update the counts in a racy + * fashion + */ + if (findAndProfileType(type, nextFreeSlot)) { + return; } if (typesSaturated()) { // all types saturated, racy update to notRecorded @@ -392,6 +403,16 @@ public void incrementTypeProfile(ResolvedJavaType type) { addTypeAndIncrement(type); } + private boolean findAndProfileType(ResolvedJavaType type, int currentNextFreeSlot) { + for (int i = 0; i < types.length && i < currentNextFreeSlot; i++) { + if (types[i] != null && types[i].equals(type)) { + counts[i]++; + return true; + } + } + return false; + } + /** * Determine if there is still space to add more types to this profile. */ @@ -403,8 +424,10 @@ private boolean typesSaturated() { private void addTypeAndIncrement(ResolvedJavaType type) { final long currentThreadId = Thread.currentThread().threadId(); assert currentThreadId != 0L : Assertions.errorMessage("ThreadID must never be 0", currentThreadId); - // we are adding a new type to the profile, we have to perform this under some kind of - // lock, we cas on a volatile field + /* + * we are adding a new type to the profile, we have to perform this under some kind of + * lock, we cas on a volatile field + */ while (true) { if (!PROFILING_STATE_UPDATER.compareAndSet(this, 0, currentThreadId)) { // try to acquire the state lock, spin if this fails until we are allowed to @@ -414,9 +437,10 @@ private void addTypeAndIncrement(ResolvedJavaType type) { break; } } + try { /* - * in the meantime (while waiting in the spin loop above) its possible another + * in the meantime (while waiting in the spin loop above) it's possible another * thread already saturated the list of profiles, in this case we have to add to the * notRecordedCount */ @@ -424,11 +448,19 @@ private void addTypeAndIncrement(ResolvedJavaType type) { notRecordedCount++; return; } - // free space, add the new type + final int profileSlot = nextFreeSlot; + + // check if another thread in the meantime added the same type while we waited + if (findAndProfileType(type, profileSlot)) { + return; + } + + // we have free space -> add the new type types[profileSlot] = type; counts[profileSlot]++; nextFreeSlot = profileSlot + 1; + } finally { if (PROFILING_STATE_UPDATER.compareAndSet(this, currentThreadId, 0)) { return; @@ -439,20 +471,21 @@ private void addTypeAndIncrement(ResolvedJavaType type) { } public JavaTypeProfile toTypeProfile() { - if (nextFreeSlot == 0L || counter == 0L) { + final int freeSlot = nextFreeSlot; + if (freeSlot == 0 || counter == 0L) { // nothing recorded return null; } // taken from HotSpotMethodData.java#createTypeProfile - sync any bug fixes there - JavaTypeProfile.ProfiledType[] ptypes = new JavaTypeProfile.ProfiledType[nextFreeSlot]; - for (int i = 0; i < nextFreeSlot; i++) { + JavaTypeProfile.ProfiledType[] ptypes = new JavaTypeProfile.ProfiledType[freeSlot]; + for (int i = 0; i < freeSlot; i++) { double p = counts[i]; p = p / counter; ptypes[i] = new JavaTypeProfile.ProfiledType(types[i], p); } Arrays.sort(ptypes); - double notRecordedTypeProbability = nextFreeSlot < types.length ? 0.0 : Math.min(1.0, Math.max(0.0, notRecordedProbability())); - assert notRecordedTypeProbability == 0 || nextFreeSlot == types.length; + double notRecordedTypeProbability = freeSlot < types.length ? 0.0 : Math.min(1.0, Math.max(0.0, notRecordedProbability())); + assert notRecordedTypeProbability == 0.0 || freeSlot == types.length; return new JavaTypeProfile(TriState.UNKNOWN, notRecordedTypeProbability, ptypes); } @@ -469,7 +502,11 @@ public String toString() { } ResolvedJavaType t = types[i]; long c = counts[i]; - sb.append(t.getName()).append(':').append(c); + if (t != null) { + sb.append(t.getName()).append(':').append(c); + } else { + sb.append("null:").append(c); + } } sb.append(']'); if (limit >= types.length) { From f28060c61553c918fc66c8491d946296b26b2309 Mon Sep 17 00:00:00 2001 From: David Leopoldseder Date: Wed, 10 Dec 2025 15:11:37 +0100 Subject: [PATCH 14/19] ristretto: add missing null check --- .../interpreter/ristretto/profile/RistrettoProfilingInfo.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 e4faa97400df..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 @@ -52,7 +52,7 @@ public int getCodeSize() { @Override public JavaTypeProfile getTypeProfile(int bci) { final var typeProfile = methodProfile.getTypeProfile(bci); - if (Assertions.assertionsEnabled()) { + 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(), From 39bc6d16cf2c0f891c57c274922e48a1fef58b36 Mon Sep 17 00:00:00 2001 From: David Leopoldseder Date: Fri, 12 Dec 2025 12:59:51 +0100 Subject: [PATCH 15/19] ristretto: address review comments --- .../metadata/profile/MethodProfile.java | 238 +++++++----------- .../ristretto/meta/RistrettoConstantPool.java | 12 +- .../ristretto/meta/RistrettoType.java | 4 +- 3 files changed, 102 insertions(+), 152 deletions(-) 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 14a054c426e2..4dddb20ca34c 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 @@ -29,15 +29,13 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.concurrent.atomic.AtomicLongFieldUpdater; import com.oracle.svm.core.hub.DynamicHub; -import com.oracle.svm.core.util.VMError; import com.oracle.svm.interpreter.metadata.Bytecodes; import jdk.graal.compiler.bytecode.BytecodeStream; -import jdk.graal.compiler.debug.Assertions; -import jdk.graal.compiler.nodes.PauseNode; +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; @@ -61,6 +59,8 @@ *

*/ public final class MethodProfile { + private static final Unsafe UNSAFE = Unsafe.getUnsafe(); + /** Artificial byte code index for the method entry profile. */ private static final int JVMCI_METHOD_ENTRY_BCI = -1; @@ -72,9 +72,13 @@ public final class MethodProfile { /** * 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 its a 32 bit field an on all platforms SVM supports + * 32 writes are always atomic. */ - private volatile int lastIndex; + private int lastIndex; private final ResolvedJavaMethod method; @@ -246,8 +250,8 @@ public String toString() { } /** - * Abstraction for a binary branch profile for bytecode if instructions. In compiled code - * normally represented by {@link jdk.graal.compiler.nodes.IfNode}. + * 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; @@ -290,13 +294,15 @@ public String toString() { * checks, etc. *

* Abstraction is generic to record any set of types together with their count and a - * {@link JavaTypeProfile#getNotRecordedProbability() not recorded probability}. In compiled - * code normally represented as a {@link JavaTypeProfile} attached to 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 AtomicLongFieldUpdater PROFILING_STATE_UPDATER = AtomicLongFieldUpdater.newUpdater(TypeProfile.class, "state"); + 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 @@ -310,47 +316,6 @@ public static class TypeProfile extends CountingProfile { */ private final long[] counts; - /** - * Number of times a type was seen that could not fit into {@link #types}. - */ - private long notRecordedCount; - - /** - * Next free index to use when lazily filling {@link #types}. {@link #typesSaturated()} - * determines if there is space for additional types. - */ - private volatile int nextFreeSlot; - - /** - * Synchronization mechanism for lazily populating the types array. - * - *

- * When {@link #typesSaturated()} returns false, interpreter threads may add - * new types concurrently. To avoid heavyweight locking and accidental overwrites, updates - * are coordinated via a CAS on the state field. - * - *

- * Semantics: - * - *

    - *
  • state == 0: no thread is currently writing (READING state).
  • - *
  • state != 0: holds the thread ID of the writer currently updating the - * profile (WRITING state).
  • - *
- *

- * Protocol: - * - *

    - *
  • Writers spin until they successfully CAS state from 0 to - * their thread ID.
  • - *
  • Once acquired, a writer may check capacity, insert a new type, and update - * counters/indexes safely.
  • - *
  • Writers must always release by CAS-ing state back to 0, - * allowing subsequent writers to proceed.
  • - *
- */ - private volatile long state; - public TypeProfile(int bci) { super(bci); final int typeProfileWidth = InterpreterProfilingOptions.JITProfileTypeProfileWidth.getValue(); @@ -360,12 +325,24 @@ public TypeProfile(int bci) { /** * See {@link JavaTypeProfile#getNotRecordedProbability()}. + * + * Do not use from performance sensitive code, computes the notRecordedProbability via + * {@link #toTypeProfile()} . */ public double notRecordedProbability() { - if (counter == 0L) { - return -1D; + 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 (double) notRecordedCount / (double) counter; + return types.length; } /** @@ -376,7 +353,7 @@ public double getProbability(ResolvedJavaType type) { // no type profiled yet return -1D; } - for (int i = 0; i < nextFreeSlot; i++) { + for (int i = 0; i < getProfiledTypeCount(); i++) { ResolvedJavaType t = types[i]; if (t.equals(type)) { return (double) counts[i] / (double) counter; @@ -385,107 +362,81 @@ public double getProbability(ResolvedJavaType type) { return -1; } - public void incrementTypeProfile(ResolvedJavaType type) { - counter++; - /* - * check if the type was already recorded, in which case we update the counts in a racy - * fashion - */ - if (findAndProfileType(type, nextFreeSlot)) { - return; - } - if (typesSaturated()) { - // all types saturated, racy update to notRecorded - notRecordedCount++; - return; - } - // type was not seen, and we have space, "lock" and increment - addTypeAndIncrement(type); - } - - private boolean findAndProfileType(ResolvedJavaType type, int currentNextFreeSlot) { - for (int i = 0; i < types.length && i < currentNextFreeSlot; i++) { - if (types[i] != null && types[i].equals(type)) { - counts[i]++; - return true; - } - } - return false; - } - /** - * Determine if there is still space to add more types to this profile. + * 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 (sequentialzied 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. */ - private boolean typesSaturated() { - return nextFreeSlot >= types.length; - } - - @SuppressWarnings("finally") - private void addTypeAndIncrement(ResolvedJavaType type) { - final long currentThreadId = Thread.currentThread().threadId(); - assert currentThreadId != 0L : Assertions.errorMessage("ThreadID must never be 0", currentThreadId); - /* - * we are adding a new type to the profile, we have to perform this under some kind of - * lock, we cas on a volatile field - */ - while (true) { - if (!PROFILING_STATE_UPDATER.compareAndSet(this, 0, currentThreadId)) { - // try to acquire the state lock, spin if this fails until we are allowed to - PauseNode.pause(); - } else { - // we got the "lock" to write the profile - break; - } - } - - try { - /* - * in the meantime (while waiting in the spin loop above) it's possible another - * thread already saturated the list of profiles, in this case we have to add to the - * notRecordedCount - */ - if (typesSaturated()) { - notRecordedCount++; - return; - } - - final int profileSlot = nextFreeSlot; - - // check if another thread in the meantime added the same type while we waited - if (findAndProfileType(type, profileSlot)) { - return; - } - - // we have free space -> add the new type - types[profileSlot] = type; - counts[profileSlot]++; - nextFreeSlot = profileSlot + 1; - - } finally { - if (PROFILING_STATE_UPDATER.compareAndSet(this, currentThreadId, 0)) { - return; + public void incrementTypeProfile(ResolvedJavaType type) { + for (int i = 0; i < types.length; i++) { + ResolvedJavaType slotType = types[i]; + if (slotType != null) { + if (slotType.equals(type)) { + // racily update counts and counter + counts[i]++; + counter++; + return; + } + // type not found, continue iterating with the next slot } else { - throw VMError.shouldNotReachHere("Must always be able to set back threadID lock to 0 after profile update"); + /* + * Free slot found, and we have not found the type in the ifs above. Try to + * "claim" this slot for the current type. If the CAS succeeds increment it, + * else give up and try the next free slot. + */ + final int freeSlot = i; + final long offset = TYPE_ARRAY_BASE + (long) freeSlot * TYPE_ARRAY_SHIFT; + if (UNSAFE.compareAndSetReference(types, offset, null, type)) { + // success, we recorded this type + counts[freeSlot]++; + counter++; + return; + } else { + /* + * Some other thread wrote this slot in the meantime. We cannot just re-do + * the iteration and try freeSlot + 1 because the concurrently written type + * can be quals to type and we do not want to record the same type with 2 + * slots in a profile. We also cannot check freeSlot + 1 because multiple + * slots might have been claimed in the meantime. We just call + * incrementTypeProfile again, either we find our type because it was + * already added or we try to claim one again. Note that this recursion is + * bound by the type profile width. + */ + incrementTypeProfile(type); + return; + } } } + // we tried to record this type but lost, update the counter only to increment a + // notRecordedProbability + counter++; } public JavaTypeProfile toTypeProfile() { - final int freeSlot = nextFreeSlot; - if (freeSlot == 0 || counter == 0L) { + 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[freeSlot]; - for (int i = 0; i < freeSlot; i++) { + 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 = freeSlot < types.length ? 0.0 : Math.min(1.0, Math.max(0.0, notRecordedProbability())); - assert notRecordedTypeProbability == 0.0 || freeSlot == types.length; + 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); } @@ -493,8 +444,7 @@ public JavaTypeProfile toTypeProfile() { public String toString() { StringBuilder sb = new StringBuilder(128); sb.append("{TypeProfile:bci=").append(bci).append(", counter=").append(counter); - sb.append(", state=").append(state); - int limit = Math.min(nextFreeSlot, types.length); + int limit = Math.min(getProfiledTypeCount(), types.length); sb.append(", types=["); for (int i = 0; i < limit; i++) { if (i > 0) { @@ -514,9 +464,7 @@ public String toString() { } else { sb.append(", freeSlots=").append(types.length - limit); } - if (notRecordedCount > 0) { - sb.append(", notRecorded=").append(notRecordedCount); - } + sb.append(", notRecorded=").append(notRecordedProbability()); sb.append('}'); return sb.toString(); } 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 f4745751fad5..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 @@ -138,12 +138,14 @@ public Object lookupConstant(int cpi, boolean resolve) { @Override public JavaConstant lookupAppendix(int rawIndex, int opcode) { - if (opcode == Bytecodes.INVOKEVIRTUAL) { - // The parser has support for special runtimes that rewrite invokes of methods handles - // to static adapters. Crema does not do that. - return null; + if (opcode == Bytecodes.INVOKEDYNAMIC) { + return interpreterConstantPool.lookupAppendix(rawIndex, opcode); } - 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 9d1c07b59cc4..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 @@ -83,8 +83,8 @@ public boolean isArray() { @Override public boolean isLinked() { /* - * TODO GR-71851 - crema does not implement linking at the moment, so we assume all resolved - * (==loaded) types successfully linked as well + * 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; } From 78805a66db400350f9b74555669023172d1bacaf Mon Sep 17 00:00:00 2001 From: David Leopoldseder Date: Fri, 12 Dec 2025 15:43:07 +0100 Subject: [PATCH 16/19] ristretto: simplify incrementTypeProfile - contributed by christian.haeubl@oracle.com --- .../metadata/profile/MethodProfile.java | 58 ++++++------------- 1 file changed, 17 insertions(+), 41 deletions(-) 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 4dddb20ca34c..633e2d5a293c 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 @@ -59,7 +59,6 @@ *

*/ public final class MethodProfile { - private static final Unsafe UNSAFE = Unsafe.getUnsafe(); /** Artificial byte code index for the method entry profile. */ private static final int JVMCI_METHOD_ENTRY_BCI = -1; @@ -75,8 +74,8 @@ public final class MethodProfile { * 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 its a 32 bit field an on all platforms SVM supports - * 32 writes are always atomic. + * reads. Also tearing cannot happen because it is a 32 bit field and such writes are atomic on + * all supported platforms. */ private int lastIndex; @@ -300,6 +299,8 @@ public String toString() { */ 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); @@ -369,52 +370,27 @@ public double getProbability(ResolvedJavaType type) { * 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 (sequentialzied via + * 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) { - if (slotType.equals(type)) { - // racily update counts and counter - counts[i]++; - counter++; - return; - } - // type not found, continue iterating with the next slot - } else { - /* - * Free slot found, and we have not found the type in the ifs above. Try to - * "claim" this slot for the current type. If the CAS succeeds increment it, - * else give up and try the next free slot. - */ - final int freeSlot = i; - final long offset = TYPE_ARRAY_BASE + (long) freeSlot * TYPE_ARRAY_SHIFT; - if (UNSAFE.compareAndSetReference(types, offset, null, type)) { - // success, we recorded this type - counts[freeSlot]++; - counter++; - return; - } else { - /* - * Some other thread wrote this slot in the meantime. We cannot just re-do - * the iteration and try freeSlot + 1 because the concurrently written type - * can be quals to type and we do not want to record the same type with 2 - * slots in a profile. We also cannot check freeSlot + 1 because multiple - * slots might have been claimed in the meantime. We just call - * incrementTypeProfile again, either we find our type because it was - * already added or we try to claim one again. Note that this recursion is - * bound by the type profile width. - */ - incrementTypeProfile(type); - return; - } + if (slotType == null) { + /* Try to "claim" this slot for the current type. */ + long offset = TYPE_ARRAY_BASE + ((long) i) * TYPE_ARRAY_SHIFT; + slotType = (ResolvedJavaType) UNSAFE.compareAndExchangeReference(types, offset, null, type); + } + + if (slotType.equals(type)) { + /* Either the CAS succeeded or another thread wrote the same type already. */ + counts[i]++; + break; } } - // we tried to record this type but lost, update the counter only to increment a - // notRecordedProbability + + /* Always update the total count, even if recording the type failed. */ counter++; } From cafebea142c14efdde0cec00d746037ed132ce7f Mon Sep 17 00:00:00 2001 From: David Leopoldseder Date: Fri, 12 Dec 2025 16:04:33 +0100 Subject: [PATCH 17/19] ristretto: fix npe after cas --- .../svm/interpreter/metadata/profile/MethodProfile.java | 4 ++++ 1 file changed, 4 insertions(+) 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 633e2d5a293c..3cadbe937625 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 @@ -381,6 +381,10 @@ public void incrementTypeProfile(ResolvedJavaType type) { /* Try to "claim" this slot for the current type. */ long offset = TYPE_ARRAY_BASE + ((long) i) * TYPE_ARRAY_SHIFT; slotType = (ResolvedJavaType) UNSAFE.compareAndExchangeReference(types, offset, null, type); + if (slotType == null) { + /* CAS succeeded. */ + slotType = type; + } } if (slotType.equals(type)) { From a90d79214db6bcf8faa7f82eb47a3dc14d21af9b Mon Sep 17 00:00:00 2001 From: David Leopoldseder Date: Fri, 12 Dec 2025 17:08:47 +0100 Subject: [PATCH 18/19] ristretto: style fix --- .../oracle/svm/interpreter/metadata/profile/MethodProfile.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 3cadbe937625..1520e1097bbf 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 @@ -379,7 +379,7 @@ public void incrementTypeProfile(ResolvedJavaType type) { ResolvedJavaType slotType = types[i]; if (slotType == null) { /* Try to "claim" this slot for the current type. */ - long offset = TYPE_ARRAY_BASE + ((long) i) * TYPE_ARRAY_SHIFT; + long offset = TYPE_ARRAY_BASE + i * TYPE_ARRAY_SHIFT; slotType = (ResolvedJavaType) UNSAFE.compareAndExchangeReference(types, offset, null, type); if (slotType == null) { /* CAS succeeded. */ From 811f5de6ee7de9914a5e124fbef78d0c46e071d9 Mon Sep 17 00:00:00 2001 From: David Leopoldseder Date: Mon, 15 Dec 2025 09:18:48 +0100 Subject: [PATCH 19/19] ristretto: use toClassName instead of getName --- .../oracle/svm/interpreter/metadata/profile/MethodProfile.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 1520e1097bbf..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 @@ -433,7 +433,7 @@ public String toString() { ResolvedJavaType t = types[i]; long c = counts[i]; if (t != null) { - sb.append(t.getName()).append(':').append(c); + sb.append(t.toClassName()).append(':').append(c); } else { sb.append("null:").append(c); }