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 extends InterpreterProfile> 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);
}