diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/ObjectScanner.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/ObjectScanner.java index 894bb46e9dd7..0fe53901ec21 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/ObjectScanner.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/ObjectScanner.java @@ -172,7 +172,7 @@ protected final void scanStaticFieldRoot(AnalysisField field) { protected void scanField(AnalysisField field, JavaConstant receiver, ScanReason prevReason) { ScanReason reason = new FieldScan(field, receiver, prevReason); try { - if (!bb.getUniverse().getHeapScanner().isValueAvailable(field)) { + if (!bb.getUniverse().getHeapScanner().isValueAvailable(field, receiver)) { /* The value is not available yet. */ return; } 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 88dc3bf77b7c..2f833d36c71c 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 @@ -211,7 +211,7 @@ private void verifyStaticFieldValue(TypeData typeData, AnalysisField field, Java } private void verifyInstanceFieldValue(AnalysisField field, JavaConstant receiver, ImageHeapInstance receiverObject, JavaConstant fieldSnapshot, JavaConstant fieldValue, ScanReason reason) { - if (fieldSnapshot instanceof ImageHeapConstant ihc && ihc.isInBaseLayer() && ihc.getHostedObject() == null) { + if (fieldSnapshot instanceof ImageHeapConstant ihc && ihc.isInBaseLayer() && ihc.getHostedObject() == null && !(ihc instanceof ImageHeapRelocatableConstant)) { /* * We cannot verify a base layer constant which doesn't have a backing hosted * object. Since the hosted object is missing the constant would be replaced with 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 38edfd08218a..a472a15a3779 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 @@ -153,7 +153,7 @@ private void onStaticFieldRead(AnalysisField field) { } else if (bb.trackPrimitiveValues() && field.getStorageKind().isPrimitive()) { ((PointsToAnalysisField) field).saturatePrimitiveField(); } - } else if (isValueAvailable(field)) { + } else if (isValueAvailable(field, null)) { JavaConstant fieldValue = readStaticFieldValue(field); if (fieldValue instanceof ImageHeapConstant imageHeapConstant && field.isFinal()) { AnalysisError.guarantee(imageHeapConstant.getOrigin() != null, "The origin of the constant %s should have been registered before", imageHeapConstant); @@ -362,6 +362,10 @@ private ImageHeapArray createImageHeapObjectArray(JavaConstant constant, Analysi } public void registerBaseLayerValue(ImageHeapConstant constant, Object reason) { + if (constant instanceof ImageHeapRelocatableConstant) { + // relocatable constants have no backing hosted object + return; + } JavaConstant hostedValue = constant.getHostedObject(); AnalysisError.guarantee(hostedValue.isNonNull(), "A relinked constant cannot have a NULL_CONSTANT hosted value."); Object existingSnapshot = imageHeap.getSnapshot(hostedValue); @@ -646,14 +650,15 @@ protected void onObjectReachable(ImageHeapConstant imageHeapConstant, ScanReason } private void updateInstanceField(AnalysisField field, ImageHeapInstance imageHeapInstance, ScanReason reason, Consumer onAnalysisModified) { - if (isValueAvailable(field)) { + if (isValueAvailable(field, imageHeapInstance)) { JavaConstant fieldValue = imageHeapInstance.readFieldValue(field); markReachable(fieldValue, reason, onAnalysisModified); notifyAnalysis(field, imageHeapInstance, fieldValue, reason, onAnalysisModified); } } - public boolean isValueAvailable(@SuppressWarnings("unused") AnalysisField field) { + @SuppressWarnings("unused") + public boolean isValueAvailable(AnalysisField field, JavaConstant receiver) { return true; } diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/linux/LinuxImageHeapProvider.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/linux/LinuxImageHeapProvider.java index 5808134de7c4..b0e19d7d3d22 100644 --- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/linux/LinuxImageHeapProvider.java +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/linux/LinuxImageHeapProvider.java @@ -234,7 +234,9 @@ public static void patchLayeredImageHeap() { offset = applyLayerCodePointerPatches(data, offset, layerHeapRelocs, negativeCodeBase); /* Patch references in the image heap. */ - applyLayerImageHeapRefPatches(data.add(offset), initialLayerImageHeap); + offset = applyLayerImageHeapRefPatches(data, offset, initialLayerImageHeap); + + applyLayerImageHeapFieldUpdatePatches(data, offset, initialLayerImageHeap); layerSection = layerSection.readWord(ImageLayerSection.getEntryOffset(NEXT_SECTION)); } @@ -287,12 +289,17 @@ private static int applyLayerCodePointerPatches(Pointer data, int startOffset, P return offset; } + /** + * See {@code CrossLayerConstantRegistryFeature#generateRelocationPatchArray} for more details + * about the layout. + */ @Uninterruptible(reason = "Thread state not yet set up.") - private static void applyLayerImageHeapRefPatches(Pointer patches, Pointer layerImageHeap) { + private static int applyLayerImageHeapRefPatches(Pointer patches, int startOffset, Pointer layerImageHeap) { int referenceSize = ConfigurationValues.getObjectLayout().getReferenceSize(); - long countAsLong = patches.readLong(0); + int offset = startOffset; + long countAsLong = patches.readLong(offset); int count = UninterruptibleUtils.NumUtil.safeToInt(countAsLong); - int offset = Long.BYTES; + offset += Long.BYTES; int endOffset = offset + count * Integer.BYTES; while (offset < endOffset) { int heapOffset = patches.readInt(offset); @@ -304,6 +311,62 @@ private static void applyLayerImageHeapRefPatches(Pointer patches, Pointer layer layerImageHeap.writeLong(heapOffset, referenceEncoding); } } + return endOffset; + } + + /** + * See {@code CrossLayerUpdaterFeature#generateUpdatePatchArray} for more details about the + * layout. + */ + @Uninterruptible(reason = "Thread state not yet set up.") + private static int applyLayerImageHeapFieldUpdatePatches(Pointer patches, int startOffset, Pointer layerImageHeap) { + long countAsLong = patches.readLong(startOffset); + if (countAsLong == 0) { + // empty - nothing to do + return startOffset + Long.BYTES; + } + + int headerSize = patches.readInt(startOffset + Long.BYTES); + + int headerOffset = startOffset + Long.BYTES + Integer.BYTES; + int headerEndOffset = headerOffset + headerSize; + + // calculate entry offset start + int entryOffset = headerEndOffset; + + /* Now update values. */ + while (headerOffset < headerEndOffset) { + // read appropriate slot of header + int valueSize = patches.readInt(headerOffset); + headerOffset += Integer.BYTES; + int numEntries = patches.readInt(headerOffset); + headerOffset += Integer.BYTES; + for (int j = 0; j < numEntries; j++) { + int heapOffset = patches.readInt(entryOffset); + entryOffset += Integer.BYTES; + switch (valueSize) { + case 1 -> { + byte value = patches.readByte(entryOffset); + layerImageHeap.writeByte(heapOffset, value); + entryOffset += Byte.BYTES; + } + case 4 -> { + int value = patches.readInt(entryOffset); + layerImageHeap.writeInt(heapOffset, value); + entryOffset += Integer.BYTES; + } + case 8 -> { + long value = patches.readLong(entryOffset); + layerImageHeap.writeLong(heapOffset, value); + entryOffset += Long.BYTES; + } + default -> throw VMError.shouldNotReachHereAtRuntime(); + } + } + } + + VMError.guarantee((startOffset + Long.BYTES + Integer.BYTES + countAsLong) == entryOffset); + return entryOffset; } @Override diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/fieldvaluetransformer/FieldValueTransformerWithReceiverBasedAvailability.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/fieldvaluetransformer/FieldValueTransformerWithReceiverBasedAvailability.java new file mode 100644 index 000000000000..003dad617c58 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/fieldvaluetransformer/FieldValueTransformerWithReceiverBasedAvailability.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.fieldvaluetransformer; + +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; + +import com.oracle.svm.core.util.VMError; + +import jdk.vm.ci.meta.JavaConstant; + +/** + * Similar to {@link FieldValueTransformerWithAvailability}, except now also the value of the + * receiver is taken into consideration when deciding whether a value is available or not. This + * allows availability to be decided at a finer granularity (i.e. for a given field, only some + * values may be available at a given moment). For static fields, the receiver is always null. + */ +@Platforms(Platform.HOSTED_ONLY.class) +public abstract class FieldValueTransformerWithReceiverBasedAvailability implements FieldValueTransformerWithAvailability { + + @Override + public final boolean isAvailable() { + throw VMError.intentionallyUnimplemented(); + } + + /** + * Returns true when the value for this custom computation is available. + */ + public abstract boolean isAvailable(JavaConstant receiver); +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHubCompanion.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHubCompanion.java index 256eeaf2e11e..b4af0ffa0df3 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHubCompanion.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHubCompanion.java @@ -36,6 +36,9 @@ import com.oracle.svm.core.classinitialization.ClassInitializationInfo; import com.oracle.svm.core.heap.UnknownObjectField; import com.oracle.svm.core.heap.UnknownPrimitiveField; +import com.oracle.svm.core.imagelayer.ImageLayerBuildingSupport; +import com.oracle.svm.core.layered.LayeredFieldValue; +import com.oracle.svm.core.layered.LayeredFieldValueTransformer; import com.oracle.svm.core.meta.SharedType; import jdk.internal.vm.annotation.Stable; @@ -90,8 +93,10 @@ public final class DynamicHubCompanion { /** * The hub for an array of this type, or null if the array type has been determined as - * uninstantiated by the static analysis. + * uninstantiated by the static analysis. In layered builds, it is possible for this value to be + * initially set to null and then updated in a subsequent layer. */ + @LayeredFieldValue(transformer = ArrayHubTransformer.class) // @Stable DynamicHub arrayHub; /** @@ -182,4 +187,45 @@ public void setHubMetadata(RuntimeDynamicHubMetadata hubMetadata) { public void setReflectionMetadata(RuntimeReflectionMetadata reflectionMetadata) { this.reflectionMetadata = reflectionMetadata; } + + /** + * In layered builds it is possible for a {@link DynamicHubCompanion#arrayHub} to become + * reachable in a later layer than the layer in which the companion is installed in. When this + * happens we must update the companion's field to point to the newly installed value. + */ + static class ArrayHubTransformer extends LayeredFieldValueTransformer { + boolean appLayer = ImageLayerBuildingSupport.buildingApplicationLayer(); + + @Override + public boolean isValueAvailable(DynamicHubCompanion receiver) { + /* + * As soon as we have a non-null value we set the field. Otherwise, once the ready for + * compilation phase has been reached no new dynamic hubs will be added and the value + * can set. + */ + return receiver.arrayHub != null || BuildPhaseProvider.isReadyForCompilation(); + } + + @Override + public Result transform(DynamicHubCompanion receiver) { + /* + * When there is a concrete array hub value, then no update is needed in later layers. + * However, when both the value is null and we are not in the application layer, then we + * may update the value in a later layer. + */ + return new Result(receiver.arrayHub, receiver.arrayHub == null && !appLayer); + } + + @Override + public boolean isUpdateAvailable(DynamicHubCompanion receiver) { + /* A concrete new value has been found. */ + return receiver.arrayHub != null; + } + + @Override + public Result update(DynamicHubCompanion receiver) { + assert receiver.arrayHub != null : "update should only be called when a valid arrayHub is available"; + return new Result(receiver.arrayHub, false); + } + } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/image/ImageHeapLayoutInfo.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/image/ImageHeapLayoutInfo.java index a0558a64a8f2..a4e2d0268af9 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/image/ImageHeapLayoutInfo.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/image/ImageHeapLayoutInfo.java @@ -114,4 +114,8 @@ public long getWritablePatchedOffset() { public long getWritablePatchedSize() { return writablePatchedSize; } + + public boolean isWritablePatched(long offset) { + return offset >= writablePatchedOffset && offset < writablePatchedOffset + writablePatchedSize; + } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/layered/LayeredFieldValue.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/layered/LayeredFieldValue.java new file mode 100644 index 000000000000..ec43b4441604 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/layered/LayeredFieldValue.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.layered; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; + +import com.oracle.svm.core.heap.UnknownObjectField; +import com.oracle.svm.core.heap.UnknownPrimitiveField; + +/** + * Denotes a field which requires a {@link LayeredFieldValueTransformer} when building layered + * images. Note this annotation is only relevant for layered builds and it is legal for a field to + * have both this annotation and {@link UnknownObjectField}/{@link UnknownPrimitiveField}. For + * layered builds, this annotation will take priority and the Unknown field annotation will be + * ignored. + *

+ * See {@link LayeredFieldValueTransformer} for an explanation of the transformation behavior. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +@Platforms(Platform.HOSTED_ONLY.class) +public @interface LayeredFieldValue { + + Class> transformer(); +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/layered/LayeredFieldValueTransformer.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/layered/LayeredFieldValueTransformer.java new file mode 100644 index 000000000000..4c9142cf71b8 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/layered/LayeredFieldValueTransformer.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.layered; + +import com.oracle.svm.core.fieldvaluetransformer.FieldValueTransformerWithReceiverBasedAvailability; +import com.oracle.svm.core.util.VMError; + +/** + * Layered Images specific field value transformer. This transformer, in addition to the behavior of + * {@link FieldValueTransformerWithReceiverBasedAvailability}, also allows for the value to be + * updated in a later layer. + *

+ * Before a given receiver is installed in the image heap, {@link #isValueAvailable} and + * {@link #transform} are used determine its field value. If the {@link Result} returned by + * {@link #transform} has {@link Result#updatable()} set, then it will be possible to update this + * field value in later layers; otherwise the value cannot be changed. + *

+ * For receivers installed in prior layers with updatable field values, then + * {@link #isUpdateAvailable} and {@link #update} are used to determine if and/or when the field + * should be updated. + *

+ * Note in a given layer and for a given receiver, either the pair ({@link #isValueAvailable}, + * {@link #transform}) or ({@link #isUpdateAvailable}, {@link #update}) will be exclusively called + * and {@link #transform}/{@link #update} will be called at most once. + */ +public abstract class LayeredFieldValueTransformer { + + /** + * @param value result of the transformation/update. + * @param updatable indicates whether this value can be updated in later layers via + * {@link #update}. + */ + public record Result(Object value, boolean updatable) { + + } + + /** + * Returns true if the value can be set. Note this method will only be called for receivers that + * have yet to be installed in the image heap. + */ + public abstract boolean isValueAvailable(T receiver); + + /** + * Returns the non-null transformation result. Note this method will only be called for + * receivers that have yet to be installed in the image heap. + */ + public abstract Result transform(T receiver); + + /** + * Returns whether an update is available for the given receiver. Note this method will only be + * called for receivers which were installed in a prior layer. + */ + public boolean isUpdateAvailable(@SuppressWarnings("unused") T receiver) { + throw VMError.shouldNotReachHere("isUpdateAvailable not implemented"); + } + + /** + * Returns the non-null update result. Note this method will only be called for receivers which + * were installed in a prior layer. + */ + public Result update(@SuppressWarnings("unused") T receiver) { + throw VMError.shouldNotReachHere("update not implemented"); + } +} diff --git a/substratevm/src/com.oracle.svm.hosted/resources/SharedLayerSnapshotCapnProtoSchema.capnp b/substratevm/src/com.oracle.svm.hosted/resources/SharedLayerSnapshotCapnProtoSchema.capnp index f6ea6f35a864..e715f603c69d 100644 --- a/substratevm/src/com.oracle.svm.hosted/resources/SharedLayerSnapshotCapnProtoSchema.capnp +++ b/substratevm/src/com.oracle.svm.hosted/resources/SharedLayerSnapshotCapnProtoSchema.capnp @@ -156,6 +156,7 @@ struct PersistedAnalysisField { priorInstalledLayerNum @17 :Int32; assignmentStatus @18 :Int32; simulatedFieldValue @19 :ConstantReference; + updatableReceivers @20 :List(ConstantId); } struct CEntryPointLiteralReference { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ameta/AnalysisConstantReflectionProvider.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ameta/AnalysisConstantReflectionProvider.java index ef20bd274fb0..b515957c2513 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ameta/AnalysisConstantReflectionProvider.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ameta/AnalysisConstantReflectionProvider.java @@ -237,7 +237,7 @@ public JavaConstant readValue(AnalysisField field, JavaConstant receiver, boolea return null; } - if (!fieldValueInterceptionSupport.isValueAvailable(field)) { + if (!fieldValueInterceptionSupport.isValueAvailable(field, receiver)) { /* Value is not yet available. */ return null; } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ameta/CustomTypeFieldHandler.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ameta/CustomTypeFieldHandler.java index 282598f34716..2a79e9d333ca 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ameta/CustomTypeFieldHandler.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ameta/CustomTypeFieldHandler.java @@ -59,7 +59,7 @@ public void handleField(AnalysisField field) { */ assert field.isAccessed(); if (fieldValueInterceptionSupport.hasFieldValueTransformer(field)) { - if (field.getStorageKind().isObject() && !fieldValueInterceptionSupport.isValueAvailable(field)) { + if (field.getStorageKind().isObject() && !fieldValueInterceptionSupport.isValueAvailable(field, null, !field.isStatic())) { injectFieldTypes(field, List.of(field.getType()), true); } else if (bb.trackPrimitiveValues() && field.getStorageKind().isPrimitive() && field instanceof PointsToAnalysisField ptaField) { ptaField.saturatePrimitiveField(); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ameta/FieldValueInterceptionSupport.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ameta/FieldValueInterceptionSupport.java index b2726968bfce..958b40e6f59e 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ameta/FieldValueInterceptionSupport.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ameta/FieldValueInterceptionSupport.java @@ -49,17 +49,23 @@ import com.oracle.svm.core.annotate.RecomputeFieldValue; import com.oracle.svm.core.config.ConfigurationValues; import com.oracle.svm.core.fieldvaluetransformer.FieldValueTransformerWithAvailability; +import com.oracle.svm.core.fieldvaluetransformer.FieldValueTransformerWithReceiverBasedAvailability; import com.oracle.svm.core.heap.UnknownObjectField; import com.oracle.svm.core.heap.UnknownPrimitiveField; +import com.oracle.svm.core.layered.LayeredFieldValue; import com.oracle.svm.core.util.UserError; import com.oracle.svm.core.util.VMError; import com.oracle.svm.hosted.analysis.FieldValueComputer; +import com.oracle.svm.hosted.imagelayer.HostedImageLayerBuildingSupport; +import com.oracle.svm.hosted.imagelayer.LayeredFieldValueTransformerImpl; +import com.oracle.svm.hosted.imagelayer.LayeredFieldValueTransformerSupport; import com.oracle.svm.hosted.substitute.AnnotationSubstitutionProcessor; import com.oracle.svm.hosted.substitute.AutomaticUnsafeTransformationSupport; import com.oracle.svm.hosted.substitute.FieldValueTransformation; import com.oracle.svm.util.ClassUtil; import com.oracle.svm.util.ReflectionUtil; +import jdk.graal.compiler.debug.Assertions; import jdk.graal.compiler.nodes.ValueNode; import jdk.graal.compiler.nodes.java.LoadFieldNode; import jdk.graal.compiler.nodes.spi.CoreProviders; @@ -88,6 +94,7 @@ public final class FieldValueInterceptionSupport { private final AnnotationSubstitutionProcessor annotationSubstitutions; private final Map fieldValueInterceptors = new ConcurrentHashMap<>(); + private final LayeredFieldValueTransformerSupport layeredSupport = HostedImageLayerBuildingSupport.buildingImageLayer() ? LayeredFieldValueTransformerSupport.singleton() : null; public static FieldValueInterceptionSupport singleton() { return ImageSingletons.lookup(FieldValueInterceptionSupport.class); @@ -160,38 +167,54 @@ private Object computeAndCacheFieldValueInterceptor(AnalysisField field) { field.beforeFieldValueAccess(); ResolvedJavaField oField = OriginalFieldProvider.getOriginalField(field); - FieldValueComputer computer = createFieldValueComputer(field); - Object result; - if (computer != null) { - VMError.guarantee(oField != null, "Cannot have a @UnknownObjectField or @UnknownPrimitiveField annotation on synthetic field %s", field); - - var interceptor = fieldValueInterceptors.computeIfAbsent(oField, _ -> computer); - /* - * There can be a race with another thread, so `interceptor` might not be the same - * object as `computer`. But that is not a problem because they are equivalent - * `FieldValueComputer`. We only need to check that there was no field value transformer - * registered beforehand. Unfortunately, we do not have a good stack trace for the user - * showing how the field value transformer was created. But we expect this to be a rare - * error, since the `@Unknown*Field` annotations are not public API. - */ - if (!(interceptor instanceof FieldValueComputer)) { - throw UserError.abort("Cannot register a field value transformer for field %s: %s", field.format("%H.%n"), - "The field is annotated with @UnknownObjectField or @UnknownPrimitiveField."); + Object result = null; + if (layeredSupport != null) { + var transformation = createLayeredFieldValueTransformation(oField, field); + if (transformation != null) { + /* + * There can be a race with another thread, so `result` might not be the same object + * as created by this thread. But that is not a problem because they are equivalent + * transformers. We only need to check that there was no traditional field value + * transformer registered beforehand. + */ + result = fieldValueInterceptors.computeIfAbsent(oField, _ -> transformation); + VMError.guarantee(result instanceof FieldValueTransformation fvt && fvt.getFieldValueTransformer() instanceof LayeredFieldValueTransformerImpl); } - result = interceptor; + } + if (result == null) { + FieldValueComputer computer = createFieldValueComputer(field); + if (computer != null) { + VMError.guarantee(oField != null, "Cannot have a @UnknownObjectField or @UnknownPrimitiveField annotation on synthetic field %s", field); - } else if (oField != null) { - /* - * If no field value transformer was registered beforehand, install our marker value so - * that later registration of a field value transformer is reported as an error. - */ - result = fieldValueInterceptors.computeIfAbsent(oField, _ -> INTERCEPTOR_ACCESSED_MARKER); - } else { - /* - * This is a synthetic field, so it is not possible to install a field value transformer - * for it. - */ - result = INTERCEPTOR_ACCESSED_MARKER; + var interceptor = fieldValueInterceptors.computeIfAbsent(oField, _ -> computer); + /* + * There can be a race with another thread, so `interceptor` might not be the same + * object as `computer`. But that is not a problem because they are equivalent + * `FieldValueComputer`s. We only need to check that there was no field value + * transformer registered beforehand. Unfortunately, we do not have a good stack + * trace for the user showing how the field value transformer was created. But we + * expect this to be a rare error, since the `@Unknown*Field` annotations are not + * public API. + */ + if (!(interceptor instanceof FieldValueComputer)) { + throw UserError.abort("Cannot register a field value transformer for field %s: %s", field.format("%H.%n"), + "The field is annotated with @UnknownObjectField or @UnknownPrimitiveField."); + } + result = interceptor; + + } else if (oField != null) { + /* + * If no field value transformer was registered beforehand, install our marker value + * so that later registration of a field value transformer is reported as an error. + */ + result = fieldValueInterceptors.computeIfAbsent(oField, _ -> INTERCEPTOR_ACCESSED_MARKER); + } else { + /* + * This is a synthetic field, so it is not possible to install a field value + * transformer for it. + */ + result = INTERCEPTOR_ACCESSED_MARKER; + } } Objects.requireNonNull(result, "Must have a non-null value now to avoid repeated invocation of this method"); @@ -208,16 +231,27 @@ private Object computeAndCacheFieldValueInterceptor(AnalysisField field) { * Check if the value of the provided field is currently available. After this method has been * called, it is not possible to install a transformer anymore. */ - public boolean isValueAvailable(AnalysisField field) { + public boolean isValueAvailable(AnalysisField field, JavaConstant receiver) { + return isValueAvailable(field, receiver, false); + } + + boolean isValueAvailable(AnalysisField field, JavaConstant receiver, boolean unknownReceiver) { var interceptor = lookupFieldValueInterceptor(field); if (interceptor instanceof FieldValueTransformation transformation) { - if (!transformation.getFieldValueTransformer().isAvailable()) { - return false; + var transformer = transformation.getFieldValueTransformer(); + if (transformer instanceof FieldValueTransformerWithReceiverBasedAvailability transformerWithReceiver) { + assert unknownReceiver || (!field.isStatic() && receiver != null) : Assertions.errorMessage("Missing receiver", field, receiver); + if (unknownReceiver) { + // Receiver is unknown - we cannot resolve this query + return false; + } else { + return transformerWithReceiver.isAvailable(receiver); + } + } else { + return transformer.isAvailable(); } } else if (interceptor instanceof FieldValueComputer computer) { - if (!computer.isAvailable()) { - return false; - } + return computer.isAvailable(); } return true; } @@ -280,7 +314,7 @@ public ValueNode tryIntrinsifyFieldLoad(CoreProviders providers, LoadFieldNode n } JavaConstant readFieldValue(AnalysisField field, JavaConstant receiver) { - assert isValueAvailable(field) : field; + assert isValueAvailable(field, receiver) : field; JavaConstant value; var interceptor = lookupFieldValueInterceptor(field); if (interceptor instanceof FieldValueTransformation transformation) { @@ -375,6 +409,15 @@ private static JavaConstant interceptWordField(AnalysisField field, JavaConstant return value; } + private FieldValueTransformation createLayeredFieldValueTransformation(ResolvedJavaField oField, AnalysisField aField) { + LayeredFieldValue layeredFieldValue = aField.getAnnotation(LayeredFieldValue.class); + if (layeredFieldValue != null) { + var transformer = layeredSupport.createTransformer(aField, layeredFieldValue); + return new FieldValueTransformation(OriginalClassProvider.getJavaClass(oField.getType()), transformer); + } + return null; + } + private static FieldValueComputer createFieldValueComputer(AnalysisField field) { UnknownObjectField unknownObjectField = field.getAnnotation(UnknownObjectField.class); if (unknownObjectField != null) { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ameta/SVMHostedValueProvider.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ameta/SVMHostedValueProvider.java index 6e8a47f96598..b0b919f588d8 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ameta/SVMHostedValueProvider.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ameta/SVMHostedValueProvider.java @@ -65,7 +65,7 @@ public SVMHostedValueProvider(AnalysisMetaAccess metaAccess, AnalysisUniverse un */ @Override public ValueSupplier readFieldValue(AnalysisField field, JavaConstant receiver) { - if (fieldValueInterceptionSupport.isValueAvailable(field)) { + if (fieldValueInterceptionSupport.isValueAvailable(field, receiver)) { /* Materialize and return the value. */ return ValueSupplier.eagerValue(doReadValue(field, receiver)); } @@ -76,7 +76,7 @@ public ValueSupplier readFieldValue(AnalysisField field, JavaConst * phase. Attempts to materialize the value before it becomes available will result in an * error. */ - return ValueSupplier.lazyValue(() -> doReadValue(field, receiver), () -> fieldValueInterceptionSupport.isValueAvailable(field)); + return ValueSupplier.lazyValue(() -> doReadValue(field, receiver), () -> fieldValueInterceptionSupport.isValueAvailable(field, receiver)); } /** Returns the hosted field value with replacements applied. */ diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/fieldfolding/StaticFinalFieldFoldingFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/fieldfolding/StaticFinalFieldFoldingFeature.java index 698d84ecf1df..a120c0f3de91 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/fieldfolding/StaticFinalFieldFoldingFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/fieldfolding/StaticFinalFieldFoldingFeature.java @@ -398,7 +398,7 @@ static boolean isOptimizationCandidate(AnalysisField aField, AnalysisMethod defi return false; } - if (!fieldValueInterceptionSupport.isValueAvailable(aField)) { + if (!fieldValueInterceptionSupport.isValueAvailable(aField, null)) { /* * Cannot optimize static field whose value is recomputed and is not yet available, * i.e., it may depend on analysis/compilation derived data. 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 4276c2a76848..73c7fd18292c 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 @@ -89,8 +89,8 @@ protected ImageHeapConstant getOrCreateImageHeapConstant(JavaConstant javaConsta } @Override - public boolean isValueAvailable(AnalysisField field) { - return fieldValueInterceptionSupport.isValueAvailable(field); + public boolean isValueAvailable(AnalysisField field, JavaConstant receiver) { + return fieldValueInterceptionSupport.isValueAvailable(field, receiver); } /** diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageHeap.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageHeap.java index 8ffcae523613..85fd4c411d52 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageHeap.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageHeap.java @@ -46,7 +46,6 @@ import java.util.Set; import java.util.function.Predicate; -import com.oracle.svm.core.option.HostedOptionValues; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.c.function.RelocatedPointer; import org.graalvm.word.UnsignedWord; @@ -80,6 +79,7 @@ import com.oracle.svm.core.imagelayer.ImageLayerBuildingSupport; import com.oracle.svm.core.jdk.StringInternSupport; import com.oracle.svm.core.meta.MethodOffset; +import com.oracle.svm.core.option.HostedOptionValues; import com.oracle.svm.core.util.HostedStringDeduplication; import com.oracle.svm.core.util.UserError; import com.oracle.svm.core.util.VMError; @@ -89,6 +89,7 @@ import com.oracle.svm.hosted.config.HybridLayout; import com.oracle.svm.hosted.heap.ImageHeapObjectAdder; import com.oracle.svm.hosted.imagelayer.HostedImageLayerBuildingSupport; +import com.oracle.svm.hosted.imagelayer.LayeredFieldValueTransformerSupport; import com.oracle.svm.hosted.meta.HostedArrayClass; import com.oracle.svm.hosted.meta.HostedClass; import com.oracle.svm.hosted.meta.HostedConstantReflectionProvider; @@ -132,6 +133,7 @@ public final class NativeImageHeap implements ImageHeap { private final boolean layeredBuild = ImageLayerBuildingSupport.buildingImageLayer(); private final boolean initialLayerBuild = layeredBuild && ImageLayerBuildingSupport.buildingInitialLayer(); + private final LayeredFieldValueTransformerSupport layeredFieldValueTransformerSupport = layeredBuild ? LayeredFieldValueTransformerSupport.singleton() : null; /** * A Map from objects at construction-time to native image objects. @@ -318,7 +320,7 @@ private void addStaticFields() { */ for (HostedField field : hUniverse.getFields()) { if (field.getWrapped().installableInLayer() && Modifier.isStatic(field.getModifiers()) && field.hasLocation() && field.getType().getStorageKind() == JavaKind.Object && field.isRead()) { - assert field.isWritten() || !field.isValueAvailable() || MaterializedConstantFields.singleton().contains(field.wrapped); + assert field.isWritten() || !field.isValueAvailable(null) || MaterializedConstantFields.singleton().contains(field.wrapped); /* GR-56699 currently static fields cannot be ImageHeapRelocatableConstants. */ addConstant(hConstantReflection.readConstantField(field, null), false, field); } @@ -592,13 +594,23 @@ private void addObjectToImageHeap(final JavaConstant constant, boolean immutable // Recursively add all the fields of the object. final boolean fieldsAreImmutable = hMetaAccess.isInstanceOf(constant, String.class); for (HostedField field : clazz.getInstanceFields(true)) { + boolean fieldPatchable = false; + if (layeredFieldValueTransformerSupport != null) { + fieldPatchable = layeredFieldValueTransformerSupport.finalizeFieldValue(field, constant); + } boolean fieldRelocatable = false; /* * Fields that are only available after heap layout, such as * StringInternSupport.imageInternedStrings and all ImageHeapInfo fields will * not be processed. */ - if (field.isRead() && field.isValueAvailable() && !ignoredFields.contains(field)) { + if (field.isRead() && field.isValueAvailable(constant) && !ignoredFields.contains(field)) { + /* + * We will only patch read fields. Hence, we only need to assign objects to + * the patchable partition when the field value is part of the image heap. + */ + patched = patched || fieldPatchable; + if (field.getJavaKind() == JavaKind.Object) { assert field.hasLocation(); JavaConstant fieldValueConstant = hConstantReflection.readConstantField(field, constant); @@ -625,7 +637,7 @@ private void addObjectToImageHeap(final JavaConstant constant, boolean immutable */ relocatable = relocatable || fieldRelocatable; } - written = written || ((field.isWritten() || !field.isValueAvailable()) && !field.isFinal() && !fieldRelocatable); + written = written || ((field.isWritten() || !field.isValueAvailable(constant)) && !field.isFinal() && !fieldRelocatable); } if (hybridArray instanceof Object[]) { relocatable = addArrayElements((Object[]) hybridArray, relocatable, info); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageHeapWriter.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageHeapWriter.java index 678047cd46f3..c76754c5f44c 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageHeapWriter.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageHeapWriter.java @@ -64,6 +64,7 @@ import com.oracle.svm.hosted.config.HybridLayout; import com.oracle.svm.hosted.image.NativeImageHeap.ObjectInfo; import com.oracle.svm.hosted.imagelayer.CrossLayerConstantRegistryFeature; +import com.oracle.svm.hosted.imagelayer.LayeredFieldValueTransformerSupport; import com.oracle.svm.hosted.imagelayer.LayeredImageHooks; import com.oracle.svm.hosted.meta.HostedClass; import com.oracle.svm.hosted.meta.HostedConstantReflectionProvider; @@ -94,6 +95,7 @@ public final class NativeImageHeapWriter { private final ImageHeapLayoutInfo heapLayout; private final boolean imageLayer = ImageLayerBuildingSupport.buildingImageLayer(); private final LayeredImageHooks layerHooks = imageLayer ? LayeredImageHooks.singleton() : null; + private final LayeredFieldValueTransformerSupport layeredFieldSupport = imageLayer ? LayeredFieldValueTransformerSupport.singleton() : null; private final CrossLayerConstantRegistryFeature layerConstantRegistry = imageLayer ? CrossLayerConstantRegistryFeature.singleton() : null; private final JavaKind wordKind = ConfigurationValues.getWordKind(); private long sectionOffsetOfARelocatablePointer = -1; @@ -146,7 +148,7 @@ private void writeStaticFields(RelocatableBuffer buffer, DeadlockWatchdog watchd ObjectInfo objectFields = heap.getObjectInfo(StaticFieldsSupport.getCurrentLayerStaticObjectFields()); for (HostedField field : heap.hUniverse.getFields()) { if (field.getWrapped().installableInLayer() && Modifier.isStatic(field.getModifiers()) && field.hasLocation() && field.isRead()) { - assert field.isWritten() || !field.isValueAvailable() || MaterializedConstantFields.singleton().contains(field.wrapped); + assert field.isWritten() || !field.isValueAvailable(null) || MaterializedConstantFields.singleton().contains(field.wrapped); ObjectInfo fields = (field.getStorageKind() == JavaKind.Object) ? objectFields : primitiveFields; writeField(buffer, fields, field, null, null); } @@ -241,6 +243,7 @@ private void writeConstant(RelocatableBuffer buffer, int index, JavaKind kind, J int offsetInHeap = NumUtil.safeToInt(index + heapLayout.getStartOffset()); if (constant instanceof ImageHeapRelocatableConstant ihrc) { + VMError.guarantee(heapLayout.isWritablePatched(offsetInHeap), "ImageHeapRelocatableConstants must always be placed in the writable patched partition: %s", ihrc); layerConstantRegistry.markFutureHeapConstantPatchSite(ihrc, offsetInHeap); fillReferenceWithGarbage(buffer, index); return; @@ -468,6 +471,9 @@ private void writeObject(ObjectInfo info, RelocatableBuffer buffer) { (field.getLocation() < instanceClazz.getAfterFieldsOffset()) : Assertions.errorMessage(field, instanceClazz.getFirstInstanceFieldOffset(), instanceClazz.getAfterFieldsOffset()); writeField(buffer, info, field, con, info); + if (layeredFieldSupport != null) { + layeredFieldSupport.recordWrittenField(field, info, heapLayout); + } }); /* Write the identity hashcode */ diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/CrossLayerConstantRegistryFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/CrossLayerConstantRegistryFeature.java index a7e0c0aeb469..faef89ac4373 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/CrossLayerConstantRegistryFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/CrossLayerConstantRegistryFeature.java @@ -25,6 +25,7 @@ package com.oracle.svm.hosted.imagelayer; import static com.oracle.svm.hosted.imagelayer.CrossLayerConstantRegistryFeature.INVALID; +import static com.oracle.svm.hosted.imagelayer.CrossLayerConstantRegistryFeature.NULL_CONSTANT_ID; import java.util.ArrayList; import java.util.EnumSet; @@ -64,6 +65,7 @@ @AutomaticallyRegisteredFeature public class CrossLayerConstantRegistryFeature implements InternalFeature, FeatureSingleton, CrossLayerConstantRegistry { static final int INVALID = -1; + static final int NULL_CONSTANT_ID = -1; private static final Object NULL_CONSTANT_MARKER = new Object(); private record FutureConstantCandidateInfo(ImageHeapRelocatableConstant constant) { @@ -201,16 +203,19 @@ public void beforeImageWrite(BeforeImageWriteAccess access) { for (var entry : finalizedFutureConstants.entrySet()) { // We know these constants have been installed via addInitialObjects Object value = entry.getValue(); + int loaderId; + int offset; if (value == NULL_CONSTANT_MARKER) { - FutureTrackingInfo info = (FutureTrackingInfo) tracker.getTrackingInfo(entry.getKey()); - tracker.updateFutureTrackingInfo(new FutureTrackingInfo(info.key(), FutureTrackingInfo.State.Final, INVALID, INVALID)); + loaderId = NULL_CONSTANT_ID; + offset = INVALID; } else { var futureConstant = (ImageHeapConstant) snippetReflection.forObject(value); var objectInfo = heap.getConstantInfo(futureConstant); - int id = ImageHeapConstant.getConstantID(futureConstant); - FutureTrackingInfo info = (FutureTrackingInfo) tracker.getTrackingInfo(entry.getKey()); - tracker.updateFutureTrackingInfo(new FutureTrackingInfo(info.key(), FutureTrackingInfo.State.Final, id, NumUtil.safeToInt(objectInfo.getOffset()))); + loaderId = ImageHeapConstant.getConstantID(futureConstant); + offset = NumUtil.safeToInt(objectInfo.getOffset()); } + FutureTrackingInfo info = (FutureTrackingInfo) tracker.getTrackingInfo(entry.getKey()); + tracker.updateFutureTrackingInfo(new FutureTrackingInfo(info.key(), FutureTrackingInfo.State.Final, loaderId, offset)); } if (ImageLayerBuildingSupport.buildingApplicationLayer()) { @@ -223,8 +228,8 @@ public void beforeImageWrite(BeforeImageWriteAccess access) { * which need to be patched. The reference encoding uses the appropriate compress encoding * format. Both the heap offset and reference can be stored in 4-byte integers due to the length * restrictions of the native-image heap. - * - * has the following format: + *

+ * Overall, this array has the following format: * *

      *     ---------------------------------
@@ -242,6 +247,9 @@ public void beforeImageWrite(BeforeImageWriteAccess access) {
      * All patching is performed relative to the initial layer's
      * {@link com.oracle.svm.core.Isolates#IMAGE_HEAP_BEGIN}, so we must subtract this offset
      * (relative to the image heap start) away from all offsets to patch.
+     * 

+ * In addition, within the Image Layer Section we immediately before the array store the total + * array size as a long value. */ private void generateRelocationPatchArray() { int shift = ImageSingletons.lookup(CompressEncoding.class).getShift(); @@ -254,7 +262,13 @@ private void generateRelocationPatchArray() { VMError.guarantee(info.state() == FutureTrackingInfo.State.Final, "Invalid future %s", info); int offset = info.offset(); - int referenceEncoding = offset == INVALID ? 0 : offset >>> shift; + int referenceEncoding; + if (offset == INVALID) { + referenceEncoding = 0; + } else { + assert (NumUtil.getNbitNumberInt(shift) & offset) == 0 : offset; + referenceEncoding = offset >>> shift; + } for (int heapOffset : offsetsToPatch) { patchArray.add(heapOffset - heapBeginOffset); patchArray.add(referenceEncoding); @@ -300,20 +314,26 @@ public JavaConstant getConstant(String keyName) { if (idInfo instanceof FutureTrackingInfo future) { VMError.guarantee(!finalizedFutureConstants.containsKey(keyName), "Future was finalized in this layer: %s", future); - if (future.loaderId() == INVALID) { - return JavaConstant.NULL_POINTER; - } - - if (future.state() != FutureTrackingInfo.State.Type) { - return loader.getOrCreateConstant(future.loaderId()); - } - - // A constant has not been stored in the heap yet. Create and cache a constant candidate - FutureConstantCandidateInfo info = (FutureConstantCandidateInfo) constantCandidates.computeIfAbsent(keyName, (k) -> { - AnalysisType type = loader.getAnalysisTypeForBaseLayerId(future.loaderId()); - return new FutureConstantCandidateInfo(ImageHeapRelocatableConstant.create(type, k)); - }); - return info.constant(); + return switch (future.state()) { + case Relocatable, Final -> { + int constantId = future.loaderId(); + if (constantId == NULL_CONSTANT_ID) { + yield JavaConstant.NULL_POINTER; + } + yield loader.getOrCreateConstant(constantId); + } + case Type -> { + /* + * A constant has not been stored in the heap yet. Create and cache a constant + * candidate. + */ + FutureConstantCandidateInfo info = (FutureConstantCandidateInfo) constantCandidates.computeIfAbsent(keyName, (k) -> { + AnalysisType type = loader.getAnalysisTypeForBaseLayerId(future.loaderId()); + return new FutureConstantCandidateInfo(ImageHeapRelocatableConstant.create(type, k)); + }); + yield info.constant(); + } + }; } throw VMError.shouldNotReachHere("Missing key: %s", keyName); @@ -530,8 +550,21 @@ record PriorTrackingInfo(int constantId) implements TrackingInfo { record FutureTrackingInfo(String key, State state, int loaderId, int offset) implements TrackingInfo { enum State { + /** + * Indicates a future constant has been registered, but not yet seen in the heap. In this + * state {@link #loaderId} will store a typeId. + */ Type, + /** + * Indicates a {@link ImageHeapRelocatableConstant} has been seen in the heap. In this state + * {@link #loaderId} will store a constantId referring to the + * {@link ImageHeapRelocatableConstant}. + */ Relocatable, + /** + * Indicates the constant has been finalized. In this state {@link #loaderId} will store a + * constantId referring to the final {@link ImageHeapConstant}. + */ Final } @@ -543,7 +576,7 @@ enum State { assert offset == INVALID : Assertions.errorMessage(state, offset); break; case Final: - assert offset > 0 || (offset == INVALID && loaderId == INVALID) : Assertions.errorMessage(state, offset); + assert offset > 0 || (offset == INVALID && loaderId == NULL_CONSTANT_ID) : Assertions.errorMessage(state, offset); } } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/CrossLayerFieldUpdaterFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/CrossLayerFieldUpdaterFeature.java new file mode 100644 index 000000000000..2f145b572d26 --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/CrossLayerFieldUpdaterFeature.java @@ -0,0 +1,415 @@ +/* + * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.hosted.imagelayer; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.ToIntFunction; + +import org.graalvm.collections.EconomicSet; +import org.graalvm.nativeimage.ImageSingletons; + +import com.oracle.graal.pointsto.heap.ImageHeapConstant; +import com.oracle.graal.pointsto.meta.AnalysisField; +import com.oracle.svm.core.config.ConfigurationValues; +import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; +import com.oracle.svm.core.feature.InternalFeature; +import com.oracle.svm.core.heap.Heap; +import com.oracle.svm.core.image.ImageHeapLayoutInfo; +import com.oracle.svm.core.imagelayer.ImageLayerBuildingSupport; +import com.oracle.svm.core.layeredimagesingleton.ImageSingletonLoader; +import com.oracle.svm.core.layeredimagesingleton.ImageSingletonWriter; +import com.oracle.svm.core.layeredimagesingleton.LayeredImageSingleton; +import com.oracle.svm.core.traits.BuiltinTraits; +import com.oracle.svm.core.traits.SingletonLayeredCallbacks; +import com.oracle.svm.core.traits.SingletonLayeredCallbacksSupplier; +import com.oracle.svm.core.traits.SingletonLayeredInstallationKind; +import com.oracle.svm.core.traits.SingletonTrait; +import com.oracle.svm.core.traits.SingletonTraitKind; +import com.oracle.svm.core.traits.SingletonTraits; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.hosted.FeatureImpl; +import com.oracle.svm.hosted.heap.ImageHeapObjectAdder; +import com.oracle.svm.hosted.image.NativeImageHeap; +import com.oracle.svm.hosted.meta.HostedField; +import com.oracle.svm.hosted.meta.HostedUniverse; + +import jdk.graal.compiler.api.replacements.SnippetReflectionProvider; +import jdk.graal.compiler.core.common.CompressEncoding; +import jdk.graal.compiler.core.common.NumUtil; +import jdk.vm.ci.meta.JavaKind; + +/** + * This feature enables an object's field to be updated by a later layer. In particular, this + * feature provides: + *

    + *
  1. A method to register an updatable field {@link #markUpdatableField}
  2. + *
  3. A method to register when a field is updated {@link #updateField}
  4. + *
  5. Methods to calculate {@link #computeUpdatePatchesLength} and produce + * {@link #generateUpdatePatchArray} a patch array used to perform this updates during startup
  6. + *
+ */ +@AutomaticallyRegisteredFeature +@SingletonTraits(access = BuiltinTraits.BuildtimeAccessOnly.class, layeredCallbacks = CrossLayerFieldUpdaterFeature.LayeredCallbacks.class, layeredInstallationKind = SingletonLayeredInstallationKind.Independent.class) +public class CrossLayerFieldUpdaterFeature implements InternalFeature { + private static final int INVALID = -1; + + /** + * Marks when it is no longer legal to call {@link #updateField}. + */ + private boolean sealed = false; + private byte[] updatePatches = null; + private int updatePatchesLength = INVALID; + private int updatePatchesHeaderSize = INVALID; + + private final boolean extensionLayer = ImageLayerBuildingSupport.buildingExtensionLayer(); + + Map updateInfoMap = extensionLayer ? null : new ConcurrentHashMap<>(); + + /** + * Stores the {@link ImageHeapConstant#getConstantID} of the receiver and + * {@link AnalysisField#getId()} of the {@link AnalysisField} for the value which may be + * updated. + */ + record UpdatableField(int receiverId, int fieldId) { + } + + /** + * Tracks the status of a field which can be updated. + */ + private static class UpdatableFieldStatus { + final UpdatableField fieldInfo; + /** + * Holds the offset of the location to update relative to the heap base pointer. + */ + final int heapBaseRelativeOffset; + final JavaKind kind; + + boolean updated; + Object updatedValue; + + UpdatableFieldStatus(UpdatableField fieldInfo, int heapBaseRelativeOffset, JavaKind kind) { + this.fieldInfo = fieldInfo; + this.heapBaseRelativeOffset = heapBaseRelativeOffset; + this.kind = kind; + } + } + + static CrossLayerFieldUpdaterFeature singleton() { + return ImageSingletons.lookup(CrossLayerFieldUpdaterFeature.class); + } + + @Override + public boolean isInConfiguration(IsInConfigurationAccess access) { + return ImageLayerBuildingSupport.buildingImageLayer(); + } + + @Override + public void duringSetup(DuringSetupAccess access) { + if (extensionLayer) { + ImageHeapObjectAdder.singleton().registerObjectAdder(this::addInitialObjects); + } + } + + @Override + public void beforeImageWrite(BeforeImageWriteAccess access) { + if (extensionLayer) { + FeatureImpl.BeforeImageWriteAccessImpl config = (FeatureImpl.BeforeImageWriteAccessImpl) access; + generateUpdatePatchArray(config.getImage().getHeap(), config.getHostedUniverse().getSnippetReflection()); + } + } + + /** + * Record a field which may be updated by a later layer. + */ + void markUpdatableField(long heapOffset, ImageHeapConstant receiver, HostedField field, ImageHeapLayoutInfo heapLayout) { + VMError.guarantee(heapLayout.isWritablePatched(heapOffset), "Field must be located in the writable patched section"); + int receiverId = ImageHeapConstant.getConstantID(receiver); + int fieldId = field.getWrapped().getId(); + UpdatableField fieldInfo = new UpdatableField(receiverId, fieldId); + JavaKind kind = JavaKind.fromJavaClass(field.getType().getJavaClass()); + UpdatableFieldStatus updateInfo = new UpdatableFieldStatus(fieldInfo, NumUtil.safeToInt(heapOffset), kind); + var prev = updateInfoMap.put(fieldInfo, updateInfo); + VMError.guarantee(prev == null); + } + + /** + * Records a field which has been updated. This field must have been marked via + * {@link #markUpdatableField} in a prior layer. + */ + void updateField(ImageHeapConstant receiver, AnalysisField field, Object value) { + assert extensionLayer && !sealed : "Updates can only been performed in extension layers"; + int receiverId = ImageHeapConstant.getConstantID(receiver); + int fieldId = field.getId(); + UpdatableField fieldInfo = new UpdatableField(receiverId, fieldId); + UpdatableFieldStatus updateInfo = updateInfoMap.get(fieldInfo); + VMError.guarantee(updateInfo != null && !updateInfo.updated, "Illegal update %s", updateInfo); + + updateInfo.updated = true; + updateInfo.updatedValue = value; + } + + /** + * We must explicitly ensure all objects referenced by updated fields are added to the heap. + * This is because these objects will not be scanned during the creation of the current layer's + * image heap. + */ + private void addInitialObjects(NativeImageHeap heap, HostedUniverse hUniverse) { + assert extensionLayer && !sealed : "objects can only be added in extension layers"; + sealed = true; + String addReason = "Registered as a required heap constant within the CrossLayerFieldUpdaterFeature"; + + for (var info : updateInfoMap.values()) { + if (patchFilter(info)) { + ImageHeapConstant singletonConstant = (ImageHeapConstant) hUniverse.getSnippetReflection().forObject(info.updatedValue); + heap.addConstant(singletonConstant, false, addReason); + } + } + } + + /** + * See {@link #generateUpdatePatchArray} for more details. + * + * @return byte size length of this array. + */ + int computeUpdatePatchesLength() { + assert sealed; + int referenceSize = ConfigurationValues.getObjectLayout().getReferenceSize(); + VMError.guarantee(updatePatchesLength == INVALID && updatePatchesHeaderSize == INVALID, "called multiple times"); + + /* + * Number of offsets which need to be patched. Each of these will be always of size + * Integer.BYTES. + */ + long numPatchOffsets = 0; + /* + * Total size of the values to be patched in. The size of each entry is dependent on the + * field kind. + */ + long patchEntriesSize = 0; + EconomicSet patchSizes = EconomicSet.create(); + for (var info : updateInfoMap.values()) { + if (patchFilter(info)) { + numPatchOffsets++; + int patchSize = getPatchSize(info, referenceSize); + patchEntriesSize += patchSize; + patchSizes.add(patchSize); + } + } + updatePatchesHeaderSize = 2 * patchSizes.size() * Integer.BYTES; + long totalLength = updatePatchesHeaderSize + (numPatchOffsets * Integer.BYTES) + patchEntriesSize; + updatePatchesLength = NumUtil.safeToInt(totalLength); + return updatePatchesLength; + } + + private static int getPatchSize(UpdatableFieldStatus info, int referenceSize) { + if (info.kind.isObject()) { + return referenceSize; + } else { + return info.kind.getByteCount(); + } + } + + /** + * Currently we patch all updated field values. However, we could in the future choose to + * monitor the value written in the past and only perform the update if the value has changed. + */ + private boolean patchFilter(UpdatableFieldStatus status) { + return status.updated; + } + + /** + * The field update patch array contains a list of all field update patches which need to be + * performed. Because the size of the value to patch in is dependent on the field type, not all + * entries are of uniform size. Therefore, we first output a header containing the sizes of the + * value patches and the number of entries with each value patch size. + *

+ * After the header we then output the patching information. For each entry, we first store the + * heap offset where the patch occurs and then the value to patch in. While the heap offset will + * always be stored as a 4-byte integer, the value to patch in is stored in a slot size + * according to the patch size described in the header. + *

+ * Overall, this array has the following format: + * + *

+     *     --------------------------------------------
+     *     | Header Information                       |
+     *     --------------------------------------------
+     *     | offset          | description            |
+     *     | 0               | patch size A           |
+     *     | 4               | # patches with size A  |
+     *     | 8               | patch size B           |
+     *     | 12              | # patches with size B  |
+     *     --------------------------------------------
+     *     | Entries                                  |
+     *     --------------------------------------------
+     *     | offset          | description            |
+     *     | 0               | heap offset            |
+     *     | 4               | value to patch         |
+     *     | 4 + sizeA       | heap offset            |
+     *     | 8 + sizeA       | value to patch         |
+     *     | 8 + 2*sizeA     | heap offset            |
+     *     | 12 + 2*sizeA    | value to patch         |
+     *     | ...                                      |
+     *     | X               | heap offset            |
+     *     | X + 4           | value to patch         |
+     *     | X + 4 + sizeB   | heap offset            |
+     *     | X + 8 + sizeB   | value to patch         |
+     *     --------------------------------------------
+     *
+     * 
+ * + * Note all patching is performed relative to the initial layer's + * {@link com.oracle.svm.core.Isolates#IMAGE_HEAP_BEGIN}, so we must subtract this offset + * (relative to the image heap start) away from all offsets to patch. + *

+ * In addition, within the Image Layer Section we immediately store before the array the total + * array size as a long and the header size as an int value. + */ + private void generateUpdatePatchArray(NativeImageHeap heap, SnippetReflectionProvider snippetReflection) { + int referenceSize = ConfigurationValues.getObjectLayout().getReferenceSize(); + int shift = ImageSingletons.lookup(CompressEncoding.class).getShift(); + int heapBeginOffset = Heap.getHeap().getImageHeapOffsetInAddressSpace(); + + Comparator comparator = Comparator.comparingInt((ToIntFunction) info -> getPatchSize(info, referenceSize)) + .thenComparingInt(info -> info.heapBaseRelativeOffset); + var sortedValues = updateInfoMap.values().stream().filter(this::patchFilter).sorted(comparator).toList(); + byte[] patchArray = new byte[updatePatchesLength]; + ByteBuffer buffer = ByteBuffer.wrap(patchArray).order(ByteOrder.LITTLE_ENDIAN); + + // The header counts are written last. Move to entries section. + buffer.position(updatePatchesHeaderSize); + Map patchSizeCounts = new HashMap<>(); + for (var value : sortedValues) { + /* + * When performing patching, the offset to patch is relative to the start of the image + * heap, not the heap base pointer. + */ + buffer.putInt(value.heapBaseRelativeOffset - heapBeginOffset); + switch (value.kind) { + case Boolean -> buffer.put((byte) (((Boolean) value.updatedValue) ? 1 : 0)); + case Byte -> buffer.put((Byte) value.updatedValue); + case Int -> buffer.putInt((Integer) value.updatedValue); + case Long -> buffer.putLong((Long) value.updatedValue); + case Object -> { + int encodedValue; + if (value.updatedValue == null) { + encodedValue = 0; + } else { + var newValue = (ImageHeapConstant) snippetReflection.forObject(value.updatedValue); + var objectInfo = heap.getConstantInfo(newValue); + long heapBaseRelativeOffset = objectInfo.getOffset(); + encodedValue = NumUtil.safeToInt(heapBaseRelativeOffset) >>> shift; + } + if (referenceSize == Integer.BYTES) { + buffer.putInt(encodedValue); + } else { + assert referenceSize == Long.BYTES; + buffer.putLong(encodedValue); + } + } + default -> throw VMError.shouldNotReachHere("Unexpected value %s", value); + } + int patchSize = getPatchSize(value, referenceSize); + patchSizeCounts.compute(patchSize, (_, v) -> v == null ? 1 : v + 1); + } + + VMError.guarantee(buffer.position() == updatePatchesLength); + + // write the header now + buffer.position(0); + patchSizeCounts.entrySet().stream().sorted(Comparator.comparingInt(Map.Entry::getKey)).forEachOrdered(e -> { + buffer.putInt(e.getKey()); + buffer.putInt(e.getValue()); + }); + VMError.guarantee(buffer.position() == updatePatchesHeaderSize); + updatePatches = patchArray; + } + + byte[] getUpdatePatches() { + VMError.guarantee(updatePatches != null); + return updatePatches; + } + + int getHeaderSize() { + assert updatePatchesHeaderSize != INVALID; + return updatePatchesHeaderSize; + } + + static class LayeredCallbacks extends SingletonLayeredCallbacksSupplier { + @Override + public SingletonTrait getLayeredCallbacksTrait() { + return new SingletonTrait(SingletonTraitKind.LAYERED_CALLBACKS, new SingletonLayeredCallbacks() { + @Override + public LayeredImageSingleton.PersistFlags doPersist(ImageSingletonWriter writer, Object singleton) { + var updateInfoMap = ((CrossLayerFieldUpdaterFeature) singleton).updateInfoMap; + ArrayList fieldIds = new ArrayList<>(); + ArrayList receiverIds = new ArrayList<>(); + ArrayList offsets = new ArrayList<>(); + ArrayList javaKindOrdinals = new ArrayList<>(); + for (var info : updateInfoMap.values()) { + fieldIds.add(info.fieldInfo.fieldId()); + receiverIds.add(info.fieldInfo.receiverId()); + offsets.add(info.heapBaseRelativeOffset); + javaKindOrdinals.add(info.kind.ordinal()); + } + writer.writeIntList("fieldIds", fieldIds); + writer.writeIntList("receiverIds", receiverIds); + writer.writeIntList("offsets", offsets); + writer.writeIntList("javaKindOrdinals", javaKindOrdinals); + return LayeredImageSingleton.PersistFlags.CALLBACK_ON_REGISTRATION; + } + + @Override + public void onSingletonRegistration(ImageSingletonLoader loader, Object singleton) { + Map map = new HashMap<>(); + Iterator fieldIds = loader.readIntList("fieldIds").iterator(); + Iterator receiverIds = loader.readIntList("receiverIds").iterator(); + Iterator offsets = loader.readIntList("offsets").iterator(); + Iterator javaKindOrdinals = loader.readIntList("javaKindOrdinals").iterator(); + while (fieldIds.hasNext()) { + int fieldId = fieldIds.next(); + int receiverId = receiverIds.next(); + int offset = offsets.next(); + JavaKind kind = JavaKind.values()[javaKindOrdinals.next()]; + CrossLayerFieldUpdaterFeature.UpdatableField fieldInfo = new CrossLayerFieldUpdaterFeature.UpdatableField(receiverId, fieldId); + CrossLayerFieldUpdaterFeature.UpdatableFieldStatus updateInfo = new CrossLayerFieldUpdaterFeature.UpdatableFieldStatus(fieldInfo, offset, kind); + var prev = map.put(fieldInfo, updateInfo); + assert prev == null : prev; + } + assert !receiverIds.hasNext() && !offsets.hasNext() && !javaKindOrdinals.hasNext() : "information is not properly synced"; + ((CrossLayerFieldUpdaterFeature) singleton).updateInfoMap = Collections.unmodifiableMap(map); + } + }); + } + } +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/ImageLayerSectionFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/ImageLayerSectionFeature.java index f69b7824e224..36cb6d7f5590 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/ImageLayerSectionFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/ImageLayerSectionFeature.java @@ -92,6 +92,7 @@ * | .. | bitmap of code offsets to patch | \ for how these are gathered, see * | .. | bitmap of code addresses to patch | / {@link LayeredDispatchTableFeature} * | .. | image heap reference patches | + * | .. | image heap field update patches | * --------------------------------------------------------- *

*/ @@ -129,7 +130,7 @@ public boolean isInConfiguration(IsInConfigurationAccess access) { @Override public List> getRequiredFeatures() { - return List.of(HostedDynamicLayerInfoFeature.class, LoadImageSingletonFeature.class, CrossLayerConstantRegistryFeature.class, CGlobalDataFeature.class); + return List.of(HostedDynamicLayerInfoFeature.class, LoadImageSingletonFeature.class, CrossLayerConstantRegistryFeature.class, CGlobalDataFeature.class, CrossLayerFieldUpdaterFeature.class); } @Override @@ -206,13 +207,24 @@ public void createSection(ObjectFile objectFile, ImageHeapLayoutInfo heapLayout) sectionMaxSize += Long.BYTES + relocsBitmapMaxSize; // offsets to patch sectionMaxSize += Long.BYTES + relocsBitmapMaxSize; // addresses to patch - sectionMaxSize += Long.BYTES; + /* Account for the length of the image heap reference and field update arrays. */ + sectionMaxSize += 2 * Long.BYTES; if (ImageLayerBuildingSupport.buildingApplicationLayer()) { /* * Patches for object references in image heaps of other layers. For a description of * the table layout, see CrossLayerConstantRegistryFeature#generateRelocationPatchArray. */ sectionMaxSize += Integer.BYTES * CrossLayerConstantRegistryFeature.singleton().computeRelocationPatchesLength(); + /* + * Patches for updated fields. For a description of the table layout see + * CrossLayerFieldUpdatersFeature#generateUpdatePatchArray. + */ + int patchesLength = CrossLayerFieldUpdaterFeature.singleton().computeUpdatePatchesLength(); + if (patchesLength > 0) { + // When the patch array is non-zero, then we must also store the header length + int headerLength = Integer.BYTES; + sectionMaxSize += headerLength + patchesLength; + } } layeredSectionData = new BasicProgbitsSectionImpl(new byte[sectionMaxSize]); @@ -307,8 +319,22 @@ public void beforeImageWrite(BeforeImageWriteAccess access) { for (int value : relocationPatches) { buffer.putInt(value); } + + /* + * Currently we place field update patches exclusively in the application layer. + * + * See CrossLayerFieldUpdatersFeature#generateUpdatePatchArray for a thorough + * description of the field update patch info layout. + */ + byte[] fieldUpdatePatches = CrossLayerFieldUpdaterFeature.singleton().getUpdatePatches(); + buffer.putLong(fieldUpdatePatches.length); + if (fieldUpdatePatches.length != 0) { + buffer.putInt(CrossLayerFieldUpdaterFeature.singleton().getHeaderSize()); + buffer.put(fieldUpdatePatches); + } } else { buffer.putLong(0); // no image heap reference patches + buffer.putLong(0); // no image heap field update patches } int size = buffer.position(); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/LayeredDynamicHubFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/LayeredDynamicHubFeature.java deleted file mode 100644 index 8cb840c5c30a..000000000000 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/LayeredDynamicHubFeature.java +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ -package com.oracle.svm.hosted.imagelayer; - -import java.util.EnumSet; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; - -import org.graalvm.nativeimage.ImageSingletons; -import org.graalvm.nativeimage.hosted.Feature; - -import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; -import com.oracle.svm.core.feature.AutomaticallyRegisteredImageSingleton; -import com.oracle.svm.core.feature.InternalFeature; -import com.oracle.svm.core.hub.DynamicHub; -import com.oracle.svm.core.imagelayer.BuildingInitialLayerPredicate; -import com.oracle.svm.core.imagelayer.ImageLayerBuildingSupport; -import com.oracle.svm.core.layeredimagesingleton.FeatureSingleton; -import com.oracle.svm.core.layeredimagesingleton.ImageSingletonLoader; -import com.oracle.svm.core.layeredimagesingleton.ImageSingletonWriter; -import com.oracle.svm.core.layeredimagesingleton.LayeredImageSingleton; -import com.oracle.svm.core.layeredimagesingleton.LayeredImageSingletonBuilderFlags; -import com.oracle.svm.core.meta.MethodRef; -import com.oracle.svm.hosted.FeatureImpl; -import com.oracle.svm.hosted.meta.HostedType; -import com.oracle.svm.util.LogUtils; - -import jdk.graal.compiler.debug.Assertions; - -/** - * Tracks information about {@link DynamicHub} which should be consistent across builds. - */ -@AutomaticallyRegisteredFeature -public class LayeredDynamicHubFeature implements InternalFeature, FeatureSingleton { - - @Override - public boolean isInConfiguration(Feature.IsInConfigurationAccess access) { - return ImageLayerBuildingSupport.buildingImageLayer(); - } - - @Override - public void duringSetup(DuringSetupAccess access) { - if (ImageLayerBuildingSupport.buildingSharedLayer()) { - LayeredImageHooks.singleton().registerDynamicHubWrittenCallback(this::onDynamicHubWritten); - } - } - - private void onDynamicHubWritten(DynamicHub hub, @SuppressWarnings("unused") MethodRef[] vTable) { - if (hub.getArrayHub() == null) { - DynamicHubMetadataTracking.singleton().recordMissingArrayHub(hub); - } - } - - @Override - public void beforeCompilation(BeforeCompilationAccess access) { - if (ImageLayerBuildingSupport.buildingApplicationLayer()) { - /* - * Scan all DynamicHubs to see if new missing array hubs have been installed. Currently - * we must wait to do this until after typeIDs have been assigned. - */ - DynamicHubMetadataTracking tracking = DynamicHubMetadataTracking.singleton(); - FeatureImpl.BeforeCompilationAccessImpl config = (FeatureImpl.BeforeCompilationAccessImpl) access; - config.getUniverse().getTypes().stream().filter(tracking::missingArrayHub).forEach(hType -> { - if (hType.getHub().getArrayHub() != null) { - var missingArrayName = hType.getHub().getArrayHub().getName(); - String message = String.format("New array type seen in application layer which was not installed within the dynamic hub.%nHub: %s%nArrayType: %s", hType.getHub().getName(), - missingArrayName); - LogUtils.warning(message); - } - }); - } - } -} - -@AutomaticallyRegisteredImageSingleton(onlyWith = BuildingInitialLayerPredicate.class) -class DynamicHubMetadataTracking implements LayeredImageSingleton { - private final Set typeIDsWithMissingHubs; - - DynamicHubMetadataTracking() { - this.typeIDsWithMissingHubs = ConcurrentHashMap.newKeySet(); - } - - DynamicHubMetadataTracking(Set missingHubSet) { - this.typeIDsWithMissingHubs = missingHubSet; - } - - static DynamicHubMetadataTracking singleton() { - return ImageSingletons.lookup(DynamicHubMetadataTracking.class); - } - - boolean missingArrayHub(HostedType hType) { - return typeIDsWithMissingHubs.contains(hType.getTypeID()); - } - - void recordMissingArrayHub(DynamicHub hub) { - var added = typeIDsWithMissingHubs.add(hub.getTypeID()); - assert added : Assertions.errorMessage("type recorded twice:", hub); - } - - @Override - public EnumSet getImageBuilderFlags() { - return LayeredImageSingletonBuilderFlags.BUILDTIME_ACCESS_ONLY; - } - - @Override - public PersistFlags preparePersist(ImageSingletonWriter writer) { - writer.writeIntList("typeIDsWithMissingArrayHubs", typeIDsWithMissingHubs.stream().toList()); - return PersistFlags.CREATE; - } - - @SuppressWarnings("unused") - public static Object createFromLoader(ImageSingletonLoader loader) { - Set missingHubs = Set.copyOf(loader.readIntList("typeIDsWithMissingArrayHubs")); - - return new DynamicHubMetadataTracking(missingHubs); - } -} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/LayeredFieldValueTransformerImpl.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/LayeredFieldValueTransformerImpl.java new file mode 100644 index 000000000000..5d219e47e4cf --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/LayeredFieldValueTransformerImpl.java @@ -0,0 +1,266 @@ +/* + * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.hosted.imagelayer; + +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import org.graalvm.nativeimage.hosted.FieldValueTransformer; + +import com.oracle.graal.pointsto.heap.ImageHeapConstant; +import com.oracle.graal.pointsto.meta.AnalysisField; +import com.oracle.graal.pointsto.util.GraalAccess; +import com.oracle.svm.core.SubstrateUtil; +import com.oracle.svm.core.fieldvaluetransformer.FieldValueTransformerWithReceiverBasedAvailability; +import com.oracle.svm.core.layered.LayeredFieldValueTransformer; +import com.oracle.svm.core.util.VMError; + +import jdk.graal.compiler.debug.Assertions; +import jdk.vm.ci.meta.JavaConstant; + +/** + * Provides logic for representing a {@link LayeredFieldValueTransformer} as a + * {@link FieldValueTransformerWithReceiverBasedAvailability} and keeping track of all needed state + * information. Because we cannot perform constant folding of updatable values, upon any call to + * {@link #isAvailable(JavaConstant)} we must also eagerly perform the transformation to see if we + * can expose the result. Hence, logic is needed for caching the result of updated. This logic is + * contained with {@link TransformedValueState}. + */ +public class LayeredFieldValueTransformerImpl extends FieldValueTransformerWithReceiverBasedAvailability { + final AnalysisField aField; + final LayeredFieldValueTransformer layerTransformer; + + /** + * Set of ConstantID for which this field is updatable. We are using the integer ids, instead of + * the actual image heap constants, so that we don't need to force load these values too + * eagerly. This can be especially problematic for image heap constants because they may need to + * be relinked to their backing host object, which we cannot do eagerly. + **/ + final Set priorLayerReceiversWithUpdatableValues; + + final Map receiverToValueStateMap = new ConcurrentHashMap<>(); + + boolean currentLayerHasUpdatableValues = false; + + LayeredFieldValueTransformerImpl(AnalysisField aField, LayeredFieldValueTransformer layerTransformer, Set priorLayerReceiversWithUpdatableValues) { + this.aField = aField; + this.layerTransformer = layerTransformer; + this.priorLayerReceiversWithUpdatableValues = priorLayerReceiversWithUpdatableValues; + } + + boolean isUpdatableReceiver(Object receiver) { + var valueState = receiverToValueStateMap.get(computeCanonicalReceiver(receiver)); + return valueState.isUpdatable(); + } + + List computeUpdatableReceivers() { + return receiverToValueStateMap.values().stream().filter( + TransformedValueState::isUpdatable) + .map(state -> Objects.requireNonNull(state.ihcReceiver)) + .toList(); + } + + /** + * Used to get the canonical representation of a receiver. This is needed because the receiver + * can be represented as a {@link ImageHeapConstant}, {@link JavaConstant}, or a plain object, + * but they all refer to the same underlying value. + * + * Note in extension layers we expect base layer {@link ImageHeapConstant}s to be relinked to + * their Hosted Object. + */ + private static Object computeCanonicalReceiver(Object receiver) { + if (receiver instanceof ImageHeapConstant ihc) { + return GraalAccess.getOriginalSnippetReflection().asObject(Object.class, Objects.requireNonNull(ihc.getHostedObject())); + } else if (receiver instanceof JavaConstant jc) { + return GraalAccess.getOriginalSnippetReflection().asObject(Object.class, jc); + } + return receiver; + } + + /** + * This method is called during image heap layouting. At this point all compiler optimization + * have already been performed and so it is now legal to expose all values. + * + * @return whether this value is updatable. + */ + boolean finalizeFieldValue(ImageHeapConstant ihc) { + // We assume this is single threaded + var info = receiverToValueStateMap.get(computeCanonicalReceiver(ihc)); + info.maybeTransform(); + VMError.guarantee(!info.isUnresolved() && !info.exposeUpdatableResults); + + /* + * At this point all constant folding is done. We now expose the value. + */ + info.exposeUpdatableResults = true; + + if (info.isUpdatable()) { + info.ihcReceiver = ihc; + currentLayerHasUpdatableValues = true; + return true; + } + + return false; + } + + /** + * If this value was installed in a prior layer, then + * {@link LayeredFieldValueTransformer#update} should be called instead of + * {@link LayeredFieldValueTransformer#transform}. + */ + private TransformedValueState createValueStatue(Object canonicalReceiver, Object receiver) { + boolean useUpdate = false; + if (receiver instanceof ImageHeapConstant ihc && ihc.isInBaseLayer()) { + useUpdate = priorLayerReceiversWithUpdatableValues.contains(ImageHeapConstant.getConstantID(ihc)); + } + return new TransformedValueState(canonicalReceiver, useUpdate); + } + + TransformedValueState maybeUpdateState(Object receiver) { + Object canonicalReceiver = computeCanonicalReceiver(receiver); + var valueState = receiverToValueStateMap.computeIfAbsent(canonicalReceiver, _ -> createValueStatue(canonicalReceiver, receiver)); + valueState.maybeTransform(); + return valueState; + } + + /** + * Returns {@link LayeredFieldValueTransformer#update} result if available. + * + * @return the result of the update or {@code null} if an updated result is not available. + */ + LayeredFieldValueTransformer.Result updateAndGetResult(ImageHeapConstant receiver) { + var state = maybeUpdateState(receiver); + assert state.useUpdate : Assertions.errorMessage("Wrong behavior associated with transformer", receiver); + return state.transformerResult; + } + + @Override + public boolean isAvailable(JavaConstant receiver) { + return maybeUpdateState(Objects.requireNonNull(receiver)).isAvailableAndExposed(); + } + + @Override + public Object transform(Object receiver, Object originalValue) { + var valueState = receiverToValueStateMap.get(computeCanonicalReceiver(Objects.requireNonNull(receiver))); + VMError.guarantee(valueState.isAvailableAndExposed()); + return valueState.transformerResult.value(); + } + + /** + * This class acts the adapter between the "normal" {@link FieldValueTransformer} world and + * {@link LayeredFieldValueTransformer} for a given receiver value. In addition, it performs + * caching of results and maintains logic for determining when to expose transformed values. + */ + private class TransformedValueState { + /** + * This is the value stored as a key within {@link #priorLayerReceiversWithUpdatableValues} + * and is passed to the transformation. This value is calculated via + * {@link #computeCanonicalReceiver}. + */ + final Object canonicalReceiver; + /** + * Flag indicating whether use to the update logic (e.g. + * {@link LayeredFieldValueTransformer#isUpdateAvailable} and + * {@link LayeredFieldValueTransformer#update}) or the "normal" transformation logic (e.g. + * {@link LayeredFieldValueTransformer#isValueAvailable} and + * {@link LayeredFieldValueTransformer#transform}). If the receiver object was installed in + * a prior layer, then we must use the update logic; otherwise we use the normal + * transformation logic. + */ + final boolean useUpdate; + + ImageHeapConstant ihcReceiver; + private LayeredFieldValueTransformer.Result transformerResult; + /** + * Because we cannot allow updatable results to be constant folded, we must wait to show + * updatable results until {@link #finalizeFieldValue} is triggered. + */ + private boolean exposeUpdatableResults = false; + + TransformedValueState(Object canonicalReceiver, boolean useUpdate) { + this.canonicalReceiver = canonicalReceiver; + this.useUpdate = useUpdate; + } + + /** + * If the result is not yet cached, do the transformation if it is available. + */ + @SuppressWarnings("unchecked") + void maybeTransform() { + if (isUnresolved()) { + var transformer = SubstrateUtil.cast(layerTransformer, LayeredFieldValueTransformer.class); + boolean transformAvailable; + if (useUpdate) { + transformAvailable = transformer.isUpdateAvailable(canonicalReceiver); + } else { + transformAvailable = transformer.isValueAvailable(canonicalReceiver); + } + if (transformAvailable) { + doTransform(); + } + } + } + + @SuppressWarnings("unchecked") + synchronized void doTransform() { + if (isUnresolved()) { + LayeredFieldValueTransformer.Result result; + var transformer = SubstrateUtil.cast(layerTransformer, LayeredFieldValueTransformer.class); + if (useUpdate) { + result = transformer.update(canonicalReceiver); + } else { + result = transformer.transform(canonicalReceiver); + } + transformerResult = result; + } + } + + boolean isUnresolved() { + return transformerResult == null; + } + + /** + * @return true if the transformation result is available and it is safe to expose the + * result. + */ + boolean isAvailableAndExposed() { + if (transformerResult != null) { + return !transformerResult.updatable() || exposeUpdatableResults; + } + return false; + } + + /** + * @return true if the result may be updated by a subsequent layer. + */ + boolean isUpdatable() { + assert isAvailableAndExposed(); + return transformerResult.updatable(); + } + } +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/LayeredFieldValueTransformerSupport.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/LayeredFieldValueTransformerSupport.java new file mode 100644 index 000000000000..d379a8ff3521 --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/LayeredFieldValueTransformerSupport.java @@ -0,0 +1,291 @@ +/* + * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.hosted.imagelayer; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.hosted.Feature; + +import com.oracle.graal.pointsto.heap.ImageHeapConstant; +import com.oracle.graal.pointsto.meta.AnalysisField; +import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; +import com.oracle.svm.core.feature.InternalFeature; +import com.oracle.svm.core.image.ImageHeapLayoutInfo; +import com.oracle.svm.core.imagelayer.ImageLayerBuildingSupport; +import com.oracle.svm.core.layered.LayeredFieldValue; +import com.oracle.svm.core.layered.LayeredFieldValueTransformer; +import com.oracle.svm.core.layeredimagesingleton.ImageSingletonLoader; +import com.oracle.svm.core.layeredimagesingleton.ImageSingletonWriter; +import com.oracle.svm.core.layeredimagesingleton.LayeredImageSingleton; +import com.oracle.svm.core.traits.BuiltinTraits.BuildtimeAccessOnly; +import com.oracle.svm.core.traits.SingletonLayeredCallbacks; +import com.oracle.svm.core.traits.SingletonLayeredCallbacksSupplier; +import com.oracle.svm.core.traits.SingletonLayeredInstallationKind.Independent; +import com.oracle.svm.core.traits.SingletonTrait; +import com.oracle.svm.core.traits.SingletonTraitKind; +import com.oracle.svm.core.traits.SingletonTraits; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.hosted.FeatureImpl; +import com.oracle.svm.hosted.image.NativeImageHeap; +import com.oracle.svm.hosted.meta.HostedField; +import com.oracle.svm.util.ReflectionUtil; + +import jdk.vm.ci.meta.JavaConstant; + +/** + * Support for managing {@link LayeredFieldValueTransformer}s and ensuring updatable values are + * properly relayed to {@link CrossLayerFieldUpdaterFeature}. + */ +@AutomaticallyRegisteredFeature +@SingletonTraits(access = BuildtimeAccessOnly.class, layeredCallbacks = LayeredFieldValueTransformerSupport.LayeredCallbacks.class, layeredInstallationKind = Independent.class) +public class LayeredFieldValueTransformerSupport implements InternalFeature { + + private final Map fieldToLayeredTransformer = new ConcurrentHashMap<>(); + + private final boolean extensionLayer = ImageLayerBuildingSupport.buildingExtensionLayer(); + + /** + * Contains the {@link AnalysisField#getId()}s of fields which have receivers with updatable + * values. + */ + private Set fieldsWithUpdatableValues = Set.of(); + + private List priorUpdatableValues; + + private CrossLayerFieldUpdaterFeature cachedFieldUpdater; + + private CrossLayerFieldUpdaterFeature getFieldUpdater() { + if (cachedFieldUpdater == null) { + cachedFieldUpdater = CrossLayerFieldUpdaterFeature.singleton(); + } + return cachedFieldUpdater; + } + + /** + * Keeps track of the state of fields which have been marked as updatable in prior layer and may + * be updated in the current layer. + */ + private static class UpdatableValueState { + final LayeredFieldValueTransformerImpl transformer; + ImageHeapConstant receiver; + final int receiverId; + boolean updated = false; + + UpdatableValueState(LayeredFieldValueTransformerImpl transformer, int receiverId, ImageHeapConstant receiver) { + this.transformer = transformer; + this.receiverId = receiverId; + this.receiver = receiver; + } + } + + public static LayeredFieldValueTransformerSupport singleton() { + return ImageSingletons.lookup(LayeredFieldValueTransformerSupport.class); + } + + @Override + public boolean isInConfiguration(IsInConfigurationAccess access) { + return ImageLayerBuildingSupport.buildingImageLayer(); + } + + @Override + public List> getRequiredFeatures() { + return List.of(CrossLayerConstantRegistryFeature.class); + } + + /** + * In extension layers we must install {@link UpdatableValueState}s for potential field updates + * within this layer. Note we cannot do this earlier as we need the analysis world to be + * instantiated beforehand. + */ + @Override + public void beforeAnalysis(BeforeAnalysisAccess access) { + if (extensionLayer) { + SVMImageLayerLoader loader = HostedImageLayerBuildingSupport.singleton().getLoader(); + + List newPriorUpdatableValues = new ArrayList<>(); + for (var fieldId : fieldsWithUpdatableValues) { + var aField = loader.getAnalysisFieldForBaseLayerId(fieldId); + List receiverIds = loader.getUpdatableFieldReceiverIds(fieldId); + var proxy = createTransformer(aField, aField.getAnnotation(LayeredFieldValue.class), Set.copyOf(receiverIds)); + + for (int receiverId : receiverIds) { + ImageHeapConstant constant = loader.getConstant(receiverId); + var state = new UpdatableValueState(proxy, receiverId, constant); + newPriorUpdatableValues.add(state); + } + } + priorUpdatableValues = Collections.unmodifiableList(newPriorUpdatableValues); + } + } + + /** + * Because we are checking for updates on objects which have already been installed in the heap + * in a prior layer, it is not guaranteed that these objects will be reached during the usual + * analysis process. Hence, we must actively poll updatable values during analysis to see if an + * update is available. + */ + @Override + public void duringAnalysis(DuringAnalysisAccess access) { + if (extensionLayer) { + boolean changed = processUpdatableValues((FeatureImpl.DuringAnalysisAccessImpl) access); + if (changed) { + // new objects were added which need to be scanned + access.requireAnalysisIteration(); + } + } + } + + /** + * Because these fields have already been installed in the heap, these objects with updatable + * field will not be reached while laying out the current layer's image heap. Hence, we call + * {@link LayeredFieldValueTransformerSupport#processUpdatableValues} once more before + * performing the heap layout. + */ + @Override + public void beforeHeapLayout(BeforeHeapLayoutAccess access) { + if (extensionLayer) { + processUpdatableValues(null); + } + } + + /** + * Go through all potential updates to find any new updates which need to processed. + */ + public boolean processUpdatableValues(FeatureImpl.DuringAnalysisAccessImpl access) { + SVMImageLayerLoader loader = HostedImageLayerBuildingSupport.singleton().getLoader(); + boolean updated = false; + for (var updatableValue : priorUpdatableValues) { + if (!updatableValue.updated) { + if (updatableValue.receiver == null) { + // See if the constant is now available. + updatableValue.receiver = loader.getConstant(updatableValue.receiverId); + } + if (updatableValue.receiver != null) { + var result = updatableValue.transformer.updateAndGetResult(updatableValue.receiver); + if (result != null) { + /* + * As part of updating process we must record the field update and also, if + * during analysis, ensure the object is scanned. + */ + VMError.guarantee(!result.updatable(), "Currently values can only be updated once."); + updatableValue.updated = true; + var newValue = result.value(); + getFieldUpdater().updateField(updatableValue.receiver, updatableValue.transformer.aField, newValue); + if (access != null) { + access.rescanObject(newValue); + } + updated = true; + } + } + } + } + return updated; + } + + public LayeredFieldValueTransformerImpl createTransformer(AnalysisField aField, LayeredFieldValue layeredFieldValue) { + var result = fieldToLayeredTransformer.get(aField); + if (result != null) { + return result; + } + VMError.guarantee(!aField.isInBaseLayer() || !fieldsWithUpdatableValues.contains(aField.getId()), + "Field value transformer should have already been installed via setupUpdatableValueTransformers."); + return createTransformer(aField, layeredFieldValue, Set.of()); + } + + private LayeredFieldValueTransformerImpl createTransformer(AnalysisField aField, LayeredFieldValue layeredFieldValue, Set delayedValueReceivers) { + return fieldToLayeredTransformer.computeIfAbsent(aField, _ -> { + var transformer = ReflectionUtil.newInstance(layeredFieldValue.transformer()); + return new LayeredFieldValueTransformerImpl(aField, transformer, delayedValueReceivers); + }); + } + + /** + * Called on all field values before heap layout. Note, that because we cannot fold updatable + * values, we need to have an explicit call to signal that it is safe to expose updatable + * values. + * + * @return whether this receiver needs to be patched. + */ + public boolean finalizeFieldValue(HostedField hField, JavaConstant receiver) { + AnalysisField aField = hField.getWrapped(); + ImageHeapConstant ihc = (ImageHeapConstant) receiver; + var transformer = fieldToLayeredTransformer.get(aField); + if (transformer != null) { + return transformer.finalizeFieldValue(ihc); + } + return false; + } + + /** Marks all updatable fields with objects installed within the heap. */ + public void recordWrittenField(HostedField hField, NativeImageHeap.ObjectInfo receiver, ImageHeapLayoutInfo heapLayout) { + var transformer = fieldToLayeredTransformer.get(hField.getWrapped()); + if (transformer != null) { + ImageHeapConstant ihc = receiver.getConstant(); + if (transformer.isUpdatableReceiver(ihc)) { + long heapOffset = receiver.getOffset() + hField.getLocation(); + getFieldUpdater().markUpdatableField(heapOffset, ihc, hField, heapLayout); + } + } + } + + /** @return all receivers which have an updatable value within this field */ + public List getUpdatableReceivers(AnalysisField aField) { + var transformer = fieldToLayeredTransformer.get(aField); + if (transformer == null || !transformer.currentLayerHasUpdatableValues) { + return List.of(); + } + var updatableReceivers = transformer.computeUpdatableReceivers(); + assert !updatableReceivers.isEmpty(); + return updatableReceivers; + } + + static class LayeredCallbacks extends SingletonLayeredCallbacksSupplier { + @Override + public SingletonTrait getLayeredCallbacksTrait() { + return new SingletonTrait(SingletonTraitKind.LAYERED_CALLBACKS, new SingletonLayeredCallbacks() { + @Override + public LayeredImageSingleton.PersistFlags doPersist(ImageSingletonWriter writer, Object singleton) { + LayeredFieldValueTransformerSupport support = (LayeredFieldValueTransformerSupport) singleton; + var fieldsWithUpdatableValues = support.fieldToLayeredTransformer.entrySet().stream() + .filter(e -> e.getValue().currentLayerHasUpdatableValues) + .map(e -> e.getKey().getId()).toList(); + writer.writeIntList("fieldsWithUpdatableValues", fieldsWithUpdatableValues); + return LayeredImageSingleton.PersistFlags.CALLBACK_ON_REGISTRATION; + } + + @Override + public void onSingletonRegistration(ImageSingletonLoader loader, Object singleton) { + ((LayeredFieldValueTransformerSupport) singleton).fieldsWithUpdatableValues = Set.copyOf(loader.readIntList("fieldsWithUpdatableValues")); + } + }); + } + } +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/SVMImageLayerLoader.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/SVMImageLayerLoader.java index 17a68836a0c1..8dc703e68c93 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/SVMImageLayerLoader.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/SVMImageLayerLoader.java @@ -44,6 +44,7 @@ import java.nio.channels.FileChannel; import java.util.Arrays; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; @@ -1401,6 +1402,11 @@ public ImageHeapConstant getOrCreateConstant(int id) { return getOrCreateConstant(id, null); } + /* Retrieves the given constant iff it has already been relinked. */ + public ImageHeapConstant getConstant(int id) { + return constants.get(id); + } + /** * Get the {@link ImageHeapConstant} representation for a specific base layer constant id. If * known, the parentReachableHostedObject will point to the corresponding constant in the @@ -1734,15 +1740,21 @@ private void addBaseLayerValueToImageHeap(ImageHeapConstant constant, ImageHeapC universe.getHeapScanner().registerBaseLayerValue(constant, getFieldFromIndex(imageHeapInstance, i)); } else if (parentConstant instanceof ImageHeapObjectArray) { universe.getHeapScanner().registerBaseLayerValue(constant, i); + } else if (parentConstant instanceof ImageHeapRelocatableConstant) { + // skip - nothing to do } else { throw AnalysisError.shouldNotReachHere("unexpected constant: " + constant); } } private void ensureHubInitialized(ImageHeapConstant constant) { - JavaConstant javaConstant = constant.getHostedObject(); + if (constant instanceof ImageHeapRelocatableConstant) { + // not a hub + return; + } + if (constant.getType().getJavaClass().equals(Class.class)) { - DynamicHub hub = universe.getHostedValuesProvider().asObject(DynamicHub.class, javaConstant); + DynamicHub hub = universe.getHostedValuesProvider().asObject(DynamicHub.class, constant.getHostedObject()); AnalysisType type = ((SVMHost) universe.hostVM()).lookupType(hub); ensureHubInitialized(type); /* @@ -1938,4 +1950,9 @@ public JavaConstant get(SVMImageLayerLoader imageLayerLoader) { public static JavaConstantSupplier getConstant(ConstantReference.Reader constantReference) { return new JavaConstantSupplier(constantReference); } + + public List getUpdatableFieldReceiverIds(int fid) { + var updatableReceivers = findField(fid).getUpdatableReceivers(); + return IntStream.range(0, updatableReceivers.size()).map(updatableReceivers::get).boxed().toList(); + } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/SVMImageLayerWriter.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/SVMImageLayerWriter.java index b9d873b7fca0..41f400811ef8 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/SVMImageLayerWriter.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/SVMImageLayerWriter.java @@ -732,6 +732,14 @@ private void persistField(AnalysisField field, Supplier { public Factory() { } @@ -2252,6 +2252,18 @@ public final void setSimulatedFieldValue(com.oracle.svm.hosted.imagelayer.Shared public final com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.ConstantReference.Builder initSimulatedFieldValue() { return _initPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.ConstantReference.factory,3, 0); } + public final boolean hasUpdatableReceivers() { + return !_pointerFieldIsNull(4); + } + public final com.oracle.svm.shaded.org.capnproto.PrimitiveList.Int.Builder getUpdatableReceivers() { + return _getPointerField(com.oracle.svm.shaded.org.capnproto.PrimitiveList.Int.factory, 4, null, 0); + } + public final void setUpdatableReceivers(com.oracle.svm.shaded.org.capnproto.PrimitiveList.Int.Reader value) { + _setPointerField(com.oracle.svm.shaded.org.capnproto.PrimitiveList.Int.factory, 4, value); + } + public final com.oracle.svm.shaded.org.capnproto.PrimitiveList.Int.Builder initUpdatableReceivers(int size) { + return _initPointerField(com.oracle.svm.shaded.org.capnproto.PrimitiveList.Int.factory, 4, size); + } } public static final class Reader extends com.oracle.svm.shaded.org.capnproto.StructReader { @@ -2351,6 +2363,13 @@ public com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder return _getPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.ConstantReference.factory,3,null, 0); } + public final boolean hasUpdatableReceivers() { + return !_pointerFieldIsNull(4); + } + public final com.oracle.svm.shaded.org.capnproto.PrimitiveList.Int.Reader getUpdatableReceivers() { + return _getPointerField(com.oracle.svm.shaded.org.capnproto.PrimitiveList.Int.factory, 4, null, 0); + } + } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedField.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedField.java index ee762d1dbaf7..2b3a5e94c3f7 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedField.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedField.java @@ -35,6 +35,8 @@ import com.oracle.svm.core.util.VMError; import com.oracle.svm.hosted.ameta.FieldValueInterceptionSupport; +import jdk.graal.compiler.debug.Assertions; +import jdk.vm.ci.meta.JavaConstant; import jdk.vm.ci.meta.JavaKind; import jdk.vm.ci.meta.ResolvedJavaField; @@ -55,6 +57,8 @@ public class HostedField extends HostedElement implements OriginalFieldProvider, protected int location; private int installedLayerNum; + private final FieldValueInterceptionSupport fieldValueInterceptionSupport = FieldValueInterceptionSupport.singleton(); + public HostedField(AnalysisField wrapped, HostedType holder, HostedType type) { this.wrapped = wrapped; this.holder = holder; @@ -144,8 +148,13 @@ public boolean isWritten() { return wrapped.isWritten(); } - public boolean isValueAvailable() { - return FieldValueInterceptionSupport.singleton().isValueAvailable(wrapped); + /** + * Returns true if the field's value is available at the time of querying. For unknown fields + * this depends on the image build stage when the value is computed. + */ + public boolean isValueAvailable(JavaConstant receiver) { + assert (receiver == null) == isStatic() : Assertions.errorMessage("The receiver should be null iff this is a static field", this, receiver); + return fieldValueInterceptionSupport.isValueAvailable(wrapped, receiver); } @Override diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/SharedConstantFieldProvider.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/SharedConstantFieldProvider.java index a8c3717aa1d2..6fb882bab9a6 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/SharedConstantFieldProvider.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/SharedConstantFieldProvider.java @@ -101,7 +101,7 @@ private boolean allowConstantFolding(ResolvedJavaField field, ConstantFieldTool< * for initialization at build time) before any constant folding of static fields is * attempted. */ - if (!fieldValueInterceptionSupport.isValueAvailable(aField)) { + if (!fieldValueInterceptionSupport.isValueAvailable(aField, tool == null ? null : tool.getReceiver())) { return false; } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/UniverseBuilder.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/UniverseBuilder.java index 6b7d82f8f659..bd2f4a1ed380 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/UniverseBuilder.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/UniverseBuilder.java @@ -773,7 +773,7 @@ private static boolean skipStaticField0(HostedField field) { return true; } - boolean available = field.isValueAvailable(); + boolean available = field.isValueAvailable(null); if (!available) { /* * Since the value is not yet available we must register it as a diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/FieldValueTransformation.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/FieldValueTransformation.java index f86e42c5b5bc..49c98754e0f8 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/FieldValueTransformation.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/FieldValueTransformation.java @@ -36,8 +36,8 @@ import com.oracle.graal.pointsto.meta.AnalysisField; import com.oracle.graal.pointsto.util.GraalAccess; import com.oracle.svm.core.SubstrateUtil; -import com.oracle.svm.core.util.UserError; import com.oracle.svm.core.fieldvaluetransformer.ObjectToConstantFieldValueTransformer; +import com.oracle.svm.core.util.UserError; import jdk.vm.ci.meta.JavaConstant; import jdk.vm.ci.meta.ResolvedJavaField; diff --git a/web-image/src/com.oracle.svm.hosted.webimage/src/com/oracle/svm/hosted/webimage/codegen/heap/WebImageObjectInspector.java b/web-image/src/com.oracle.svm.hosted.webimage/src/com/oracle/svm/hosted/webimage/codegen/heap/WebImageObjectInspector.java index 9b44b8db3c67..83280202cb70 100644 --- a/web-image/src/com.oracle.svm.hosted.webimage/src/com/oracle/svm/hosted/webimage/codegen/heap/WebImageObjectInspector.java +++ b/web-image/src/com.oracle.svm.hosted.webimage/src/com/oracle/svm/hosted/webimage/codegen/heap/WebImageObjectInspector.java @@ -208,7 +208,7 @@ private void buildObjectType(ObjectType out, JavaConstant c, ConstantIdentityMap HostedUniverse hUniverse = ((WebImageTypeControl) typeControl).getHUniverse(); for (HostedField f : fields.fields) { if (f.getJavaKind().isObject() && f.getType().getStorageKind().isObject()) { - if (!f.isValueAvailable()) { + if (!f.isValueAvailable(c)) { // Use NULL for computed fields such as StringInternSupport.imageInternedStrings // WebImageTypeControl.postProcess will patch the right value members.add(NULL); diff --git a/web-image/src/com.oracle.svm.hosted.webimage/src/com/oracle/svm/hosted/webimage/wasmgc/codegen/WasmGCHeapWriter.java b/web-image/src/com.oracle.svm.hosted.webimage/src/com/oracle/svm/hosted/webimage/wasmgc/codegen/WasmGCHeapWriter.java index da17bccc3f85..3170ef1fba83 100644 --- a/web-image/src/com.oracle.svm.hosted.webimage/src/com/oracle/svm/hosted/webimage/wasmgc/codegen/WasmGCHeapWriter.java +++ b/web-image/src/com.oracle.svm.hosted.webimage/src/com/oracle/svm/hosted/webimage/wasmgc/codegen/WasmGCHeapWriter.java @@ -448,7 +448,7 @@ private List registerAndFillStaticFields(WasmModule module) { for (HostedField field : heap.hUniverse.getFields()) { if (shouldGenerateStaticField(field)) { - assert field.isWritten() || !field.isValueAvailable() || MaterializedConstantFields.singleton().contains(field.wrapped); + assert field.isWritten() || !field.isValueAvailable(null) || MaterializedConstantFields.singleton().contains(field.wrapped); WasmValType fieldType = providers.util().typeForJavaType(field.getType()); WasmId.Global staticFieldId = providers.idFactory().forStaticField(fieldType, field);