Skip to content

Commit e742187

Browse files
author
Thomas Schrott
committed
Add heap allocation
1 parent 9c7c1c5 commit e742187

File tree

7 files changed

+377
-27
lines changed

7 files changed

+377
-27
lines changed

substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GCImpl.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,8 @@ assert getCollectionEpoch().equal(data.getRequestingEpoch()) ||
245245

246246
Timer collectionTimer = timers.collection.start();
247247
try {
248-
ThreadLocalAllocation.disableAndFlushForAllThreads();
248+
HeapImpl.getHeapImpl().makeParseable();
249+
249250
GenScavengeMemoryPoolMXBeans.singleton().notifyBeforeCollection();
250251
HeapImpl.getAccounting().notifyBeforeCollection();
251252

Lines changed: 308 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,308 @@
1+
/*
2+
* Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
package com.oracle.svm.core.genscavenge;
26+
27+
import static com.oracle.svm.core.Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE;
28+
import static com.oracle.svm.core.genscavenge.AlignedHeapChunk.AlignedHeader;
29+
import static com.oracle.svm.core.genscavenge.HeapChunk.CHUNK_HEADER_TOP_IDENTITY;
30+
import static jdk.graal.compiler.nodes.extended.MembarNode.FenceKind;
31+
32+
import org.graalvm.nativeimage.Platform;
33+
import org.graalvm.nativeimage.Platforms;
34+
import org.graalvm.nativeimage.StackValue;
35+
import org.graalvm.nativeimage.c.type.WordPointer;
36+
import org.graalvm.word.Pointer;
37+
import org.graalvm.word.UnsignedWord;
38+
39+
import com.oracle.svm.core.Uninterruptible;
40+
import com.oracle.svm.core.config.ConfigurationValues;
41+
import com.oracle.svm.core.config.ObjectLayout;
42+
import com.oracle.svm.core.log.Log;
43+
import com.oracle.svm.core.thread.JavaSpinLockUtils;
44+
import com.oracle.svm.core.thread.VMOperation;
45+
import com.oracle.svm.core.util.BasedOnJDKFile;
46+
import com.oracle.svm.core.util.UnsignedUtils;
47+
48+
import jdk.graal.compiler.nodes.extended.MembarNode;
49+
import jdk.graal.compiler.word.Word;
50+
import jdk.internal.misc.Unsafe;
51+
52+
/**
53+
* Per-isolate bump-pointer allocation inside {@link AlignedHeapChunk}. First the allocation is
54+
* tried within {@link HeapAllocation#retainedChunk}. If this fails the allocation is tried within
55+
* {@link HeapAllocation#currentChunk}. If this also fails a new chunk is requested.
56+
*
57+
* Both chunk fields may only be written if {@link HeapAllocation#lock} is locked with
58+
* {@link JavaSpinLockUtils} or during a safepoint.
59+
*/
60+
public final class HeapAllocation {
61+
62+
private static final Unsafe UNSAFE = Unsafe.getUnsafe();
63+
private static final long LOCK_OFFSET = UNSAFE.objectFieldOffset(HeapAllocation.class, "lock");
64+
65+
@SuppressWarnings("unused") private volatile int lock;
66+
67+
/**
68+
* Current allocation chunk, and also the head of the list of aligned chunks that were allocated
69+
* since the last collection.
70+
*/
71+
private AlignedHeader currentChunk;
72+
73+
/**
74+
* Retained allocation chunk. Used to lower the waste generated during mutation by having two
75+
* active chunks if the free space in a chunk about to be retired still could fit a TLAB.
76+
*/
77+
private AlignedHeader retainedChunk;
78+
79+
@Platforms(Platform.HOSTED_ONLY.class)
80+
public HeapAllocation() {
81+
}
82+
83+
@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-23-ga/src/hotspot/share/gc/g1/g1CollectedHeap.cpp#L383-L390")
84+
@Uninterruptible(reason = "Returns uninitialized memory.", callerMustBe = true)
85+
public Pointer allocateNewTlab(UnsignedWord minSize, UnsignedWord requestedSize, WordPointer actualSize) {
86+
assert fitsInAlignedChunk(requestedSize) : "We do not allow TLABs larger than an aligned chunk.";
87+
return attemptAllocation(minSize, requestedSize, actualSize);
88+
}
89+
90+
@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-23-ga/src/hotspot/share/gc/g1/g1CollectedHeap.cpp#L392-L402")
91+
@Uninterruptible(reason = "Returns uninitialized memory.", callerMustBe = true)
92+
public Pointer allocateOutsideTlab(UnsignedWord size) {
93+
assert fitsInAlignedChunk(size) : "Must not be called for allocation requests that require an unaligned chunk.";
94+
WordPointer actualSize = StackValue.get(WordPointer.class);
95+
Pointer result = attemptAllocation(size, size, actualSize);
96+
assert actualSize.read() == size;
97+
return result;
98+
}
99+
100+
@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-25+15/src/hotspot/share/gc/g1/g1Allocator.inline.hpp#L52-L62")
101+
@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-23-ga/src/hotspot/share/gc/g1/g1AllocRegion.inline.hpp#L89-L95")
102+
@Uninterruptible(reason = "Returns uninitialized memory and acquires a lock without a thread state transition.", callerMustBe = true)
103+
private Pointer attemptAllocation(UnsignedWord minSize, UnsignedWord requestedSize, WordPointer actualSize) {
104+
Pointer result = attemptAllocationParallel(retainedChunk, minSize, requestedSize, actualSize);
105+
if (result.isNonNull()) {
106+
return result;
107+
}
108+
109+
result = attemptAllocationParallel(currentChunk, minSize, requestedSize, actualSize);
110+
if (result.isNonNull()) {
111+
return result;
112+
}
113+
114+
JavaSpinLockUtils.lockNoTransition(this, LOCK_OFFSET);
115+
try {
116+
// Another thread might already have allocated a new chunk.
117+
result = attemptAllocationParallel(retainedChunk, minSize, requestedSize, actualSize);
118+
if (result.isNonNull()) {
119+
return result;
120+
}
121+
result = attemptAllocationParallel(currentChunk, minSize, requestedSize, actualSize);
122+
if (result.isNonNull()) {
123+
return result;
124+
}
125+
return attemptAllocationInNewChunk(requestedSize, actualSize);
126+
} finally {
127+
JavaSpinLockUtils.unlock(this, LOCK_OFFSET);
128+
}
129+
}
130+
131+
@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-25+4/src/hotspot/share/gc/g1/g1AllocRegion.inline.hpp#L51-L65")
132+
@Uninterruptible(reason = "Returns uninitialized memory.", callerMustBe = true)
133+
private static Pointer attemptAllocationParallel(AlignedHeader chunk, UnsignedWord minSize, UnsignedWord requestedSize, WordPointer actualSize) {
134+
if (chunk.isNonNull()) {
135+
return allocateParallel(chunk, minSize, requestedSize, actualSize);
136+
}
137+
return Word.nullPointer();
138+
}
139+
140+
@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-23-ga/src/hotspot/share/gc/g1/g1AllocRegion.inline.hpp#L97-L109")
141+
@Uninterruptible(reason = "Returns uninitialized memory.", callerMustBe = true)
142+
private Pointer attemptAllocationInNewChunk(UnsignedWord requestedSize, WordPointer actualSize) {
143+
assert JavaSpinLockUtils.isLocked(this, LOCK_OFFSET);
144+
145+
retainAllocChunk();
146+
Pointer result = newAllocChunkAndAllocate(requestedSize);
147+
actualSize.write(requestedSize);
148+
return result;
149+
}
150+
151+
@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-23-ga/src/hotspot/share/gc/g1/g1AllocRegion.cpp#L289-L311")
152+
@Uninterruptible(reason = "Modifies allocation chunks.")
153+
private void retainAllocChunk() {
154+
assert JavaSpinLockUtils.isLocked(this, LOCK_OFFSET);
155+
156+
if (currentChunk.isNonNull()) {
157+
/*
158+
* Retain the current chunk if it fits a TLAB and has more free space than the currently
159+
* retained chunk.
160+
*/
161+
if (shouldRetain()) {
162+
retainedChunk = currentChunk;
163+
}
164+
}
165+
}
166+
167+
@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-23-ga/src/hotspot/share/gc/g1/g1AllocRegion.cpp#L275-L287")
168+
@Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true)
169+
private boolean shouldRetain() {
170+
assert JavaSpinLockUtils.isLocked(this, LOCK_OFFSET);
171+
172+
UnsignedWord freeBytes = HeapChunk.availableObjectMemory(currentChunk);
173+
UnsignedWord minTlabSize = Word.unsigned(TlabOptionCache.singleton().getMinTlabSize());
174+
if (freeBytes.belowThan(minTlabSize)) {
175+
return false;
176+
}
177+
178+
return retainedChunk.isNull() || freeBytes.aboveOrEqual(HeapChunk.availableObjectMemory(retainedChunk));
179+
}
180+
181+
@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-25+4/src/hotspot/share/gc/g1/g1AllocRegion.cpp#L130-L154")
182+
@Uninterruptible(reason = "Returns uninitialized memory.", callerMustBe = true)
183+
private Pointer newAllocChunkAndAllocate(UnsignedWord requestedSize) {
184+
assert JavaSpinLockUtils.isLocked(this, LOCK_OFFSET);
185+
186+
AlignedHeader newChunk = requestNewAlignedChunk();
187+
if (newChunk.isNonNull()) {
188+
Pointer result = AlignedHeapChunk.allocateMemory(newChunk, requestedSize);
189+
assert result.isNonNull();
190+
191+
HeapChunk.setNext(newChunk, currentChunk);
192+
193+
/* Publish the new chunk (other threads need to see a fully initialized chunk). */
194+
MembarNode.memoryBarrier(FenceKind.STORE_STORE);
195+
currentChunk = newChunk;
196+
return result;
197+
} else {
198+
return Word.nullPointer();
199+
}
200+
}
201+
202+
@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-25+4/src/hotspot/share/gc/g1/g1HeapRegion.inline.hpp#L186-L208")
203+
@Uninterruptible(reason = "Returns uninitialized memory, modifies alloc chunk.", callerMustBe = true)
204+
private static Pointer allocateParallel(AlignedHeader chunk, UnsignedWord minSize, UnsignedWord requestedSize, WordPointer actualSize) {
205+
206+
do {
207+
Pointer top = (Pointer) chunk.getTopOffset(CHUNK_HEADER_TOP_IDENTITY);
208+
209+
UnsignedWord available = chunk.getEndOffset().subtract(top);
210+
UnsignedWord wantToAllocate = UnsignedUtils.min(available, requestedSize);
211+
if (wantToAllocate.belowThan(minSize)) {
212+
return Word.nullPointer();
213+
}
214+
215+
UnsignedWord newTop = top.add(wantToAllocate);
216+
ObjectLayout ol = ConfigurationValues.getObjectLayout();
217+
assert ol.isAligned(top.rawValue()) && ol.isAligned(newTop.rawValue());
218+
if (((Pointer) chunk).logicCompareAndSwapWord(HeapChunk.Header.offsetOfTopOffset(), top, newTop, CHUNK_HEADER_TOP_IDENTITY)) {
219+
actualSize.write(wantToAllocate);
220+
return HeapChunk.asPointer(chunk).add(top);
221+
}
222+
} while (true);
223+
224+
}
225+
226+
@Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true)
227+
private static AlignedHeader requestNewAlignedChunk() {
228+
AlignedHeader newChunk = HeapImpl.getChunkProvider().produceAlignedChunk();
229+
HeapImpl.getAccounting().increaseEdenUsedBytes(HeapParameters.getAlignedHeapChunkSize());
230+
return newChunk;
231+
}
232+
233+
public void retireChunksToEden() {
234+
VMOperation.guaranteeInProgressAtSafepoint("HeapAllocation.retireChunksToEden");
235+
236+
AlignedHeader chunk = currentChunk;
237+
currentChunk = Word.nullPointer();
238+
retainedChunk = Word.nullPointer();
239+
240+
Space eden = HeapImpl.getHeapImpl().getYoungGeneration().getEden();
241+
while (chunk.isNonNull()) {
242+
AlignedHeader next = HeapChunk.getNext(chunk);
243+
HeapChunk.setNext(chunk, Word.nullPointer());
244+
eden.appendAlignedHeapChunk(chunk);
245+
chunk = next;
246+
}
247+
248+
}
249+
250+
/**
251+
* Return the remaining space in the current alloc chunk, but not less than the min. TLAB size.
252+
*
253+
* Also, this value can be at most the size available for objects within an aligned chunk, as
254+
* bigger TLABs are not possible.
255+
*/
256+
@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-23-ga/src/hotspot/share/gc/g1/g1Allocator.cpp#L184-L203")
257+
@Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true)
258+
public UnsignedWord unsafeMaxTlabAllocSize() {
259+
UnsignedWord maxTlabSize = AlignedHeapChunk.getUsableSizeForObjects();
260+
UnsignedWord minTlabSize = Word.unsigned(TlabOptionCache.singleton().getMinTlabSize());
261+
if (currentChunk.isNull() || HeapChunk.availableObjectMemory(currentChunk).belowThan(minTlabSize)) {
262+
/*
263+
* The next TLAB allocation will most probably happen in a new chunk, therefore we can
264+
* attempt to allocate the maximum allowed TLAB size.
265+
*/
266+
return maxTlabSize;
267+
}
268+
269+
return UnsignedUtils.min(HeapChunk.availableObjectMemory(currentChunk), maxTlabSize);
270+
}
271+
272+
@Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true)
273+
private static boolean fitsInAlignedChunk(UnsignedWord size) {
274+
return size.belowOrEqual(AlignedHeapChunk.getUsableSizeForObjects());
275+
}
276+
277+
@Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true)
278+
public void tearDown() {
279+
// This implicitly frees retainedChunk as well.
280+
HeapChunkProvider.freeAlignedChunkList(currentChunk);
281+
currentChunk = Word.nullPointer();
282+
retainedChunk = Word.nullPointer();
283+
}
284+
285+
boolean printLocationInfo(Log log, Pointer ptr) {
286+
AlignedHeader chunk = currentChunk;
287+
while (chunk.isNonNull()) {
288+
if (HeapChunk.asPointer(chunk).belowOrEqual(ptr) && ptr.belowThan(HeapChunk.getEndPointer(chunk))) {
289+
boolean unusablePart = ptr.aboveOrEqual(HeapChunk.getTopPointer(chunk));
290+
printChunkInfo(log, chunk, unusablePart);
291+
return true;
292+
}
293+
294+
chunk = HeapChunk.getNext(chunk);
295+
}
296+
return false;
297+
}
298+
299+
private static void printChunkInfo(Log log, AlignedHeader chunk, boolean unusablePart) {
300+
String unusable = unusablePart ? "unusable part of " : "";
301+
log.string("points into ").string(unusable).string("heap allocation aligned chunk").spaces(1).zhex(chunk).spaces(1);
302+
}
303+
304+
void logChunks(Log log, String spaceName) {
305+
HeapChunkLogging.logChunks(log, currentChunk, spaceName, false);
306+
}
307+
308+
}

substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapChunk.java

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import java.util.function.IntUnaryOperator;
2828

2929
import org.graalvm.nativeimage.c.struct.RawField;
30+
import org.graalvm.nativeimage.c.struct.RawFieldOffset;
3031
import org.graalvm.nativeimage.c.struct.RawFieldAddress;
3132
import org.graalvm.nativeimage.c.struct.RawStructure;
3233
import org.graalvm.nativeimage.c.struct.UniqueLocationIdentity;
@@ -44,8 +45,10 @@
4445
import com.oracle.svm.core.heap.ObjectVisitor;
4546
import com.oracle.svm.core.hub.LayoutEncoding;
4647
import com.oracle.svm.core.identityhashcode.IdentityHashCodeSupport;
48+
import com.oracle.svm.core.util.VMError;
4749

4850
import jdk.graal.compiler.api.directives.GraalDirectives;
51+
import jdk.graal.compiler.nodes.NamedLocationIdentity;
4952
import jdk.graal.compiler.word.Word;
5053

5154
/**
@@ -78,6 +81,9 @@
7881
* allocated within the HeapChunk are examined by the collector.
7982
*/
8083
public final class HeapChunk {
84+
85+
public static final LocationIdentity CHUNK_HEADER_TOP_IDENTITY = NamedLocationIdentity.mutable("ChunkHeader.top");
86+
8187
private HeapChunk() { // all static
8288
}
8389

@@ -106,12 +112,16 @@ public interface Header<T extends Header<T>> extends HeaderPadding {
106112
* in the chunk.
107113
*/
108114
@RawField
109-
@UniqueLocationIdentity
110-
UnsignedWord getTopOffset();
115+
UnsignedWord getTopOffset(LocationIdentity topIdentity);
111116

112117
@RawField
113-
@UniqueLocationIdentity
114-
void setTopOffset(UnsignedWord newTop);
118+
void setTopOffset(UnsignedWord newTop, LocationIdentity topIdentity);
119+
120+
@RawFieldOffset
121+
static int offsetOfTopOffset() {
122+
// replaced
123+
throw VMError.shouldNotReachHereAtRuntime(); // ExcludeFromJacocoGeneratedReport
124+
}
115125

116126
/** Offset of the limit of memory available for allocation. */
117127
@RawField
@@ -194,18 +204,18 @@ public static void initialize(Header<?> chunk, Pointer objectsStart, UnsignedWor
194204
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
195205
public static UnsignedWord getTopOffset(Header<?> that) {
196206
assert getTopPointer(that).isNonNull() : "Not safe: top currently points to NULL.";
197-
return that.getTopOffset();
207+
return that.getTopOffset(CHUNK_HEADER_TOP_IDENTITY);
198208
}
199209

200210
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
201211
public static Pointer getTopPointer(Header<?> that) {
202-
return asPointer(that).add(that.getTopOffset());
212+
return asPointer(that).add(that.getTopOffset(CHUNK_HEADER_TOP_IDENTITY));
203213
}
204214

205215
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
206216
public static void setTopPointer(Header<?> that, Pointer newTop) {
207217
// Note that the address arithmetic also works for newTop == NULL, e.g. in TLAB allocation
208-
that.setTopOffset(newTop.subtract(asPointer(that)));
218+
that.setTopOffset(newTop.subtract(asPointer(that)), CHUNK_HEADER_TOP_IDENTITY);
209219
}
210220

211221
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
@@ -324,7 +334,7 @@ private static void callVisitor(ObjectVisitor visitor, Object obj) {
324334

325335
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
326336
public static UnsignedWord availableObjectMemory(Header<?> that) {
327-
return that.getEndOffset().subtract(that.getTopOffset());
337+
return that.getEndOffset().subtract(that.getTopOffset(CHUNK_HEADER_TOP_IDENTITY));
328338
}
329339

330340
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)

0 commit comments

Comments
 (0)