From d200a655347dfbe4ffca94f84201a06e7ed14133 Mon Sep 17 00:00:00 2001 From: asselyam Date: Wed, 29 Oct 2025 23:47:30 +0100 Subject: [PATCH 1/5] Implement native object handles feature --- .../svm/core/handles/ObjectHandlesImpl.java | 26 +----- .../oracle/svm/core/jni/JNIObjectHandles.java | 83 ++++++++++++++++--- 2 files changed, 72 insertions(+), 37 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/handles/ObjectHandlesImpl.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/handles/ObjectHandlesImpl.java index 6c5d6417dd45..6f9927636773 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/handles/ObjectHandlesImpl.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/handles/ObjectHandlesImpl.java @@ -38,10 +38,7 @@ * This class implements {@link ObjectHandle word}-sized integer handles that refer to Java objects. * {@link #create(Object) Creating}, {@link #get(ObjectHandle) dereferencing} and * {@link #destroy(ObjectHandle) destroying} handles is thread-safe and the handles themselves are - * valid across threads. This class also supports weak handles, with which the referenced object may - * be garbage-collected, after which {@link #get(ObjectHandle)} returns {@code null}. Still, weak - * handles must also be {@link #destroyWeak(ObjectHandle) explicitly destroyed} to reclaim their - * handle value. + * valid across threads. *

* The implementation uses a variable number of object arrays, in which each array element * represents a handle. The array element's index determines the handle's integer value, and the @@ -53,13 +50,6 @@ */ public final class ObjectHandlesImpl implements ObjectHandles { - /** Private subclass to distinguish from regular handles to {@link WeakReference} objects. */ - private static final class HandleWeakReference extends WeakReference { - HandleWeakReference(T referent) { - super(referent); - } - } - private static final int MAX_FIRST_BUCKET_CAPACITY = 1024; static { // must be a power of 2 for the arithmetic below to work assert Integer.lowestOneBit(MAX_FIRST_BUCKET_CAPACITY) == MAX_FIRST_BUCKET_CAPACITY; @@ -210,17 +200,10 @@ public ObjectHandle create(Object obj) { } } - public ObjectHandle createWeak(Object obj) { - return create(new HandleWeakReference<>(obj)); - } - @SuppressWarnings("unchecked") @Override public T get(ObjectHandle handle) { Object obj = doGet(handle); - if (obj instanceof HandleWeakReference) { - obj = ((HandleWeakReference) obj).get(); - } return (T) obj; } @@ -240,10 +223,6 @@ private Object doGet(ObjectHandle handle) { return Unsafe.getUnsafe().getReferenceVolatile(bucket, getObjectArrayByteOffset(indexInBucket)); } - public boolean isWeak(ObjectHandle handle) { - return (doGet(handle) instanceof HandleWeakReference); - } - @Override public void destroy(ObjectHandle handle) { if (handle.equal(nullHandle)) { @@ -261,9 +240,6 @@ public void destroy(ObjectHandle handle) { Unsafe.getUnsafe().putReferenceRelease(bucket, getObjectArrayByteOffset(indexInBucket), null); } - public void destroyWeak(ObjectHandle handle) { - destroy(handle); - } public long computeCurrentCount() { long count = 0; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/JNIObjectHandles.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/JNIObjectHandles.java index 91b39f6ec6eb..92910e55f4b0 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/JNIObjectHandles.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/JNIObjectHandles.java @@ -34,7 +34,6 @@ import com.oracle.svm.core.RuntimeAssertionsSupport; import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.Uninterruptible; -import com.oracle.svm.core.handles.ObjectHandlesImpl; import com.oracle.svm.core.handles.ThreadLocalHandles; import com.oracle.svm.core.heap.Heap; import com.oracle.svm.core.jni.headers.JNIObjectHandle; @@ -299,13 +298,29 @@ final class JNIGlobalHandles { assert JNIObjectHandles.nullHandle().equal(Word.zero()); } + // Define the mid-point to split the range in half + private static final SignedWord HANDLE_RANGE_SPLIT_POINT = Word.signed(1L << 30); + private static final int HANDLE_BITS_COUNT = 31; private static final SignedWord HANDLE_BITS_MASK = Word.signed((1L << HANDLE_BITS_COUNT) - 1); private static final int VALIDATION_BITS_SHIFT = HANDLE_BITS_COUNT; - private static final int VALIDATION_BITS_COUNT = 32; + private static final int VALIDATION_BITS_COUNT = 31; private static final SignedWord VALIDATION_BITS_MASK = Word.signed((1L << VALIDATION_BITS_COUNT) - 1).shiftLeft(VALIDATION_BITS_SHIFT); + private static final SignedWord WEAK_HANDLE_FLAG = Word.signed(1L << 62); private static final SignedWord MSB = Word.signed(1L << 63); - private static final ObjectHandlesImpl globalHandles = new ObjectHandlesImpl(JNIObjectHandles.nullHandle().add(1), HANDLE_BITS_MASK, JNIObjectHandles.nullHandle()); + + // Strong global handles will occupy the lower half of the global handles range + public static final SignedWord STRONG_GLOBAL_RANGE_MIN = JNIObjectHandles.nullHandle().add(1);; + public static final SignedWord STRONG_GLOBAL_RANGE_MAX = HANDLE_RANGE_SPLIT_POINT.subtract(1); + + // Weak global handles will occupy the upper half of the global handles range + public static final SignedWord WEAK_GLOBAL_RANGE_MIN = HANDLE_RANGE_SPLIT_POINT; + public static final SignedWord WEAK_GLOBAL_RANGE_MAX = HANDLE_BITS_MASK; + + private static final ObjectHandlesImpl strongGlobalHandles + = new ObjectHandlesImpl(STRONG_GLOBAL_RANGE_MIN, STRONG_GLOBAL_RANGE_MAX, JNIObjectHandles.nullHandle()); + private static final ObjectHandlesImpl weakGlobalHandles + = new ObjectHandlesImpl(WEAK_GLOBAL_RANGE_MIN, WEAK_GLOBAL_RANGE_MAX, JNIObjectHandles.nullHandle()); @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) static boolean isInRange(JNIObjectHandle handle) { @@ -317,6 +332,19 @@ private static Word isolateHash() { return Word.unsigned(isolateHash); } + /** + * Encodes a raw {@code ObjectHandle} into a strong {@code JNIObjectHandle}. + * * A strong handle guarantees the referenced object remains alive as long as + * the handle itself exists. + * * The handle is encoded by: + * 1. Asserting the handle fits within the available bit range. + * 2. Inserting validation bits (derived from the isolate hash) for security. + * 3. Setting the Most Significant Bit (MSB, bit 63) to mark it as an encoded handle. + * 4. The WEAK_HANDLE_FLAG bit (bit 62) remains 0. + * + * @param handle The raw, unencoded handle to the Java object. + * @return The resulting strong JNI object handle with embedded metadata. + */ private static JNIObjectHandle encode(ObjectHandle handle) { SignedWord h = (Word) handle; if (JNIObjectHandles.haveAssertions()) { @@ -330,6 +358,24 @@ private static JNIObjectHandle encode(ObjectHandle handle) { return (JNIObjectHandle) h; } + /** + * Encodes a raw {@code ObjectHandle} into a weak {@code JNIObjectHandle}. + * * A weak handle allows the referenced object to be garbage collected even + * if the handle exists. The handle will be cleared when the object dies. + * * This method calls {@link #encodeStrong(ObjectHandle)} to perform all + * common encoding steps, and then explicitly sets the {@code WEAK_HANDLE_FLAG} + * bit (bit 62) to mark the handle as weak. + * + * @param handle The raw, unencoded handle to the Java object. + * @return The resulting weak JNI object handle with embedded metadata. + */ + private static JNIObjectHandle encodeWeak(ObjectHandle handle) { + SignedWord h = (Word) encodeStrong(handle); + h = h.or(WEAK_HANDLE_FLAG); + assert isInRange((JNIObjectHandle) h); + return (JNIObjectHandle) h; + } + private static ObjectHandle decode(JNIObjectHandle handle) { assert isInRange(handle); assert ((Word) handle).and(VALIDATION_BITS_MASK).unsignedShiftRight(VALIDATION_BITS_SHIFT) @@ -338,35 +384,48 @@ private static ObjectHandle decode(JNIObjectHandle handle) { } static T getObject(JNIObjectHandle handle) { - return globalHandles.get(decode(handle)); + SignedWord handleValue = (Word) handle; + if ((handleValue.toLong() & WEAK_HANDLE_FLAG.toLong()) == 0) { + return strongGlobalHandles.get(decode(handle)); + } + + if ((handleValue.toLong() & WEAK_HANDLE_FLAG.toLong()) == 1) { + return weakGlobalHandles.get(decode((handle))); + } + + throw new IllegalArgumentException("Invalid handle"); } static JNIObjectRefType getHandleType(JNIObjectHandle handle) { - assert isInRange(handle); - if (globalHandles.isWeak(decode(handle))) { + SignedWord handleValue = (Word) handle; + if ((handleValue.toLong() & WEAK_HANDLE_FLAG.toLong()) == 0) { + return JNIObjectRefType.Global; + } + + if ((handleValue.toLong() & WEAK_HANDLE_FLAG.toLong()) == 1) { return JNIObjectRefType.WeakGlobal; } - return JNIObjectRefType.Global; + return JNIObjectRefType.Invalid; } static JNIObjectHandle create(Object obj) { - return encode(globalHandles.create(obj)); + return encode(strongGlobalHandles.create(obj)); } static void destroy(JNIObjectHandle handle) { - globalHandles.destroy(decode(handle)); + strongGlobalHandles.destroy(decode(handle)); } static JNIObjectHandle createWeak(Object obj) { - return encode(globalHandles.createWeak(obj)); + return encodeWeak(weakGlobalHandles.create(obj)); } static void destroyWeak(JNIObjectHandle weakRef) { - globalHandles.destroyWeak(decode(weakRef)); + weakGlobalHandles.destroy(decode(weakRef)); } public static long computeCurrentCount() { - return globalHandles.computeCurrentCount(); + return strongGlobalHandles.computeCurrentCount() + weakGlobalHandles.computeCurrentCount(); } } From ca5edbea021eabbbf3c14ecc77ec6789ebddba92 Mon Sep 17 00:00:00 2001 From: asselyam Date: Thu, 13 Nov 2025 10:17:19 +0100 Subject: [PATCH 2/5] Use of native-memory instead of java heap --- .../svm/core/handles/ObjectHandlesImpl.java | 101 +++++++++++------- .../oracle/svm/core/jni/JNIObjectHandles.java | 21 ++-- 2 files changed, 75 insertions(+), 47 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/handles/ObjectHandlesImpl.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/handles/ObjectHandlesImpl.java index 6f9927636773..ce9989f34f5c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/handles/ObjectHandlesImpl.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/handles/ObjectHandlesImpl.java @@ -24,15 +24,19 @@ */ package com.oracle.svm.core.handles; -import java.lang.ref.WeakReference; - -import jdk.graal.compiler.word.Word; +import com.oracle.svm.core.memory.NullableNativeMemory; +import com.oracle.svm.core.nmt.NmtCategory; import org.graalvm.nativeimage.ObjectHandle; import org.graalvm.nativeimage.ObjectHandles; +import org.graalvm.nativeimage.c.type.WordPointer; +import org.graalvm.word.LocationIdentity; +import org.graalvm.word.Pointer; import org.graalvm.word.SignedWord; import org.graalvm.word.WordBase; -import jdk.internal.misc.Unsafe; +import com.oracle.svm.core.config.ConfigurationValues; + +import jdk.graal.compiler.word.Word; /** * This class implements {@link ObjectHandle word}-sized integer handles that refer to Java objects. @@ -51,6 +55,7 @@ public final class ObjectHandlesImpl implements ObjectHandles { private static final int MAX_FIRST_BUCKET_CAPACITY = 1024; + private static final int wordSize = ConfigurationValues.getTarget().wordSize; static { // must be a power of 2 for the arithmetic below to work assert Integer.lowestOneBit(MAX_FIRST_BUCKET_CAPACITY) == MAX_FIRST_BUCKET_CAPACITY; } @@ -59,7 +64,8 @@ public final class ObjectHandlesImpl implements ObjectHandles { private final SignedWord rangeMax; private final SignedWord nullHandle; - private final Object[][] buckets; + private final WordPointer[] buckets; + private final int[] bucketCapacities; private volatile long unusedHandleSearchIndex = 0; public ObjectHandlesImpl() { @@ -76,12 +82,17 @@ public ObjectHandlesImpl(SignedWord rangeMin, SignedWord rangeMax, SignedWord nu long maxIndex = toIndex(rangeMax); int lastBucketIndex = getBucketIndex(maxIndex); int lastBucketCapacity = getIndexInBucket(maxIndex) + 1; - buckets = new Object[lastBucketIndex + 1][]; + buckets = new WordPointer[lastBucketIndex + 1]; + bucketCapacities = new int[lastBucketIndex + 1]; int firstBucketCapacity = MAX_FIRST_BUCKET_CAPACITY; + if (lastBucketIndex == 0) { // if our range is small, we may have only a single small bucket firstBucketCapacity = lastBucketCapacity; } - buckets[0] = new Object[firstBucketCapacity]; + + Pointer bucketBasePtr = NullableNativeMemory.malloc(Word.unsigned(firstBucketCapacity * wordSize), NmtCategory.JNI); + buckets[0] = (WordPointer) bucketBasePtr; + bucketCapacities[0] = firstBucketCapacity; } public boolean isInRange(ObjectHandle handle) { @@ -115,15 +126,13 @@ private static int getIndexInBucket(long index) { } private static long getObjectArrayByteOffset(int index) { - return Unsafe.getUnsafe().arrayBaseOffset(Object[].class) + index * Unsafe.getUnsafe().arrayIndexScale(Object[].class); + return (long) index * wordSize; } - private Object[] getBucket(int bucketIndex) { + private WordPointer getBucket(int bucketIndex) { // buckets[i] is changed only once from null to its final value: try without volatile first - Object[] bucket = buckets[bucketIndex]; - if (bucket == null) { - bucket = (Object[]) Unsafe.getUnsafe().getReferenceVolatile(buckets, getObjectArrayByteOffset(bucketIndex)); - } + WordPointer bucket = buckets[bucketIndex]; + return bucket; } @@ -139,6 +148,7 @@ public ObjectHandle create(Object obj) { if (obj == null) { return (ObjectHandle) nullHandle; } + outer: for (;;) { long startIndex = unusedHandleSearchIndex; int startBucketIndex = getBucketIndex(startIndex); @@ -147,12 +157,16 @@ public ObjectHandle create(Object obj) { int bucketIndex = startBucketIndex; int indexInBucket = startIndexInBucket; int lastExistingBucketIndex = -1; - Object[] bucket = getBucket(bucketIndex); + Pointer bucket = (Pointer) getBucket(bucketIndex); + int bucketCapacity = bucketCapacities[bucketIndex]; + for (;;) { - while (indexInBucket < bucket.length) { - if (bucket[indexInBucket] == null) { - if (Unsafe.getUnsafe().compareAndSetReference(bucket, getObjectArrayByteOffset(indexInBucket), null, obj)) { - int newSearchIndexInBucket = (indexInBucket + 1 < bucket.length) ? (indexInBucket + 1) : indexInBucket; + while (indexInBucket < bucketCapacity) { + long offset = getObjectArrayByteOffset(bucketIndex); + Object currentObj = bucket.readObject(Word.unsigned(offset)); + if (currentObj.equals(nullHandle)) { + if (bucket.logicCompareAndSwapObject(Word.unsigned(offset), null, obj, LocationIdentity.ANY_LOCATION)) { + int newSearchIndexInBucket = (indexInBucket + 1 < bucketCapacity) ? (indexInBucket + 1) : indexInBucket; unusedHandleSearchIndex = toIndex(bucketIndex, newSearchIndexInBucket); // (if the next index is in another bucket, we let the next create() // figure it out) @@ -177,23 +191,28 @@ public ObjectHandle create(Object obj) { // last bucket may be smaller newBucketCapacity = getIndexInBucket(maxIndex) + 1; } - Object[] newBucket = new Object[newBucketCapacity]; - Unsafe.getUnsafe().putReferenceVolatile(newBucket, getObjectArrayByteOffset(0), obj); - if (Unsafe.getUnsafe().compareAndSetReference(buckets, getObjectArrayByteOffset(newBucketIndex), null, newBucket)) { + Pointer newBucketBasePtr = NullableNativeMemory.malloc(Word.unsigned(newBucketCapacity * wordSize), NmtCategory.JNI); + newBucketBasePtr.writeObject(Word.unsigned(0), obj); + + buckets[indexInBucket] = (WordPointer) newBucketBasePtr; + bucketCapacities[indexInBucket] = newBucketCapacity; + +// long newBucketOffset = getObjectArrayByteOffset(newBucketIndex); +// if (bucketsBasePtr.logicCompareAndSwapObject(Word.unsigned(newBucketOffset), null, newBucket, LocationIdentity.ANY_LOCATION)) { unusedHandleSearchIndex = toIndex(newBucketIndex, 1); return toHandle(newBucketIndex, 0); - } +// } // start over: another thread has raced us to create another bucket and won - continue outer; +// continue outer; } } bucketIndex++; - bucket = getBucket(bucketIndex); + bucket = (Pointer)getBucket(bucketIndex); if (bucket == null) { lastExistingBucketIndex = bucketIndex - 1; bucketIndex = 0; - bucket = getBucket(bucketIndex); + bucket = (Pointer)getBucket(bucketIndex); } indexInBucket = 0; } @@ -215,12 +234,12 @@ private Object doGet(ObjectHandle handle) { throw new IllegalArgumentException("Invalid handle"); } long index = toIndex(handle); - Object[] bucket = getBucket(getBucketIndex(index)); - if (bucket == null) { + WordPointer bucket = getBucket(getBucketIndex(index)); + if (bucket.isNull()) { throw new IllegalArgumentException("Invalid handle"); } int indexInBucket = getIndexInBucket(index); - return Unsafe.getUnsafe().getReferenceVolatile(bucket, getObjectArrayByteOffset(indexInBucket)); + return ((Pointer)bucket).readObject(Word.unsigned(getObjectArrayByteOffset(indexInBucket))); } @Override @@ -232,22 +251,26 @@ public void destroy(ObjectHandle handle) { throw new IllegalArgumentException("Invalid handle"); } long index = toIndex(handle); - Object[] bucket = getBucket(getBucketIndex(index)); - if (bucket == null) { + WordPointer bucket = getBucket(getBucketIndex(index)); + if (bucket.isNull()) { throw new IllegalArgumentException("Invalid handle"); } int indexInBucket = getIndexInBucket(index); - Unsafe.getUnsafe().putReferenceRelease(bucket, getObjectArrayByteOffset(indexInBucket), null); + ((Pointer)bucket).writeObject(Word.unsigned(getObjectArrayByteOffset(indexInBucket)), null); } public long computeCurrentCount() { long count = 0; int bucketIndex = 0; - Object[] bucket = getBucket(bucketIndex); - while (bucket != null) { - for (int i = 0; i < bucket.length; i++) { - if (bucket[i] != null) { + long offset = 0; + Object currentObj; + WordPointer bucket = getBucket(bucketIndex); + while (!bucket.isNull()) { + for (int i = 0; i < bucketCapacities[bucketIndex]; i++) { + offset = getObjectArrayByteOffset(i); + currentObj = ((Pointer)bucket).readObject(Word.unsigned(offset)); + if (!currentObj.equals(nullHandle)) { count++; } } @@ -260,12 +283,12 @@ public long computeCurrentCount() { public long computeCurrentCapacity() { long capacity = 0; int bucketIndex = 0; - Object[] bucket = getBucket(bucketIndex); - while (bucket != null) { - capacity += bucket.length; + WordPointer bucket = getBucket(bucketIndex); + while (!bucket.isNull()) { + capacity += bucketCapacities[bucketIndex]; bucketIndex++; bucket = getBucket(bucketIndex); } return capacity; } -} +} \ No newline at end of file diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/JNIObjectHandles.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/JNIObjectHandles.java index 92910e55f4b0..f3f0c0c12ada 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/JNIObjectHandles.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/JNIObjectHandles.java @@ -34,6 +34,7 @@ import com.oracle.svm.core.RuntimeAssertionsSupport; import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.handles.ObjectHandlesImpl; import com.oracle.svm.core.handles.ThreadLocalHandles; import com.oracle.svm.core.heap.Heap; import com.oracle.svm.core.jni.headers.JNIObjectHandle; @@ -242,7 +243,7 @@ public static JNIObjectHandle newGlobalRef(JNIObjectHandle handle) { result = JNIImageHeapHandles.toGlobal(handle); } else { Object obj = getObject(handle); - if (obj != null) { + if (!obj.equals(nullHandle())) { result = JNIGlobalHandles.create(obj); } } @@ -261,7 +262,7 @@ public static JNIObjectHandle newWeakGlobalRef(JNIObjectHandle handle) { result = JNIImageHeapHandles.toWeakGlobal(handle); } else { Object obj = getObject(handle); - if (obj != null) { + if (!obj.equals(nullHandle())) { result = JNIGlobalHandles.createWeak(obj); } } @@ -345,7 +346,7 @@ private static Word isolateHash() { * @param handle The raw, unencoded handle to the Java object. * @return The resulting strong JNI object handle with embedded metadata. */ - private static JNIObjectHandle encode(ObjectHandle handle) { + private static JNIObjectHandle encodeStrong(ObjectHandle handle) { SignedWord h = (Word) handle; if (JNIObjectHandles.haveAssertions()) { assert h.and(HANDLE_BITS_MASK).equal(h) : "unencoded handle must fit in range"; @@ -385,11 +386,13 @@ private static ObjectHandle decode(JNIObjectHandle handle) { static T getObject(JNIObjectHandle handle) { SignedWord handleValue = (Word) handle; - if ((handleValue.toLong() & WEAK_HANDLE_FLAG.toLong()) == 0) { + if (handleValue.greaterOrEqual(STRONG_GLOBAL_RANGE_MIN) && + handleValue.lessOrEqual(STRONG_GLOBAL_RANGE_MAX)) { return strongGlobalHandles.get(decode(handle)); } - if ((handleValue.toLong() & WEAK_HANDLE_FLAG.toLong()) == 1) { + if (handleValue.greaterOrEqual(WEAK_GLOBAL_RANGE_MIN) && + handleValue.lessOrEqual(WEAK_GLOBAL_RANGE_MAX)) { return weakGlobalHandles.get(decode((handle))); } @@ -398,18 +401,20 @@ static T getObject(JNIObjectHandle handle) { static JNIObjectRefType getHandleType(JNIObjectHandle handle) { SignedWord handleValue = (Word) handle; - if ((handleValue.toLong() & WEAK_HANDLE_FLAG.toLong()) == 0) { + if (handleValue.greaterOrEqual(STRONG_GLOBAL_RANGE_MIN) && + handleValue.lessOrEqual(STRONG_GLOBAL_RANGE_MAX)) { return JNIObjectRefType.Global; } - if ((handleValue.toLong() & WEAK_HANDLE_FLAG.toLong()) == 1) { + if (handleValue.greaterOrEqual(WEAK_GLOBAL_RANGE_MIN) && + handleValue.lessOrEqual(WEAK_GLOBAL_RANGE_MAX)) { return JNIObjectRefType.WeakGlobal; } return JNIObjectRefType.Invalid; } static JNIObjectHandle create(Object obj) { - return encode(strongGlobalHandles.create(obj)); + return encodeStrong(strongGlobalHandles.create(obj)); } static void destroy(JNIObjectHandle handle) { From 44ec6e3b218a1866215fc7b7c80950714807eb3a Mon Sep 17 00:00:00 2001 From: asselyam Date: Sat, 15 Nov 2025 13:56:12 +0100 Subject: [PATCH 3/5] Update ObjectHandlesImpl memory allocation --- .../svm/core/handles/ObjectHandlesImpl.java | 88 ++++++++++++++----- 1 file changed, 65 insertions(+), 23 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/handles/ObjectHandlesImpl.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/handles/ObjectHandlesImpl.java index ce9989f34f5c..0acefba974e2 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/handles/ObjectHandlesImpl.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/handles/ObjectHandlesImpl.java @@ -24,8 +24,10 @@ */ package com.oracle.svm.core.handles; +import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.memory.NullableNativeMemory; import com.oracle.svm.core.nmt.NmtCategory; +import org.graalvm.nativeimage.ImageInfo; import org.graalvm.nativeimage.ObjectHandle; import org.graalvm.nativeimage.ObjectHandles; import org.graalvm.nativeimage.c.type.WordPointer; @@ -55,7 +57,6 @@ public final class ObjectHandlesImpl implements ObjectHandles { private static final int MAX_FIRST_BUCKET_CAPACITY = 1024; - private static final int wordSize = ConfigurationValues.getTarget().wordSize; static { // must be a power of 2 for the arithmetic below to work assert Integer.lowestOneBit(MAX_FIRST_BUCKET_CAPACITY) == MAX_FIRST_BUCKET_CAPACITY; } @@ -67,6 +68,7 @@ public final class ObjectHandlesImpl implements ObjectHandles { private final WordPointer[] buckets; private final int[] bucketCapacities; private volatile long unusedHandleSearchIndex = 0; + private int deferredFirstBucketCapacity = -1; public ObjectHandlesImpl() { this(Word.signed(1), Word.signed(Long.MAX_VALUE), Word.signed(0)); @@ -90,9 +92,7 @@ public ObjectHandlesImpl(SignedWord rangeMin, SignedWord rangeMax, SignedWord nu firstBucketCapacity = lastBucketCapacity; } - Pointer bucketBasePtr = NullableNativeMemory.malloc(Word.unsigned(firstBucketCapacity * wordSize), NmtCategory.JNI); - buckets[0] = (WordPointer) bucketBasePtr; - bucketCapacities[0] = firstBucketCapacity; + deferredFirstBucketCapacity = firstBucketCapacity; } public boolean isInRange(ObjectHandle handle) { @@ -126,13 +126,38 @@ private static int getIndexInBucket(long index) { } private static long getObjectArrayByteOffset(int index) { - return (long) index * wordSize; + return (long) index * ConfigurationValues.getTarget().wordSize; + } + + @Uninterruptible(reason = "Called from critical sections") + private static WordPointer allocateBucket(int capacity) { + if (ImageInfo.inImageCode()) { + return null; + } + long bytes = (long) capacity * ConfigurationValues.getTarget().wordSize; + Pointer ptr = NullableNativeMemory.malloc(Word.unsigned(bytes), NmtCategory.JNI); + return (WordPointer) ptr; } private WordPointer getBucket(int bucketIndex) { // buckets[i] is changed only once from null to its final value: try without volatile first WordPointer bucket = buckets[bucketIndex]; + if (bucket != null) { + return bucket; + } + if (bucketIndex == 0 && deferredFirstBucketCapacity != -1) { + // This is the first time we are accessing the capacity at runtime. + bucketCapacities[0] = deferredFirstBucketCapacity; + deferredFirstBucketCapacity = -1; // Mark as initialized + } + + if (ImageInfo.inImageCode()) { + return null; + } + + bucket = allocateBucket(bucketCapacities[bucketIndex]); + buckets[bucketIndex] = bucket; return bucket; } @@ -162,9 +187,12 @@ public ObjectHandle create(Object obj) { for (;;) { while (indexInBucket < bucketCapacity) { - long offset = getObjectArrayByteOffset(bucketIndex); + long offset = getObjectArrayByteOffset(indexInBucket); + if (bucket == null) { + throw new IllegalStateException("Bucket not allocated"); + } Object currentObj = bucket.readObject(Word.unsigned(offset)); - if (currentObj.equals(nullHandle)) { + if (currentObj == null) { if (bucket.logicCompareAndSwapObject(Word.unsigned(offset), null, obj, LocationIdentity.ANY_LOCATION)) { int newSearchIndexInBucket = (indexInBucket + 1 < bucketCapacity) ? (indexInBucket + 1) : indexInBucket; unusedHandleSearchIndex = toIndex(bucketIndex, newSearchIndexInBucket); @@ -191,19 +219,30 @@ public ObjectHandle create(Object obj) { // last bucket may be smaller newBucketCapacity = getIndexInBucket(maxIndex) + 1; } - Pointer newBucketBasePtr = NullableNativeMemory.malloc(Word.unsigned(newBucketCapacity * wordSize), NmtCategory.JNI); - newBucketBasePtr.writeObject(Word.unsigned(0), obj); - buckets[indexInBucket] = (WordPointer) newBucketBasePtr; - bucketCapacities[indexInBucket] = newBucketCapacity; + // Allocate new bucket memory manually + WordPointer newBucket = allocateBucket(newBucketCapacity); + if (newBucket == null) { + // Allocation failed at build time or due to error (shouldn't happen at runtime now) + continue outer; + } + Pointer newBucketPtr = (Pointer) newBucket; + // Initialize the first slot with the object + newBucketPtr.writeObject(Word.unsigned(0), obj); + + // CAS-insert bucket pointer into `buckets[newBucketIndex]` + if (buckets[newBucketIndex] == null) { + buckets[newBucketIndex] = newBucket; + bucketCapacities[newBucketIndex] = newBucketCapacity; -// long newBucketOffset = getObjectArrayByteOffset(newBucketIndex); -// if (bucketsBasePtr.logicCompareAndSwapObject(Word.unsigned(newBucketOffset), null, newBucket, LocationIdentity.ANY_LOCATION)) { unusedHandleSearchIndex = toIndex(newBucketIndex, 1); return toHandle(newBucketIndex, 0); -// } - // start over: another thread has raced us to create another bucket and won -// continue outer; + } else { + if (!ImageInfo.inImageCode()) { + NullableNativeMemory.free(newBucketPtr); + } + continue outer; + } } } @@ -235,8 +274,11 @@ private Object doGet(ObjectHandle handle) { } long index = toIndex(handle); WordPointer bucket = getBucket(getBucketIndex(index)); - if (bucket.isNull()) { - throw new IllegalArgumentException("Invalid handle"); + if (bucket == null) { + if (ImageInfo.inImageCode()) { + return nullHandle; // or skip writing + } + throw new IllegalStateException("Bucket not allocated"); } int indexInBucket = getIndexInBucket(index); return ((Pointer)bucket).readObject(Word.unsigned(getObjectArrayByteOffset(indexInBucket))); @@ -252,8 +294,8 @@ public void destroy(ObjectHandle handle) { } long index = toIndex(handle); WordPointer bucket = getBucket(getBucketIndex(index)); - if (bucket.isNull()) { - throw new IllegalArgumentException("Invalid handle"); + if (bucket == null) { + throw new IllegalStateException("Bucket not allocated"); } int indexInBucket = getIndexInBucket(index); ((Pointer)bucket).writeObject(Word.unsigned(getObjectArrayByteOffset(indexInBucket)), null); @@ -266,11 +308,11 @@ public long computeCurrentCount() { long offset = 0; Object currentObj; WordPointer bucket = getBucket(bucketIndex); - while (!bucket.isNull()) { + while (bucket != null) { for (int i = 0; i < bucketCapacities[bucketIndex]; i++) { offset = getObjectArrayByteOffset(i); currentObj = ((Pointer)bucket).readObject(Word.unsigned(offset)); - if (!currentObj.equals(nullHandle)) { + if (currentObj != null) { count++; } } @@ -284,7 +326,7 @@ public long computeCurrentCapacity() { long capacity = 0; int bucketIndex = 0; WordPointer bucket = getBucket(bucketIndex); - while (!bucket.isNull()) { + while (bucket != null) { capacity += bucketCapacities[bucketIndex]; bucketIndex++; bucket = getBucket(bucketIndex); From 546a8d54a280187e256c0728f291b0513d7d93ac Mon Sep 17 00:00:00 2001 From: asselyam Date: Fri, 21 Nov 2025 07:59:51 +0100 Subject: [PATCH 4/5] Update ObjectHandlesImpl.java --- .../svm/core/handles/ObjectHandlesImpl.java | 78 +++++++++++-------- 1 file changed, 45 insertions(+), 33 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/handles/ObjectHandlesImpl.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/handles/ObjectHandlesImpl.java index 0acefba974e2..8131ddbc8507 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/handles/ObjectHandlesImpl.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/handles/ObjectHandlesImpl.java @@ -25,6 +25,7 @@ package com.oracle.svm.core.handles; import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.genscavenge.GreyToBlackObjRefVisitor; import com.oracle.svm.core.memory.NullableNativeMemory; import com.oracle.svm.core.nmt.NmtCategory; import org.graalvm.nativeimage.ImageInfo; @@ -85,6 +86,11 @@ public ObjectHandlesImpl(SignedWord rangeMin, SignedWord rangeMax, SignedWord nu int lastBucketIndex = getBucketIndex(maxIndex); int lastBucketCapacity = getIndexInBucket(maxIndex) + 1; buckets = new WordPointer[lastBucketIndex + 1]; + for (int i = 0; i < buckets.length; i++) { + // Pointer.zero() returns the canonical zero pointer; cast to WordPointer + buckets[i] = Word.zero(); + } + bucketCapacities = new int[lastBucketIndex + 1]; int firstBucketCapacity = MAX_FIRST_BUCKET_CAPACITY; @@ -131,18 +137,14 @@ private static long getObjectArrayByteOffset(int index) { @Uninterruptible(reason = "Called from critical sections") private static WordPointer allocateBucket(int capacity) { - if (ImageInfo.inImageCode()) { - return null; - } long bytes = (long) capacity * ConfigurationValues.getTarget().wordSize; Pointer ptr = NullableNativeMemory.malloc(Word.unsigned(bytes), NmtCategory.JNI); return (WordPointer) ptr; } private WordPointer getBucket(int bucketIndex) { - // buckets[i] is changed only once from null to its final value: try without volatile first WordPointer bucket = buckets[bucketIndex]; - if (bucket != null) { + if (!bucket.isNull()) { return bucket; } @@ -152,10 +154,6 @@ private WordPointer getBucket(int bucketIndex) { deferredFirstBucketCapacity = -1; // Mark as initialized } - if (ImageInfo.inImageCode()) { - return null; - } - bucket = allocateBucket(bucketCapacities[bucketIndex]); buckets[bucketIndex] = bucket; return bucket; @@ -182,18 +180,19 @@ public ObjectHandle create(Object obj) { int bucketIndex = startBucketIndex; int indexInBucket = startIndexInBucket; int lastExistingBucketIndex = -1; - Pointer bucket = (Pointer) getBucket(bucketIndex); + WordPointer bucket = getBucket(bucketIndex); int bucketCapacity = bucketCapacities[bucketIndex]; for (;;) { while (indexInBucket < bucketCapacity) { long offset = getObjectArrayByteOffset(indexInBucket); - if (bucket == null) { + if (bucketCapacities[bucketIndex] == 0) { throw new IllegalStateException("Bucket not allocated"); } - Object currentObj = bucket.readObject(Word.unsigned(offset)); + + Object currentObj = ((Pointer)bucket).readObject(Word.unsigned(offset)); if (currentObj == null) { - if (bucket.logicCompareAndSwapObject(Word.unsigned(offset), null, obj, LocationIdentity.ANY_LOCATION)) { + if (((Pointer)bucket).logicCompareAndSwapObject(Word.unsigned(offset), null, obj, LocationIdentity.ANY_LOCATION)) { int newSearchIndexInBucket = (indexInBucket + 1 < bucketCapacity) ? (indexInBucket + 1) : indexInBucket; unusedHandleSearchIndex = toIndex(bucketIndex, newSearchIndexInBucket); // (if the next index is in another bucket, we let the next create() @@ -211,7 +210,7 @@ public ObjectHandle create(Object obj) { throw new IllegalStateException("Handle space exhausted"); } int newBucketIndex = lastExistingBucketIndex + 1; - if (getBucket(newBucketIndex) != null) { + if (getBucket(newBucketIndex).isNonNull()) { continue outer; // start over: another thread has created a new bucket } int newBucketCapacity = (MAX_FIRST_BUCKET_CAPACITY << newBucketIndex); @@ -222,16 +221,12 @@ public ObjectHandle create(Object obj) { // Allocate new bucket memory manually WordPointer newBucket = allocateBucket(newBucketCapacity); - if (newBucket == null) { - // Allocation failed at build time or due to error (shouldn't happen at runtime now) - continue outer; - } Pointer newBucketPtr = (Pointer) newBucket; // Initialize the first slot with the object newBucketPtr.writeObject(Word.unsigned(0), obj); - // CAS-insert bucket pointer into `buckets[newBucketIndex]` - if (buckets[newBucketIndex] == null) { + // CAS-insert bucket pointer into `buckets[newBucketIndex] + if (newBucketPtr.logicCompareAndSwapObject(Word.unsigned(getObjectArrayByteOffset(newBucketIndex)), null, newBucket, LocationIdentity.ANY_LOCATION)) { buckets[newBucketIndex] = newBucket; bucketCapacities[newBucketIndex] = newBucketCapacity; @@ -247,11 +242,11 @@ public ObjectHandle create(Object obj) { } bucketIndex++; - bucket = (Pointer)getBucket(bucketIndex); - if (bucket == null) { + bucket = getBucket(bucketIndex); + if (bucket.isNull()) { lastExistingBucketIndex = bucketIndex - 1; bucketIndex = 0; - bucket = (Pointer)getBucket(bucketIndex); + bucket = getBucket(bucketIndex); } indexInBucket = 0; } @@ -266,7 +261,7 @@ public T get(ObjectHandle handle) { } private Object doGet(ObjectHandle handle) { - if (handle.equal(nullHandle)) { + if (handle == nullHandle) { return null; } if (!isInRange(handle)) { @@ -274,19 +269,18 @@ private Object doGet(ObjectHandle handle) { } long index = toIndex(handle); WordPointer bucket = getBucket(getBucketIndex(index)); - if (bucket == null) { - if (ImageInfo.inImageCode()) { - return nullHandle; // or skip writing - } + + if (bucket.isNull()) { throw new IllegalStateException("Bucket not allocated"); } + int indexInBucket = getIndexInBucket(index); return ((Pointer)bucket).readObject(Word.unsigned(getObjectArrayByteOffset(indexInBucket))); } @Override public void destroy(ObjectHandle handle) { - if (handle.equal(nullHandle)) { + if (handle == nullHandle) { return; } if (!isInRange(handle)) { @@ -294,9 +288,11 @@ public void destroy(ObjectHandle handle) { } long index = toIndex(handle); WordPointer bucket = getBucket(getBucketIndex(index)); - if (bucket == null) { + + if (bucket.isNull()) { throw new IllegalStateException("Bucket not allocated"); } + int indexInBucket = getIndexInBucket(index); ((Pointer)bucket).writeObject(Word.unsigned(getObjectArrayByteOffset(indexInBucket)), null); } @@ -308,7 +304,7 @@ public long computeCurrentCount() { long offset = 0; Object currentObj; WordPointer bucket = getBucket(bucketIndex); - while (bucket != null) { + while (bucket.isNonNull()) { for (int i = 0; i < bucketCapacities[bucketIndex]; i++) { offset = getObjectArrayByteOffset(i); currentObj = ((Pointer)bucket).readObject(Word.unsigned(offset)); @@ -326,11 +322,27 @@ public long computeCurrentCapacity() { long capacity = 0; int bucketIndex = 0; WordPointer bucket = getBucket(bucketIndex); - while (bucket != null) { + while (bucket.isNonNull()) { capacity += bucketCapacities[bucketIndex]; bucketIndex++; bucket = getBucket(bucketIndex); } return capacity; } -} \ No newline at end of file + + public void visitBuckets(GreyToBlackObjRefVisitor visitor) { + for (int i = 0; i < buckets.length; i++) { + Pointer bucket = (Pointer) buckets[i]; + if (bucket.isNonNull()) { + visitor.visitObjectReferences( + bucket, + false, + ConfigurationValues.getTarget().wordSize, + null, + bucketCapacities[i] + ); + } + } + } + +} From 0508a1f6ddcbe3fa3daf5b85d421dd602b849abc Mon Sep 17 00:00:00 2001 From: asselyam Date: Sun, 23 Nov 2025 21:54:32 +0100 Subject: [PATCH 5/5] Update ObjectHandlesImpl.java --- .../svm/core/handles/ObjectHandlesImpl.java | 53 +++++++------------ 1 file changed, 19 insertions(+), 34 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/handles/ObjectHandlesImpl.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/handles/ObjectHandlesImpl.java index 8131ddbc8507..60bbccd55a6b 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/handles/ObjectHandlesImpl.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/handles/ObjectHandlesImpl.java @@ -25,7 +25,6 @@ package com.oracle.svm.core.handles; import com.oracle.svm.core.Uninterruptible; -import com.oracle.svm.core.genscavenge.GreyToBlackObjRefVisitor; import com.oracle.svm.core.memory.NullableNativeMemory; import com.oracle.svm.core.nmt.NmtCategory; import org.graalvm.nativeimage.ImageInfo; @@ -40,6 +39,7 @@ import com.oracle.svm.core.config.ConfigurationValues; import jdk.graal.compiler.word.Word; +import org.graalvm.word.WordFactory; /** * This class implements {@link ObjectHandle word}-sized integer handles that refer to Java objects. @@ -86,10 +86,6 @@ public ObjectHandlesImpl(SignedWord rangeMin, SignedWord rangeMax, SignedWord nu int lastBucketIndex = getBucketIndex(maxIndex); int lastBucketCapacity = getIndexInBucket(maxIndex) + 1; buckets = new WordPointer[lastBucketIndex + 1]; - for (int i = 0; i < buckets.length; i++) { - // Pointer.zero() returns the canonical zero pointer; cast to WordPointer - buckets[i] = Word.zero(); - } bucketCapacities = new int[lastBucketIndex + 1]; int firstBucketCapacity = MAX_FIRST_BUCKET_CAPACITY; @@ -142,6 +138,14 @@ private static WordPointer allocateBucket(int capacity) { return (WordPointer) ptr; } + /** + * Helper to get the base address of the buckets array data block. + * This uses the standard idiom for accessing the native backing of a Word-based array. + */ + private Pointer getBucketsDataPointer() { + return Word.objectToUntrackedPointer(buckets); + } + private WordPointer getBucket(int bucketIndex) { WordPointer bucket = buckets[bucketIndex]; if (!bucket.isNull()) { @@ -186,13 +190,10 @@ public ObjectHandle create(Object obj) { for (;;) { while (indexInBucket < bucketCapacity) { long offset = getObjectArrayByteOffset(indexInBucket); - if (bucketCapacities[bucketIndex] == 0) { - throw new IllegalStateException("Bucket not allocated"); - } - Object currentObj = ((Pointer)bucket).readObject(Word.unsigned(offset)); if (currentObj == null) { - if (((Pointer)bucket).logicCompareAndSwapObject(Word.unsigned(offset), null, obj, LocationIdentity.ANY_LOCATION)) { + Object prev = ((Pointer)bucket).compareAndSwapObject(Word.unsigned(offset), null, obj, LocationIdentity.ANY_LOCATION); + if (prev == null) { int newSearchIndexInBucket = (indexInBucket + 1 < bucketCapacity) ? (indexInBucket + 1) : indexInBucket; unusedHandleSearchIndex = toIndex(bucketIndex, newSearchIndexInBucket); // (if the next index is in another bucket, we let the next create() @@ -219,14 +220,13 @@ public ObjectHandle create(Object obj) { newBucketCapacity = getIndexInBucket(maxIndex) + 1; } - // Allocate new bucket memory manually WordPointer newBucket = allocateBucket(newBucketCapacity); - Pointer newBucketPtr = (Pointer) newBucket; - // Initialize the first slot with the object - newBucketPtr.writeObject(Word.unsigned(0), obj); + ((Pointer) newBucket).writeObject(Word.unsigned(0), obj); - // CAS-insert bucket pointer into `buckets[newBucketIndex] - if (newBucketPtr.logicCompareAndSwapObject(Word.unsigned(getObjectArrayByteOffset(newBucketIndex)), null, newBucket, LocationIdentity.ANY_LOCATION)) { + offset = getObjectArrayByteOffset(newBucketIndex); + Object prev = getBucketsDataPointer().compareAndSwapObject(Word.unsigned(offset), Word.nullPointer(), newBucket, LocationIdentity.ANY_LOCATION); + + if (((WordPointer)prev).isNull()) { buckets[newBucketIndex] = newBucket; bucketCapacities[newBucketIndex] = newBucketCapacity; @@ -234,7 +234,7 @@ public ObjectHandle create(Object obj) { return toHandle(newBucketIndex, 0); } else { if (!ImageInfo.inImageCode()) { - NullableNativeMemory.free(newBucketPtr); + NullableNativeMemory.free(newBucket); } continue outer; } @@ -261,7 +261,7 @@ public T get(ObjectHandle handle) { } private Object doGet(ObjectHandle handle) { - if (handle == nullHandle) { + if (handle.rawValue() == nullHandle.rawValue()) { return null; } if (!isInRange(handle)) { @@ -280,7 +280,7 @@ private Object doGet(ObjectHandle handle) { @Override public void destroy(ObjectHandle handle) { - if (handle == nullHandle) { + if (handle.rawValue() == nullHandle.rawValue()) { return; } if (!isInRange(handle)) { @@ -330,19 +330,4 @@ public long computeCurrentCapacity() { return capacity; } - public void visitBuckets(GreyToBlackObjRefVisitor visitor) { - for (int i = 0; i < buckets.length; i++) { - Pointer bucket = (Pointer) buckets[i]; - if (bucket.isNonNull()) { - visitor.visitObjectReferences( - bucket, - false, - ConfigurationValues.getTarget().wordSize, - null, - bucketCapacities[i] - ); - } - } - } - }