diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HeapDumpMetadata.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HeapDumpMetadata.java index 09f6ce9112cd..ce24d900edb1 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HeapDumpMetadata.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HeapDumpMetadata.java @@ -238,13 +238,10 @@ public boolean initialize() { Heap.getHeap().walkImageHeapObjects(computeHubDataVisitor); Metaspace.singleton().walkObjects(computeHubDataVisitor); - /* - * Classes that are loaded at runtime don't have any declared fields at the moment. This - * needs to be changed once GR-60069 is merged. - */ for (int i = TypeIDs.singleton().getFirstRuntimeTypeId(); i < classInfoCount; i++) { ClassInfo classInfo = getClassInfo(i); if (ClassInfoAccess.isValid(classInfo)) { + /* GR-69330 */ classInfo.setStaticFieldCount(0); classInfo.setInstanceFieldCount(0); } 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 46dafe7b2417..646082476010 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 @@ -93,7 +93,6 @@ import com.oracle.svm.configure.ClassNameSupport; import com.oracle.svm.configure.config.SignatureUtil; - import com.oracle.svm.core.AlwaysInline; import com.oracle.svm.core.BuildPhaseProvider.AfterHeapLayout; import com.oracle.svm.core.BuildPhaseProvider.AfterHostedUniverse; @@ -483,7 +482,9 @@ public static DynamicHub allocate(String name, DynamicHub superHub, Object inter int[] interfaceHashTableHeapArray, int openTypeWorldInterfaceHashParam, int vTableEntries, - int afterFieldsOffset, boolean valueBased) { + int[] declaredInstanceReferenceFieldOffsets, + int afterFieldsOffset, + boolean valueBased) { VMError.guarantee(RuntimeClassLoading.isSupported()); ReferenceType referenceType = ReferenceType.computeReferenceType(DynamicHub.toClass(superHub)); @@ -541,7 +542,7 @@ public static DynamicHub allocate(String name, DynamicHub superHub, Object inter boolean needsMonitorOffset = !valueBased; if (needsMonitorOffset) { - // GR-60069 could look for gaps + // GR-69304 could look for gaps int size = ol.getReferenceSize(); int bits = size - 1; int alignmentAdjust = ((instanceSize + bits) & ~bits) - instanceSize; @@ -552,7 +553,7 @@ public static DynamicHub allocate(String name, DynamicHub superHub, Object inter if (ol.isIdentityHashFieldInObjectHeader()) { identityHashOffset = ol.getObjectHeaderIdentityHashOffset(); } else if (ol.isIdentityHashFieldAtTypeSpecificOffset() || ol.isIdentityHashFieldOptional()) { - // GR-60069 could look for gaps + // GR-69304 could look for gaps int bits = Integer.BYTES - 1; int alignmentAdjust = ((instanceSize + bits) & ~bits) - instanceSize; identityHashOffset = instanceSize + alignmentAdjust; @@ -591,7 +592,7 @@ public static DynamicHub allocate(String name, DynamicHub superHub, Object inter DynamicHub hub = Metaspace.singleton().allocateDynamicHub(vTableEntries); int[] openTypeWorldTypeCheckSlots = Metaspace.singleton().copyToMetaspace(typeCheckSlotsHeapArray); int[] openTypeWorldInterfaceHashTable = Metaspace.singleton().copyToMetaspace(interfaceHashTableHeapArray); - int referenceMapCompressedOffset = RuntimeInstanceReferenceMapSupport.singleton().getOrCreateReferenceMap(superHub); + int referenceMapCompressedOffset = RuntimeInstanceReferenceMapSupport.singleton().getOrCreateReferenceMap(superHub, declaredInstanceReferenceFieldOffsets); /* Write fields in defining order. */ DynamicHubOffsets dynamicHubOffsets = DynamicHubOffsets.singleton(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/RuntimeInstanceReferenceMapSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/RuntimeInstanceReferenceMapSupport.java index 236254a6ac8f..4bdd95ec5b58 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/RuntimeInstanceReferenceMapSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/RuntimeInstanceReferenceMapSupport.java @@ -71,13 +71,11 @@ public static RuntimeInstanceReferenceMapSupport singleton() { * @return a compressed offset, relative to the heap base, that points to an * {@link InstanceReferenceMap}. */ - public int getOrCreateReferenceMap(DynamicHub superHub, FieldInfo... declaredInstanceFields) { + public int getOrCreateReferenceMap(DynamicHub superHub, int... declaredInstanceReferenceFieldsOffsets) { /* Create a bitmap and mark where there are declared object fields. */ SubstrateReferenceMap map = new SubstrateReferenceMap(); - for (var field : declaredInstanceFields) { - if (field.hasObjectType()) { - map.markReferenceAtOffset(field.offset(), true); - } + for (int offset : declaredInstanceReferenceFieldsOffsets) { + map.markReferenceAtOffset(offset, true); } /* If there are no declared object fields, reuse the reference map from the super class. */ @@ -122,10 +120,6 @@ private static int toCompressedOffset(byte[] metaspaceRefMapArray) { return InstanceReferenceMapEncoder.computeReferenceMapCompressedOffset(metaspaceMap); } - /* Remove once GR-60069 is merged. */ - public record FieldInfo(int offset, boolean hasObjectType) { - } - private record ReferenceMapHolder(byte[] refMap) { @Override public int hashCode() { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/RuntimeReflectionMetadata.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/RuntimeReflectionMetadata.java index 051898624513..8db4d81412fb 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/RuntimeReflectionMetadata.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/RuntimeReflectionMetadata.java @@ -24,6 +24,13 @@ */ package com.oracle.svm.core.hub; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.RecordComponent; +import java.util.ArrayList; + import com.oracle.svm.core.configure.RuntimeConditionSet; import com.oracle.svm.core.hub.crema.CremaResolvedJavaField; import com.oracle.svm.core.hub.crema.CremaResolvedJavaMethod; @@ -36,13 +43,6 @@ import jdk.vm.ci.meta.ResolvedJavaType; import jdk.vm.ci.meta.UnresolvedJavaType; -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.lang.reflect.RecordComponent; -import java.util.ArrayList; - /** * Instances of this class are used to represent the reflection metadata for Dynamic hubs loaded at * runtime. Note, the use of the term 'Runtime' is not to be confused with e.g. diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/crema/CremaSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/crema/CremaSupport.java index dabfdb137bdb..138d54780996 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/crema/CremaSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/crema/CremaSupport.java @@ -26,11 +26,11 @@ import java.util.List; -import com.oracle.svm.core.hub.DynamicHub; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; +import com.oracle.svm.core.hub.DynamicHub; import com.oracle.svm.espresso.classfile.ParserKlass; import jdk.vm.ci.meta.JavaType; @@ -47,6 +47,10 @@ interface CremaDispatchTable { int vtableLength(); int itableLength(Class iface); + + int afterFieldsOffset(int superAfterFieldsOffset); + + int[] getDeclaredInstanceReferenceFieldOffsets(); } CremaDispatchTable getDispatchTable(ParserKlass parsed, Class superClass, List> superInterfaces); @@ -63,6 +67,8 @@ interface CremaDispatchTable { Class resolveOrNull(JavaType unresolvedJavaType, ResolvedJavaType accessingClass); + Class findLoadedClass(JavaType unresolvedJavaType, ResolvedJavaType accessingClass); + static CremaSupport singleton() { return ImageSingletons.lookup(CremaSupport.class); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/registry/AbstractRuntimeClassRegistry.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/registry/AbstractRuntimeClassRegistry.java index 3fa34a7ffd22..910150dd4cd4 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/registry/AbstractRuntimeClassRegistry.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/registry/AbstractRuntimeClassRegistry.java @@ -55,7 +55,6 @@ import com.oracle.svm.espresso.classfile.ClassfileStream; import com.oracle.svm.espresso.classfile.ParserConstantPool; import com.oracle.svm.espresso.classfile.ParserException; -import com.oracle.svm.espresso.classfile.ParserField; import com.oracle.svm.espresso.classfile.ParserKlass; import com.oracle.svm.espresso.classfile.ParserMethod; import com.oracle.svm.espresso.classfile.attributes.Attribute; @@ -395,15 +394,7 @@ private Class createClass(ParserKlass parsed, ClassDefinitionInfo info, Symbo afterFieldsOffset = 0; } else { int superAfterFieldsOffset = CremaSupport.singleton().getAfterFieldsOffset(superHub); - // GR-60069: field layout - int numDeclaredInstanceFields = 0; - for (ParserField field : parsed.getFields()) { - if (!field.isStatic()) { - numDeclaredInstanceFields += 1; - } - } - assert numDeclaredInstanceFields == 0; - afterFieldsOffset = Math.toIntExact(superAfterFieldsOffset); + afterFieldsOffset = dispatchTable.afterFieldsOffset(superAfterFieldsOffset); } boolean isValueBased = (parsed.getFlags() & ACC_VALUE_BASED) != 0; @@ -415,7 +406,8 @@ private Class createClass(ParserKlass parsed, ClassDefinitionInfo info, Symbo DynamicHub hub = DynamicHub.allocate(externalName, superHub, interfacesEncoding, null, sourceFile, modifiers, classFileAccessFlags, flags, getClassLoader(), simpleBinaryName, module, enclosingClass, classSignature, typeID, interfaceID, numClassTypes, typeIDDepth, numIterableInterfaces, openTypeWorldTypeCheckSlots, openTypeWorldInterfaceHashTable, openTypeWorldInterfaceHashParam, - dispatchTableLength, afterFieldsOffset, isValueBased); + dispatchTableLength, + dispatchTable.getDeclaredInstanceReferenceFieldOffsets(), afterFieldsOffset, isValueBased); CremaSupport.singleton().fillDynamicHubInfo(hub, dispatchTable, transitiveSuperInterfaces, iTableStartingIndices); diff --git a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/CremaMethodAccess.java b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/CremaMethodAccess.java index 147a8f776363..72303747196e 100644 --- a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/CremaMethodAccess.java +++ b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/CremaMethodAccess.java @@ -42,6 +42,9 @@ public interface CremaMethodAccess extends WithModifiers, MethodAccess { static LineNumberTable toJVMCI(LineNumberTableAttribute parserTable) { + if (parserTable == LineNumberTableAttribute.EMPTY) { + return null; + } List entries = parserTable.getEntries(); int[] bcis = new int[entries.size()]; int[] lineNumbers = new int[entries.size()]; diff --git a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/CremaResolvedJavaFieldImpl.java b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/CremaResolvedJavaFieldImpl.java new file mode 100644 index 000000000000..e834b5bf1b4e --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/CremaResolvedJavaFieldImpl.java @@ -0,0 +1,107 @@ +/* + * 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.interpreter.metadata; + +import com.oracle.svm.core.hub.DynamicHub; +import com.oracle.svm.core.hub.crema.CremaResolvedJavaField; +import com.oracle.svm.core.hub.crema.CremaSupport; +import com.oracle.svm.espresso.classfile.ParserField; + +import jdk.vm.ci.meta.JavaType; +import jdk.vm.ci.meta.UnresolvedJavaType; + +public class CremaResolvedJavaFieldImpl extends InterpreterResolvedJavaField implements CremaResolvedJavaField { + public static final CremaResolvedJavaFieldImpl[] EMPTY_ARRAY = new CremaResolvedJavaFieldImpl[0]; + + CremaResolvedJavaFieldImpl(InterpreterResolvedObjectType declaringClass, ParserField f, int offset) { + super(f.getName(), f.getType(), f.getFlags(), + /*- resolvedType */ null, + declaringClass, + offset, + /*- constantValue */ null, + /*- isWordStorage */ false); + } + + public static CremaResolvedJavaFieldImpl createAtRuntime(InterpreterResolvedObjectType declaringClass, ParserField f, int offset) { + return new CremaResolvedJavaFieldImpl(declaringClass, f, offset); + } + + @Override + public JavaType getType() { + /* + * For fields created at build-time, the type is set if it is available. We explicitly do + * not want to trigger field type resolution at build-time. + * + * If the resolvedType is null, the type was not included in the image. If we were to + * eagerly create a ResolvedJavaType for it, we would force it back in. + */ + if (resolvedType == null) { + UnresolvedJavaType unresolvedJavaType = UnresolvedJavaType.create(getSymbolicType().toString()); + /* + * This should not trigger actual class loading. Instead, we query the loader registry + * for an already loaded class. + */ + Class cls = CremaSupport.singleton().findLoadedClass(unresolvedJavaType, getDeclaringClass()); + if (cls == null) { + // Not loaded: return the unresolved type + return unresolvedJavaType; + } + resolvedType = (InterpreterResolvedJavaType) DynamicHub.fromClass(cls).getInterpreterType(); + } + return resolvedType; + } + + @Override + public InterpreterResolvedJavaType getResolvedType() { + if (resolvedType == null) { + Class cls = CremaSupport.singleton().resolveOrThrow(UnresolvedJavaType.create(getSymbolicType().toString()), getDeclaringClass()); + resolvedType = (InterpreterResolvedJavaType) DynamicHub.fromClass(cls).getInterpreterType(); + } + return resolvedType; + } + + @Override + public boolean isTrustedFinal() { + return isFinal() && (isStatic() || Record.class.isAssignableFrom(getDeclaringClass().getJavaClass()) /*- GR-69549: || getDeclaringClass().isHidden() */); + } + + @Override + public byte[] getRawAnnotations() { + /* (GR-69096) resolvedJavaField.getRawAnnotations() */ + return new byte[0]; + } + + @Override + public byte[] getRawTypeAnnotations() { + /* (GR-69096) resolvedJavaMethod.getRawTypeAnnotations() */ + return new byte[0]; + } + + @Override + public String getGenericSignature() { + /* (GR-69096) resolvedJavaMethod.getGenericSignature() */ + return getSymbolicType().toString(); + } +} diff --git a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/CremaResolvedObjectType.java b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/CremaResolvedObjectType.java index 59db4b2fc9ad..f3ff09347ec4 100644 --- a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/CremaResolvedObjectType.java +++ b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/CremaResolvedObjectType.java @@ -24,7 +24,8 @@ */ package com.oracle.svm.interpreter.metadata; -import com.oracle.svm.core.hub.crema.CremaResolvedJavaField; +import java.util.ArrayList; + import com.oracle.svm.core.hub.crema.CremaResolvedJavaMethod; import com.oracle.svm.core.hub.crema.CremaResolvedJavaRecordComponent; import com.oracle.svm.core.hub.crema.CremaResolvedJavaType; @@ -35,8 +36,6 @@ import jdk.vm.ci.meta.JavaType; import jdk.vm.ci.meta.ResolvedJavaType; -import java.util.ArrayList; - public final class CremaResolvedObjectType extends InterpreterResolvedObjectType implements CremaResolvedJavaType { public CremaResolvedObjectType(Symbol type, int modifiers, InterpreterResolvedJavaType componentType, InterpreterResolvedObjectType superclass, InterpreterResolvedObjectType[] interfaces, @@ -45,9 +44,8 @@ public CremaResolvedObjectType(Symbol type, int modifiers, InterpreterReso } @Override - public CremaResolvedJavaField[] getDeclaredFields() { - // (GR-69098) - throw VMError.unimplemented("getDeclaredFields"); + public CremaResolvedJavaFieldImpl[] getDeclaredFields() { + return (CremaResolvedJavaFieldImpl[]) declaredFields; } @Override diff --git a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/CremaTypeAccess.java b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/CremaTypeAccess.java index 03f81f16fec4..8b74ac26c238 100644 --- a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/CremaTypeAccess.java +++ b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/CremaTypeAccess.java @@ -27,8 +27,11 @@ import com.oracle.svm.core.hub.registry.SymbolsSupport; import com.oracle.svm.espresso.classfile.descriptors.Symbol; import com.oracle.svm.espresso.classfile.descriptors.Type; +import com.oracle.svm.espresso.classfile.descriptors.TypeSymbols; import com.oracle.svm.espresso.shared.meta.TypeAccess; +import jdk.vm.ci.meta.JavaKind; + public interface CremaTypeAccess extends WithModifiers, TypeAccess { static Symbol jvmciNameToType(String name) { // hidden classes and SVM stable proxy name contain a `.` @@ -39,4 +42,11 @@ static Symbol jvmciNameToType(String name) { } return type; } + + static JavaKind symbolToJvmciKind(Symbol type) { + if (TypeSymbols.isPrimitive(type)) { + return JavaKind.fromPrimitiveOrVoidTypeChar((char) type.byteAt(0)); + } + return JavaKind.Object; + } } diff --git a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedJavaField.java b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedJavaField.java index 7ce0b519e7fc..89593f1638f2 100644 --- a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedJavaField.java +++ b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedJavaField.java @@ -30,30 +30,44 @@ import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; +import com.oracle.graal.pointsto.meta.AnalysisField; import com.oracle.svm.core.hub.DynamicHub; +import com.oracle.svm.core.hub.registry.SymbolsSupport; import com.oracle.svm.core.util.VMError; import com.oracle.svm.espresso.classfile.descriptors.Name; import com.oracle.svm.espresso.classfile.descriptors.Symbol; +import com.oracle.svm.espresso.classfile.descriptors.Type; +import com.oracle.svm.espresso.classfile.descriptors.TypeSymbols; import jdk.vm.ci.meta.JavaConstant; import jdk.vm.ci.meta.JavaKind; +import jdk.vm.ci.meta.JavaType; import jdk.vm.ci.meta.PrimitiveConstant; import jdk.vm.ci.meta.ResolvedJavaField; +import jdk.vm.ci.meta.UnresolvedJavaType; -public final class InterpreterResolvedJavaField implements ResolvedJavaField, CremaFieldAccess { +public class InterpreterResolvedJavaField implements ResolvedJavaField, CremaFieldAccess { + public static final InterpreterResolvedJavaField[] EMPTY_ARRAY = new InterpreterResolvedJavaField[0]; + + // Special offset values + private static final int FIELD_UNMATERIALIZED = -10; + private static final int OFFSET_UNINITIALIZED = -11; - // Computed after analysis. - private int offset; private final int modifiers; - private final String name; + private final Symbol name; + private final Symbol typeSymbol; - private final InterpreterResolvedJavaType type; + // Computed after analysis. + private int offset; private final InterpreterResolvedObjectType declaringClass; + protected InterpreterResolvedJavaType resolvedType; + + private final boolean isWordStorage; private JavaConstant constantValue; - @Platforms(Platform.HOSTED_ONLY.class) private ResolvedJavaField originalField; + @Platforms(Platform.HOSTED_ONLY.class) private AnalysisField originalField; /** * Ensures that the field metadata is kept for the interpreter, without forcing it into the @@ -64,52 +78,72 @@ public final class InterpreterResolvedJavaField implements ResolvedJavaField, Cr */ @Platforms(Platform.HOSTED_ONLY.class) private boolean artificiallyReachable; - @Platforms(Platform.HOSTED_ONLY.class) - private InterpreterResolvedJavaField(ResolvedJavaField originalField, String name, int modifiers, InterpreterResolvedJavaType type, InterpreterResolvedObjectType declaringClass, int offset, - JavaConstant constant) { - this.originalField = originalField; + protected InterpreterResolvedJavaField( + Symbol name, Symbol typeSymbol, int modifiers, + InterpreterResolvedJavaType resolvedType, InterpreterResolvedObjectType declaringClass, + int offset, + JavaConstant constant, + boolean isWordStorage) { this.name = MetadataUtil.requireNonNull(name); + this.typeSymbol = MetadataUtil.requireNonNull(typeSymbol); this.modifiers = modifiers; - this.type = MetadataUtil.requireNonNull(type); this.declaringClass = MetadataUtil.requireNonNull(declaringClass); this.offset = offset; this.constantValue = constant; + this.isWordStorage = isWordStorage; + this.resolvedType = resolvedType; + if (resolvedType == null && TypeSymbols.isPrimitive(typeSymbol)) { + // Primitive types are trivially resolved. + this.resolvedType = InterpreterResolvedPrimitiveType.fromKind(CremaTypeAccess.symbolToJvmciKind(typeSymbol)); + } } - private InterpreterResolvedJavaField(String name, int modifiers, InterpreterResolvedJavaType type, InterpreterResolvedObjectType declaringClass, int offset, JavaConstant constant) { - this.name = MetadataUtil.requireNonNull(name); - this.modifiers = modifiers; - this.type = MetadataUtil.requireNonNull(type); - this.declaringClass = MetadataUtil.requireNonNull(declaringClass); - this.offset = offset; - this.constantValue = constant; + @Platforms(Platform.HOSTED_ONLY.class) + public static InterpreterResolvedJavaField createAtBuildTime(AnalysisField originalField, InterpreterResolvedObjectType declaringClass) { + Symbol nameSymbol = SymbolsSupport.getNames().getOrCreate(originalField.getName()); + Symbol typeSymbol = CremaTypeAccess.jvmciNameToType(originalField.getType().getName()); + InterpreterResolvedJavaField field = new InterpreterResolvedJavaField( + nameSymbol, typeSymbol, originalField.getModifiers(), + /*- resolvedType */ null, + declaringClass, + OFFSET_UNINITIALIZED, + /*- constantValue */ null, + originalField.getType().isWordType()); + field.setOriginalField(originalField); + return field; } - public static InterpreterResolvedJavaField create(String name, int modifiers, InterpreterResolvedJavaType type, InterpreterResolvedObjectType declaringClass, int offset, JavaConstant constant) { - return new InterpreterResolvedJavaField(name, modifiers, type, declaringClass, offset, constant); + public static InterpreterResolvedJavaField createForInterpreter(String name, int modifiers, + JavaType type, InterpreterResolvedObjectType declaringClass, + int offset, + JavaConstant constant, + boolean isWordStorage) { + MetadataUtil.requireNonNull(type); + MetadataUtil.requireNonNull(declaringClass); + Symbol nameSymbol = SymbolsSupport.getNames().getOrCreate(name); + InterpreterResolvedJavaType resolvedType = type instanceof InterpreterResolvedJavaType ? (InterpreterResolvedJavaType) type : null; + Symbol symbolicType = resolvedType == null ? CremaTypeAccess.jvmciNameToType(type.getName()) : resolvedType.getSymbolicType(); + return new InterpreterResolvedJavaField(nameSymbol, symbolicType, modifiers, resolvedType, declaringClass, offset, constant, isWordStorage); } @Platforms(Platform.HOSTED_ONLY.class) - public static InterpreterResolvedJavaField create(ResolvedJavaField originalField, String name, int modifiers, InterpreterResolvedJavaType type, InterpreterResolvedObjectType declaringClass, - int offset, JavaConstant constant) { - return new InterpreterResolvedJavaField(originalField, name, modifiers, type, declaringClass, offset, constant); + public final AnalysisField getOriginalField() { + return originalField; } @Platforms(Platform.HOSTED_ONLY.class) - public ResolvedJavaField getOriginalField() { - return originalField; + public final void setOriginalField(AnalysisField originalField) { + this.originalField = originalField; } @Platforms(Platform.HOSTED_ONLY.class) - public void setUnmaterializedConstant(JavaConstant constant) { + public final void setUnmaterializedConstant(JavaConstant constant) { assert JavaConstant.NULL_POINTER.equals(constant) || constant instanceof PrimitiveConstant || constant instanceof ReferenceConstant; this.offset = InterpreterResolvedJavaField.FIELD_UNMATERIALIZED; this.constantValue = constant; } - public static final int FIELD_UNMATERIALIZED = -10; - - public boolean isUnmaterializedConstant() { + public final boolean isUnmaterializedConstant() { return this.offset == FIELD_UNMATERIALIZED; } @@ -118,68 +152,109 @@ public boolean isUnmaterializedConstant() { * interpreter. Examples of undefined fields include: {@link jdk.graal.compiler.word.Word} * subtypes, {@link DynamicHub}'s vtable. */ - public boolean isUndefined() { + public final boolean isUndefined() { return this.isUnmaterializedConstant() && this.getUnmaterializedConstant().getJavaKind() == JavaKind.Illegal; } - public void setOffset(int offset) { + @Platforms(Platform.HOSTED_ONLY.class) + public final void setOffset(int offset) { + VMError.guarantee(this.offset == OFFSET_UNINITIALIZED || this.offset == offset, "InterpreterField offset should not be set twice."); this.offset = offset; } + @Platforms(Platform.HOSTED_ONLY.class) + public final void setResolvedType(InterpreterResolvedJavaType resolvedType) { + VMError.guarantee(this.resolvedType == null || this.resolvedType.equals(resolvedType), + "InterpreterField resolvedType should not be set twice."); + this.resolvedType = resolvedType; + } + @Override - public int getModifiers() { + public final int getModifiers() { return modifiers; } @Override - public int getOffset() { + public final int getOffset() { return offset; } @Override - public String getName() { + public final String getName() { + return name.toString(); + } + + @Override + public final Symbol getSymbolicName() { return name; } @Override - public InterpreterResolvedJavaType getType() { - return type; + public JavaType getType() { + /* + * For fields created at build-time, the type is set if it is available. We explicitly do + * not want to trigger field type resolution at build-time. + * + * If the resolvedType is null, the type was not included in the image. If we were to + * eagerly create a ResolvedJavaType for it, we would force it back in. + */ + if (resolvedType == null) { + // Not included. return the unresolved type. + return UnresolvedJavaType.create(typeSymbol.toString()); + } + return resolvedType; + } + + public InterpreterResolvedJavaType getResolvedType() { + return resolvedType; } @Override - public InterpreterResolvedObjectType getDeclaringClass() { + public final JavaKind getJavaKind() { + return CremaTypeAccess.symbolToJvmciKind(getSymbolicType()); + } + + public final boolean isWordStorage() { + return isWordStorage; + } + + public final Symbol getSymbolicType() { + return typeSymbol; + } + + @Override + public final InterpreterResolvedObjectType getDeclaringClass() { return declaringClass; } - public JavaConstant getUnmaterializedConstant() { + public final JavaConstant getUnmaterializedConstant() { assert offset == FIELD_UNMATERIALIZED; // constantValue can be "Illegal" for some folded constants, for which the value is not // stored in the image heap. // Also take into account WordBase types, which have an Object kind, but the constantValue // is a long. - assert this.getType().isWordType() || - constantValue == JavaConstant.ILLEGAL || getJavaKind() == constantValue.getJavaKind(); + assert (isWordStorage()) || constantValue == JavaConstant.ILLEGAL || getJavaKind() == constantValue.getJavaKind(); return constantValue; } @Platforms(Platform.HOSTED_ONLY.class) - public boolean isArtificiallyReachable() { + public final boolean isArtificiallyReachable() { return artificiallyReachable; } @Platforms(Platform.HOSTED_ONLY.class) - public void markAsArtificiallyReachable() { + public final void markAsArtificiallyReachable() { this.artificiallyReachable = true; } @Override - public String toString() { - return "InterpreterResolvedJavaField"; + public final String toString() { + return "InterpreterResolvedJavaField"; } @Override - public boolean equals(Object other) { + public final boolean equals(Object other) { if (this == other) { return true; } @@ -187,61 +262,56 @@ public boolean equals(Object other) { return false; } InterpreterResolvedJavaField that = (InterpreterResolvedJavaField) other; - return name.equals(that.name) && declaringClass.equals(that.declaringClass) && type.equals(that.type); + return name.equals(that.name) && declaringClass.equals(that.declaringClass) && typeSymbol.equals(that.typeSymbol); } @Override - public int hashCode() { + public final int hashCode() { int result = MetadataUtil.hashCode(name); result = 31 * result + MetadataUtil.hashCode(declaringClass); - result = 31 * result + MetadataUtil.hashCode(type); + result = 31 * result + MetadataUtil.hashCode(typeSymbol); return result; } // region Unimplemented methods @Override - public boolean shouldEnforceInitializerCheck() { + public final boolean shouldEnforceInitializerCheck() { throw VMError.unimplemented("shouldEnforceInitializerCheck"); } @Override - public boolean accessChecks(InterpreterResolvedJavaType accessingClass, InterpreterResolvedJavaType holderClass) { + public final boolean accessChecks(InterpreterResolvedJavaType accessingClass, InterpreterResolvedJavaType holderClass) { throw VMError.unimplemented("accessChecks"); } @Override - public void loadingConstraints(InterpreterResolvedJavaType accessingClass, Function errorHandler) { + public final void loadingConstraints(InterpreterResolvedJavaType accessingClass, Function errorHandler) { throw VMError.unimplemented("loadingConstraints"); } @Override - public Symbol getSymbolicName() { - throw VMError.unimplemented("getSymbolicName"); - } - - @Override - public boolean isInternal() { + public final boolean isInternal() { throw VMError.intentionallyUnimplemented(); } @Override - public boolean isSynthetic() { + public final boolean isSynthetic() { throw VMError.intentionallyUnimplemented(); } @Override - public T getAnnotation(Class annotationClass) { + public final T getAnnotation(Class annotationClass) { throw VMError.intentionallyUnimplemented(); } @Override - public Annotation[] getAnnotations() { + public final Annotation[] getAnnotations() { throw VMError.intentionallyUnimplemented(); } @Override - public Annotation[] getDeclaredAnnotations() { + public final Annotation[] getDeclaredAnnotations() { throw VMError.intentionallyUnimplemented(); } diff --git a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedJavaMethod.java b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedJavaMethod.java index 3a1879d9360d..e315399ebf2e 100644 --- a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedJavaMethod.java +++ b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedJavaMethod.java @@ -65,7 +65,7 @@ * also abstract methods for vtable calls. */ public class InterpreterResolvedJavaMethod implements ResolvedJavaMethod, CremaMethodAccess { - + public static final InterpreterResolvedJavaMethod[] EMPTY_ARRAY = new InterpreterResolvedJavaMethod[0]; public static final LocalVariableTable EMPTY_LOCAL_VARIABLE_TABLE = new LocalVariableTable(new Local[0]); public static final int UNKNOWN_METHOD_ID = 0; diff --git a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedJavaType.java b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedJavaType.java index b6c91ac3f72d..c97d565d6eeb 100644 --- a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedJavaType.java +++ b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedJavaType.java @@ -43,8 +43,6 @@ import jdk.vm.ci.meta.Assumptions; import jdk.vm.ci.meta.JavaConstant; -import jdk.vm.ci.meta.JavaKind; -import jdk.vm.ci.meta.ResolvedJavaField; import jdk.vm.ci.meta.ResolvedJavaMethod; import jdk.vm.ci.meta.ResolvedJavaType; @@ -53,8 +51,6 @@ * closed world e.g. instantiable, instantiated, effectively final ... */ public abstract class InterpreterResolvedJavaType implements ResolvedJavaType, CremaTypeAccess { - public static final InterpreterResolvedJavaMethod[] NO_METHODS = new InterpreterResolvedJavaMethod[0]; - private final Symbol type; protected final Class clazz; private final JavaConstant clazzConstant; @@ -272,21 +268,6 @@ public final Assumptions.AssumptionResult findUniqueConcrete throw VMError.intentionallyUnimplemented(); } - @Override - public final ResolvedJavaField[] getInstanceFields(boolean includeSuperclasses) { - throw VMError.intentionallyUnimplemented(); - } - - @Override - public ResolvedJavaField[] getStaticFields() { - throw VMError.intentionallyUnimplemented(); - } - - @Override - public final ResolvedJavaField findInstanceFieldWithOffset(long offset, JavaKind expectedKind) { - throw VMError.intentionallyUnimplemented(); - } - @Override public final InterpreterResolvedObjectType getArrayClass() { throw VMError.intentionallyUnimplemented(); diff --git a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedObjectType.java b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedObjectType.java index f1ba770e9fe5..e2393f17966e 100644 --- a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedObjectType.java +++ b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedObjectType.java @@ -54,7 +54,7 @@ public class InterpreterResolvedObjectType extends InterpreterResolvedJavaType { private final InterpreterResolvedObjectType superclass; private final InterpreterResolvedObjectType[] interfaces; private InterpreterResolvedJavaMethod[] declaredMethods; - private InterpreterResolvedJavaField[] declaredFields; + protected InterpreterResolvedJavaField[] declaredFields; private int afterFieldsOffset; // Populated after analysis. @@ -150,8 +150,8 @@ public static InterpreterResolvedObjectType createForInterpreter(String name, in } public static CremaResolvedObjectType createForCrema(ParserKlass parserKlass, int modifiers, InterpreterResolvedJavaType componentType, InterpreterResolvedObjectType superclass, - InterpreterResolvedObjectType[] interfaces, Class javaClass, boolean isWordType) { - return new CremaResolvedObjectType(parserKlass.getType(), modifiers, componentType, superclass, interfaces, null, javaClass, isWordType); + InterpreterResolvedObjectType[] interfaces, Class javaClass) { + return new CremaResolvedObjectType(parserKlass.getType(), modifiers, componentType, superclass, interfaces, null, javaClass, false); } @VisibleForSerialization @@ -277,6 +277,10 @@ public final InterpreterResolvedJavaMethod[] getDeclaredMethods(boolean link) { return declaredMethods; } + public InterpreterResolvedJavaField[] getDeclaredFields() { + return declaredFields; + } + public final void setDeclaredMethods(InterpreterResolvedJavaMethod[] declaredMethods) { this.declaredMethods = declaredMethods; } @@ -293,6 +297,21 @@ public final int getAfterFieldsOffset() { return afterFieldsOffset; } + @Override + public InterpreterResolvedJavaField[] getInstanceFields(boolean includeSuperclasses) { + throw VMError.unimplemented("getInstanceFields: Likely not used until JIT added to runtime loaded classes."); + } + + @Override + public InterpreterResolvedJavaField[] getStaticFields() { + throw VMError.unimplemented("getStaticFields: Likely not used until JIT added to runtime loaded classes."); + } + + @Override + public InterpreterResolvedJavaField findInstanceFieldWithOffset(long offset, JavaKind expectedKind) { + throw VMError.unimplemented("findInstanceFieldWithOffset: Likely not used until JIT added to runtime loaded classes."); + } + @Override public final String getJavaName() { if (clazz != null) { @@ -325,7 +344,7 @@ public final Symbol getSymbolicRuntimePackage() { @Override public final InterpreterResolvedJavaField lookupField(Symbol name, Symbol type) { for (InterpreterResolvedJavaField field : this.declaredFields) { - if (name.equals(field.getSymbolicName()) && type.equals(field.getType().getSymbolicType())) { + if (name.equals(field.getSymbolicName()) && type.equals(field.getSymbolicType())) { return field; } } diff --git a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedPrimitiveType.java b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedPrimitiveType.java index 3de08aeba8ee..94f5f5c7ef73 100644 --- a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedPrimitiveType.java +++ b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedPrimitiveType.java @@ -37,6 +37,7 @@ import com.oracle.svm.espresso.classfile.descriptors.Type; import jdk.vm.ci.meta.JavaKind; +import jdk.vm.ci.meta.ResolvedJavaField; import jdk.vm.ci.meta.ResolvedJavaType; public final class InterpreterResolvedPrimitiveType extends InterpreterResolvedJavaType { @@ -119,7 +120,22 @@ public boolean isAssignableFrom(ResolvedJavaType other) { @Override public InterpreterResolvedJavaMethod[] getDeclaredMethods(boolean link) { - return NO_METHODS; + return InterpreterResolvedJavaMethod.EMPTY_ARRAY; + } + + @Override + public ResolvedJavaField[] getInstanceFields(boolean includeSuperclasses) { + return InterpreterResolvedJavaField.EMPTY_ARRAY; + } + + @Override + public ResolvedJavaField[] getStaticFields() { + return InterpreterResolvedJavaField.EMPTY_ARRAY; + } + + @Override + public ResolvedJavaField findInstanceFieldWithOffset(long offset, JavaKind expectedKind) { + return null; } @Override diff --git a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/serialization/SerializationContextImpl.java b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/serialization/SerializationContextImpl.java index dfaaeaf4cc84..3992c0cd715c 100644 --- a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/serialization/SerializationContextImpl.java +++ b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/serialization/SerializationContextImpl.java @@ -25,13 +25,18 @@ package com.oracle.svm.interpreter.metadata.serialization; import java.util.ArrayList; +import java.util.HashMap; import java.util.IdentityHashMap; import java.util.List; +import java.util.Map; abstract class SerializationContextImpl implements SerializationContext { - // The following two objects represent a bidirectional map between references and indices. + // The following three objects represent a bidirectional map between references and indices. protected final List indexToReference; + // Records and maintains a mapping from identity-based objects to their index; protected final IdentityHashMap referenceToIndex; + // Records and maintains a mapping from equality-based objects (such as Strings) to their index; + protected final HashMap stringToIndex; protected final List> knownClasses; @@ -42,6 +47,7 @@ protected SerializationContextImpl(List> knownClasses) { this.knownClasses = knownClasses; this.referenceToIndex = new IdentityHashMap<>(); + this.stringToIndex = new HashMap<>(); this.indexToReference = new ArrayList<>(); indexToReference.add(null); // index 0 == null @@ -56,15 +62,25 @@ protected SerializationContextImpl(List> knownClasses) { @Override public int recordReference(T value) { + Map map; + if (value instanceof String) { + map = stringToIndex; + } else { + map = referenceToIndex; + } + return record(value, map); + } + + private int record(T value, Map map) { if (value == null) { return NULL_REFERENCE_INDEX; } - if (referenceToIndex.containsKey(value)) { + if (map.containsKey(value)) { throw new IllegalStateException("Duplicated reference: " + value); } int refIndex = indexToReference.size(); indexToReference.add(value); - referenceToIndex.put(value, refIndex); + map.put(value, refIndex); return refIndex; } } diff --git a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/serialization/Serializers.java b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/serialization/Serializers.java index 2f6f134f4569..6de015cadb50 100644 --- a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/serialization/Serializers.java +++ b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/serialization/Serializers.java @@ -546,12 +546,13 @@ public static ValueSerializer> newReferenceConstantSerializ static final ValueSerializer RESOLVED_FIELD = createSerializer( (context, in) -> { String name = context.readReference(in); - InterpreterResolvedJavaType type = context.readReference(in); + JavaType type = context.readReference(in); InterpreterResolvedObjectType declaringClass = context.readReference(in); int modifiers = LEB128.readUnsignedInt(in); int offset = LEB128.readUnsignedInt(in); JavaConstant constant = context.readReference(in); - return InterpreterResolvedJavaField.create(name, modifiers, type, declaringClass, offset, constant); + boolean isWordStorage = in.readBoolean(); + return InterpreterResolvedJavaField.createForInterpreter(name, modifiers, type, declaringClass, offset, constant, isWordStorage); }, (context, out, value) -> { context.writeReference(out, value.getName()); @@ -565,6 +566,7 @@ public static ValueSerializer> newReferenceConstantSerializ } else { context.writeReference(out, null); } + out.writeBoolean(value.isWordStorage()); }); static final ValueSerializer OBJECT_TYPE = createSerializer( diff --git a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/serialization/WriterImpl.java b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/serialization/WriterImpl.java index 812768e70951..f9619b53dc74 100644 --- a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/serialization/WriterImpl.java +++ b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/serialization/WriterImpl.java @@ -63,6 +63,9 @@ public int referenceToIndex(T value) { if (value == null) { return NULL_REFERENCE_INDEX; } + if (value instanceof String str) { + return stringToIndex.getOrDefault(str, UNKNOWN_REFERENCE_INDEX); + } return referenceToIndex.getOrDefault(value, UNKNOWN_REFERENCE_INDEX); } diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/BuildTimeInterpreterUniverse.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/BuildTimeInterpreterUniverse.java index cb385d715f3b..0e6e66fe6dcc 100644 --- a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/BuildTimeInterpreterUniverse.java +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/BuildTimeInterpreterUniverse.java @@ -43,7 +43,6 @@ import java.util.function.Function; import java.util.stream.Collectors; -import com.oracle.svm.interpreter.classfile.ClassFile; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; @@ -56,11 +55,13 @@ import com.oracle.svm.core.util.HostedStringDeduplication; import com.oracle.svm.core.util.UserError; import com.oracle.svm.core.util.VMError; +import com.oracle.svm.hosted.meta.HostedField; import com.oracle.svm.hosted.meta.HostedMethod; import com.oracle.svm.hosted.meta.HostedType; import com.oracle.svm.hosted.meta.HostedUniverse; import com.oracle.svm.hosted.pltgot.GOTEntryAllocator; import com.oracle.svm.hosted.substitute.SubstitutionMethod; +import com.oracle.svm.interpreter.classfile.ClassFile; import com.oracle.svm.interpreter.metadata.BytecodeStream; import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaField; import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaMethod; @@ -168,17 +169,20 @@ public static InterpreterResolvedObjectType createResolvedObjectType(ResolvedJav } @Platforms(Platform.HOSTED_ONLY.class) - public static InterpreterResolvedJavaField createResolvedJavaField(ResolvedJavaField resolvedJavaField) { - ResolvedJavaField originalField = resolvedJavaField; + public static InterpreterResolvedJavaField createResolvedJavaField(AnalysisField analysisField) { BuildTimeInterpreterUniverse universe = BuildTimeInterpreterUniverse.singleton(); - String name = universe.dedup(resolvedJavaField.getName()); - int modifiers = resolvedJavaField.getModifiers(); - JavaType fieldType = originalField.getType(); - - InterpreterResolvedJavaType type = universe.getOrCreateType((ResolvedJavaType) fieldType); - InterpreterResolvedObjectType declaringClass = universe.referenceType(originalField.getDeclaringClass()); + InterpreterResolvedObjectType declaringClass = universe.referenceType(analysisField.getDeclaringClass()); + return InterpreterResolvedJavaField.createAtBuildTime(analysisField, declaringClass); + } - return InterpreterResolvedJavaField.create(originalField, name, modifiers, type, declaringClass, 0, null); + @Platforms(Platform.HOSTED_ONLY.class) + public void initializeJavaFieldFromHosted(HostedField hostedField, InterpreterResolvedJavaField resolvedJavaField) { + resolvedJavaField.setOffset(hostedField.getOffset()); + InterpreterResolvedJavaType fType = getType(hostedField.getType().getWrapped()); + if (fType != null) { + // If the resolvedType is included, we can prepare it for the interpreter field. + resolvedJavaField.setResolvedType(fType); + } } @Platforms(Platform.HOSTED_ONLY.class) @@ -332,7 +336,7 @@ public static void setUnmaterializedConstantValue(InterpreterResolvedJavaField t } else if (constant.getJavaKind() == JavaKind.Illegal) { // Materialized field without location e.g. DynamicHub#vtable. thiz.setUnmaterializedConstant(buildTimeInterpreterUniverse.constant(JavaConstant.ILLEGAL)); - } else if (thiz.getType().isWordType()) { + } else if (thiz.isWordStorage()) { // Can be a WordType with a primitive constant value. thiz.setUnmaterializedConstant(buildTimeInterpreterUniverse.constant(constant)); } else if (constant instanceof ImageHeapConstant imageHeapConstant) { @@ -347,7 +351,7 @@ public static void setUnmaterializedConstantValue(InterpreterResolvedJavaField t throw VMError.shouldNotReachHere("Invalid field kind: " + thiz.getJavaKind()); } if (!thiz.isUndefined()) { - if (thiz.getType().isWordType()) { + if (thiz.isWordStorage()) { VMError.guarantee(thiz.getUnmaterializedConstant().getJavaKind() == InterpreterToVM.wordJavaKind()); } else { VMError.guarantee(thiz.getUnmaterializedConstant().getJavaKind() == thiz.getJavaKind()); @@ -500,22 +504,29 @@ public InterpreterResolvedJavaType getOrCreateType(ResolvedJavaType resolvedJava return result; } - public InterpreterResolvedJavaField getOrCreateField(ResolvedJavaField resolvedJavaField) { - assert resolvedJavaField instanceof AnalysisField; - InterpreterResolvedJavaField result = fields.get(resolvedJavaField); + public InterpreterResolvedJavaField getField(ResolvedJavaField resolvedJavaField) { + ResolvedJavaField wrapped = resolvedJavaField; + if (wrapped instanceof HostedField hostedField) { + wrapped = hostedField.getWrapped(); + } + return fields.get(wrapped); + } + + public InterpreterResolvedJavaField getOrCreateField(AnalysisField analysisField) { + InterpreterResolvedJavaField result = fields.get(analysisField); if (result != null) { return result; } - result = createResolvedJavaField(resolvedJavaField); + result = createResolvedJavaField(analysisField); - InterpreterResolvedJavaField previous = fields.putIfAbsent(resolvedJavaField, result); + InterpreterResolvedJavaField previous = fields.putIfAbsent(analysisField, result); if (previous != null) { return previous; } - InterpreterUtil.log("[universe] Adding field '%s'", resolvedJavaField); + InterpreterUtil.log("[universe] Adding field '%s'", analysisField); return result; } @@ -730,10 +741,10 @@ private static boolean isReachable(InterpreterResolvedJavaType type) { } static boolean isReachable(InterpreterResolvedJavaField field) { - AnalysisField originalField = (AnalysisField) field.getOriginalField(); + AnalysisField originalField = field.getOriginalField(); // Artificial reachability ensures that the interpreter keeps the field metadata around, // but reachability still depends on the reachability of the declaring class and field type. - return field.isArtificiallyReachable() || (originalField.isReachable() && originalField.getDeclaringClass().isReachable()); + return field.isArtificiallyReachable() || (originalField.isReachable() && originalField.getDeclaringClass().isReachable() && originalField.getType().isReachable()); } static boolean isReachable(InterpreterResolvedJavaMethod method) { @@ -790,9 +801,9 @@ public void purgeUnreachable(MetaAccessProvider metaAccessProvider) { } Iterator> iteratorFields = fields.entrySet().iterator(); while (iteratorFields.hasNext()) { - Map.Entry next = iteratorFields.next(); - if (!isReachable(next.getValue()) || !isReachable(next.getValue().getDeclaringClass()) || !isReachable(next.getValue().getType())) { - InterpreterUtil.log("[purge] remove field '%s'", next.getValue()); + InterpreterResolvedJavaField next = iteratorFields.next().getValue(); + if (!isReachable(next)) { + InterpreterUtil.log("[purge] remove field '%s'", next); iteratorFields.remove(); } } @@ -915,7 +926,7 @@ public void mirrorSVMVTable(HostedType hostedType, Consumer rescanFieldI InterpreterResolvedJavaMethod[] iVTable; if (hostedDispatchTable.length == 0) { - iVTable = InterpreterResolvedJavaType.NO_METHODS; + iVTable = InterpreterResolvedJavaMethod.EMPTY_ARRAY; } else { iVTable = new InterpreterResolvedJavaMethod[hostedDispatchTable.length]; diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/CremaFeature.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/CremaFeature.java index a67483e5ea7d..88cb8b41c638 100644 --- a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/CremaFeature.java +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/CremaFeature.java @@ -44,9 +44,11 @@ import com.oracle.svm.core.hub.RuntimeClassLoading; import com.oracle.svm.core.util.VMError; import com.oracle.svm.hosted.FeatureImpl; +import com.oracle.svm.hosted.meta.HostedField; import com.oracle.svm.hosted.meta.HostedInstanceClass; import com.oracle.svm.hosted.meta.HostedType; import com.oracle.svm.hosted.meta.HostedUniverse; +import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaField; import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaType; import com.oracle.svm.interpreter.metadata.InterpreterResolvedObjectType; import com.oracle.svm.util.ReflectionUtil; @@ -134,9 +136,25 @@ public void afterCompilation(AfterCompilationAccess access) { assert !type.getWrapped().isReachable() : "No interpreter type for " + type; continue; } + + // Setup fields info InterpreterResolvedObjectType objectType = (InterpreterResolvedObjectType) iType; HostedInstanceClass instanceClass = (HostedInstanceClass) type; objectType.setAfterFieldsOffset(instanceClass.getAfterFieldsOffset()); + + initializeInterpreterFields(iUniverse, instanceClass.getInstanceFields(false)); + initializeInterpreterFields(iUniverse, (HostedField[]) instanceClass.getStaticFields()); + } + } + + private void initializeInterpreterFields(BuildTimeInterpreterUniverse iUniverse, HostedField[] fields) { + for (HostedField hostedField : fields) { + InterpreterResolvedJavaField iField = iUniverse.getField(hostedField.getWrapped()); + if (iField == null) { + assert !hostedField.isAccessed() : "No interpreter field for " + hostedField; + continue; + } + iUniverse.initializeJavaFieldFromHosted(hostedField, iField); } } diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/CremaSupportImpl.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/CremaSupportImpl.java index cf534036bd79..f8ebb35ed397 100644 --- a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/CremaSupportImpl.java +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/CremaSupportImpl.java @@ -26,36 +26,38 @@ import static com.oracle.svm.interpreter.InterpreterStubSection.getCremaStubForVTableIndex; +import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import com.oracle.graal.pointsto.meta.AnalysisMethod; -import com.oracle.graal.pointsto.meta.AnalysisType; -import com.oracle.graal.pointsto.meta.AnalysisUniverse; -import com.oracle.graal.pointsto.constraints.UnsupportedFeatureException; - -import com.oracle.svm.core.hub.RuntimeDynamicHubMetadata; -import com.oracle.svm.core.hub.RuntimeReflectionMetadata; import org.graalvm.collections.EconomicMap; import org.graalvm.collections.Equivalence; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.word.WordBase; +import com.oracle.graal.pointsto.constraints.UnsupportedFeatureException; +import com.oracle.graal.pointsto.meta.AnalysisField; +import com.oracle.graal.pointsto.meta.AnalysisMethod; +import com.oracle.graal.pointsto.meta.AnalysisType; +import com.oracle.graal.pointsto.meta.AnalysisUniverse; import com.oracle.svm.core.graal.meta.KnownOffsets; -import com.oracle.svm.core.hub.crema.CremaSupport; import com.oracle.svm.core.hub.DynamicHub; +import com.oracle.svm.core.hub.RuntimeDynamicHubMetadata; +import com.oracle.svm.core.hub.RuntimeReflectionMetadata; +import com.oracle.svm.core.hub.crema.CremaSupport; import com.oracle.svm.core.hub.registry.AbstractClassRegistry; import com.oracle.svm.core.hub.registry.ClassRegistries; import com.oracle.svm.core.hub.registry.SymbolsSupport; import com.oracle.svm.core.meta.MethodPointer; +import com.oracle.svm.espresso.classfile.ParserField; import com.oracle.svm.espresso.classfile.ParserKlass; import com.oracle.svm.espresso.classfile.ParserMethod; +import com.oracle.svm.espresso.classfile.descriptors.ByteSequence; import com.oracle.svm.espresso.classfile.descriptors.Name; import com.oracle.svm.espresso.classfile.descriptors.ParserSymbols; -import com.oracle.svm.espresso.classfile.descriptors.ByteSequence; import com.oracle.svm.espresso.classfile.descriptors.Signature; import com.oracle.svm.espresso.classfile.descriptors.Symbol; import com.oracle.svm.espresso.classfile.descriptors.Type; @@ -64,6 +66,9 @@ import com.oracle.svm.espresso.shared.vtable.PartialType; import com.oracle.svm.espresso.shared.vtable.Tables; import com.oracle.svm.espresso.shared.vtable.VTable; +import com.oracle.svm.hosted.substitute.DeletedElementException; +import com.oracle.svm.interpreter.fieldlayout.FieldLayout; +import com.oracle.svm.interpreter.metadata.CremaResolvedJavaFieldImpl; import com.oracle.svm.interpreter.metadata.CremaResolvedJavaMethodImpl; import com.oracle.svm.interpreter.metadata.CremaResolvedObjectType; import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaField; @@ -73,6 +78,7 @@ import jdk.graal.compiler.word.Word; import jdk.vm.ci.meta.JavaType; +import jdk.vm.ci.meta.ResolvedJavaField; import jdk.vm.ci.meta.ResolvedJavaMethod; import jdk.vm.ci.meta.ResolvedJavaType; @@ -88,12 +94,23 @@ public ResolvedJavaType createInterpreterType(DynamicHub hub, ResolvedJavaType t InterpreterResolvedJavaType interpreterType = btiUniverse.getOrCreateType(analysisType); ResolvedJavaMethod[] declaredMethods = interpreterType.getDeclaredMethods(false); - assert declaredMethods == null || declaredMethods == InterpreterResolvedJavaType.NO_METHODS : "should only be set once"; + assert declaredMethods == null || declaredMethods == InterpreterResolvedJavaMethod.EMPTY_ARRAY : "should only be set once"; if (analysisType.isPrimitive()) { return interpreterType; } + List methods = buildInterpreterMethods(analysisType, analysisUniverse, btiUniverse); + List fields = buildInterpreterFields(analysisType, analysisUniverse, btiUniverse); + + ((InterpreterResolvedObjectType) interpreterType).setDeclaredMethods(methods.toArray(InterpreterResolvedJavaMethod.EMPTY_ARRAY)); + ((InterpreterResolvedObjectType) interpreterType).setDeclaredFields(fields.toArray(InterpreterResolvedJavaField.EMPTY_ARRAY)); + + return interpreterType; + } + + @Platforms(Platform.HOSTED_ONLY.class) + private static List buildInterpreterMethods(AnalysisType analysisType, AnalysisUniverse analysisUniverse, BuildTimeInterpreterUniverse btiUniverse) { List methods = new ArrayList<>(); // add declared methods @@ -105,9 +122,7 @@ public ResolvedJavaType createInterpreterType(DynamicHub hub, ResolvedJavaType t addSupportedElements(btiUniverse, analysisUniverse, methods, wrappedMethod); } - ((InterpreterResolvedObjectType) interpreterType).setDeclaredMethods(methods.toArray(new InterpreterResolvedJavaMethod[0])); - - return interpreterType; + return methods; } private static void addSupportedElements(BuildTimeInterpreterUniverse btiUniverse, AnalysisUniverse analysisUniverse, List methods, @@ -120,11 +135,11 @@ private static void addSupportedElements(BuildTimeInterpreterUniverse btiUnivers AnalysisMethod analysisMethod; try { analysisMethod = analysisUniverse.lookup(wrappedMethod); + } catch (DeletedElementException e) { + /* deleted via substitution */ + return; } catch (UnsupportedFeatureException e) { - /* - * We are expecting to see exceptions for methods removed by substitutions and for - * methods which refer to unsupported types (in the signature or other metadata). - */ + /* GR-69550: Method has hosted type in signature */ return; } InterpreterResolvedJavaMethod method = btiUniverse.getOrCreateMethod(analysisMethod); @@ -132,6 +147,42 @@ private static void addSupportedElements(BuildTimeInterpreterUniverse btiUnivers methods.add(method); } + @Platforms(Platform.HOSTED_ONLY.class) + private static List buildInterpreterFields(AnalysisType analysisType, AnalysisUniverse analysisUniverse, BuildTimeInterpreterUniverse btiUniverse) { + List fields = new ArrayList<>(); + buildInterpreterFieldsFromArray(analysisUniverse, btiUniverse, analysisType.getWrapped().getInstanceFields(false), fields); + buildInterpreterFieldsFromArray(analysisUniverse, btiUniverse, analysisType.getWrapped().getStaticFields(), fields); + return fields; + } + + @Platforms(Platform.HOSTED_ONLY.class) + private static void buildInterpreterFieldsFromArray(AnalysisUniverse analysisUniverse, BuildTimeInterpreterUniverse btiUniverse, ResolvedJavaField[] declaredFields, + List fields) { + for (ResolvedJavaField wrappedField : declaredFields) { + if (wrappedField.isInternal()) { + /* ignore internal fields */ + continue; + } + if (!analysisUniverse.hostVM().platformSupported(wrappedField)) { + /* ignore e.g. hosted fields */ + continue; + } + if (!analysisUniverse.hostVM().platformSupported((AnnotatedElement) wrappedField.getType())) { + /* ignore fields with unsupported types */ + continue; + } + AnalysisField analysisField; + try { + analysisField = analysisUniverse.lookup(wrappedField); + } catch (DeletedElementException e) { + /* deleted */ + continue; + } + InterpreterResolvedJavaField field = btiUniverse.getOrCreateField(analysisField); + fields.add(field); + } + } + @Override public void fillDynamicHubInfo(DynamicHub hub, CremaDispatchTable dispatchTable, List> transitiveSuperInterfaces, int[] interfaceIndicies) { CremaDispatchTableImpl table = (CremaDispatchTableImpl) dispatchTable; @@ -148,21 +199,22 @@ public void fillDynamicHubInfo(DynamicHub hub, CremaDispatchTable dispatchTable, if (componentHub != null) { componentType = (InterpreterResolvedJavaType) componentHub.getInterpreterType(); } - CremaResolvedObjectType thisType = InterpreterResolvedObjectType.createForCrema(table.partialType.parserKlass, hub.getModifiers(), componentType, superType, interfaces, - DynamicHub.toClass(hub), - false); + CremaResolvedObjectType thisType = InterpreterResolvedObjectType.createForCrema( + table.getParserKlass(), + hub.getModifiers(), + componentType, superType, interfaces, + DynamicHub.toClass(hub)); ParserKlass parserKlass = table.partialType.parserKlass; thisType.setConstantPool(new RuntimeInterpreterConstantPool(thisType, parserKlass.getConstantPool())); table.registerClass(thisType); + // Methods thisType.setDeclaredMethods(table.declaredMethods()); - // TODO(peterssen): GR-60069 Set declared fields. - // thisType.setDeclaredFields(declaredFields.toArray(new InterpreterResolvedJavaField[0])); List completeTable = table.cremaVTable(transitiveSuperInterfaces); - thisType.setVtable(completeTable.toArray(new InterpreterResolvedJavaMethod[0])); + thisType.setVtable(completeTable.toArray(InterpreterResolvedJavaMethod.EMPTY_ARRAY)); long vTableBaseOffset = KnownOffsets.singleton().getVTableBaseOffset(); long vTableEntrySize = KnownOffsets.singleton().getVTableEntrySize(); @@ -179,6 +231,17 @@ public void fillDynamicHubInfo(DynamicHub hub, CremaDispatchTable dispatchTable, i++; } + // Fields + ParserField[] fields = table.getParserKlass().getFields(); + CremaResolvedJavaFieldImpl[] declaredFields = fields.length == 0 ? CremaResolvedJavaFieldImpl.EMPTY_ARRAY : new CremaResolvedJavaFieldImpl[fields.length]; + for (int j = 0; j < fields.length; j++) { + ParserField f = fields[j]; + declaredFields[j] = CremaResolvedJavaFieldImpl.createAtRuntime(thisType, f, table.layout.getOffset(j)); + } + thisType.setAfterFieldsOffset(table.layout().afterInstanceFieldsOffset()); + thisType.setDeclaredFields(declaredFields); + + // Done hub.setInterpreterType(thisType); hub.getCompanion().setHubMetadata(new RuntimeDynamicHubMetadata(thisType)); @@ -354,20 +417,44 @@ public InterpreterResolvedJavaMethod asMethodAccess() { private abstract static class CremaDispatchTableImpl implements CremaDispatchTable { protected final CremaPartialType partialType; + private final FieldLayout layout; CremaDispatchTableImpl(CremaPartialType partialType) { this.partialType = partialType; + this.layout = FieldLayout.build(getParserKlass().getFields(), getSuperResolvedType().getAfterFieldsOffset()); } - public void registerClass(InterpreterResolvedObjectType thisType) { + public final void registerClass(InterpreterResolvedObjectType thisType) { partialType.thisJavaType = thisType; } - public Class superType() { + @Override + public int afterFieldsOffset(int superAfterFieldsOffset) { + return layout.afterInstanceFieldsOffset(); + } + + @Override + public int[] getDeclaredInstanceReferenceFieldOffsets() { + return layout().getReferenceFieldsOffsets(); + } + + public final ParserKlass getParserKlass() { + return partialType.parserKlass; + } + + public final FieldLayout layout() { + return layout; + } + + public final Class superType() { return partialType.superClass; } - public InterpreterResolvedJavaMethod[] declaredMethods() { + public final InterpreterResolvedObjectType getSuperResolvedType() { + return (InterpreterResolvedObjectType) DynamicHub.fromClass(superType()).getInterpreterType(); + } + + public final InterpreterResolvedJavaMethod[] declaredMethods() { InterpreterResolvedJavaMethod[] result = new CremaResolvedJavaMethodImpl[partialType.getDeclaredMethodsList().size()]; int i = 0; for (var m : partialType.getDeclaredMethodsList()) { @@ -503,6 +590,14 @@ private static Class loadClass(JavaType unresolvedJavaType, InterpreterResolv return registry.loadClass(symbolicType); } + @Override + public Class findLoadedClass(JavaType unresolvedJavaType, ResolvedJavaType accessingClass) { + ByteSequence type = ByteSequence.create(unresolvedJavaType.getName()); + Symbol symbolicType = SymbolsSupport.getTypes().getOrCreateValidType(type); + AbstractClassRegistry registry = ClassRegistries.singleton().getRegistry(((InterpreterResolvedJavaType) accessingClass).getJavaClass().getClassLoader()); + return registry.findLoadedClass(symbolicType); + } + @Override public Object execute(ResolvedJavaMethod targetMethod, Object[] args) { return Interpreter.execute((InterpreterResolvedJavaMethod) targetMethod, args); diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/DebuggerFeature.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/DebuggerFeature.java index ad4dfa0ba9f5..57d56e050481 100644 --- a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/DebuggerFeature.java +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/DebuggerFeature.java @@ -458,7 +458,7 @@ public void beforeCompilation(BeforeCompilationAccess access) { if (staticField.isStatic() && staticField.isSynthetic() && staticField.getName().startsWith(SYNTHETIC_ASSERTIONS_DISABLED_FIELD_NAME)) { Class declaringClass = aType.getJavaClass(); boolean value = !RuntimeAssertionsSupport.singleton().desiredAssertionStatus(declaringClass); - InterpreterResolvedJavaField field = iUniverse.getOrCreateField(staticField); + InterpreterResolvedJavaField field = iUniverse.getOrCreateField(analysisStaticField); JavaConstant javaConstant = iUniverse.constant(JavaConstant.forBoolean(value)); BuildTimeInterpreterUniverse.setUnmaterializedConstantValue(field, javaConstant); field.markAsArtificiallyReachable(); @@ -602,7 +602,7 @@ public void afterCompilation(AfterCompilationAccess access) { VMError.guarantee(field.isUnmaterializedConstant()); VMError.guarantee(field.getUnmaterializedConstant() != null); } else if (hostedField.isUnmaterialized()) { - AnalysisField analysisField = (AnalysisField) field.getOriginalField(); + AnalysisField analysisField = field.getOriginalField(); if (hostedField.getType().isWordType() || analysisField.getJavaKind().isPrimitive()) { JavaConstant constant = heap.hConstantReflection.readFieldValue(hostedField, null); assert constant instanceof PrimitiveConstant; @@ -621,8 +621,7 @@ public void afterCompilation(AfterCompilationAccess access) { InterpreterUtil.log("Found materialized field without location: %s", hostedField); BuildTimeInterpreterUniverse.setUnmaterializedConstantValue(field, JavaConstant.forIllegal()); } else { - int fieldOffset = hostedField.getOffset(); - field.setOffset(fieldOffset); + BuildTimeInterpreterUniverse.singleton().initializeJavaFieldFromHosted(hostedField, field); } } @@ -649,7 +648,7 @@ public void afterHeapLayout(AfterHeapLayoutAccess access) { } HostedField hostedField = accessImpl.getMetaAccess().getUniverse().optionalLookup(field.getOriginalField()); if (hostedField.isUnmaterialized()) { - AnalysisField analysisField = (AnalysisField) field.getOriginalField(); + AnalysisField analysisField = field.getOriginalField(); if (hostedField.getType().isWordType()) { // Ignore, words are stored as primitive values. } else if ((analysisField.isFolded() && analysisField.getJavaKind().isObject())) { diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/InterpreterToVM.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/InterpreterToVM.java index 25965bf4cac1..08faf1d24230 100644 --- a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/InterpreterToVM.java +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/InterpreterToVM.java @@ -323,7 +323,7 @@ private static Unsafe initUnsafe() { public static WordBase getFieldWord(Object obj, InterpreterResolvedJavaField wordField) throws SemanticJavaException { assert obj != null; - assert wordField.getType().isWordType(); + assert wordField.isWordStorage(); return switch (wordJavaKind()) { case Long -> Word.signed(getFieldLong(obj, wordField)); case Int -> Word.signed(getFieldInt(obj, wordField)); @@ -482,7 +482,7 @@ public static void setFieldShort(short value, Object obj, InterpreterResolvedJav public static void setFieldInt(int value, Object obj, InterpreterResolvedJavaField field) { assert obj != null; - assert field.getJavaKind() == JavaKind.Int || field.getType().isWordType(); + assert field.getJavaKind() == JavaKind.Int || field.isWordStorage(); if (field.isVolatile()) { U.putIntVolatile(obj, field.getOffset(), value); } else { @@ -492,7 +492,7 @@ public static void setFieldInt(int value, Object obj, InterpreterResolvedJavaFie public static void setFieldLong(long value, Object obj, InterpreterResolvedJavaField field) { assert obj != null; - assert field.getJavaKind() == JavaKind.Long || field.getType().isWordType(); + assert field.getJavaKind() == JavaKind.Long || field.isWordStorage(); if (field.isVolatile()) { U.putLongVolatile(obj, field.getOffset(), value); } else { diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/fieldlayout/FieldLayout.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/fieldlayout/FieldLayout.java new file mode 100644 index 000000000000..f7f0a7fc061c --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/fieldlayout/FieldLayout.java @@ -0,0 +1,88 @@ +/* + * 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.interpreter.fieldlayout; + +import com.oracle.svm.espresso.classfile.ParserField; + +/** + * Represents the result of a field layout strategy. + *

+ * The {@code offset} provided at index {@code idx} corresponds to the offset of the field at index + * {@code idx} in the fields array provided to the {@link FieldLayout#build(ParserField[], long)} + * call. + *

+ * If the field is a static field, that offset corresponds to the offset in the static storage of + * the class being constructed. Otherwise, the offset corresponds to an offset in instances of that + * class. + */ +public final class FieldLayout { + private final int afterInstanceFieldsOffset; + private final int[] offsets; + private final int[] referenceFieldsOffsets; + + FieldLayout(int afterInstanceFieldsOffset, int[] offsets, int[] referenceFieldsOffsets) { + this.afterInstanceFieldsOffset = afterInstanceFieldsOffset; + this.offsets = offsets; + this.referenceFieldsOffsets = referenceFieldsOffsets; + } + + /** + * Creates a field layout from the given fields array and start offset. + * + * @param parsedDeclaredFields The parsed declared fields, obtained from + * {@link com.oracle.svm.espresso.classfile.ClassfileParser#parse parsing} a + * classfile. + * @param startOffset The offset at which the fields should start. This generally corresponds to + * the offset after a class' super's own fields. + * + * @implNote The strategy used for layouting fields is implemented at + * {@link GreedyFieldLayoutStrategy}. + */ + public static FieldLayout build(ParserField[] parsedDeclaredFields, long startOffset) { + return GreedyFieldLayoutStrategy.build(parsedDeclaredFields, startOffset); + } + + /** + * @return The offset after all instance fields. + */ + public int afterInstanceFieldsOffset() { + return afterInstanceFieldsOffset; + } + + /** + * @param idx index in the fields array of the field to obtain an offset for. + * @return the offset of the field. + */ + public int getOffset(int idx) { + return offsets[idx]; + } + + /** + * @return Offsets of all the declared instance fields that have a reference type. + */ + public int[] getReferenceFieldsOffsets() { + return referenceFieldsOffsets; + } +} diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/fieldlayout/GreedyFieldLayoutStrategy.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/fieldlayout/GreedyFieldLayoutStrategy.java new file mode 100644 index 000000000000..c6b03fdde7a5 --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/fieldlayout/GreedyFieldLayoutStrategy.java @@ -0,0 +1,215 @@ +/* + * 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.interpreter.fieldlayout; + +import com.oracle.svm.core.config.ConfigurationValues; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.espresso.classfile.JavaKind; +import com.oracle.svm.espresso.classfile.ParserField; + +import jdk.graal.compiler.core.common.NumUtil; + +/** + * Greedy implementation of a field layout strategy. + *

+ * Places large-size fields first, near the {@code startOffset}, and small fields last. + *

+ * There is no hole-filling strategy. + *

+ * For field layouting in AOT code, see {@code UniverseBuilder.layoutInstanceFields}. + */ +/* GR-69569: This should move closer to UniverseBuilder.layoutInstanceFields */ +final class GreedyFieldLayoutStrategy { + static FieldLayout build(ParserField[] declaredParsedFields, long startOffset) { + FieldLayoutImpl.ForInstanceFields forInstance = new FieldLayoutImpl.ForInstanceFields(declaredParsedFields, startOffset); + FieldLayoutImpl.ForStaticReferenceFields forStaticReferences = new FieldLayoutImpl.ForStaticReferenceFields(declaredParsedFields, /*- GR-69003 */ 0); + FieldLayoutImpl.ForStaticPrimitiveFields forStaticPrimitives = new FieldLayoutImpl.ForStaticPrimitiveFields(declaredParsedFields, /*- GR-69003 */ 0); + + int[] offsets = new int[declaredParsedFields.length]; + int[] referenceOffsets = new int[forInstance.getCountFor(JavaKind.Object)]; + int referencePos = 0; + for (int i = 0; i < declaredParsedFields.length; i++) { + ParserField f = declaredParsedFields[i]; + int offset = -1; + if (forInstance.accepts(f)) { + assert !forStaticReferences.accepts(f) && !forStaticPrimitives.accepts(f); + offset = NumUtil.safeToInt(forInstance.findOffset(f)); + + if (f.getKind() == JavaKind.Object && !f.isStatic()) { + referenceOffsets[referencePos] = offset; + referencePos++; + } + } else if (forStaticReferences.accepts(f)) { + assert !forStaticPrimitives.accepts(f); + offset = NumUtil.safeToInt(forStaticReferences.findOffset(f)); + } else if (forStaticPrimitives.accepts(f)) { + offset = NumUtil.safeToInt(forStaticPrimitives.findOffset(f)); + } + assert offset >= 0; + offsets[i] = offset; + } + assert referencePos == referenceOffsets.length; + return new FieldLayout(NumUtil.safeToInt(forInstance.getAfterFieldsOffset()), offsets, referenceOffsets); + } + + private abstract static class FieldLayoutImpl { + private static final int[] SIZES_IN_BYTES = new int[]{ + /* LONG, DOUBLE */ 8, + /* OBJECT */ ConfigurationValues.getObjectLayout().getReferenceSize(), + /* INT, FLOAT */ 4, + /* CHAR, SHORT */ 2, + /* BYTE, BOOLEAN */ 1 + }; + + private static final int COUNT_LEN = SIZES_IN_BYTES.length; + + private static final int NO_FIELDS = -1; + + private static final int LD_INDEX = 0; + private static final int OBJECT_INDEX = 1; + private static final int IF_INDEX = 2; + private static final int CS_INDEX = 3; + private static final int BB_INDEX = 4; + + private final long startOffset; + private final long afterFieldsOffset; + + private final int[] counts = new int[COUNT_LEN]; + private final long[] offsets = new long[COUNT_LEN]; + + public abstract boolean accepts(ParserField f); + + public final long findOffset(ParserField f) { + assert accepts(f); + int idx = indexOf(f.getKind()); + long result = offsets[idx]; + offsets[idx] = result + SIZES_IN_BYTES[idx]; + return result; + } + + public final long getAfterFieldsOffset() { + return afterFieldsOffset; + } + + public final int getCountFor(JavaKind kind) { + return counts[indexOf(kind)]; + } + + FieldLayoutImpl(ParserField[] declaredParsedFields, long startOffset) { + this.startOffset = startOffset; + this.afterFieldsOffset = init(declaredParsedFields); + } + + private long init(ParserField[] declaredParsedFields) { + for (ParserField f : declaredParsedFields) { + if (accepts(f)) { + int idx = indexOf(f.getKind()); + counts[idx]++; + } + } + + // Find the largest existing field, and align the starting offset to it. + int firstCount = findLargestFieldIndex(); + if (firstCount == NO_FIELDS) { + return startOffset; + } + offsets[firstCount] = align(startOffset, SIZES_IN_BYTES[firstCount]); + + // Compute first offset for remaining sizes + for (int i = firstCount + 1; i < COUNT_LEN; i++) { + long kindStartOffset = offsetAfterKind(i - 1); + assert kindStartOffset == align(kindStartOffset, SIZES_IN_BYTES[i]) : "By construction, the ith kind is aligned to the (i-1)th."; + offsets[i] = kindStartOffset; + } + return offsetAfterKind(COUNT_LEN - 1); + } + + private long offsetAfterKind(int kind) { + return offsets[kind] + (counts[kind] * (long) SIZES_IN_BYTES[kind]); + } + + private int findLargestFieldIndex() { + for (int i = 0; i < COUNT_LEN; i++) { + if (counts[i] > 0) { + return i; + } + } + return NO_FIELDS; + } + + private static int indexOf(JavaKind kind) { + return switch (kind) { + case Boolean, Byte -> BB_INDEX; + case Short, Char -> CS_INDEX; + case Int, Float -> IF_INDEX; + case Long, Double -> LD_INDEX; + case Object -> OBJECT_INDEX; + default -> throw VMError.shouldNotReachHere("No such field kind: " + kind); + }; + } + + private static long align(long value, long to) { + long misalignment = value % to; + if (misalignment == 0) { + return value; + } + return value + (to - misalignment); + } + + private static final class ForInstanceFields extends FieldLayoutImpl { + ForInstanceFields(ParserField[] declaredParsedFields, long startOffset) { + super(declaredParsedFields, startOffset); + } + + @Override + public boolean accepts(ParserField f) { + return !f.isStatic(); + } + } + + private static final class ForStaticReferenceFields extends FieldLayoutImpl { + ForStaticReferenceFields(ParserField[] declaredParsedFields, long startOffset) { + super(declaredParsedFields, startOffset); + } + + @Override + public boolean accepts(ParserField f) { + return f.isStatic() && f.getKind().isObject(); + } + } + + private static final class ForStaticPrimitiveFields extends FieldLayoutImpl { + ForStaticPrimitiveFields(ParserField[] declaredParsedFields, long startOffset) { + super(declaredParsedFields, startOffset); + } + + @Override + public boolean accepts(ParserField f) { + return f.isStatic() && f.getKind().isPrimitive(); + } + } + } + +} diff --git a/substratevm/src/com.oracle.svm.jdwp.resident/src/com/oracle/svm/jdwp/resident/impl/ResidentJDWP.java b/substratevm/src/com.oracle.svm.jdwp.resident/src/com/oracle/svm/jdwp/resident/impl/ResidentJDWP.java index 02eb46e6a7fe..465d5b6f94a6 100644 --- a/substratevm/src/com.oracle.svm.jdwp.resident/src/com/oracle/svm/jdwp/resident/impl/ResidentJDWP.java +++ b/substratevm/src/com.oracle.svm.jdwp.resident/src/com/oracle/svm/jdwp/resident/impl/ResidentJDWP.java @@ -1324,7 +1324,7 @@ private static void sharedReadField(Packet.Writer writer, Object typeOrReceiver, if (field.isStatic()) { assert typeOrReceiver instanceof InterpreterResolvedJavaType; // typeOrReceiver is ignored, all static fields are grouped together. - receiver = (fieldKind.isPrimitive() || field.getType().isWordType()) + receiver = (fieldKind.isPrimitive() || field.isWordStorage()) ? StaticFieldsSupport.getStaticPrimitiveFieldsAtRuntime(MultiLayeredImageSingleton.UNKNOWN_LAYER_NUMBER) : StaticFieldsSupport.getStaticObjectFieldsAtRuntime(MultiLayeredImageSingleton.UNKNOWN_LAYER_NUMBER); } else { @@ -1339,7 +1339,7 @@ private static void sharedReadField(Packet.Writer writer, Object typeOrReceiver, assert !field.isUndefined() : "Cannot read undefined field " + field; - if (field.getType().isWordType()) { + if (field.isWordStorage()) { switch (InterpreterToVM.wordJavaKind()) { case Int -> { writer.writeByte(TagConstants.INT); @@ -1715,11 +1715,10 @@ public Packet ClassType_SetValues(Packet packet) throws JDWPException { assert fieldCount >= 0; for (int i = 0; i < fieldCount; i++) { InterpreterResolvedJavaField field = readField(reader); - InterpreterResolvedJavaType fieldType = field.getType(); if (!field.isStatic()) { throw JDWPException.raise(ErrorCode.ILLEGAL_ARGUMENT); } - if (field.isUndefined() || fieldType.isWordType() || field.isUnmaterializedConstant()) { + if (field.isUndefined() || field.isWordStorage() || field.isUnmaterializedConstant()) { throw JDWPException.raise(ErrorCode.ILLEGAL_ARGUMENT); } sharedWriteField(reader, type, field); @@ -1742,11 +1741,10 @@ public Packet ObjectReference_SetValues(Packet packet) throws JDWPException { assert fieldCount >= 0; for (int i = 0; i < fieldCount; i++) { InterpreterResolvedJavaField field = readField(reader); - InterpreterResolvedJavaType fieldType = field.getType(); if (field.isStatic()) { throw JDWPException.raise(ErrorCode.ILLEGAL_ARGUMENT); } - if (field.isUndefined() || fieldType.isWordType() || field.isUnmaterializedConstant()) { + if (field.isUndefined() || field.isWordStorage() || field.isUnmaterializedConstant()) { throw JDWPException.raise(ErrorCode.ILLEGAL_ARGUMENT); } sharedWriteField(reader, receiver, field); @@ -1764,7 +1762,7 @@ private static void sharedWriteField(Packet.Reader reader, Object typeOrReceiver if (field.isStatic()) { assert typeOrReceiver instanceof InterpreterResolvedJavaType; // typeOrReceiver is ignored, all static fields are grouped together. - receiver = (fieldKind.isPrimitive() || field.getType().isWordType()) + receiver = (fieldKind.isPrimitive() || field.isWordStorage()) ? StaticFieldsSupport.getStaticPrimitiveFieldsAtRuntime(MultiLayeredImageSingleton.UNKNOWN_LAYER_NUMBER) : StaticFieldsSupport.getStaticObjectFieldsAtRuntime(MultiLayeredImageSingleton.UNKNOWN_LAYER_NUMBER); } else { @@ -1779,7 +1777,7 @@ private static void sharedWriteField(Packet.Reader reader, Object typeOrReceiver assert !field.isUndefined() && !field.isUnmaterializedConstant() // : "Cannot write undefined or unmaterialized field " + field; - if (field.getType().isWordType()) { + if (field.isWordStorage()) { switch (InterpreterToVM.wordJavaKind()) { case Int -> InterpreterToVM.setFieldWord(Word.signed(reader.readInt()), receiver, field); @@ -1802,10 +1800,12 @@ private static void sharedWriteField(Packet.Reader reader, Object typeOrReceiver case Long -> InterpreterToVM.setFieldLong(reader.readLong(), receiver, field); case Double -> InterpreterToVM.setFieldDouble(reader.readDouble(), receiver, field); case Object -> { - assert !field.getType().isWordType() : field; // handled above + assert !field.isWordStorage() : field; // handled above Object value = readReferenceOrNull(reader); - if (value != null && !field.getType().getJavaClass().isInstance(value)) { - throw JDWPException.raise(ErrorCode.TYPE_MISMATCH); + if (field.getResolvedType() != null) { + if (value != null && !field.getResolvedType().getJavaClass().isInstance(value)) { + throw JDWPException.raise(ErrorCode.TYPE_MISMATCH); + } } InterpreterToVM.setFieldObject(value, receiver, field); }