From 740e1f29df34e446b8e7f2b412654f955ca8bf76 Mon Sep 17 00:00:00 2001 From: Codrut Stancu Date: Tue, 8 Mar 2022 10:09:45 -0800 Subject: [PATCH] Reduce heap snapshot footprint. --- .../pointsto/heap/HeapSnapshotVerifier.java | 141 ++++++++++++------ .../oracle/graal/pointsto/heap/ImageHeap.java | 49 +++++- .../graal/pointsto/heap/ImageHeapObject.java | 72 ++++++--- .../graal/pointsto/heap/ImageHeapScanner.java | 91 +++++++---- .../oracle/graal/pointsto/heap/TypeData.java | 46 +++--- .../graal/pointsto/meta/AnalysisType.java | 5 +- .../diagnostic/HostedHeapDumpFeature.java | 12 +- .../svm/hosted/heap/SVMImageHeapScanner.java | 3 +- 8 files changed, 295 insertions(+), 124 deletions(-) diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/HeapSnapshotVerifier.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/HeapSnapshotVerifier.java index 23f367ec643a..01802174d168 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/HeapSnapshotVerifier.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/HeapSnapshotVerifier.java @@ -134,45 +134,66 @@ public boolean forNullFieldValue(JavaConstant receiver, AnalysisField field, Sca } @Override + @SuppressWarnings({"unchecked", "rawtypes"}) public boolean forNonNullFieldValue(JavaConstant receiver, AnalysisField field, JavaConstant fieldValue, ScanReason reason) { if (field.isStatic()) { TypeData typeData = field.getDeclaringClass().getOrComputeData(); - AnalysisFuture fieldValueTask = typeData.getFieldTask(field); - if (fieldValueTask.isDone()) { - JavaConstant fieldSnapshot = fieldValueTask.guardedGet(); - if (!Objects.equals(fieldSnapshot, fieldValue)) { - Consumer onAnalysisModified = (deepReason) -> onStaticFieldMismatch(field, fieldSnapshot, fieldValue, deepReason); - scanner.patchStaticField(typeData, field, fieldValue, reason, onAnalysisModified).ensureDone(); - heapPatched = true; + Object fieldValueTask = typeData.getFieldValue(field); + if (fieldValueTask instanceof JavaConstant) { + JavaConstant fieldSnapshot = (JavaConstant) fieldValueTask; + verifyStaticFieldValue(typeData, field, fieldSnapshot, fieldValue, reason); + } else if (fieldValueTask instanceof AnalysisFuture) { + AnalysisFuture future = (AnalysisFuture) fieldValueTask; + if (future.isDone()) { + JavaConstant fieldSnapshot = future.guardedGet(); + verifyStaticFieldValue(typeData, field, fieldSnapshot, fieldValue, reason); + } else { + onStaticFieldNotComputed(field, fieldValue, reason); } - } else { - onStaticFieldNotComputed(field, fieldValue, reason); } } else { ImageHeapInstance receiverObject = (ImageHeapInstance) getReceiverObject(receiver, reason); - AnalysisFuture fieldValueTask = receiverObject.getFieldTask(field); - if (fieldValueTask.isDone()) { - JavaConstant fieldSnapshot = fieldValueTask.guardedGet(); - if (!Objects.equals(fieldSnapshot, fieldValue)) { - Consumer onAnalysisModified = (deepReason) -> onInstanceFieldMismatch(receiverObject, field, fieldSnapshot, fieldValue, deepReason); + Object fieldValueTask = receiverObject.getFieldValue(field); + if (fieldValueTask instanceof JavaConstant) { + JavaConstant fieldSnapshot = (JavaConstant) fieldValueTask; + verifyInstanceFieldValue(field, receiverObject, fieldSnapshot, fieldValue, reason); + } else if (fieldValueTask instanceof AnalysisFuture) { + AnalysisFuture future = (AnalysisFuture) fieldValueTask; + if (future.isDone()) { + JavaConstant fieldSnapshot = future.guardedGet(); + verifyInstanceFieldValue(field, receiverObject, fieldSnapshot, fieldValue, reason); + } else { + /* + * There may be some instance fields not yet computed because the verifier + * can insert new objects for annotation proxy implementations when scanning + * types. The annotations are set lazily, based on reachability, since we + * only want annotations in the heap that are otherwise marked as used. + */ + Consumer onAnalysisModified = (deepReason) -> onInstanceFieldNotComputed(receiverObject, field, fieldValue, deepReason); scanner.patchInstanceField(receiverObject, field, fieldValue, reason, onAnalysisModified).ensureDone(); heapPatched = true; } - } else { - /* - * There may be some instance fields not yet computed because the verifier can - * insert new objects for annotation proxy implementations when scanning types. - * The annotations are set lazily, based on reachability, since we only want - * annotations in the heap that are otherwise marked as used. - */ - Consumer onAnalysisModified = (deepReason) -> onInstanceFieldNotComputed(receiverObject, field, fieldValue, deepReason); - scanner.patchInstanceField(receiverObject, field, fieldValue, reason, onAnalysisModified).ensureDone(); - heapPatched = true; } } return false; } + private void verifyStaticFieldValue(TypeData typeData, AnalysisField field, JavaConstant fieldSnapshot, JavaConstant fieldValue, ScanReason reason) { + if (!Objects.equals(fieldSnapshot, fieldValue)) { + Consumer onAnalysisModified = (deepReason) -> onStaticFieldMismatch(field, fieldSnapshot, fieldValue, deepReason); + scanner.patchStaticField(typeData, field, fieldValue, reason, onAnalysisModified).ensureDone(); + heapPatched = true; + } + } + + private void verifyInstanceFieldValue(AnalysisField field, ImageHeapInstance receiverObject, JavaConstant fieldSnapshot, JavaConstant fieldValue, ScanReason reason) { + if (!Objects.equals(fieldSnapshot, fieldValue)) { + Consumer onAnalysisModified = (deepReason) -> onInstanceFieldMismatch(receiverObject, field, fieldSnapshot, fieldValue, deepReason); + scanner.patchInstanceField(receiverObject, field, fieldValue, reason, onAnalysisModified).ensureDone(); + heapPatched = true; + } + } + @Override public boolean forNullArrayElement(JavaConstant array, AnalysisType arrayType, int elementIndex, ScanReason reason) { boolean result = scanner.getScanningObserver().forNullArrayElement(array, arrayType, elementIndex, reason); @@ -194,26 +215,47 @@ public boolean forNonNullArrayElement(JavaConstant array, AnalysisType arrayType return false; } + @SuppressWarnings({"unchecked", "rawtypes"}) private ImageHeapObject getReceiverObject(JavaConstant constant, ScanReason reason) { - AnalysisFuture task = imageHeap.getTask(constant); - if (task == null || !task.isDone()) { - throw error(reason, "Task %s for constant %s.", (task == null ? "is null" : "not yet executed"), constant); + Object task = imageHeap.getTask(constant); + if (task == null) { + throw error(reason, "Task is null for constant %s.", constant); + } else if (task instanceof ImageHeapObject) { + return (ImageHeapObject) task; + } else { + assert task instanceof AnalysisFuture; + AnalysisFuture future = ((AnalysisFuture) task); + if (future.isDone()) { + return future.guardedGet(); + } else { + throw error(reason, "Task not yet executed for constant %s.", constant); + } } - return task.guardedGet(); } @Override + @SuppressWarnings({"unchecked", "rawtypes"}) public void forEmbeddedRoot(JavaConstant root, ScanReason reason) { - AnalysisFuture rootTask = imageHeap.getTask(root); + Object rootTask = imageHeap.getTask(root); if (rootTask == null) { throw error(reason, "No snapshot task found for embedded root %s %n", root); - } else if (rootTask.isDone()) { - JavaConstant rootSnapshot = rootTask.guardedGet().getObject(); - if (!Objects.equals(rootSnapshot, root)) { - throw error(reason, "Value mismatch for embedded root %n snapshot: %s %n new value: %s %n", rootSnapshot, root); - } + } else if (rootTask instanceof ImageHeapObject) { + JavaConstant snapshot = ((ImageHeapObject) rootTask).getObject(); + verifyEmbeddedRoot(snapshot, root, reason); } else { - throw error(reason, "Snapshot not yet computed for embedded root %n new value: %s %n", root); + AnalysisFuture future = ((AnalysisFuture) rootTask); + if (future.isDone()) { + JavaConstant snapshot = future.guardedGet().getObject(); + verifyEmbeddedRoot(snapshot, root, reason); + } else { + throw error(reason, "Snapshot not yet computed for embedded root %n new value: %s %n", root); + } + } + } + + private void verifyEmbeddedRoot(JavaConstant rootSnapshot, JavaConstant root, ScanReason reason) { + if (!Objects.equals(rootSnapshot, root)) { + throw error(reason, "Value mismatch for embedded root %n snapshot: %s %n new value: %s %n", rootSnapshot, root); } } @@ -241,26 +283,37 @@ private void ensureTypeScanned(JavaConstant typeConstant, AnalysisType type, Sca ensureTypeScanned(null, typeConstant, type, reason); } - private void ensureTypeScanned(JavaConstant object, JavaConstant typeConstant, AnalysisType type, ScanReason reason) { + @SuppressWarnings({"unchecked", "rawtypes"}) + private void ensureTypeScanned(JavaConstant value, JavaConstant typeConstant, AnalysisType type, ScanReason reason) { AnalysisError.guarantee(type.isReachable(), "The heap snapshot verifier discovered a type not marked as reachable " + type.toJavaName()); - AnalysisFuture task = imageHeap.getTask(typeConstant); + Object task = imageHeap.getTask(typeConstant); /* Make sure the DynamicHub value is scanned. */ if (task == null) { onNoTaskForClassConstant(type, reason); scanner.toImageHeapObject(typeConstant, reason, null); heapPatched = true; + } else if (task instanceof ImageHeapObject) { + JavaConstant snapshot = ((ImageHeapObject) task).getObject(); + verifyTypeConstant(snapshot, typeConstant, reason); } else { - if (task.isDone()) { - JavaConstant snapshot = task.guardedGet().getObject(); - if (!Objects.equals(snapshot, typeConstant)) { - throw error(reason, "Value mismatch for class constant snapshot: %s %n new value: %s %n", snapshot, typeConstant); - } + assert task instanceof AnalysisFuture; + AnalysisFuture future = ((AnalysisFuture) task); + if (future.isDone()) { + JavaConstant snapshot = future.guardedGet().getObject(); + verifyTypeConstant(snapshot, typeConstant, reason); } else { - onTaskForClassConstantNotDone(object, type, reason); - task.ensureDone(); + onTaskForClassConstantNotDone(value, type, reason); + future.ensureDone(); } } } + + private void verifyTypeConstant(JavaConstant snapshot, JavaConstant typeConstant, ScanReason reason) { + if (!Objects.equals(snapshot, typeConstant)) { + throw error(reason, "Value mismatch for class constant snapshot: %s %n new value: %s %n", snapshot, typeConstant); + } + } + } private void onNoTaskForClassConstant(AnalysisType type, ScanReason reason) { diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageHeap.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageHeap.java index cd9efdaddd9c..c60737c3775e 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageHeap.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageHeap.java @@ -34,24 +34,59 @@ import jdk.vm.ci.meta.JavaConstant; +/** + * The heap snapshot. It stores all object snapshots and provides methods to access and update them. + */ public class ImageHeap { - /** Map the original object *and* the replaced object to the HeapObject snapshot. */ - protected ConcurrentHashMap> heapObjects; - /** Store a mapping from types to all scanned objects. */ - protected Map> typesToObjects; + /** + * Map the original object *and* the replaced object to the same snapshot. The value is either a + * not-yet-executed {@link AnalysisFuture} of {@link ImageHeapObject} or its results, an + * {@link ImageHeapObject}. + */ + private final ConcurrentHashMap heapObjects; + /** Store a mapping from types to object snapshots. */ + private final Map> typesToObjects; + + /* + * Note on the idea of merging the heapObjects and typesToObjects maps: + * + * - heapObjects maps both the original and the replaced JavaConstant objects to the + * corresponding ImageHeapObject (which also wraps the replaced JavaConstant). + * + * - typesToObjects maps the AnalysisType of the replaced objects to the collection of + * corresponding ImageHeapObject + * + * - If we were to combine the two into a Map> which + * type do we use as a key? That would be the type of the JavaConstant object, but that doesn't + * always match with the type of the ImageHeapObject in case of object replacers that change + * type. This can lead to issues when trying to iterate objects of a specific type, e.g., to + * walk its fields. We could use the type of the replaced object wrapped in the ImageHeapObject, + * but then we wouldn't have a direct way to get from the original JavaConstant to the + * corresponding ImageHeapObject. Which means that we would need to run the replacers *before* + * checking if we already have a registered ImageHeapObject task (in + * ImageHeapScanner.getOrCreateConstantReachableTask()), so may end up running the replacers + * more often, otherwise we can end up with duplicated ImageHeapObject snapshots. + */ public ImageHeap() { heapObjects = new ConcurrentHashMap<>(); typesToObjects = new ConcurrentHashMap<>(); } - public AnalysisFuture getTask(JavaConstant constant) { + /** Record the future computing the snapshot or its result. */ + public Object getTask(JavaConstant constant) { return heapObjects.get(constant); } - public AnalysisFuture addTask(JavaConstant constant, AnalysisFuture object) { - return heapObjects.putIfAbsent(constant, object); + /** Record the future computing the snapshot in the heap. */ + public Object setTask(JavaConstant constant, AnalysisFuture task) { + return heapObjects.putIfAbsent(constant, task); + } + + /** Record the snapshot in the heap. */ + public void setValue(JavaConstant constant, ImageHeapObject value) { + heapObjects.put(constant, value); } public Set getObjects(AnalysisType type) { diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageHeapObject.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageHeapObject.java index 6b22b0c8e8f1..37f338afd7ca 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageHeapObject.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageHeapObject.java @@ -24,6 +24,8 @@ */ package com.oracle.graal.pointsto.heap; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; import java.util.function.Consumer; import com.oracle.graal.pointsto.ObjectScanner; @@ -34,13 +36,15 @@ import jdk.vm.ci.meta.JavaConstant; /** - * Model for snapshotted objects. It stores the replaced object, i.e., the result of applying object - * replacers on the original object, and the instance field values of this object. The field values - * are stored as JavaConstant to also encode primitive values. ImageHeapObject are created only - * after an object is processed through the object replacers. + * It represents an object snapshot. It stores the replaced object, i.e., the result of applying + * object replacers on the original object, and the instance field values or array elements of this + * object. The field values are stored as JavaConstant to also encode primitive values. + * ImageHeapObject are created only after an object is processed through the object replacers. */ public class ImageHeapObject { - /** Store the object, already processed by the object transformers. */ + /** + * Store the object, already processed by the object transformers. + */ private final JavaConstant object; ImageHeapObject(JavaConstant object) { @@ -68,42 +72,64 @@ public int hashCode() { } } +/** + * This class implements an instance object snapshot. It stores the field values in an Object[], + * indexed by {@link AnalysisField#getPosition()}. Each array entry is either + *
  • a not-yet-executed {@link AnalysisFuture} of {@link JavaConstant} which captures the + * original, hosted field value and contains logic to transform and replace this value
  • , or + *
  • the result of executing the future, a replaced {@link JavaConstant}, i.e., the snapshot.
  • + * + * The future task is executed when the field is marked as read. Moreover, the future is + * self-replacing, i.e., when it is executed it also calls + * {@link #setFieldValue(AnalysisField, JavaConstant)} and updates the corresponding entry. + */ final class ImageHeapInstance extends ImageHeapObject { - /** Store original field values of the object. */ - private final AnalysisFuture[] objectFieldValues; + private static final VarHandle arrayHandle = MethodHandles.arrayElementVarHandle(Object[].class); - ImageHeapInstance(JavaConstant object, AnalysisFuture[] objectFieldValues) { - super(object); - this.objectFieldValues = objectFieldValues; - } + /** + * Stores either an {@link AnalysisFuture} of {@link JavaConstant} or its result, a + * {@link JavaConstant}. + */ + private final Object[] values; - public JavaConstant readFieldValue(AnalysisField field) { - return objectFieldValues[field.getPosition()].ensureDone(); + ImageHeapInstance(JavaConstant object, int length) { + super(object); + this.values = new Object[length]; } /** - * Return a task for transforming and snapshotting the field value, effectively a future for - * {@link ImageHeapScanner#onFieldValueReachable(AnalysisField, JavaConstant, JavaConstant, ObjectScanner.ScanReason, Consumer)}. + * Record the task computing the field value. It will be retrieved and executed when the field + * is marked as read. */ - public AnalysisFuture getFieldTask(AnalysisField field) { - return objectFieldValues[field.getPosition()]; + public void setFieldTask(AnalysisField field, AnalysisFuture task) { + arrayHandle.setVolatile(this.values, field.getPosition(), task); } /** - * Read the field value, executing the field task in this thread if not already executed. + * Record the field value produced by the task set in + * {@link #setFieldTask(AnalysisField, AnalysisFuture)}, i.e., the snapshot, already transformed + * and replaced. */ - public JavaConstant readField(AnalysisField field) { - return objectFieldValues[field.getPosition()].ensureDone(); + public void setFieldValue(AnalysisField field, JavaConstant value) { + arrayHandle.setVolatile(this.values, field.getPosition(), value); } - public void setFieldTask(AnalysisField field, AnalysisFuture task) { - objectFieldValues[field.getPosition()] = task; + /** + * Return either a task for transforming the field value, effectively a future for + * {@link ImageHeapScanner#onFieldValueReachable(AnalysisField, JavaConstant, JavaConstant, ObjectScanner.ScanReason, Consumer)}, + * or the result of executing the task, i.e., a {@link JavaConstant}. + */ + public Object getFieldValue(AnalysisField field) { + return arrayHandle.getVolatile(this.values, field.getPosition()); } } final class ImageHeapArray extends ImageHeapObject { - /** Contains the already scanned array elements. */ + + /** + * Contains the already scanned array elements. + */ private final JavaConstant[] arrayElementValues; ImageHeapArray(JavaConstant object, JavaConstant[] arrayElementValues) { diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageHeapScanner.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageHeapScanner.java index d1d678f55f08..6d95bffa8b08 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageHeapScanner.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageHeapScanner.java @@ -26,7 +26,6 @@ import java.lang.reflect.Field; import java.util.Collection; -import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.Optional; @@ -60,7 +59,6 @@ import jdk.vm.ci.meta.ConstantReflectionProvider; import jdk.vm.ci.meta.JavaConstant; import jdk.vm.ci.meta.JavaKind; -import jdk.vm.ci.meta.ResolvedJavaField; /** * Scanning is triggered when: @@ -105,7 +103,7 @@ public void scanEmbeddedRoot(JavaConstant root, BytecodePosition position) { if (isNonNullObjectConstant(root)) { AnalysisType type = metaAccess.lookupJavaType(root); type.registerAsReachable(); - getOrCreateConstantReachableTask(root, new EmbeddedRootScan(position, root), null).ensureDone(); + getOrCreateConstantReachableTask(root, new EmbeddedRootScan(position, root), null); } } @@ -115,7 +113,7 @@ public void onFieldRead(AnalysisField field) { if (isValueAvailable(field)) { AnalysisType declaringClass = field.getDeclaringClass(); if (field.isStatic()) { - postTask(() -> declaringClass.getOrComputeData().readField(field)); + snapshotFieldValue(field, declaringClass.getOrComputeData().getFieldValue(field)); } else { /* Trigger field scanning for the already processed objects. */ postTask(() -> onInstanceFieldRead(field, declaringClass)); @@ -126,7 +124,7 @@ public void onFieldRead(AnalysisField field) { private void onInstanceFieldRead(AnalysisField field, AnalysisType type) { for (AnalysisType subtype : type.getSubTypes()) { for (ImageHeapObject imageHeapObject : imageHeap.getObjects(subtype)) { - postTask(((ImageHeapInstance) imageHeapObject).getFieldTask(field)); + snapshotFieldValue(field, ((ImageHeapInstance) imageHeapObject).getFieldValue(field)); } /* Subtypes include this type itself. */ if (!subtype.equals(type)) { @@ -147,13 +145,18 @@ public TypeData computeTypeData(AnalysisType type) { * reachability status. The field value is processed when a field is marked as reachable, in * onFieldValueReachable(). */ - Map> rawStaticFieldValues = new HashMap<>(); - for (AnalysisField field : type.getStaticFields()) { + AnalysisField[] staticFields = type.getStaticFields(); + TypeData data = new TypeData(staticFields.length); + for (AnalysisField field : staticFields) { ValueSupplier rawFieldValue = readHostedFieldValue(field, null); - rawStaticFieldValues.put(field, new AnalysisFuture<>(() -> onFieldValueReachable(field, rawFieldValue, new FieldScan(field)))); + data.setFieldTask(field, new AnalysisFuture<>(() -> { + JavaConstant value = onFieldValueReachable(field, rawFieldValue, new FieldScan(field)); + data.setFieldValue(field, value); + return value; + })); } - return new TypeData(rawStaticFieldValues); + return data; } void markTypeInstantiated(AnalysisType type) { @@ -165,7 +168,7 @@ void markTypeInstantiated(AnalysisType type) { JavaConstant markConstantReachable(JavaConstant constant, ScanReason reason, Consumer onAnalysisModified) { if (isNonNullObjectConstant(constant)) { - return getOrCreateConstantReachableTask(constant, reason, onAnalysisModified).ensureDone().getObject(); + return getOrCreateConstantReachableTask(constant, reason, onAnalysisModified).getObject(); } return constant; @@ -177,31 +180,36 @@ protected ImageHeapObject toImageHeapObject(JavaConstant constant) { protected ImageHeapObject toImageHeapObject(JavaConstant constant, ScanReason reason, Consumer onAnalysisModified) { assert constant != null && isNonNullObjectConstant(constant); - return getOrCreateConstantReachableTask(constant, reason, onAnalysisModified).ensureDone(); + return getOrCreateConstantReachableTask(constant, reason, onAnalysisModified); } - protected AnalysisFuture getOrCreateConstantReachableTask(JavaConstant javaConstant, ScanReason reason, Consumer onAnalysisModified) { + @SuppressWarnings({"unchecked", "rawtypes"}) + protected ImageHeapObject getOrCreateConstantReachableTask(JavaConstant javaConstant, ScanReason reason, Consumer onAnalysisModified) { ScanReason nonNullReason = Objects.requireNonNull(reason); - AnalysisFuture existingTask = imageHeap.getTask(javaConstant); + Object existingTask = imageHeap.getTask(javaConstant); if (existingTask == null) { if (universe.sealed()) { throw AnalysisError.shouldNotReachHere("Universe is sealed. New constant reachable: " + javaConstant.toValueString()); } - AnalysisFuture newTask = new AnalysisFuture<>(() -> createImageHeapObject(javaConstant, nonNullReason, onAnalysisModified)); - existingTask = imageHeap.addTask(javaConstant, newTask); + AnalysisFuture newTask = new AnalysisFuture<>(() -> { + ImageHeapObject imageHeapObject = createImageHeapObject(javaConstant, nonNullReason, onAnalysisModified); + /* When the image heap object is created replace the future in the map. */ + imageHeap.setValue(javaConstant, imageHeapObject); + return imageHeapObject; + }); + existingTask = imageHeap.setTask(javaConstant, newTask); if (existingTask == null) { /* * Immediately schedule the new task. There is no need to have not-yet-reachable * ImageHeapObject. */ postTask(newTask); - return newTask; + return newTask.ensureDone(); } } - return existingTask; + return existingTask instanceof ImageHeapObject ? (ImageHeapObject) existingTask : ((AnalysisFuture) existingTask).ensureDone(); } - @SuppressWarnings({"unchecked", "rawtypes"}) protected ImageHeapObject createImageHeapObject(JavaConstant constant, ScanReason reason, Consumer onAnalysisModified) { assert constant.getJavaKind() == JavaKind.Object && !constant.isNull(); @@ -249,7 +257,7 @@ protected ImageHeapObject createImageHeapObject(JavaConstant constant, ScanReaso /* We are about to query the type's fields, the type must be marked as reachable. */ markTypeInstantiated(type); AnalysisField[] instanceFields = type.getInstanceFields(true); - AnalysisFuture[] instanceFieldValues = new AnalysisFuture[instanceFields.length]; + newImageHeapObject = new ImageHeapInstance(constant, instanceFields.length); for (AnalysisField field : instanceFields) { ScanReason fieldReason = new FieldScan(field, constant, reason); ValueSupplier rawFieldValue; @@ -259,9 +267,13 @@ protected ImageHeapObject createImageHeapObject(JavaConstant constant, ScanReaso /* Ignore missing type errors. */ continue; } - instanceFieldValues[field.getPosition()] = new AnalysisFuture<>(() -> onFieldValueReachable(field, constant, rawFieldValue, fieldReason, onAnalysisModified)); + ImageHeapInstance finalObject = (ImageHeapInstance) newImageHeapObject; + finalObject.setFieldTask(field, new AnalysisFuture<>(() -> { + JavaConstant value = onFieldValueReachable(field, constant, rawFieldValue, fieldReason, onAnalysisModified); + finalObject.setFieldValue(field, value); + return value; + })); } - newImageHeapObject = new ImageHeapInstance(constant, instanceFieldValues); } /* @@ -398,7 +410,7 @@ void onObjectReachable(ImageHeapObject imageHeapObject) { ImageHeapInstance imageHeapInstance = (ImageHeapInstance) imageHeapObject; for (AnalysisField field : objectType.getInstanceFields(true)) { if (field.isRead() && isValueAvailable(field)) { - postTask(imageHeapInstance.getFieldTask(field)); + snapshotFieldValue(field, imageHeapInstance.getFieldValue(field)); } } } @@ -408,6 +420,19 @@ public boolean isValueAvailable(@SuppressWarnings("unused") AnalysisField field) return true; } + /** + * Trigger execution of the field task, if not yet executed. Note that if the task is already + * executed there is nothing to do since on execution the task replaces itself with its result + * in the corresponding map. + */ + @SuppressWarnings({"unchecked", "rawtypes"}) + private void snapshotFieldValue(AnalysisField field, Object fieldTask) { + if (fieldTask instanceof AnalysisFuture) { + AnalysisError.guarantee(field.isReachable(), "Field value snapshot computed for field not reachable " + field.format("%H.%n")); + postTask((AnalysisFuture) fieldTask); + } + } + protected String formatReason(String message, ScanReason reason) { return message + ' ' + reason; } @@ -466,25 +491,37 @@ public void rescanField(Object receiver, Field reflectionField) { } protected AnalysisFuture patchStaticField(TypeData typeData, AnalysisField field, JavaConstant fieldValue, ScanReason reason, Consumer onAnalysisModified) { - AnalysisFuture task = new AnalysisFuture<>(() -> onFieldValueReachable(field, fieldValue, reason, onAnalysisModified)); + AnalysisFuture task = new AnalysisFuture<>(() -> { + JavaConstant value = onFieldValueReachable(field, fieldValue, reason, onAnalysisModified); + typeData.setFieldValue(field, value); + return value; + }); typeData.setFieldTask(field, task); return task; } protected AnalysisFuture patchInstanceField(ImageHeapInstance receiverObject, AnalysisField field, JavaConstant fieldValue, ScanReason reason, Consumer onAnalysisModified) { - AnalysisFuture task = new AnalysisFuture<>(() -> onFieldValueReachable(field, receiverObject.getObject(), fieldValue, reason, onAnalysisModified)); + AnalysisFuture task = new AnalysisFuture<>(() -> { + JavaConstant value = onFieldValueReachable(field, receiverObject.getObject(), fieldValue, reason, onAnalysisModified); + receiverObject.setFieldValue(field, value); + return value; + }); receiverObject.setFieldTask(field, task); return task; } - /** Add the object to the image heap and, if the object is a collection, rescan its elements. */ + /** + * Add the object to the image heap and, if the object is a collection, rescan its elements. + */ public void rescanObject(Object object) { rescanObject(object, OtherReason.RESCAN); rescanCollectionElements(object); } - /** Add the object to the image heap. */ + /** + * Add the object to the image heap. + */ public void rescanObject(Object object, ScanReason reason) { if (skipScanning()) { return; @@ -551,8 +588,6 @@ public JavaConstant asConstant(Object object) { public void cleanupAfterAnalysis() { scanningObserver = null; - imageHeap.heapObjects = null; - imageHeap.typesToObjects = null; } public ObjectScanningObserver getScanningObserver() { diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/TypeData.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/TypeData.java index f019c904e101..6def79829f73 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/TypeData.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/TypeData.java @@ -24,7 +24,8 @@ */ package com.oracle.graal.pointsto.heap; -import java.util.Map; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; import com.oracle.graal.pointsto.ObjectScanner; import com.oracle.graal.pointsto.heap.value.ValueSupplier; @@ -33,42 +34,53 @@ import com.oracle.graal.pointsto.util.AnalysisFuture; import jdk.vm.ci.meta.JavaConstant; -import jdk.vm.ci.meta.ResolvedJavaField; /** * Additional data for a {@link AnalysisType type} that is only available for types that are marked * as reachable. Computed lazily once the type is seen as reachable. */ public final class TypeData { + private static final VarHandle arrayHandle = MethodHandles.arrayElementVarHandle(Object[].class); + /** - * The raw values of all static fields, regardless of field reachability status. Evaluating the - * {@link AnalysisFuture} runs + * The raw values of all static fields, regardless of field reachability status. The stored + * value is either an {@link AnalysisFuture} of {@link JavaConstant} or its result, a + * {@link JavaConstant}. + * + * Evaluating the {@link AnalysisFuture} runs * {@link ImageHeapScanner#onFieldValueReachable(AnalysisField, ValueSupplier, ObjectScanner.ScanReason)} - * adds the result to the image heap}. + * which adds the result to the image heap. */ - final Map> staticFieldValues; + private final Object[] values; - public TypeData(Map> staticFieldValues) { - this.staticFieldValues = staticFieldValues; + public TypeData(int length) { + values = new Object[length]; } /** - * Return a task for transforming and snapshotting the field value, effectively a future for - * {@link ImageHeapScanner#onFieldValueReachable(AnalysisField, ValueSupplier, ObjectScanner.ScanReason)}. + * Record the task computing the field value. It will be retrieved and executed when the field + * is marked as read. */ - public AnalysisFuture getFieldTask(AnalysisField field) { - return staticFieldValues.get(field); + public void setFieldTask(AnalysisField field, AnalysisFuture task) { + arrayHandle.setVolatile(this.values, field.getPosition(), task); } /** - * Read the field value, executing the field task in this thread if not already executed. + * Record the field value produced by the task set in + * {@link #setFieldTask(AnalysisField, AnalysisFuture)}, i.e., the snapshot, already transformed + * and replaced. */ - public JavaConstant readField(AnalysisField field) { - return staticFieldValues.get(field).ensureDone(); + public void setFieldValue(AnalysisField field, JavaConstant value) { + arrayHandle.setVolatile(this.values, field.getPosition(), value); } - public void setFieldTask(AnalysisField field, AnalysisFuture task) { - staticFieldValues.put(field, task); + /** + * Return a task for transforming and snapshotting the field value, effectively a future for + * {@link ImageHeapScanner#onFieldValueReachable(AnalysisField, ValueSupplier, ObjectScanner.ScanReason)}, + * or its result, a {@link JavaConstant}. + */ + public Object getFieldValue(AnalysisField field) { + return arrayHandle.getVolatile(this.values, field.getPosition()); } } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisType.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisType.java index 234546076674..e804e318282a 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisType.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisType.java @@ -159,7 +159,7 @@ public enum UsageKind { /** * Additional information that is only available for types that are marked as reachable. */ - final AnalysisFuture typeData; + private AnalysisFuture typeData; AnalysisType(AnalysisUniverse universe, ResolvedJavaType javaType, JavaKind storageKind, AnalysisType objectType, AnalysisType cloneableType) { this.universe = universe; @@ -277,6 +277,7 @@ public void cleanupAfterAnalysis() { constantObjectsCache = null; uniqueConstant = null; unsafeAccessedFields = null; + typeData = null; } public int getId() { @@ -942,7 +943,7 @@ private AnalysisField[] convertFields(ResolvedJavaField[] original, List DumpHeap = new HostedOptionKey<>(new LocatableMultiOptionValue.Strings()); } enum Phases { + DuringAnalysis("during-analysis"), AfterAnalysis("after-analysis"), BeforeCompilation("before-compilation"); @@ -101,6 +102,15 @@ public void duringSetup(DuringSetupAccess access) { timeStamp = getTimeStamp(); } + private int iteration; + + @Override + public void duringAnalysis(DuringAnalysisAccess access) { + if (phases.contains(Phases.DuringAnalysis.getName())) { + dumpHeap(Phases.DuringAnalysis.getName() + "-" + iteration++); + } + } + @Override public void onAnalysisExit(OnAnalysisExitAccess access) { if (phases.contains(Phases.AfterAnalysis.getName())) { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageHeapScanner.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageHeapScanner.java index 81431b5a7d82..72326a6e641e 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageHeapScanner.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageHeapScanner.java @@ -39,7 +39,6 @@ import com.oracle.graal.pointsto.heap.value.ValueSupplier; import com.oracle.graal.pointsto.meta.AnalysisField; import com.oracle.graal.pointsto.meta.AnalysisMetaAccess; -import com.oracle.graal.pointsto.util.AnalysisFuture; import com.oracle.svm.core.BuildPhaseProvider; import com.oracle.svm.core.meta.ReadableJavaField; import com.oracle.svm.core.meta.SubstrateObjectConstant; @@ -84,7 +83,7 @@ protected Class getClass(String className) { } @Override - protected AnalysisFuture getOrCreateConstantReachableTask(JavaConstant javaConstant, ScanReason reason, Consumer onAnalysisModified) { + protected ImageHeapObject getOrCreateConstantReachableTask(JavaConstant javaConstant, ScanReason reason, Consumer onAnalysisModified) { VMError.guarantee(javaConstant instanceof SubstrateObjectConstant, "Not a substrate constant " + javaConstant); return super.getOrCreateConstantReachableTask(javaConstant, reason, onAnalysisModified); }