Skip to content

[GR-62503] [GR-64172] Remove excess allocations during chunked image-heap laying-out #10993

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,14 @@

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.NavigableMap;
import java.util.Queue;
import java.util.TreeMap;

import com.oracle.svm.core.config.ConfigurationValues;
import com.oracle.svm.core.image.ImageHeapObject;
import com.oracle.svm.core.image.ImageHeapPartition;
import com.oracle.svm.core.meta.SubstrateObjectConstant;

import jdk.graal.compiler.debug.Assertions;

Expand Down Expand Up @@ -124,63 +120,44 @@ private ImageHeapObject dequeueBestFit(NavigableMap<Long, Queue<ImageHeapObject>
if (nbytes < minimumObjectSize) {
return null;
}
Map.Entry<Long, Queue<ImageHeapObject>> entry = sortedObjects.floorEntry(nbytes);
if (entry == null) {

/**
* Find a floor entry. We are purposefully not calling {@link TreeMap#getFloorEntry(Object)}
* as that method allocates a new entry object. Instead, we fetch the floor key and get the
* value for the returned key.
*/
Long floorKey = sortedObjects.floorKey(nbytes);
if (floorKey == null) {
return null;
}
Queue<ImageHeapObject> queue = entry.getValue();
ImageHeapObject info = queue.remove();
Queue<ImageHeapObject> queue = sortedObjects.get(floorKey);
ImageHeapObject obj = queue.remove();
if (queue.isEmpty()) {
sortedObjects.remove(entry.getKey());
sortedObjects.remove(floorKey);
}
return info;
return obj;
}

private NavigableMap<Long, Queue<ImageHeapObject>> createSortedObjectsMap() {
ImageHeapObject[] sorted = objects.toArray(new ImageHeapObject[0]);
Arrays.sort(sorted, new SizeComparator());

NavigableMap<Long, Queue<ImageHeapObject>> map = new TreeMap<>();
Queue<ImageHeapObject> currentQueue = null;
long currentObjectsSize = -1;
for (ImageHeapObject obj : sorted) {
for (ImageHeapObject obj : objects) {
long objSize = obj.getSize();
if (objSize != currentObjectsSize) {
assert objSize > currentObjectsSize && objSize >= ConfigurationValues.getObjectLayout().getMinImageHeapObjectSize() : Assertions.errorMessage(obj, objSize);
currentObjectsSize = objSize;
currentQueue = new ArrayDeque<>();
map.put(currentObjectsSize, currentQueue);
}
assert currentQueue != null;
currentQueue.add(obj);
assert objSize >= ConfigurationValues.getObjectLayout().getMinImageHeapObjectSize() : Assertions.errorMessage(obj, objSize);
Queue<ImageHeapObject> q = map.computeIfAbsent(objSize, k -> new ArrayDeque<>());
q.add(obj);
}
return map;
}

private void appendAllocatedObject(ImageHeapObject info, long allocationOffset) {
if (firstObject == null) {
firstObject = extractObject(info);
firstObject = info.getWrapped();
}
assert info.getPartition() == this;
long offsetInPartition = allocationOffset - startOffset;
assert ConfigurationValues.getObjectLayout().isAligned(offsetInPartition) : "start: " + offsetInPartition + " must be aligned.";
info.setOffsetInPartition(offsetInPartition);
lastObject = extractObject(info);
}

private static Object extractObject(ImageHeapObject info) {
if (info.getConstant() instanceof SubstrateObjectConstant) {
return info.getObject();
} else {
/*
* The info wraps an ImageHeapObject, i.e., a build time representation of an object
* that is not backed by a raw hosted object. We set the partition limit to the actual
* constant. The constant reflection provider knows that this is a build time value, and
* it will not wrap it in a JavaConstant when reading it. This case is not different
* from normal objects referencing simulated objects.
*/
return info.getConstant();
}
lastObject = info.getWrapped();
}

@Override
Expand Down Expand Up @@ -229,11 +206,4 @@ public boolean isFiller() {
public String toString() {
return name;
}

private static final class SizeComparator implements Comparator<ImageHeapObject> {
@Override
public int compare(ImageHeapObject o1, ImageHeapObject o2) {
return Long.signum(o1.getSize() - o2.getSize());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,8 @@
*/
package com.oracle.svm.core.image;

import java.util.Collection;

public interface ImageHeap {
Collection<? extends ImageHeapObject> getObjects();
Iterable<? extends ImageHeapObject> getObjects();

ImageHeapObject addLateToImageHeap(Object object, Object reason);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,15 @@
*/
package com.oracle.svm.core.image;

import jdk.vm.ci.meta.JavaConstant;

public interface ImageHeapObject {
long getSize();

Object getWrapped();

Object getObject();

Class<?> getObjectClass();

JavaConstant getConstant();

void setHeapPartition(ImageHeapPartition value);

void setOffsetInPartition(long value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
import com.oracle.svm.core.util.UserError;
import com.oracle.svm.core.util.VMError;
import com.oracle.svm.hosted.HostedConfiguration;
import com.oracle.svm.hosted.ameta.SVMHostedValueProvider;
import com.oracle.svm.hosted.config.DynamicHubLayout;
import com.oracle.svm.hosted.config.HybridLayout;
import com.oracle.svm.hosted.imagelayer.HostedImageLayerBuildingSupport;
Expand Down Expand Up @@ -874,7 +875,6 @@ public Class<?> getObjectClass() {
return clazz.getJavaClass();
}

@Override
public ImageHeapConstant getConstant() {
return constant;
}
Expand Down Expand Up @@ -912,6 +912,25 @@ public long getSize() {
return size;
}

/**
* The image builder references heap objects via {@link ImageHeapConstant}, i.e., a build
* time representation of an object that permits hosted constants which may or may not be
* backed by a raw hosted object. For example the class initializer simulation will produce
* simulated objects, not present in the underlying VM. The {@link ImageHeapConstant} can be
* referenced directly from raw objects and the constant reflection provider knows that this
* is a build time value, and it will not wrap it again in a {@link JavaConstant} when
* reading it (see {@link SVMHostedValueProvider#interceptHosted(JavaConstant)}). This is
* useful for example when encoding the heap partitions limits: we simply use the constant
* representation regardless of whether the constant is backed by an object of the host VM
* or not. This case is not different from normal objects referencing simulated objects. The
* {@link NativeImageHeapWriter} will recursively unwrap the {@link ImageHeapConstant} all
* the way to primitive values, so there's no risk of it leaking into the runtime.
*/
@Override
public Object getWrapped() {
return getConstant();
}

public int getIdentityHashCode() {
return identityHashCode;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
package com.oracle.svm.hosted.webimage.wasmgc.image;

import java.nio.ByteBuffer;
import java.util.stream.StreamSupport;

import com.oracle.graal.pointsto.heap.ImageHeapPrimitiveArray;
import com.oracle.svm.core.image.ImageHeap;
Expand Down Expand Up @@ -64,7 +65,7 @@ public ImageHeapPartition[] getPartitions() {

@Override
public void assignObjectToPartition(ImageHeapObject info, boolean immutable, boolean references, boolean relocatable, boolean patched) {
if (info.getConstant() instanceof ImageHeapPrimitiveArray) {
if (info.getWrapped() instanceof ImageHeapPrimitiveArray) {
singlePartition.add(info);
} else {
pseudoPartition.add(info);
Expand All @@ -76,7 +77,7 @@ public WasmGCImageHeapLayoutInfo layout(ImageHeap imageHeap, int pageSize) {
layoutPseudoPartition();
doLayout();

long totalSize = imageHeap.getObjects().stream().mapToLong(ImageHeapObject::getSize).sum();
long totalSize = StreamSupport.stream(imageHeap.getObjects().spliterator(), false).mapToLong(ImageHeapObject::getSize).sum();
long serializedSize = singlePartition.getStartOffset() + singlePartition.getSize() - startOffset;
return new WasmGCImageHeapLayoutInfo(startOffset, serializedSize, totalSize);
}
Expand All @@ -85,7 +86,7 @@ private void doLayout() {
int offset = 0;
for (ImageHeapObject info : singlePartition.getObjects()) {
// Only primitive arrays are supposed to be in this partition
ImageHeapPrimitiveArray primitiveArray = (ImageHeapPrimitiveArray) info.getConstant();
ImageHeapPrimitiveArray primitiveArray = (ImageHeapPrimitiveArray) info.getWrapped();
info.setOffsetInPartition(offset);
offset += primitiveArray.getType().getComponentType().getStorageKind().getByteCount() * primitiveArray.getLength();
}
Expand Down
Loading