diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replacements/HotSpotAllocationSnippets.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replacements/HotSpotAllocationSnippets.java index 3751cff94609..565d83f83bae 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replacements/HotSpotAllocationSnippets.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replacements/HotSpotAllocationSnippets.java @@ -24,9 +24,6 @@ */ package jdk.graal.compiler.hotspot.replacements; -import static jdk.vm.ci.meta.DeoptimizationAction.InvalidateRecompile; -import static jdk.vm.ci.meta.DeoptimizationAction.None; -import static jdk.vm.ci.meta.DeoptimizationReason.RuntimeConstraint; import static jdk.graal.compiler.core.common.GraalOptions.MinimalBulkZeroingSize; import static jdk.graal.compiler.core.common.spi.ForeignCallDescriptor.CallSideEffect.NO_SIDE_EFFECT; import static jdk.graal.compiler.hotspot.GraalHotSpotVMConfig.INJECTED_OPTIONVALUES; @@ -73,6 +70,12 @@ import static jdk.graal.compiler.replacements.ReplacementsUtil.staticAssert; import static jdk.graal.compiler.replacements.SnippetTemplate.DEFAULT_REPLACER; import static jdk.graal.compiler.replacements.nodes.CStringConstant.cstring; +import static jdk.vm.ci.meta.DeoptimizationAction.InvalidateRecompile; +import static jdk.vm.ci.meta.DeoptimizationAction.None; +import static jdk.vm.ci.meta.DeoptimizationReason.RuntimeConstraint; + +import org.graalvm.word.UnsignedWord; +import org.graalvm.word.WordFactory; import jdk.graal.compiler.api.replacements.Fold; import jdk.graal.compiler.api.replacements.Fold.InjectedParameter; @@ -118,9 +121,6 @@ import jdk.graal.compiler.replacements.SnippetTemplate.Arguments; import jdk.graal.compiler.replacements.SnippetTemplate.SnippetInfo; import jdk.graal.compiler.word.Word; -import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; - import jdk.vm.ci.code.CodeUtil; import jdk.vm.ci.code.Register; import jdk.vm.ci.hotspot.HotSpotResolvedObjectType; diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/replacements/AllocationSnippets.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/replacements/AllocationSnippets.java index 12154f7ebb33..390449534b72 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/replacements/AllocationSnippets.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/replacements/AllocationSnippets.java @@ -31,6 +31,10 @@ import static jdk.graal.compiler.nodes.extended.BranchProbabilityNode.probability; import static jdk.graal.compiler.replacements.nodes.ExplodeLoopNode.explodeLoop; +import org.graalvm.word.LocationIdentity; +import org.graalvm.word.UnsignedWord; +import org.graalvm.word.WordFactory; + import jdk.graal.compiler.nodes.PrefetchAllocateNode; import jdk.graal.compiler.nodes.extended.MembarNode; import jdk.graal.compiler.nodes.memory.address.OffsetAddressNode; @@ -38,9 +42,6 @@ import jdk.graal.compiler.replacements.nodes.ExplodeLoopNode; import jdk.graal.compiler.replacements.nodes.ZeroMemoryNode; import jdk.graal.compiler.word.Word; -import org.graalvm.word.LocationIdentity; -import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; /** * Snippets used for implementing NEW, ANEWARRAY and NEWARRAY. @@ -119,11 +120,11 @@ protected UnsignedWord arrayAllocationSize(int length, int arrayBaseOffset, int return WordFactory.unsigned(arrayAllocationSize(length, arrayBaseOffset, log2ElementSize, alignment)); } - /** - * We do an unsigned multiplication so that a negative array length will result in an array size - * greater than Integer.MAX_VALUE. - */ - public static long arrayAllocationSize(long length, int arrayBaseOffset, int log2ElementSize, int alignment) { + public static long arrayAllocationSize(int length, int arrayBaseOffset, int log2ElementSize, int alignment) { + /* + * We do an unsigned multiplication so that a negative array length will result in an array + * size greater than Integer.MAX_VALUE. + */ long size = ((length & 0xFFFFFFFFL) << log2ElementSize) + arrayBaseOffset + (alignment - 1); long mask = ~(alignment - 1); return size & mask; diff --git a/docs/reference-manual/native-image/DebugInfo.md b/docs/reference-manual/native-image/DebugInfo.md index fef7e03fef54..2cfc6e7815c9 100644 --- a/docs/reference-manual/native-image/DebugInfo.md +++ b/docs/reference-manual/native-image/DebugInfo.md @@ -154,10 +154,10 @@ So, for example in the DWARF debug info model `java.lang.String` identifies a C+ This class layout type declares the expected fields like `hash` of type `int` and `value` of type `byte[]` and methods like `String(byte[])`, `charAt(int)`, etc. However, the copy constructor which appears in Java as `String(String)` appears in `gdb` with the signature `String(java.lang.String *)`. The C++ layout class inherits fields and methods from class (layout) type `java.lang.Object` using C++ public inheritance. -The latter in turn inherits standard oop (ordinary object pointer) header fields from a special struct class named `_objhdr` which includes two fields. The first field is called -`hub` and its type is `java.lang.Class *` i.e. it is a pointer to the object's -class. The second field is called `idHash` and has type `int`. It stores an -identity hashcode for the object. +The latter in turn inherits standard oop (ordinary object pointer) header fields from a special struct class named `_objhdr` which includes up to two fields (depending on the VM configuration). +The first field is called `hub` and its type is `java.lang.Class *` i.e. it is a pointer to the object's class. +The second field (optional) is called `idHash` and has type `int`. +It stores an identity hashcode for the object. The `ptype` command can be used to print details of a specific type. Note that the Java type name must be specified in quotes because to escape the embedded `.` characters. diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ObjectHeaderImpl.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ObjectHeaderImpl.java index 0582a8a18509..6c414ee63a13 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ObjectHeaderImpl.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ObjectHeaderImpl.java @@ -24,11 +24,6 @@ */ package com.oracle.svm.core.genscavenge; -import jdk.graal.compiler.api.directives.GraalDirectives; -import jdk.graal.compiler.api.replacements.Fold; -import jdk.graal.compiler.replacements.ReplacementsUtil; -import jdk.graal.compiler.word.ObjectAccess; -import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.word.LocationIdentity; @@ -51,6 +46,11 @@ import com.oracle.svm.core.thread.VMOperation; import com.oracle.svm.core.util.VMError; +import jdk.graal.compiler.api.directives.GraalDirectives; +import jdk.graal.compiler.api.replacements.Fold; +import jdk.graal.compiler.replacements.ReplacementsUtil; +import jdk.graal.compiler.word.ObjectAccess; +import jdk.graal.compiler.word.Word; import jdk.vm.ci.code.CodeUtil; /** @@ -90,13 +90,13 @@ public final class ObjectHeaderImpl extends ObjectHeader { numAlignmentBits = CodeUtil.log2(ConfigurationValues.getObjectLayout().getAlignment()); int numMinimumReservedBits = 3; VMError.guarantee(numMinimumReservedBits <= numAlignmentBits, "Minimum set of reserved bits must be provided by object alignment"); - if (hasFixedIdentityHashField()) { - numReservedBits = numMinimumReservedBits; - } else { + if (isIdentityHashFieldOptional()) { VMError.guarantee(ReferenceAccess.singleton().haveCompressedReferences(), "Ensures hubs (at the start of the image heap) remain addressable"); numReservedBits = numMinimumReservedBits + 2; VMError.guarantee(numReservedBits <= numAlignmentBits || hasShift(), "With no shift, forwarding references are stored directly in the header (with 64-bit, must be) and we cannot use non-alignment header bits"); + } else { + numReservedBits = numMinimumReservedBits; } numReservedExtraBits = numReservedBits - numAlignmentBits; reservedBitsMask = (1 << numReservedBits) - 1; @@ -136,20 +136,22 @@ public Word encodeAsUnmanagedObjectHeader(DynamicHub hub) { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) @Override - public void initializeHeaderOfNewObject(Pointer objectPointer, Word encodedHub) { + public void initializeHeaderOfNewObject(Pointer objectPointer, Word encodedHub, boolean isArrayLike) { ObjectLayout ol = ConfigurationValues.getObjectLayout(); + boolean isIdentityHashFieldInObjectHeader = ol.isIdentityHashFieldInObjectHeader() || ol.isIdentityHashFieldAtTypeSpecificOffset() && isArrayLike; if (getReferenceSize() == Integer.BYTES) { dynamicAssert(encodedHub.and(WordFactory.unsigned(0xFFFFFFFF00000000L)).isNull(), "hub can only use 32 bits"); - if (ol.hasFixedIdentityHashField()) { - dynamicAssert(ol.getFixedIdentityHashOffset() == getHubOffset() + 4, "assumed layout to optimize initializing write"); + if (isIdentityHashFieldInObjectHeader) { + /* Use a single 64-bit write to initialize the hub and the identity hashcode. */ + dynamicAssert(ol.getObjectHeaderIdentityHashOffset() == getHubOffset() + 4, "assumed layout to optimize initializing write"); objectPointer.writeLong(getHubOffset(), encodedHub.rawValue(), LocationIdentity.INIT_LOCATION); } else { objectPointer.writeInt(getHubOffset(), (int) encodedHub.rawValue(), LocationIdentity.INIT_LOCATION); } } else { objectPointer.writeWord(getHubOffset(), encodedHub, LocationIdentity.INIT_LOCATION); - if (ol.hasFixedIdentityHashField()) { - objectPointer.writeInt(ol.getFixedIdentityHashOffset(), 0, LocationIdentity.INIT_LOCATION); + if (isIdentityHashFieldInObjectHeader) { + objectPointer.writeInt(ol.getObjectHeaderIdentityHashOffset(), 0, LocationIdentity.INIT_LOCATION); } } } @@ -158,10 +160,11 @@ public void initializeHeaderOfNewObject(Pointer objectPointer, Word encodedHub) @Override public boolean hasOptionalIdentityHashField(Word header) { if (GraalDirectives.inIntrinsic()) { - ReplacementsUtil.staticAssert(!hasFixedIdentityHashField(), "use only when fields are not fixed"); + ReplacementsUtil.staticAssert(isIdentityHashFieldOptional(), "use only when hashcode fields are optional"); } else { - VMError.guarantee(!hasFixedIdentityHashField(), "use only when fields are not fixed"); + VMError.guarantee(isIdentityHashFieldOptional(), "use only when hashcode fields are optional"); } + UnsignedWord inFieldState = IDHASH_STATE_IN_FIELD.shiftLeft(IDHASH_STATE_SHIFT); return header.and(IDHASH_STATE_BITS).equal(inFieldState); } @@ -169,7 +172,7 @@ public boolean hasOptionalIdentityHashField(Word header) { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) void setIdentityHashInField(Object o) { assert VMOperation.isGCInProgress(); - VMError.guarantee(!hasFixedIdentityHashField()); + VMError.guarantee(isIdentityHashFieldOptional()); UnsignedWord oldHeader = readHeaderFromObject(o); UnsignedWord inFieldState = IDHASH_STATE_IN_FIELD.shiftLeft(IDHASH_STATE_SHIFT); UnsignedWord newHeader = oldHeader.and(IDHASH_STATE_BITS.not()).or(inFieldState); @@ -195,11 +198,12 @@ void setIdentityHashInField(Object o) { @Override public void setIdentityHashFromAddress(Pointer ptr, Word currentHeader) { if (GraalDirectives.inIntrinsic()) { - ReplacementsUtil.staticAssert(!hasFixedIdentityHashField(), "must always access field"); + ReplacementsUtil.staticAssert(isIdentityHashFieldOptional(), "use only when hashcode fields are optional"); } else { - VMError.guarantee(!hasFixedIdentityHashField()); - assert !hasIdentityHashFromAddress(currentHeader); + assert isIdentityHashFieldOptional() : "use only when hashcode fields are optional"; + assert !hasIdentityHashFromAddress(currentHeader) : "must not already have a hashcode"; } + UnsignedWord fromAddressState = IDHASH_STATE_FROM_ADDRESS.shiftLeft(IDHASH_STATE_SHIFT); UnsignedWord newHeader = currentHeader.and(IDHASH_STATE_BITS.not()).or(fromAddressState); writeHeaderToObject(ptr.toObjectNonNull(), newHeader); @@ -217,13 +221,17 @@ public boolean hasIdentityHashFromAddress(Word header) { @AlwaysInline("GC performance") @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) static boolean hasIdentityHashFromAddressInline(Word header) { - if (hasFixedIdentityHashField()) { - return false; + if (GraalDirectives.inIntrinsic()) { + ReplacementsUtil.staticAssert(isIdentityHashFieldOptional(), "use only when hashcode fields are optional"); + } else { + assert isIdentityHashFieldOptional(); } + UnsignedWord fromAddressState = IDHASH_STATE_FROM_ADDRESS.shiftLeft(IDHASH_STATE_SHIFT); return header.and(IDHASH_STATE_BITS).equal(fromAddressState); } + @AlwaysInline(value = "Helper method that needs to be optimized away.") @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private static void dynamicAssert(boolean condition, String msg) { if (GraalDirectives.inIntrinsic()) { @@ -309,7 +317,7 @@ public long encodeAsImageHeapObjectHeader(ImageHeapObject obj, long hubOffsetFro assert obj.getPartition() instanceof FillerObjectDummyPartition; } } - if (!hasFixedIdentityHashField()) { + if (isIdentityHashFieldOptional()) { header |= (IDHASH_STATE_IN_FIELD.rawValue() << IDHASH_STATE_SHIFT); } return header; @@ -427,7 +435,7 @@ static boolean hasShift() { } @Fold - static boolean hasFixedIdentityHashField() { - return ConfigurationValues.getObjectLayout().hasFixedIdentityHashField(); + static boolean isIdentityHashFieldOptional() { + return ConfigurationValues.getObjectLayout().isIdentityHashFieldOptional(); } } diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/Space.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/Space.java index 29b6f24cb1e5..83b27eff8126 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/Space.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/Space.java @@ -399,7 +399,7 @@ private Object copyAlignedObject(Object originalObj) { UnsignedWord originalSize = LayoutEncoding.getSizeFromObjectInlineInGC(originalObj, false); UnsignedWord copySize = originalSize; boolean addIdentityHashField = false; - if (!ConfigurationValues.getObjectLayout().hasFixedIdentityHashField()) { + if (ConfigurationValues.getObjectLayout().isIdentityHashFieldOptional()) { Word header = ObjectHeader.readHeaderFromObject(originalObj); if (probability(SLOW_PATH_PROBABILITY, ObjectHeaderImpl.hasIdentityHashFromAddressInline(header))) { addIdentityHashField = true; @@ -424,7 +424,7 @@ private Object copyAlignedObject(Object originalObj) { if (probability(SLOW_PATH_PROBABILITY, addIdentityHashField)) { // Must do first: ensures correct object size below and in other places int value = IdentityHashCodeSupport.computeHashCodeFromAddress(originalObj); - int offset = LayoutEncoding.getOptionalIdentityHashOffset(copy); + int offset = LayoutEncoding.getIdentityHashOffset(copy); ObjectAccess.writeInt(copy, offset, value, IdentityHashCodeSupport.IDENTITY_HASHCODE_LOCATION); ObjectHeaderImpl.getObjectHeaderImpl().setIdentityHashInField(copy); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateTargetDescription.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateTargetDescription.java index 0ce0f4a13549..07d078a1b665 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateTargetDescription.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateTargetDescription.java @@ -24,17 +24,16 @@ */ package com.oracle.svm.core; +import java.util.EnumSet; + import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import com.oracle.svm.core.code.RuntimeCodeCache; -import com.oracle.svm.core.deopt.DeoptimizedFrame; import jdk.vm.ci.code.Architecture; import jdk.vm.ci.code.TargetDescription; -import java.util.EnumSet; - public class SubstrateTargetDescription extends TargetDescription { @Platforms(Platform.HOSTED_ONLY.class) public static boolean shouldInlineObjectsInImageCode() { @@ -45,24 +44,14 @@ public static boolean shouldInlineObjectsInRuntimeCode() { return SubstrateOptions.SpawnIsolates.getValue() && RuntimeCodeCache.Options.WriteableCodeCache.getValue(); } - private final int deoptScratchSpace; private final EnumSet runtimeCheckedCPUFeatures; @Platforms(Platform.HOSTED_ONLY.class) - public SubstrateTargetDescription(Architecture arch, boolean isMP, int stackAlignment, int implicitNullCheckLimit, int deoptScratchSpace, EnumSet runtimeCheckedCPUFeatures) { + public SubstrateTargetDescription(Architecture arch, boolean isMP, int stackAlignment, int implicitNullCheckLimit, EnumSet runtimeCheckedCPUFeatures) { super(arch, isMP, stackAlignment, implicitNullCheckLimit, shouldInlineObjectsInImageCode()); - this.deoptScratchSpace = deoptScratchSpace; this.runtimeCheckedCPUFeatures = runtimeCheckedCPUFeatures; } - /** - * Returns the amount of scratch space which must be reserved for return value registers in - * {@link DeoptimizedFrame}. - */ - public int getDeoptScratchSpace() { - return deoptScratchSpace; - } - public EnumSet getRuntimeCheckedCPUFeatures() { return runtimeCheckedCPUFeatures; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/NonmovableArrays.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/NonmovableArrays.java index 05a11db9ecf5..1b765e534da2 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/NonmovableArrays.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/NonmovableArrays.java @@ -28,8 +28,6 @@ import java.nio.ByteBuffer; import java.util.Arrays; -import jdk.graal.compiler.nodes.java.ArrayLengthNode; -import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; @@ -56,6 +54,9 @@ import com.oracle.svm.core.snippets.KnownIntrinsics; import com.oracle.svm.core.util.VMError; +import jdk.graal.compiler.nodes.java.ArrayLengthNode; +import jdk.graal.compiler.word.Word; + /** * Support for allocating and accessing non-moving arrays. Such arrays are safe to access during * garbage collection. They can also be passed between isolates, provided that they do not contain @@ -99,7 +100,7 @@ private static > T createArray(int length, Class ObjectHeader header = Heap.getHeap().getObjectHeader(); Word encodedHeader = header.encodeAsUnmanagedObjectHeader(hub); - header.initializeHeaderOfNewObject(array, encodedHeader); + header.initializeHeaderOfNewObject(array, encodedHeader, true); array.writeInt(ConfigurationValues.getObjectLayout().getArrayLengthOffset(), length); // already zero-initialized thanks to calloc() diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/config/ObjectLayout.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/config/ObjectLayout.java index f8ef0f3122ff..9165ea1f2bb0 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/config/ObjectLayout.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/config/ObjectLayout.java @@ -30,7 +30,6 @@ import com.oracle.svm.core.SubstrateTargetDescription; import com.oracle.svm.core.Uninterruptible; -import com.oracle.svm.core.deopt.DeoptimizedFrame; import jdk.graal.compiler.api.directives.GraalDirectives; import jdk.graal.compiler.core.common.NumUtil; @@ -43,6 +42,18 @@ /** * Immutable class that holds all sizes and offsets that contribute to the object layout. + * + * Identity hashcode fields can either be: + *
    + *
  1. In the object header, at a fixed offset for all objects (see + * {@link #isIdentityHashFieldInObjectHeader()}).
  2. + *
  3. At a type specific offset, potentially outside the object header (see + * {@link #isIdentityHashFieldAtTypeSpecificOffset()}).
  4. + *
  5. Outside the object header, at a type- or object-specific offset (see + * {@link #isIdentityHashFieldOptional()}). Note that the field is not part of every object. When an + * object needs the field, the object is resized during garbage collection to accommodate the + * field.
  6. + *
*/ public final class ObjectLayout { @@ -54,15 +65,17 @@ public final class ObjectLayout { private final int firstFieldOffset; private final int arrayLengthOffset; private final int arrayBaseOffset; - private final int fixedIdentityHashOffset; + private final int objectHeaderIdentityHashOffset; + private final int identityHashMode; - public ObjectLayout(SubstrateTargetDescription target, int referenceSize, int objectAlignment, int hubOffset, - int firstFieldOffset, int arrayLengthOffset, int arrayBaseOffset, int fixedIdentityHashOffset) { + public ObjectLayout(SubstrateTargetDescription target, int referenceSize, int objectAlignment, int hubOffset, int firstFieldOffset, int arrayLengthOffset, int arrayBaseOffset, + int headerIdentityHashOffset, IdentityHashMode identityHashMode) { assert CodeUtil.isPowerOf2(referenceSize) : referenceSize; assert CodeUtil.isPowerOf2(objectAlignment) : objectAlignment; assert arrayLengthOffset % Integer.BYTES == 0; assert hubOffset < firstFieldOffset && hubOffset < arrayLengthOffset : hubOffset; - assert fixedIdentityHashOffset == -1 || (fixedIdentityHashOffset > 0 && fixedIdentityHashOffset < arrayLengthOffset) : fixedIdentityHashOffset; + assert (identityHashMode != IdentityHashMode.OPTIONAL && headerIdentityHashOffset > 0 && headerIdentityHashOffset < arrayLengthOffset && headerIdentityHashOffset % Integer.BYTES == 0) || + (identityHashMode == IdentityHashMode.OPTIONAL && headerIdentityHashOffset == -1); this.target = target; this.referenceSize = referenceSize; @@ -72,7 +85,8 @@ public ObjectLayout(SubstrateTargetDescription target, int referenceSize, int ob this.firstFieldOffset = firstFieldOffset; this.arrayLengthOffset = arrayLengthOffset; this.arrayBaseOffset = arrayBaseOffset; - this.fixedIdentityHashOffset = fixedIdentityHashOffset; + this.objectHeaderIdentityHashOffset = headerIdentityHashOffset; + this.identityHashMode = identityHashMode.value; } /** The minimum alignment of objects (instances and arrays). */ @@ -92,14 +106,6 @@ public int getReferenceSize() { return referenceSize; } - /** - * Returns the amount of scratch space which must be reserved for return value registers in - * {@link DeoptimizedFrame}. - */ - public int getDeoptScratchSpace() { - return target.getDeoptScratchSpace(); - } - /** * The size (in bytes) of values with the given kind. */ @@ -151,24 +157,33 @@ public int getArrayLengthOffset() { return arrayLengthOffset; } - /** If {@link #hasFixedIdentityHashField()}, then returns that offset. */ - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public int getFixedIdentityHashOffset() { - if (GraalDirectives.inIntrinsic()) { - ReplacementsUtil.dynamicAssert(hasFixedIdentityHashField(), "must check before calling"); - } else { - assert hasFixedIdentityHashField(); - } - return fixedIdentityHashOffset; - } - /** * Indicates whether all objects, including arrays, always contain an identity hash code field - * at a specific offset. + * at a specific offset in the object header. */ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public boolean hasFixedIdentityHashField() { - return fixedIdentityHashOffset >= 0; + public boolean isIdentityHashFieldInObjectHeader() { + return identityHashMode == IdentityHashMode.OBJECT_HEADER.value; + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public boolean isIdentityHashFieldAtTypeSpecificOffset() { + return identityHashMode == IdentityHashMode.TYPE_SPECIFIC.value; + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public boolean isIdentityHashFieldOptional() { + return identityHashMode == IdentityHashMode.OPTIONAL.value; + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public int getObjectHeaderIdentityHashOffset() { + if (GraalDirectives.inIntrinsic()) { + ReplacementsUtil.dynamicAssert(objectHeaderIdentityHashOffset > 0, "must check before calling"); + } else { + assert objectHeaderIdentityHashOffset > 0 : "must check before calling"; + } + return objectHeaderIdentityHashOffset; } public int getArrayBaseOffset(JavaKind kind) { @@ -176,7 +191,7 @@ public int getArrayBaseOffset(JavaKind kind) { } public long getArrayElementOffset(JavaKind kind, int index) { - return getArrayBaseOffset(kind) + index * sizeInBytes(kind); + return getArrayBaseOffset(kind) + ((long) index) * sizeInBytes(kind); } public long getArraySize(JavaKind kind, int length, boolean withOptionalIdHashField) { @@ -188,14 +203,14 @@ private long getArrayUnalignedSize(JavaKind kind, int length) { return getArrayBaseOffset(kind) + ((long) length << getArrayIndexShift(kind)); } - public long getArrayOptionalIdentityHashOffset(JavaKind kind, int length) { - return getArrayOptionalIdentityHashOffset(getArrayUnalignedSize(kind, length)); + public long getArrayIdentityHashOffset(JavaKind kind, int length) { + return getArrayIdentityHashOffset(getArrayUnalignedSize(kind, length)); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public long getArrayOptionalIdentityHashOffset(long unalignedSize) { - if (hasFixedIdentityHashField()) { - return getFixedIdentityHashOffset(); + public long getArrayIdentityHashOffset(long unalignedSize) { + if (isIdentityHashFieldInObjectHeader() || isIdentityHashFieldAtTypeSpecificOffset()) { + return getObjectHeaderIdentityHashOffset(); } int align = Integer.BYTES; return ((unalignedSize + align - 1) / align) * align; @@ -204,15 +219,15 @@ public long getArrayOptionalIdentityHashOffset(long unalignedSize) { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public long computeArrayTotalSize(long unalignedSize, boolean withOptionalIdHashField) { long size = unalignedSize; - if (withOptionalIdHashField && !hasFixedIdentityHashField()) { - size = getArrayOptionalIdentityHashOffset(size) + Integer.BYTES; + if (withOptionalIdHashField && isIdentityHashFieldOptional()) { + size = getArrayIdentityHashOffset(size) + Integer.BYTES; } return alignUp(size); } public int getMinImageHeapInstanceSize() { int unalignedSize = firstFieldOffset; // assumes no always-present "synthetic fields" - if (!hasFixedIdentityHashField()) { + if (isIdentityHashFieldAtTypeSpecificOffset() || isIdentityHashFieldOptional()) { int idHashOffset = NumUtil.roundUp(unalignedSize, Integer.BYTES); unalignedSize = idHashOffset + Integer.BYTES; } @@ -236,4 +251,19 @@ public static JavaKind getCallSignatureKind(boolean isEntryPoint, ResolvedJavaTy } return type.getJavaKind(); } + + public enum IdentityHashMode { + /* At a fixed offset, for all objects (part of the object header). */ + OBJECT_HEADER(0), + /* At a type-specific offset (potentially outside the object header). */ + TYPE_SPECIFIC(1), + /* At a type- or object-specific offset (outside the object header). */ + OPTIONAL(2); + + final int value; + + IdentityHashMode(int value) { + this.value = value; + } + } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/jdk/SubstrateObjectCloneSnippets.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/jdk/SubstrateObjectCloneSnippets.java index 2cc0e536dc85..8c9e3d18594f 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/jdk/SubstrateObjectCloneSnippets.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/jdk/SubstrateObjectCloneSnippets.java @@ -30,30 +30,6 @@ import java.util.Map; -import jdk.graal.compiler.api.replacements.Snippet; -import jdk.graal.compiler.core.common.NumUtil; -import jdk.graal.compiler.core.common.spi.ForeignCallDescriptor; -import jdk.graal.compiler.graph.Node; -import jdk.graal.compiler.graph.Node.ConstantNodeParameter; -import jdk.graal.compiler.graph.Node.NodeIntrinsic; -import jdk.graal.compiler.nodes.GraphState; -import jdk.graal.compiler.nodes.NodeView; -import jdk.graal.compiler.nodes.StructuredGraph; -import jdk.graal.compiler.nodes.ValueNode; -import jdk.graal.compiler.nodes.extended.BranchProbabilityNode; -import jdk.graal.compiler.nodes.extended.ForeignCallNode; -import jdk.graal.compiler.nodes.java.ArrayLengthNode; -import jdk.graal.compiler.nodes.spi.LoweringTool; -import jdk.graal.compiler.nodes.spi.VirtualizerTool; -import jdk.graal.compiler.nodes.virtual.VirtualObjectNode; -import jdk.graal.compiler.options.OptionValues; -import jdk.graal.compiler.phases.util.Providers; -import jdk.graal.compiler.replacements.SnippetTemplate; -import jdk.graal.compiler.replacements.SnippetTemplate.Arguments; -import jdk.graal.compiler.replacements.SnippetTemplate.SnippetInfo; -import jdk.graal.compiler.replacements.Snippets; -import jdk.graal.compiler.replacements.nodes.ObjectClone; -import jdk.graal.compiler.word.BarrieredAccess; import org.graalvm.word.LocationIdentity; import org.graalvm.word.WordFactory; @@ -79,6 +55,31 @@ import com.oracle.svm.core.util.UnsignedUtils; import com.oracle.svm.core.util.VMError; +import jdk.graal.compiler.api.replacements.Snippet; +import jdk.graal.compiler.core.common.NumUtil; +import jdk.graal.compiler.core.common.spi.ForeignCallDescriptor; +import jdk.graal.compiler.graph.Node; +import jdk.graal.compiler.graph.Node.ConstantNodeParameter; +import jdk.graal.compiler.graph.Node.NodeIntrinsic; +import jdk.graal.compiler.nodes.GraphState; +import jdk.graal.compiler.nodes.NodeView; +import jdk.graal.compiler.nodes.StructuredGraph; +import jdk.graal.compiler.nodes.ValueNode; +import jdk.graal.compiler.nodes.extended.BranchProbabilityNode; +import jdk.graal.compiler.nodes.extended.ForeignCallNode; +import jdk.graal.compiler.nodes.java.ArrayLengthNode; +import jdk.graal.compiler.nodes.spi.LoweringTool; +import jdk.graal.compiler.nodes.spi.VirtualizerTool; +import jdk.graal.compiler.nodes.virtual.VirtualObjectNode; +import jdk.graal.compiler.options.OptionValues; +import jdk.graal.compiler.phases.util.Providers; +import jdk.graal.compiler.replacements.SnippetTemplate; +import jdk.graal.compiler.replacements.SnippetTemplate.Arguments; +import jdk.graal.compiler.replacements.SnippetTemplate.SnippetInfo; +import jdk.graal.compiler.replacements.Snippets; +import jdk.graal.compiler.replacements.nodes.ObjectClone; +import jdk.graal.compiler.word.BarrieredAccess; +import jdk.graal.compiler.word.ObjectAccess; import jdk.internal.misc.Unsafe; import jdk.vm.ci.meta.ResolvedJavaType; @@ -171,6 +172,12 @@ private static Object doClone(Object original) throws CloneNotSupportedException BarrieredAccess.writeObject(result, monitorOffset, null); } + /* Reset identity hashcode if it is potentially outside the object header. */ + if (ConfigurationValues.getObjectLayout().isIdentityHashFieldAtTypeSpecificOffset()) { + int offset = LayoutEncoding.getIdentityHashOffset(result); + ObjectAccess.writeInt(result, offset, 0); + } + return result; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/SubstrateAllocationSnippets.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/SubstrateAllocationSnippets.java index a852b1dca410..a33e289fdd22 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/SubstrateAllocationSnippets.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/SubstrateAllocationSnippets.java @@ -36,6 +36,36 @@ import java.util.Arrays; import java.util.Map; +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.word.LocationIdentity; +import org.graalvm.word.UnsignedWord; +import org.graalvm.word.WordFactory; + +import com.oracle.svm.core.MissingRegistrationUtils; +import com.oracle.svm.core.SubstrateOptions; +import com.oracle.svm.core.allocationprofile.AllocationCounter; +import com.oracle.svm.core.allocationprofile.AllocationSite; +import com.oracle.svm.core.config.ConfigurationValues; +import com.oracle.svm.core.configure.ConfigurationFile; +import com.oracle.svm.core.graal.meta.SubstrateForeignCallsProvider; +import com.oracle.svm.core.graal.nodes.ForeignCallWithExceptionNode; +import com.oracle.svm.core.graal.nodes.NewPodInstanceNode; +import com.oracle.svm.core.graal.nodes.NewStoredContinuationNode; +import com.oracle.svm.core.graal.nodes.SubstrateNewHybridInstanceNode; +import com.oracle.svm.core.heap.Heap; +import com.oracle.svm.core.heap.Pod; +import com.oracle.svm.core.hub.DynamicHub; +import com.oracle.svm.core.hub.LayoutEncoding; +import com.oracle.svm.core.identityhashcode.IdentityHashCodeSupport; +import com.oracle.svm.core.meta.SharedType; +import com.oracle.svm.core.option.HostedOptionValues; +import com.oracle.svm.core.reflect.MissingReflectionRegistrationUtils; +import com.oracle.svm.core.snippets.SnippetRuntime; +import com.oracle.svm.core.snippets.SnippetRuntime.SubstrateForeignCallDescriptor; +import com.oracle.svm.core.snippets.SubstrateForeignCallTarget; +import com.oracle.svm.core.thread.ContinuationSupport; +import com.oracle.svm.core.util.VMError; + import jdk.graal.compiler.api.directives.GraalDirectives; import jdk.graal.compiler.api.replacements.Fold; import jdk.graal.compiler.api.replacements.Snippet; @@ -76,36 +106,6 @@ import jdk.graal.compiler.word.BarrieredAccess; import jdk.graal.compiler.word.ObjectAccess; import jdk.graal.compiler.word.Word; -import org.graalvm.nativeimage.ImageSingletons; -import org.graalvm.word.LocationIdentity; -import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; - -import com.oracle.svm.core.MissingRegistrationUtils; -import com.oracle.svm.core.SubstrateOptions; -import com.oracle.svm.core.allocationprofile.AllocationCounter; -import com.oracle.svm.core.allocationprofile.AllocationSite; -import com.oracle.svm.core.config.ConfigurationValues; -import com.oracle.svm.core.configure.ConfigurationFile; -import com.oracle.svm.core.graal.meta.SubstrateForeignCallsProvider; -import com.oracle.svm.core.graal.nodes.ForeignCallWithExceptionNode; -import com.oracle.svm.core.graal.nodes.NewPodInstanceNode; -import com.oracle.svm.core.graal.nodes.NewStoredContinuationNode; -import com.oracle.svm.core.graal.nodes.SubstrateNewHybridInstanceNode; -import com.oracle.svm.core.heap.Heap; -import com.oracle.svm.core.heap.Pod; -import com.oracle.svm.core.hub.DynamicHub; -import com.oracle.svm.core.hub.LayoutEncoding; -import com.oracle.svm.core.identityhashcode.IdentityHashCodeSupport; -import com.oracle.svm.core.meta.SharedType; -import com.oracle.svm.core.option.HostedOptionValues; -import com.oracle.svm.core.reflect.MissingReflectionRegistrationUtils; -import com.oracle.svm.core.snippets.SnippetRuntime; -import com.oracle.svm.core.snippets.SnippetRuntime.SubstrateForeignCallDescriptor; -import com.oracle.svm.core.snippets.SubstrateForeignCallTarget; -import com.oracle.svm.core.thread.ContinuationSupport; -import com.oracle.svm.core.util.VMError; - import jdk.vm.ci.meta.JavaKind; import jdk.vm.ci.meta.ResolvedJavaType; @@ -492,8 +492,8 @@ protected final Object callNewMultiArrayStub(Word objectHeader, int rank, Word d private static native Object callNewMultiArray(@ConstantNodeParameter ForeignCallDescriptor descriptor, Word hub, int rank, Word dimensions); @Override - public void initializeObjectHeader(Word memory, Word objectHeader, boolean isArray) { - Heap.getHeap().getObjectHeader().initializeHeaderOfNewObject(memory, objectHeader); + public void initializeObjectHeader(Word memory, Word objectHeader, boolean isArrayLike) { + Heap.getHeap().getObjectHeader().initializeHeaderOfNewObject(memory, objectHeader, isArrayLike); } @Override diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/FillerObject.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/FillerObject.java index 53cd96dd793c..1bb5f325f25d 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/FillerObject.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/FillerObject.java @@ -24,7 +24,10 @@ */ package com.oracle.svm.core.heap; -/** The smallest possible instance object, for filling in gaps in the heap. */ +/** + * The smallest possible instance object, for filling in gaps in the heap. Note that these instances + * may be larger than the object alignment. + */ public final class FillerObject { /** * This field is registered as accessed with the analysis and ensures that the class is diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/ObjectHeader.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/ObjectHeader.java index 0c443fb4ebbd..3f634de041d5 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/ObjectHeader.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/ObjectHeader.java @@ -24,9 +24,6 @@ */ package com.oracle.svm.core.heap; -import jdk.graal.compiler.api.replacements.Fold; -import jdk.graal.compiler.word.ObjectAccess; -import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.word.Pointer; @@ -38,6 +35,10 @@ import com.oracle.svm.core.image.ImageHeapObject; import com.oracle.svm.core.snippets.KnownIntrinsics; +import jdk.graal.compiler.api.replacements.Fold; +import jdk.graal.compiler.word.ObjectAccess; +import jdk.graal.compiler.word.Word; + /** * An object header is a reference-sized collection of bits in each object instance. The object * header holds a reference to a {@link DynamicHub}, which identifies the class of the instance. It @@ -107,7 +108,7 @@ public Pointer readPotentialDynamicHubFromPointer(Pointer ptr) { public abstract Pointer extractPotentialDynamicHubFromHeader(Word header); @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public abstract void initializeHeaderOfNewObject(Pointer ptr, Word header); + public abstract void initializeHeaderOfNewObject(Pointer ptr, Word header, boolean isArrayLike); @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public boolean pointsToObjectHeader(Pointer ptr) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java index 601815a36bb9..81bd810f3081 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java @@ -116,9 +116,11 @@ import com.oracle.svm.util.ReflectionUtil; import com.oracle.svm.util.ReflectionUtil.ReflectionUtilError; +import jdk.graal.compiler.api.directives.GraalDirectives; import jdk.graal.compiler.api.replacements.Fold; import jdk.graal.compiler.core.common.NumUtil; import jdk.graal.compiler.core.common.SuppressFBWarnings; +import jdk.graal.compiler.replacements.ReplacementsUtil; import jdk.internal.access.JavaLangReflectAccess; import jdk.internal.misc.Unsafe; import jdk.internal.reflect.CallerSensitive; @@ -207,7 +209,7 @@ public final class DynamicHub implements AnnotatedElement, java.lang.reflect.Typ private short monitorOffset; @UnknownPrimitiveField(availability = AfterHostedUniverse.class)// - private short optionalIdentityHashOffset; + private short identityHashOffset; /** * Bit-set for various boolean flags, to reduce size of instances. It is important that this @@ -447,22 +449,16 @@ public void setClassInitializationInfo(ClassInitializationInfo classInitializati } @Platforms(Platform.HOSTED_ONLY.class) - public void setData(int layoutEncoding, int typeID, int monitorOffset, int optionalIdentityHashOffset, short typeCheckStart, short typeCheckRange, short typeCheckSlot, + public void setData(int layoutEncoding, int typeID, int monitorOffset, int identityHashOffset, short typeCheckStart, short typeCheckRange, short typeCheckSlot, short[] typeCheckSlots, CFunctionPointer[] vtable, long referenceMapIndex, boolean isInstantiated, boolean canInstantiateAsInstance, boolean isProxyClass, boolean isRegisteredForSerialization) { assert this.vtable == null : "Initialization must be called only once"; assert !(!isInstantiated && canInstantiateAsInstance); - if (LayoutEncoding.isPureInstance(layoutEncoding)) { - ObjectLayout ol = ConfigurationValues.getObjectLayout(); - assert ol.hasFixedIdentityHashField() ? (optionalIdentityHashOffset == ol.getFixedIdentityHashOffset()) : (optionalIdentityHashOffset > 0); - } else { - assert optionalIdentityHashOffset == -1; - } this.layoutEncoding = layoutEncoding; this.typeID = typeID; this.monitorOffset = NumUtil.safeToShort(monitorOffset); - this.optionalIdentityHashOffset = NumUtil.safeToShort(optionalIdentityHashOffset); + this.identityHashOffset = NumUtil.safeToShort(identityHashOffset); this.typeCheckStart = typeCheckStart; this.typeCheckRange = typeCheckRange; this.typeCheckSlot = typeCheckSlot; @@ -634,13 +630,25 @@ public int getMonitorOffset() { return monitorOffset; } + /** + * If possible, use {@link LayoutEncoding#getIdentityHashOffset(Object)} instead. If the hash + * code field is optional, note that this method may return an offset that is outside the bounds + * of a newly allocated object. + */ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - int getOptionalIdentityHashOffset() { + public int getIdentityHashOffset() { ObjectLayout ol = ConfigurationValues.getObjectLayout(); - if (ol.hasFixedIdentityHashField()) { // enable elimination of our field - return ol.getFixedIdentityHashOffset(); + if (ol.isIdentityHashFieldInObjectHeader()) { // enable elimination of our field + return ol.getObjectHeaderIdentityHashOffset(); } - return optionalIdentityHashOffset; + + int result = identityHashOffset; + if (GraalDirectives.inIntrinsic()) { + ReplacementsUtil.dynamicAssert(result > 0, "must have an identity hash field"); + } else { + assert result > 0 : "must have an identity hash field"; + } + return result; } public DynamicHub getSuperHub() { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/Hybrid.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/Hybrid.java index 5ba011451d5e..a5d88f74cdd4 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/Hybrid.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/Hybrid.java @@ -37,9 +37,7 @@ * *
  *    +--------------------------------------------------+
- *    | pointer to DynamicHub                            |
- *    +--------------------------------------------------+
- *    | identity hashcode                                |
+ *    | object header (same header as for arrays)        |
  *    +--------------------------------------------------+
  *    | array length                                     |
  *    +--------------------------------------------------+
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/LayoutEncoding.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/LayoutEncoding.java
index bd57b4339266..5e66f82ff986 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/LayoutEncoding.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/LayoutEncoding.java
@@ -24,9 +24,6 @@
  */
 package com.oracle.svm.core.hub;
 
-import jdk.graal.compiler.core.common.calc.UnsignedMath;
-import jdk.graal.compiler.nodes.java.ArrayLengthNode;
-import jdk.graal.compiler.word.Word;
 import org.graalvm.nativeimage.Platform;
 import org.graalvm.nativeimage.Platforms;
 import org.graalvm.word.Pointer;
@@ -43,6 +40,9 @@
 import com.oracle.svm.core.util.DuplicatedInNativeCode;
 import com.oracle.svm.core.util.VMError;
 
+import jdk.graal.compiler.core.common.calc.UnsignedMath;
+import jdk.graal.compiler.nodes.java.ArrayLengthNode;
+import jdk.graal.compiler.word.Word;
 import jdk.vm.ci.meta.ResolvedJavaType;
 
 /**
@@ -201,9 +201,10 @@ public static UnsignedWord getPureInstanceAllocationSize(int encoding) {
     public static UnsignedWord getPureInstanceSize(DynamicHub hub, boolean withOptionalIdHashField) {
         UnsignedWord size = getPureInstanceAllocationSize(hub.getLayoutEncoding());
         ObjectLayout ol = ConfigurationValues.getObjectLayout();
-        if (withOptionalIdHashField && !ol.hasFixedIdentityHashField()) {
-            int afterIdHashField = hub.getOptionalIdentityHashOffset() + Integer.BYTES;
-            if (size.belowThan(afterIdHashField)) { // fits in a gap between fields
+        if (withOptionalIdHashField && ol.isIdentityHashFieldOptional()) {
+            int afterIdHashField = hub.getIdentityHashOffset() + Integer.BYTES;
+            if (size.belowThan(afterIdHashField)) {
+                /* Identity hash is at the end of the object and does not fit in a gap. */
                 size = WordFactory.unsigned(ol.alignUp(afterIdHashField));
             }
         }
@@ -295,24 +296,25 @@ public static UnsignedWord getArraySize(int encoding, int length, boolean withOp
     }
 
     @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
-    public static int getOptionalIdentityHashOffset(Object obj) {
+    public static int getIdentityHashOffset(Object obj) {
         ObjectLayout ol = ConfigurationValues.getObjectLayout();
-        if (ol.hasFixedIdentityHashField()) {
-            return ol.getFixedIdentityHashOffset();
+        if (ol.isIdentityHashFieldInObjectHeader()) {
+            return ol.getObjectHeaderIdentityHashOffset();
         }
+
         DynamicHub hub = KnownIntrinsics.readHub(obj);
         int encoding = hub.getLayoutEncoding();
-        if (isArrayLike(encoding)) {
+        if (ol.isIdentityHashFieldOptional() && isArrayLike(encoding)) {
             long unalignedSize = getArrayElementOffset(encoding, ArrayLengthNode.arrayLength(obj)).rawValue();
-            return (int) ol.getArrayOptionalIdentityHashOffset(unalignedSize);
+            return (int) ol.getArrayIdentityHashOffset(unalignedSize);
         } else {
-            return hub.getOptionalIdentityHashOffset();
+            return hub.getIdentityHashOffset();
         }
     }
 
     @Uninterruptible(reason = "Prevent a GC moving the object or interfering with its identity hash state.", callerMustBe = true)
     public static UnsignedWord getSizeFromObject(Object obj) {
-        boolean withOptionalIdHashField = !ConfigurationValues.getObjectLayout().hasFixedIdentityHashField() && checkOptionalIdentityHashField(obj);
+        boolean withOptionalIdHashField = ConfigurationValues.getObjectLayout().isIdentityHashFieldOptional() && hasOptionalIdentityHashField(obj);
         return getSizeFromObjectInline(obj, withOptionalIdHashField);
     }
 
@@ -344,7 +346,7 @@ public static UnsignedWord getSizeFromObjectInlineInGC(Object obj) {
     @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
     public static UnsignedWord getSizeFromObjectInlineInGC(Object obj, boolean addOptionalIdHashField) {
         boolean withOptionalIdHashField = addOptionalIdHashField ||
-                        (!ConfigurationValues.getObjectLayout().hasFixedIdentityHashField() && checkOptionalIdentityHashField(obj));
+                        (ConfigurationValues.getObjectLayout().isIdentityHashFieldOptional() && hasOptionalIdentityHashField(obj));
         return getSizeFromObjectInline(obj, withOptionalIdHashField);
     }
 
@@ -362,7 +364,7 @@ private static UnsignedWord getSizeFromObjectInline(Object obj, boolean withOpti
 
     @AlwaysInline("GC performance")
     @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
-    private static boolean checkOptionalIdentityHashField(Object obj) {
+    private static boolean hasOptionalIdentityHashField(Object obj) {
         ObjectHeader oh = Heap.getHeap().getObjectHeader();
         Word header = ObjectHeader.readHeaderFromPointer(Word.objectToUntrackedPointer(obj));
         return oh.hasOptionalIdentityHashField(header);
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/identityhashcode/IdentityHashCodeSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/identityhashcode/IdentityHashCodeSupport.java
index 549e28ef4110..8384c4ac1b04 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/identityhashcode/IdentityHashCodeSupport.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/identityhashcode/IdentityHashCodeSupport.java
@@ -26,12 +26,6 @@
 
 import java.util.SplittableRandom;
 
-import jdk.graal.compiler.nodes.NamedLocationIdentity;
-import jdk.graal.compiler.options.OptionValues;
-import jdk.graal.compiler.phases.util.Providers;
-import jdk.graal.compiler.replacements.IdentityHashCodeSnippets;
-import jdk.graal.compiler.word.ObjectAccess;
-import jdk.graal.compiler.word.Word;
 import org.graalvm.word.LocationIdentity;
 import org.graalvm.word.SignedWord;
 import org.graalvm.word.WordFactory;
@@ -40,11 +34,18 @@
 import com.oracle.svm.core.config.ConfigurationValues;
 import com.oracle.svm.core.config.ObjectLayout;
 import com.oracle.svm.core.heap.Heap;
+import com.oracle.svm.core.hub.LayoutEncoding;
 import com.oracle.svm.core.snippets.SubstrateForeignCallTarget;
 import com.oracle.svm.core.threadlocal.FastThreadLocalFactory;
 import com.oracle.svm.core.threadlocal.FastThreadLocalObject;
 import com.oracle.svm.core.util.VMError;
 
+import jdk.graal.compiler.nodes.NamedLocationIdentity;
+import jdk.graal.compiler.options.OptionValues;
+import jdk.graal.compiler.phases.util.Providers;
+import jdk.graal.compiler.replacements.IdentityHashCodeSnippets;
+import jdk.graal.compiler.word.ObjectAccess;
+import jdk.graal.compiler.word.Word;
 import jdk.internal.misc.Unsafe;
 
 public final class IdentityHashCodeSupport {
@@ -74,10 +75,12 @@ public static IdentityHashCodeSnippets.Templates createSnippetTemplates(OptionVa
     @SubstrateForeignCallTarget(stubCallingConvention = false)
     public static int generateIdentityHashCode(Object obj) {
         ObjectLayout ol = ConfigurationValues.getObjectLayout();
-        VMError.guarantee(ol.hasFixedIdentityHashField(), "Snippet must handle other cases");
+        VMError.guarantee(!ol.isIdentityHashFieldOptional(), "Optional hash is handled in snippet");
+
         int newHashCode = generateRandomHashCode();
-        if (!Unsafe.getUnsafe().compareAndSetInt(obj, ol.getFixedIdentityHashOffset(), 0, newHashCode)) {
-            newHashCode = ObjectAccess.readInt(obj, ol.getFixedIdentityHashOffset(), IDENTITY_HASHCODE_LOCATION);
+        int offset = LayoutEncoding.getIdentityHashOffset(obj);
+        if (!Unsafe.getUnsafe().compareAndSetInt(obj, offset, 0, newHashCode)) {
+            newHashCode = ObjectAccess.readInt(obj, offset, IDENTITY_HASHCODE_LOCATION);
         }
         VMError.guarantee(newHashCode != 0, "Missing identity hash code");
         return newHashCode;
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/identityhashcode/SubstrateIdentityHashCodeNode.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/identityhashcode/SubstrateIdentityHashCodeNode.java
index e35a3c0844ab..15f2911a1bdf 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/identityhashcode/SubstrateIdentityHashCodeNode.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/identityhashcode/SubstrateIdentityHashCodeNode.java
@@ -24,6 +24,10 @@
  */
 package com.oracle.svm.core.identityhashcode;
 
+import org.graalvm.word.LocationIdentity;
+
+import com.oracle.svm.core.config.ConfigurationValues;
+
 import jdk.graal.compiler.api.replacements.Fold;
 import jdk.graal.compiler.core.common.type.TypedConstant;
 import jdk.graal.compiler.graph.NodeClass;
@@ -32,10 +36,6 @@
 import jdk.graal.compiler.nodeinfo.NodeSize;
 import jdk.graal.compiler.nodes.ValueNode;
 import jdk.graal.compiler.replacements.nodes.IdentityHashCodeNode;
-import org.graalvm.word.LocationIdentity;
-
-import com.oracle.svm.core.config.ConfigurationValues;
-
 import jdk.vm.ci.meta.JavaConstant;
 
 @NodeInfo(cycles = NodeCycles.CYCLES_UNKNOWN, cyclesRationale = "Decided depending on identity hash code storage.", //
@@ -58,8 +58,8 @@ protected SubstrateIdentityHashCodeNode(ValueNode object, int bci) {
 
     @Override
     public LocationIdentity getKilledLocationIdentity() {
-        // Without a fixed field, we must write bits in the object header.
-        return haveFixedField() ? IdentityHashCodeSupport.IDENTITY_HASHCODE_LOCATION : LocationIdentity.any();
+        // With optional identity hash codes, we must write bits in the object header.
+        return isIdentityHashFieldOptional() ? LocationIdentity.any() : IdentityHashCodeSupport.IDENTITY_HASHCODE_LOCATION;
     }
 
     @Override
@@ -69,16 +69,16 @@ protected int getIdentityHashCode(JavaConstant constant) {
 
     @Override
     public NodeCycles estimatedNodeCycles() {
-        return haveFixedField() ? NodeCycles.CYCLES_2 : NodeCycles.CYCLES_8;
+        return isIdentityHashFieldOptional() ? NodeCycles.CYCLES_8 : NodeCycles.CYCLES_2;
     }
 
     @Override
     protected NodeSize dynamicNodeSizeEstimate() {
-        return haveFixedField() ? NodeSize.SIZE_8 : NodeSize.SIZE_32;
+        return isIdentityHashFieldOptional() ? NodeSize.SIZE_32 : NodeSize.SIZE_8;
     }
 
     @Fold
-    static boolean haveFixedField() {
-        return ConfigurationValues.getObjectLayout().hasFixedIdentityHashField();
+    static boolean isIdentityHashFieldOptional() {
+        return ConfigurationValues.getObjectLayout().isIdentityHashFieldOptional();
     }
 }
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/identityhashcode/SubstrateIdentityHashCodeSnippets.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/identityhashcode/SubstrateIdentityHashCodeSnippets.java
index db7ed4d4cb40..7f8803ddb4f5 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/identityhashcode/SubstrateIdentityHashCodeSnippets.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/identityhashcode/SubstrateIdentityHashCodeSnippets.java
@@ -24,12 +24,20 @@
  */
 package com.oracle.svm.core.identityhashcode;
 
+import static jdk.graal.compiler.core.common.spi.ForeignCallDescriptor.CallSideEffect.NO_SIDE_EFFECT;
 import static jdk.graal.compiler.nodes.extended.BranchProbabilityNode.LIKELY_PROBABILITY;
 import static jdk.graal.compiler.nodes.extended.BranchProbabilityNode.NOT_FREQUENT_PROBABILITY;
 import static jdk.graal.compiler.nodes.extended.BranchProbabilityNode.SLOW_PATH_PROBABILITY;
-import static jdk.graal.compiler.core.common.spi.ForeignCallDescriptor.CallSideEffect.NO_SIDE_EFFECT;
 import static jdk.graal.compiler.nodes.extended.BranchProbabilityNode.probability;
 
+import com.oracle.svm.core.config.ConfigurationValues;
+import com.oracle.svm.core.config.ObjectLayout;
+import com.oracle.svm.core.heap.Heap;
+import com.oracle.svm.core.heap.ObjectHeader;
+import com.oracle.svm.core.hub.LayoutEncoding;
+import com.oracle.svm.core.snippets.SnippetRuntime;
+import com.oracle.svm.core.snippets.SnippetRuntime.SubstrateForeignCallDescriptor;
+
 import jdk.graal.compiler.core.common.spi.ForeignCallDescriptor;
 import jdk.graal.compiler.graph.Node.ConstantNodeParameter;
 import jdk.graal.compiler.graph.Node.NodeIntrinsic;
@@ -40,14 +48,6 @@
 import jdk.graal.compiler.word.ObjectAccess;
 import jdk.graal.compiler.word.Word;
 
-import com.oracle.svm.core.config.ConfigurationValues;
-import com.oracle.svm.core.config.ObjectLayout;
-import com.oracle.svm.core.heap.Heap;
-import com.oracle.svm.core.heap.ObjectHeader;
-import com.oracle.svm.core.hub.LayoutEncoding;
-import com.oracle.svm.core.snippets.SnippetRuntime;
-import com.oracle.svm.core.snippets.SnippetRuntime.SubstrateForeignCallDescriptor;
-
 final class SubstrateIdentityHashCodeSnippets extends IdentityHashCodeSnippets {
 
     static final SubstrateForeignCallDescriptor GENERATE_IDENTITY_HASH_CODE = SnippetRuntime.findForeignCall(
@@ -59,28 +59,29 @@ static Templates createTemplates(OptionValues options, Providers providers) {
 
     @Override
     protected int computeIdentityHashCode(Object obj) {
-        int identityHashCode;
         ObjectLayout ol = ConfigurationValues.getObjectLayout();
-        if (ol.hasFixedIdentityHashField()) {
-            int offset = ol.getFixedIdentityHashOffset();
-            identityHashCode = ObjectAccess.readInt(obj, offset, IdentityHashCodeSupport.IDENTITY_HASHCODE_LOCATION);
-            if (probability(SLOW_PATH_PROBABILITY, identityHashCode == 0)) {
-                identityHashCode = generateIdentityHashCode(GENERATE_IDENTITY_HASH_CODE, obj);
+        if (ol.isIdentityHashFieldOptional()) {
+            int identityHashCode;
+            ObjectHeader oh = Heap.getHeap().getObjectHeader();
+            Word objPtr = Word.objectToUntrackedPointer(obj);
+            Word header = ObjectHeader.readHeaderFromPointer(objPtr);
+            if (probability(LIKELY_PROBABILITY, oh.hasOptionalIdentityHashField(header))) {
+                int offset = LayoutEncoding.getIdentityHashOffset(obj);
+                identityHashCode = ObjectAccess.readInt(obj, offset, IdentityHashCodeSupport.IDENTITY_HASHCODE_LOCATION);
+            } else {
+                identityHashCode = IdentityHashCodeSupport.computeHashCodeFromAddress(obj);
+                if (probability(NOT_FREQUENT_PROBABILITY, !oh.hasIdentityHashFromAddress(header))) {
+                    // This write leads to frame state issues that break scheduling if done earlier
+                    oh.setIdentityHashFromAddress(objPtr, header);
+                }
             }
             return identityHashCode;
         }
-        ObjectHeader oh = Heap.getHeap().getObjectHeader();
-        Word objPtr = Word.objectToUntrackedPointer(obj);
-        Word header = ObjectHeader.readHeaderFromPointer(objPtr);
-        if (probability(LIKELY_PROBABILITY, oh.hasOptionalIdentityHashField(header))) {
-            int offset = LayoutEncoding.getOptionalIdentityHashOffset(obj);
-            identityHashCode = ObjectAccess.readInt(obj, offset, IdentityHashCodeSupport.IDENTITY_HASHCODE_LOCATION);
-        } else {
-            identityHashCode = IdentityHashCodeSupport.computeHashCodeFromAddress(obj);
-            if (probability(NOT_FREQUENT_PROBABILITY, !oh.hasIdentityHashFromAddress(header))) {
-                // Note this write leads to frame state issues that break scheduling if done earlier
-                oh.setIdentityHashFromAddress(objPtr, header);
-            }
+
+        int offset = LayoutEncoding.getIdentityHashOffset(obj);
+        int identityHashCode = ObjectAccess.readInt(obj, offset, IdentityHashCodeSupport.IDENTITY_HASHCODE_LOCATION);
+        if (probability(SLOW_PATH_PROBABILITY, identityHashCode == 0)) {
+            identityHashCode = generateIdentityHashCode(GENERATE_IDENTITY_HASH_CODE, obj);
         }
         return identityHashCode;
     }
diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/HostedConfiguration.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/HostedConfiguration.java
index 554113f9cf5c..bfaeb872e6cd 100644
--- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/HostedConfiguration.java
+++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/HostedConfiguration.java
@@ -48,6 +48,7 @@
 import com.oracle.svm.core.SubstrateTargetDescription;
 import com.oracle.svm.core.config.ConfigurationValues;
 import com.oracle.svm.core.config.ObjectLayout;
+import com.oracle.svm.core.config.ObjectLayout.IdentityHashMode;
 import com.oracle.svm.core.graal.code.SubstrateMetaAccessExtensionProvider;
 import com.oracle.svm.core.graal.meta.RuntimeConfiguration;
 import com.oracle.svm.core.monitor.MultiThreadedMonitorSupport;
@@ -100,40 +101,39 @@ public static void setDefaultIfEmpty() {
             CompressEncoding compressEncoding = new CompressEncoding(SubstrateOptions.SpawnIsolates.getValue() ? 1 : 0, 0);
             ImageSingletons.add(CompressEncoding.class, compressEncoding);
 
-            ObjectLayout objectLayout = createObjectLayout();
+            ObjectLayout objectLayout = createObjectLayout(IdentityHashMode.TYPE_SPECIFIC);
             ImageSingletons.add(ObjectLayout.class, objectLayout);
 
             ImageSingletons.add(HybridLayoutSupport.class, new HybridLayoutSupport());
         }
     }
 
-    public static ObjectLayout createObjectLayout() {
-        return createObjectLayout(JavaKind.Object, false);
+    public static ObjectLayout createObjectLayout(IdentityHashMode identityHashMode) {
+        return createObjectLayout(JavaKind.Object, identityHashMode);
     }
 
     /**
-     * Defines the layout of objects. Identity hash code fields can be optional if the object header
-     * allows for it, in which case such a field is appended to individual objects after an identity
-     * hash code has been assigned to it (unless there is an otherwise unused gap in the object that
-     * can be used).
+     * Defines the serial/epsilon GC object layout. The monitor slot and the identity hash code
+     * fields are appended to instance objects (unless there is an otherwise unused gap in the
+     * object that can be used).
      *
      * The layout of instance objects is:
      * 
      *
      * The layout of array objects is:
      * 
      */
-    public static ObjectLayout createObjectLayout(JavaKind referenceKind, boolean disableOptionalIdentityHash) {
+    public static ObjectLayout createObjectLayout(JavaKind referenceKind, IdentityHashMode identityHashMode) {
         SubstrateTargetDescription target = ConfigurationValues.getTarget();
         int referenceSize = target.arch.getPlatformKind(referenceKind).getSizeInBytes();
         int intSize = target.arch.getPlatformKind(JavaKind.Int).getSizeInBytes();
@@ -142,30 +142,24 @@ public static ObjectLayout createObjectLayout(JavaKind referenceKind, boolean di
         int hubOffset = 0;
         int headerSize = hubOffset + referenceSize;
 
-        int identityHashCodeOffset;
-        if (!disableOptionalIdentityHash && SubstrateOptions.SpawnIsolates.getValue() && headerSize + referenceSize <= objectAlignment) {
-            /*
-             * References are relative to the heap base, so we should be able to use fewer bits in
-             * the object header to reference DynamicHubs which are located near the start of the
-             * heap. This means we could be unable to fit forwarding references in those header bits
-             * during GC, but every object is large enough to fit a separate forwarding reference
-             * outside its header. Therefore, we can avoid reserving an identity hash code field for
-             * every object during its allocation and use extra header bits to track if an
-             * individual object was assigned an identity hash code after allocation.
-             */
-            identityHashCodeOffset = -1;
-        } else { // need all object header bits except for lowest-order bits freed up by alignment
-            identityHashCodeOffset = headerSize;
+        int extraArrayHeaderSize = 0;
+        int headerIdentityHashOffset = headerSize;
+        if (identityHashMode == IdentityHashMode.OBJECT_HEADER) {
             headerSize += intSize;
+        } else if (identityHashMode == IdentityHashMode.TYPE_SPECIFIC) {
+            extraArrayHeaderSize = intSize;
+        } else {
+            assert identityHashMode == IdentityHashMode.OPTIONAL;
+            headerIdentityHashOffset = -1;
         }
 
         headerSize += SubstrateOptions.AdditionalHeaderBytes.getValue();
 
         int firstFieldOffset = headerSize;
-        int arrayLengthOffset = headerSize;
+        int arrayLengthOffset = headerSize + extraArrayHeaderSize;
         int arrayBaseOffset = arrayLengthOffset + intSize;
 
-        return new ObjectLayout(target, referenceSize, objectAlignment, hubOffset, firstFieldOffset, arrayLengthOffset, arrayBaseOffset, identityHashCodeOffset);
+        return new ObjectLayout(target, referenceSize, objectAlignment, hubOffset, firstFieldOffset, arrayLengthOffset, arrayBaseOffset, headerIdentityHashOffset, identityHashMode);
     }
 
     public SVMHost createHostVM(OptionValues options, ClassLoader classLoader, ClassInitializationSupport classInitializationSupport,
diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java
index 9892af70963b..7f984272f9d2 100644
--- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java
+++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java
@@ -476,24 +476,21 @@ public static SubstrateTargetDescription createTarget(Platform platform) {
                 }
             }
             AMD64 architecture = new AMD64(features, AMD64CPUFeatureAccess.allAMD64Flags());
-            int deoptScratchSpace = 2 * 8; // Space for two 64-bit registers: rax and xmm0
-            return new SubstrateTargetDescription(architecture, true, 16, 0, deoptScratchSpace, runtimeCheckedFeatures);
+            return new SubstrateTargetDescription(architecture, true, 16, 0, runtimeCheckedFeatures);
         } else if (includedIn(platform, Platform.AARCH64.class)) {
             EnumSet features = CPUTypeAArch64.getSelectedFeatures();
             features.addAll(parseCSVtoEnum(AArch64.CPUFeature.class, NativeImageOptions.CPUFeatures.getValue().values(), AArch64.CPUFeature.values()));
             AArch64 architecture = new AArch64(features, AArch64CPUFeatureAccess.enabledAArch64Flags());
             // runtime checked features are the same as static features on AArch64 for now
             EnumSet runtimeCheckedFeatures = architecture.getFeatures().clone();
-            int deoptScratchSpace = 2 * 8; // Space for two 64-bit registers: r0 and v0.
-            return new SubstrateTargetDescription(architecture, true, 16, 0, deoptScratchSpace, runtimeCheckedFeatures);
+            return new SubstrateTargetDescription(architecture, true, 16, 0, runtimeCheckedFeatures);
         } else if (includedIn(platform, Platform.RISCV64.class)) {
             EnumSet features = CPUTypeRISCV64.getSelectedFeatures();
             features.addAll(parseCSVtoEnum(RISCV64.CPUFeature.class, NativeImageOptions.CPUFeatures.getValue().values(), RISCV64.CPUFeature.values()));
             RISCV64 architecture = new RISCV64(features, RISCV64CPUFeatureAccess.enabledRISCV64Flags());
             // runtime checked features are the same as static features on RISCV64 for now
             EnumSet runtimeCheckedFeatures = architecture.getFeatures().clone();
-            int deoptScratchSpace = 2 * 8; // Space for two 64-bit registers: x0 and f0.
-            return new SubstrateTargetDescription(architecture, true, 16, 0, deoptScratchSpace, runtimeCheckedFeatures);
+            return new SubstrateTargetDescription(architecture, true, 16, 0, runtimeCheckedFeatures);
         } else {
             throw UserError.abort("Architecture specified by platform is not supported: %s", platform.getClass().getTypeName());
         }
diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/HybridLayout.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/HybridLayout.java
index 054d59ecfd69..d02d446b5c86 100644
--- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/HybridLayout.java
+++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/HybridLayout.java
@@ -24,8 +24,6 @@
  */
 package com.oracle.svm.hosted.config;
 
-import jdk.graal.compiler.core.common.NumUtil;
-
 import com.oracle.svm.core.config.ObjectLayout;
 import com.oracle.svm.core.hub.Hybrid;
 import com.oracle.svm.hosted.meta.HostedField;
@@ -33,6 +31,7 @@
 import com.oracle.svm.hosted.meta.HostedMetaAccess;
 import com.oracle.svm.hosted.meta.HostedType;
 
+import jdk.graal.compiler.core.common.NumUtil;
 import jdk.vm.ci.meta.JavaKind;
 import jdk.vm.ci.meta.MetaAccessProvider;
 import jdk.vm.ci.meta.ResolvedJavaType;
@@ -100,8 +99,8 @@ public long getTotalSize(int length, boolean withOptionalIdHashField) {
         return layout.computeArrayTotalSize(getArrayElementOffset(length), withOptionalIdHashField);
     }
 
-    public long getOptionalIdentityHashOffset(int length) {
-        return layout.getArrayOptionalIdentityHashOffset(getArrayElementOffset(length));
+    public long getIdentityHashOffset(int length) {
+        return layout.getArrayIdentityHashOffset(getArrayElementOffset(length));
     }
 
     public HostedField getArrayField() {
diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/PodSupport.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/PodSupport.java
index f06138551552..6842bb550c47 100644
--- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/PodSupport.java
+++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/PodSupport.java
@@ -98,7 +98,7 @@ static PodSupport singleton() {
 
     boolean isPodClass(Class clazz);
 
-    boolean mustReserveLengthField(Class clazz);
+    boolean mustReserveArrayFields(Class clazz);
 }
 
 @AutomaticallyRegisteredFeature
@@ -114,7 +114,7 @@ final class PodFeature implements PodSupport, InternalFeature {
      * other fields where the length field must be. If a pod subclasses {@link Object} itself, it is
      * itself included in this set.
      */
-    private final Set> classesNeedingLengthFields = ConcurrentHashMap.newKeySet();
+    private final Set> classesNeedingArrayFields = ConcurrentHashMap.newKeySet();
 
     private BeforeAnalysisAccess analysisAccess;
     private volatile boolean instantiated = false;
@@ -189,7 +189,7 @@ public void registerSuperclass(Class superClass, Class factoryInterface) {
         while (sup.getSuperclass() != Object.class) {
             sup = sup.getSuperclass();
         }
-        classesNeedingLengthFields.add(sup);
+        classesNeedingArrayFields.add(sup);
     }
 
     /**
@@ -278,8 +278,8 @@ public boolean isPodClass(Class clazz) {
     }
 
     @Override
-    public boolean mustReserveLengthField(Class clazz) {
-        return classesNeedingLengthFields.contains(clazz);
+    public boolean mustReserveArrayFields(Class clazz) {
+        return classesNeedingArrayFields.contains(clazz);
     }
 
     @Override
diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoProvider.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoProvider.java
index 0a209ebd31c3..01b93643d288 100644
--- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoProvider.java
+++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoProvider.java
@@ -25,6 +25,9 @@
  */
 package com.oracle.svm.hosted.image;
 
+import static com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugFrameSizeChange.Type.CONTRACT;
+import static com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugFrameSizeChange.Type.EXTEND;
+
 import java.lang.reflect.Modifier;
 import java.nio.file.Path;
 import java.util.ArrayList;
@@ -39,12 +42,6 @@
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
-import jdk.graal.compiler.code.CompilationResult;
-import jdk.graal.compiler.debug.DebugContext;
-import jdk.graal.compiler.graph.NodeSourcePosition;
-import jdk.graal.compiler.java.StableMethodNameFormatter;
-import jdk.vm.ci.code.CallingConvention;
-import jdk.vm.ci.meta.AllocatableValue;
 import org.graalvm.collections.EconomicMap;
 import org.graalvm.nativeimage.ImageSingletons;
 import org.graalvm.nativeimage.c.struct.CPointerTo;
@@ -62,6 +59,7 @@
 import com.oracle.svm.core.code.CompilationResultFrameTree.FrameNode;
 import com.oracle.svm.core.code.CompilationResultFrameTree.Visitor;
 import com.oracle.svm.core.config.ConfigurationValues;
+import com.oracle.svm.core.config.ObjectLayout;
 import com.oracle.svm.core.graal.code.SubstrateBackend.SubstrateMarkId;
 import com.oracle.svm.core.graal.meta.RuntimeConfiguration;
 import com.oracle.svm.core.image.ImageHeapPartition;
@@ -91,13 +89,19 @@
 import com.oracle.svm.hosted.substitute.SubstitutionMethod;
 import com.oracle.svm.util.ClassUtil;
 
+import jdk.graal.compiler.code.CompilationResult;
+import jdk.graal.compiler.debug.DebugContext;
+import jdk.graal.compiler.graph.NodeSourcePosition;
+import jdk.graal.compiler.java.StableMethodNameFormatter;
 import jdk.vm.ci.aarch64.AArch64;
 import jdk.vm.ci.amd64.AMD64;
 import jdk.vm.ci.code.BytecodeFrame;
 import jdk.vm.ci.code.BytecodePosition;
+import jdk.vm.ci.code.CallingConvention;
 import jdk.vm.ci.code.Register;
 import jdk.vm.ci.code.RegisterValue;
 import jdk.vm.ci.code.StackSlot;
+import jdk.vm.ci.meta.AllocatableValue;
 import jdk.vm.ci.meta.JavaConstant;
 import jdk.vm.ci.meta.JavaKind;
 import jdk.vm.ci.meta.JavaValue;
@@ -111,9 +115,6 @@
 import jdk.vm.ci.meta.Signature;
 import jdk.vm.ci.meta.Value;
 
-import static com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugFrameSizeChange.Type.CONTRACT;
-import static com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugFrameSizeChange.Type.EXTEND;
-
 /**
  * Implementation of the DebugInfoProvider API interface that allows type, code and heap data info
  * to be passed to an ObjectFile when generation of debug info is enabled.
@@ -428,15 +429,17 @@ public Path filePath() {
     }
 
     private Stream computeHeaderTypeInfo() {
+        ObjectLayout ol = getObjectLayout();
+
         List infos = new LinkedList<>();
-        int hubOffset = getObjectLayout().getHubOffset();
+        int hubOffset = ol.getHubOffset();
         int hubFieldSize = referenceSize;
         int objHeaderSize = hubOffset + hubFieldSize;
 
-        int idHashSize = getObjectLayout().sizeInBytes(JavaKind.Int);
+        int idHashSize = ol.sizeInBytes(JavaKind.Int);
         int fixedIdHashOffset = -1;
-        if (getObjectLayout().hasFixedIdentityHashField()) {
-            fixedIdHashOffset = getObjectLayout().getFixedIdentityHashOffset();
+        if (ol.isIdentityHashFieldInObjectHeader()) {
+            fixedIdHashOffset = ol.getObjectHeaderIdentityHashOffset();
             objHeaderSize = Math.max(objHeaderSize, fixedIdHashOffset + idHashSize);
         }
 
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 35165f6b734e..b2d1ea99dca2 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
@@ -388,10 +388,11 @@ private void writeObject(ObjectInfo info, RelocatableBuffer buffer) {
                     final Object array = Array.get(hybridArray, i);
                     writeConstant(buffer, elementIndex, elementStorageKind, array, info);
                 }
-                idHashOffset = hybridLayout.getOptionalIdentityHashOffset(length);
+                idHashOffset = hybridLayout.getIdentityHashOffset(length);
             } else {
-                idHashOffset = ((HostedInstanceClass) clazz).getOptionalIdentityHashOffset();
+                idHashOffset = ((HostedInstanceClass) clazz).getIdentityHashOffset();
             }
+            assert idHashOffset > 0;
             bufferBytes.putInt(info.getIndexInBuffer(idHashOffset), info.getIdentityHashCode());
 
         } else if (clazz.isArray()) {
@@ -401,7 +402,7 @@ private void writeObject(ObjectInfo info, RelocatableBuffer buffer) {
 
             int length = heap.hConstantReflection.readArrayLength(constant);
             bufferBytes.putInt(info.getIndexInBuffer(objectLayout.getArrayLengthOffset()), length);
-            bufferBytes.putInt(info.getIndexInBuffer(objectLayout.getArrayOptionalIdentityHashOffset(kind, length)), info.getIdentityHashCode());
+            bufferBytes.putInt(info.getIndexInBuffer(objectLayout.getArrayIdentityHashOffset(kind, length)), info.getIdentityHashCode());
 
             if (constant instanceof ImageHeapConstant) {
                 if (clazz.getComponentType().isPrimitive()) {
diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedInstanceClass.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedInstanceClass.java
index 7ecc8c2664fa..c83e95981b2d 100644
--- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedInstanceClass.java
+++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedInstanceClass.java
@@ -37,7 +37,7 @@ public class HostedInstanceClass extends HostedClass {
     protected int instanceSize;
     protected boolean monitorFieldNeeded = false;
     protected int monitorFieldOffset = 0;
-    protected int optionalIdentityHashOffset = -1;
+    protected int identityHashOffset = -1;
 
     public HostedInstanceClass(HostedUniverse universe, AnalysisType wrapped, JavaKind kind, JavaKind storageKind, HostedClass superClass, HostedInterface[] interfaces) {
         super(universe, wrapped, kind, storageKind, superClass, interfaces);
@@ -115,13 +115,13 @@ public void setMonitorFieldOffset(int monitorFieldOffset) {
         this.monitorFieldOffset = monitorFieldOffset;
     }
 
-    public int getOptionalIdentityHashOffset() {
-        return optionalIdentityHashOffset;
+    public int getIdentityHashOffset() {
+        return identityHashOffset;
     }
 
-    public void setOptionalIdentityHashOffset(int offset) {
-        assert this.optionalIdentityHashOffset == -1 : "setting identity hashcode field offset more than once";
+    public void setIdentityHashOffset(int offset) {
+        assert this.identityHashOffset == -1 : "setting identity hashcode field offset more than once";
         assert offset >= 0;
-        this.optionalIdentityHashOffset = offset;
+        this.identityHashOffset = offset;
     }
 }
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 f4e596e8e248..579eeef276cc 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
@@ -428,13 +428,14 @@ private void layoutInstanceFields() {
         layoutInstanceFields(hUniverse.getObjectClass(), new HostedField[0], usedBytes);
     }
 
-    private static boolean mustReserveLengthField(HostedInstanceClass clazz) {
-        if (PodSupport.isPresent() && PodSupport.singleton().mustReserveLengthField(clazz.getJavaClass())) {
+    private static boolean mustReserveArrayFields(HostedInstanceClass clazz) {
+        if (PodSupport.isPresent() && PodSupport.singleton().mustReserveArrayFields(clazz.getJavaClass())) {
             return true;
         }
         if (HybridLayout.isHybrid(clazz)) {
-            // A pod ancestor subclassing Object must have already reserved a length field, unless
-            // the pod subclasses Object itself, in which case we would have returned true earlier.
+            // A pod ancestor subclassing Object must have already reserved memory for the array
+            // fields, unless the pod subclasses Object itself, in which case we would have returned
+            // true earlier.
             return !PodSupport.isPresent() || !PodSupport.singleton().isPodClass(clazz.getJavaClass());
         }
         return false;
@@ -458,16 +459,21 @@ private void layoutInstanceFields(HostedInstanceClass clazz, HostedField[] super
 
         HostedConfiguration.instance().findAllFieldsForLayout(hUniverse, hMetaAccess, hUniverse.fields, rawFields, allFields, clazz);
 
-        if (mustReserveLengthField(clazz)) {
-            int lengthOffset = layout.getArrayLengthOffset();
-            int lengthSize = layout.sizeInBytes(JavaKind.Int);
-            reserve(usedBytes, lengthOffset, lengthSize);
+        if (mustReserveArrayFields(clazz)) {
+            int intSize = layout.sizeInBytes(JavaKind.Int);
+            int firstFieldOffset = layout.getFirstFieldOffset();
+            int afterArrayLengthOffset = layout.getArrayLengthOffset() + intSize;
 
-            // Type check fields in DynamicHub.
+            /* Reserve the extra memory that array fields may use (at least the array length). */
+            int arrayFieldBytes = afterArrayLengthOffset - firstFieldOffset;
+            assert arrayFieldBytes >= intSize;
+            reserve(usedBytes, firstFieldOffset, arrayFieldBytes);
+
+            /* Type check fields in DynamicHub. */
             if (clazz.equals(hMetaAccess.lookupJavaType(DynamicHub.class))) {
                 /* Each type check id slot is 2 bytes. */
                 int slotsSize = typeCheckBuilder.getNumTypeCheckSlots() * 2;
-                reserve(usedBytes, lengthOffset + lengthSize, slotsSize);
+                reserve(usedBytes, afterArrayLengthOffset, slotsSize);
             }
         }
 
@@ -531,28 +537,38 @@ private void layoutInstanceFields(HostedInstanceClass clazz, HostedField[] super
          */
         allFields.sort(FIELD_LOCATION_COMPARATOR);
 
-        int sizeWithoutIdHashField = usedBytes.length();
+        int afterFieldsOffset = usedBytes.length();
 
         // Identity hash code
-        if (!clazz.isAbstract() && !HybridLayout.isHybrid(clazz)) {
-            int offset;
-            if (layout.hasFixedIdentityHashField()) {
-                offset = layout.getFixedIdentityHashOffset();
-            } else { // optional: place in gap if any, or append on demand during GC
-                int size = Integer.BYTES;
-                int endOffset = usedBytes.length();
-                offset = findGapForField(usedBytes, 0, size, endOffset);
-                if (offset == -1) {
-                    offset = endOffset + getAlignmentAdjustment(endOffset, size);
+        if (clazz.isAbstract()) {
+            /* No identity hash field needed. */
+        } else if (layout.isIdentityHashFieldInObjectHeader()) {
+            clazz.setIdentityHashOffset(layout.getObjectHeaderIdentityHashOffset());
+        } else if (HybridLayout.isHybrid(clazz)) {
+            if (layout.isIdentityHashFieldAtTypeSpecificOffset()) {
+                clazz.setIdentityHashOffset(layout.getObjectHeaderIdentityHashOffset());
+            } else {
+                /* Nothing to do - will be treated like an array. */
+            }
+        } else if (layout.isIdentityHashFieldAtTypeSpecificOffset() || layout.isIdentityHashFieldOptional()) {
+            /* Add a synthetic field (in gap if any, or append). */
+            int hashSize = Integer.BYTES;
+            int endOffset = usedBytes.length();
+            int offset = findGapForField(usedBytes, 0, hashSize, endOffset);
+            if (offset == -1) {
+                offset = endOffset + getAlignmentAdjustment(endOffset, hashSize);
+                if (layout.isIdentityHashFieldAtTypeSpecificOffset()) {
+                    /* Include the identity hashcode field in the instance size. */
+                    afterFieldsOffset = offset + hashSize;
                 }
-                reserve(usedBytes, offset, size);
             }
-            clazz.setOptionalIdentityHashOffset(offset);
+            reserve(usedBytes, offset, hashSize);
+            clazz.setIdentityHashOffset(offset);
         }
 
         clazz.instanceFieldsWithoutSuper = allFields.toArray(new HostedField[0]);
-        clazz.afterFieldsOffset = sizeWithoutIdHashField;
-        clazz.instanceSize = layout.alignUp(clazz.afterFieldsOffset);
+        clazz.afterFieldsOffset = afterFieldsOffset;
+        clazz.instanceSize = layout.alignUp(afterFieldsOffset);
 
         if (clazz.instanceFieldsWithoutSuper.length == 0) {
             clazz.instanceFieldsWithSuper = superFields;
@@ -1022,7 +1038,7 @@ private void buildHubs() {
             int layoutHelper;
             boolean canInstantiateAsInstance = false;
             int monitorOffset = 0;
-            int optionalIdHashOffset = -1;
+            int identityHashOffset = -1;
             if (type.isInstanceClass()) {
                 HostedInstanceClass instanceClass = (HostedInstanceClass) type;
                 if (instanceClass.isAbstract()) {
@@ -1038,11 +1054,14 @@ private void buildHubs() {
                     canInstantiateAsInstance = type.isInstantiated();
                 }
                 monitorOffset = instanceClass.getMonitorFieldOffset();
-                optionalIdHashOffset = instanceClass.getOptionalIdentityHashOffset();
+                identityHashOffset = instanceClass.getIdentityHashOffset();
             } else if (type.isArray()) {
                 JavaKind storageKind = type.getComponentType().getStorageKind();
                 boolean isObject = (storageKind == JavaKind.Object);
                 layoutHelper = LayoutEncoding.forArray(type, isObject, ol.getArrayBaseOffset(storageKind), ol.getArrayIndexShift(storageKind));
+                if (ol.isIdentityHashFieldInObjectHeader() || ol.isIdentityHashFieldAtTypeSpecificOffset()) {
+                    identityHashOffset = NumUtil.safeToInt(ol.getObjectHeaderIdentityHashOffset());
+                }
             } else if (type.isInterface()) {
                 layoutHelper = LayoutEncoding.forInterface();
             } else if (type.isPrimitive()) {
@@ -1074,7 +1093,7 @@ private void buildHubs() {
 
             DynamicHub hub = type.getHub();
             SerializationRegistry s = ImageSingletons.lookup(SerializationRegistry.class);
-            hub.setData(layoutHelper, type.getTypeID(), monitorOffset, optionalIdHashOffset, type.getTypeCheckStart(), type.getTypeCheckRange(),
+            hub.setData(layoutHelper, type.getTypeID(), monitorOffset, identityHashOffset, type.getTypeCheckStart(), type.getTypeCheckRange(),
                             type.getTypeCheckSlot(), type.getTypeCheckSlots(), vtable, referenceMapIndex, type.isInstantiated(), canInstantiateAsInstance, isProxyClass,
                             s.isRegisteredForSerialization(type.getJavaClass()));
         }