From 7b4b92e2eaa34182d8c4113f3d5e1ac77cbc7f22 Mon Sep 17 00:00:00 2001 From: Christian Haeubl Date: Mon, 3 Nov 2025 17:59:02 +0100 Subject: [PATCH 1/4] Fix metaspace-related crash log output. --- .../src/com/oracle/svm/core/genscavenge/HeapImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapImpl.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapImpl.java index 55c6f1d9da1a..df6c2d8668ca 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapImpl.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapImpl.java @@ -676,7 +676,7 @@ public boolean printLocationInfo(Log log, UnsignedWord value, boolean allowJavaH if (value.equal(heapBase)) { log.string("is the heap base"); return true; - } else if (value.aboveThan(heapBase) && value.belowThan(getImageHeapStart())) { + } else if (value.aboveThan(heapBase) && value.belowThan(heapBase.add(SerialAndEpsilonGCOptions.getNullRegionSize()))) { log.string("points into the protected memory between the heap base and the image heap"); return true; } From 6281252b4ea64a0f7bd59841b9bcb1fc2e4c0db5 Mon Sep 17 00:00:00 2001 From: Christian Haeubl Date: Mon, 3 Nov 2025 17:59:15 +0100 Subject: [PATCH 2/4] Better heap verification for the metaspace. --- .../src/com/oracle/svm/core/genscavenge/HeapVerifier.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapVerifier.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapVerifier.java index b469d1d20606..47d89662c684 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapVerifier.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapVerifier.java @@ -318,9 +318,9 @@ private static boolean verifyObject(Object obj, AlignedHeader aChunk, UnalignedH // Not all objects in the image heap have the remembered set bit in the header, so // we can't verify that this bit is set. - } else if (space.isOldSpace()) { + } else if (space.isOldSpace() || space.isMetaspace()) { if (SerialGCOptions.useRememberedSet() && !RememberedSet.get().hasRememberedSet(header)) { - Log.log().string("Object ").zhex(ptr).string(" is in old generation chunk ").zhex(chunk).string(" but does not have a remembered set.").newline(); + Log.log().string("Object ").zhex(ptr).string(" is in ").string(space.getName()).string(" chunk ").zhex(chunk).string(" but does not have a remembered set.").newline(); return false; } } From b086ed1ae9bcab52899852bb7472128ee4605181 Mon Sep 17 00:00:00 2001 From: Christian Haeubl Date: Mon, 3 Nov 2025 18:01:26 +0100 Subject: [PATCH 3/4] Refactorings. --- .../AddressRangeCommittedMemoryProvider.java | 2 +- .../oracle/svm/core/code/CodeInfoTable.java | 2 +- .../com/oracle/svm/core/hub/DynamicHub.java | 4 +- .../oracle/svm/core/hub/LayoutEncoding.java | 14 +- .../svm/core/hub/crema/CremaSupport.java | 19 +- .../AbstractRuntimeClassRegistry.java | 287 +------------ .../svm/interpreter/CremaSupportImpl.java | 395 +++++++++++++++--- 7 files changed, 364 insertions(+), 359 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AddressRangeCommittedMemoryProvider.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AddressRangeCommittedMemoryProvider.java index a3af81d1fd65..12d45e56544f 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AddressRangeCommittedMemoryProvider.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AddressRangeCommittedMemoryProvider.java @@ -777,7 +777,7 @@ private static RuntimeException reportUncommitFailed(Pointer mapBegin, UnsignedW } private static RuntimeException reportUncommitFailedInterruptibly(Pointer mapBegin, UnsignedWord mappingSize) { - Log.log().string("Uncommitting ").unsigned(mappingSize).string(" bytes of unused memory at ").hex(mapBegin).string(" failed.").newline(); + Log.log().string("Uncommitting ").unsigned(mappingSize).string(" bytes of unused memory at ").zhex(mapBegin).string(" failed.").newline(); throw VMError.shouldNotReachHere(UNCOMMIT_FAILED_ERROR_MSG); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoTable.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoTable.java index 745c2925b2a7..169bda2276b5 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoTable.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoTable.java @@ -167,7 +167,7 @@ public static void visitObjectReferences(Pointer sp, CodePointer ip, CodeInfo in @Uninterruptible(reason = "Not really uninterruptible, but we are about to fail.", calleeMustBe = false) public static RuntimeException fatalErrorNoReferenceMap(Pointer sp, CodePointer ip, CodeInfo info) { - Log.log().string("ip: ").hex(ip).string(" sp: ").hex(sp).string(" info:"); + Log.log().string("ip: ").zhex(ip).string(", sp: ").zhex(sp).string(", "); CodeInfoAccess.log(info, Log.log()).newline(); throw VMError.shouldNotReachHere("No reference map information found"); } 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 f2aeb8bd652a..f813771946b5 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 @@ -86,7 +86,6 @@ import java.util.function.BiFunction; import java.util.function.IntFunction; -import com.oracle.svm.core.code.RuntimeMetadataDecoderImpl; import org.graalvm.nativeimage.AnnotationAccess; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; @@ -113,6 +112,7 @@ import com.oracle.svm.core.annotate.TargetElement; import com.oracle.svm.core.classinitialization.ClassInitializationInfo; import com.oracle.svm.core.classinitialization.EnsureClassInitializedNode; +import com.oracle.svm.core.code.RuntimeMetadataDecoderImpl; import com.oracle.svm.core.config.ConfigurationValues; import com.oracle.svm.core.config.ObjectLayout; import com.oracle.svm.core.configure.RuntimeDynamicAccessMetadata; @@ -633,7 +633,7 @@ public static DynamicHub allocate(String name, DynamicHub superHub, Object inter writeInt(hub, dynamicHubOffsets.getReferenceMapCompressedOffsetOffset(), referenceMapCompressedOffset); writeByte(hub, dynamicHubOffsets.getLayerIdOffset(), NumUtil.safeToByte(DynamicImageLayerInfo.CREMA_LAYER_ID)); - // skip vtable (special treatment) + /* Skip vtable (special treatment). */ return finishInitialization(hub, companion); } 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 2de4ef47e337..3765c0a6c752 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,6 +24,8 @@ */ package com.oracle.svm.core.hub; +import static com.oracle.svm.core.Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE; + import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.word.Pointer; @@ -35,6 +37,7 @@ 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.metaspace.Metaspace; import com.oracle.svm.core.snippets.KnownIntrinsics; import com.oracle.svm.core.util.DuplicatedInNativeCode; import com.oracle.svm.core.util.VMError; @@ -406,11 +409,20 @@ public static Pointer getObjectEndInlineInGC(Object obj) { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static Pointer getImageHeapObjectEnd(Object obj) { - // Image heap objects never move and always have an identity hash code field. + assert Heap.getHeap().isInImageHeap(obj); + /* Image heap objects never move and always have an identity hash code field. */ UnsignedWord size = getSizeFromObjectInline(obj, true); return Word.objectToUntrackedPointer(obj).add(size); } + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public static Pointer getMetaspaceObjectEnd(Object obj) { + assert Metaspace.isSupported() && Metaspace.singleton().isInAddressSpace(obj); + /* Metaspace objects don't move and have no identity hash code field. */ + UnsignedWord size = getSizeFromObjectInline(obj, false); + return Word.objectToUntrackedPointer(obj).add(size); + } + public static boolean isArray(Object obj) { final int encoding = KnownIntrinsics.readHub(obj).getLayoutEncoding(); return isArray(encoding); 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 9d4cb0e43538..5e2bb13777f9 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 @@ -24,13 +24,12 @@ */ package com.oracle.svm.core.hub.crema; -import java.util.List; - 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.core.hub.RuntimeClassLoading.ClassDefinitionInfo; import com.oracle.svm.core.hub.registry.SymbolsSupport; import com.oracle.svm.core.invoke.Target_java_lang_invoke_MemberName; import com.oracle.svm.espresso.classfile.ParserKlass; @@ -48,8 +47,6 @@ public interface CremaSupport { @Platforms(Platform.HOSTED_ONLY.class) ResolvedJavaType createInterpreterType(DynamicHub hub, ResolvedJavaType analysisType); - int getAfterFieldsOffset(DynamicHub hub); - Target_java_lang_invoke_MemberName resolveMemberName(Target_java_lang_invoke_MemberName mn, Class caller); Object invokeBasic(Target_java_lang_invoke_MemberName memberName, Object methodHandle, Object[] args); @@ -64,19 +61,7 @@ public interface CremaSupport { Object getStaticStorage(ResolvedJavaField resolved); - interface CremaDispatchTable { - int vtableLength(); - - int itableLength(Class iface); - - int afterFieldsOffset(int superAfterFieldsOffset); - - int[] getDeclaredInstanceReferenceFieldOffsets(); - } - - CremaDispatchTable getDispatchTable(ParserKlass parsed, Class superClass, List> superInterfaces); - - void fillDynamicHubInfo(DynamicHub hub, CremaDispatchTable table, List> transitiveSuperInterfaces, int[] interfaceIndices); + DynamicHub createHub(ParserKlass parsed, ClassDefinitionInfo info, int typeID, String externalName, Module module, ClassLoader classLoader, Class superClass, Class[] superInterfaces); /** * Creates a new instance of {@code type} without running any constructor yet. The caller should 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 fa6b9c826425..9632df00856b 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 @@ -24,18 +24,11 @@ */ package com.oracle.svm.core.hub.registry; -import static com.oracle.svm.espresso.classfile.Constants.ACC_SUPER; -import static com.oracle.svm.espresso.classfile.Constants.ACC_VALUE_BASED; -import static com.oracle.svm.espresso.classfile.Constants.JVM_ACC_WRITTEN_FLAGS; - import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.Comparator; -import java.util.HashSet; -import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; @@ -43,9 +36,7 @@ import org.graalvm.nativeimage.impl.ClassLoading; import com.oracle.svm.core.SubstrateUtil; -import com.oracle.svm.core.graal.meta.KnownOffsets; import com.oracle.svm.core.hub.DynamicHub; -import com.oracle.svm.core.hub.DynamicHubTypeCheckUtil; import com.oracle.svm.core.hub.RuntimeClassLoading; import com.oracle.svm.core.hub.RuntimeClassLoading.ClassDefinitionInfo; import com.oracle.svm.core.hub.crema.CremaSupport; @@ -54,17 +45,9 @@ import com.oracle.svm.core.util.VMError; import com.oracle.svm.espresso.classfile.ClassfileParser; 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.ParserKlass; -import com.oracle.svm.espresso.classfile.ParserMethod; import com.oracle.svm.espresso.classfile.attributes.Attribute; -import com.oracle.svm.espresso.classfile.attributes.InnerClassesAttribute; -import com.oracle.svm.espresso.classfile.attributes.PermittedSubclassesAttribute; -import com.oracle.svm.espresso.classfile.attributes.RecordAttribute; -import com.oracle.svm.espresso.classfile.attributes.SignatureAttribute; -import com.oracle.svm.espresso.classfile.attributes.SourceFileAttribute; -import com.oracle.svm.espresso.classfile.descriptors.Name; import com.oracle.svm.espresso.classfile.descriptors.ParserSymbols.ParserNames; import com.oracle.svm.espresso.classfile.descriptors.Symbol; import com.oracle.svm.espresso.classfile.descriptors.Type; @@ -245,30 +228,6 @@ private ParserKlass parseClass(Symbol typeOrNull, ClassDefinitionInfo info } } - private static List> transitiveSuperInterfaces(Class superClass, Class[] superInterfaces) { - HashSet> result = new HashSet<>(); - Class current = superClass; - while (current != null) { - for (Class interfaceClass : current.getInterfaces()) { - collectInterfaces(interfaceClass, result); - } - current = current.getSuperclass(); - } - for (Class interfaceClass : superInterfaces) { - collectInterfaces(interfaceClass, result); - } - return new ArrayList<>(result); - } - - private static void collectInterfaces(Class interfaceClass, HashSet> result) { - // note that this is and must be called only _after_ class circularity detection - if (result.add(interfaceClass)) { - for (Class superInterface : interfaceClass.getInterfaces()) { - collectInterfaces(superInterface, result); - } - } - } - private Class createClass(ParserKlass parsed, ClassDefinitionInfo info, Symbol type, int typeID) { Symbol superKlassType = parsed.getSuperKlass(); assert superKlassType != null; // j.l.Object is always AOT @@ -291,121 +250,7 @@ private Class createClass(ParserKlass parsed, ClassDefinitionInfo info, Symbo } // GR-62339: Perform super class and interfaces access checks - String externalName = getExternalName(parsed, info); - String simpleBinaryName = getSimpleBinaryName(parsed); - String sourceFile = getSourceFile(parsed); - // The declaring class must be computed lazily - Object declaringClass = UNINITIALIZED_DECLARING_CLASS_SENTINEL; - String classSignature = getClassSignature(parsed); - - int modifiers = getClassModifiers(parsed); - - /* - * The TypeCheckBuilder considers interface arrays as interfaces. Since we are dealing with - * loading from class files, interface arrays need not be considered. - */ - boolean isInterface = Modifier.isInterface(modifiers); - boolean isRecord = Modifier.isFinal(modifiers) && superClass == Record.class && parsed.getAttribute(RecordAttribute.NAME) != null; - // GR-62320 This should be set based on build-time and run-time arguments. - boolean assertionsEnabled = true; - boolean isSealed = isSealed(parsed); - - Object interfacesEncoding = null; - if (superInterfaces.length == 1) { - interfacesEncoding = DynamicHub.fromClass(superInterfaces[0]); - } else if (superInterfaces.length > 1) { - DynamicHub[] superHubs = new DynamicHub[superInterfaces.length]; - for (int i = 0; i < superHubs.length; ++i) { - superHubs[i] = DynamicHub.fromClass(superInterfaces[i]); - } - interfacesEncoding = superHubs; - } - - List> transitiveSuperInterfaces = transitiveSuperInterfaces(superClass, superInterfaces); - transitiveSuperInterfaces.sort(Comparator.comparing(c -> DynamicHub.fromClass(c).getInterfaceID())); - - CremaSupport.CremaDispatchTable dispatchTable = CremaSupport.singleton().getDispatchTable(parsed, superClass, transitiveSuperInterfaces); - - boolean declaresDefaultMethods = isInterface && declaresDefaultMethods(parsed); - boolean hasDefaultMethods = declaresDefaultMethods || hasInheritedDefaultMethods(superClass, superInterfaces); - - boolean isLambdaFormHidden = false; - boolean isProxyClass = false; - short flags = DynamicHub.makeFlags(false, isInterface, info.isHidden(), isRecord, assertionsEnabled, hasDefaultMethods, declaresDefaultMethods, isSealed, false, isLambdaFormHidden, false, - isProxyClass); - - /* - * The dispatch table will look like: - * @formatter:off - * [vtable..., itable(I1)..., itable(I2)...] - * ^ idx1 ^ idx2 - * @formatter:on - * First compute idx* in iTableStartingIndices - */ - int dispatchTableLength = dispatchTable.vtableLength(); - int[] iTableStartingIndices = new int[transitiveSuperInterfaces.size()]; - int i = 0; - for (Class iface : transitiveSuperInterfaces) { - iTableStartingIndices[i++] = dispatchTableLength; - dispatchTableLength += dispatchTable.itableLength(iface); - } - /* - * Compute the type check slots depending on the kind of type - * @formatter:off - * ## Instance types - * [Object.id, Super1.id, ..., Current.id, I1.id, off1, I2.id, off2, ...] - * - display with all super classes from Object to self (included) - * - followed by transitive interfaces (ordered by type id) - * - each interface is followed by its itable offset - * ## Interface types - * a) Without interface hashing - * [Object.id, I1.id, bad, I2.id, bad] - * - display with Object - * - followed by transitive interfaces (ordered by type id, including self) - * - using 0xBADD0D1DL as interface starting index - * b) With interface hashing - * - Interfaces with interfaceIDs <= THRESHOLD are covered in per-type hash tables. - * hashTableEntry = interfaceID < 16 | iTableOffset - * - Interfaces with interfaceIDs > THRESHOLD are covered by the type check slot array above. - * @formatter:on - */ - DynamicHub superHub = DynamicHub.fromClass(superClass); - int interfaceID = isInterface ? TypeIDs.singleton().nextInterfaceId() : DynamicHub.NO_INTERFACE_ID; - short numInterfacesTypes = (short) transitiveSuperInterfaces.size(); - short numClassTypes; - short typeIDDepth; - if (isInterface) { - assert superHub.getNumClassTypes() == 1; - typeIDDepth = -1; - numClassTypes = 1; - } else { - int intDepth = superHub.getTypeIDDepth() + 1; - int intNumClassTypes = superHub.getNumClassTypes() + 1; - VMError.guarantee(intDepth == (short) intDepth, "Type depth overflow"); - VMError.guarantee(intNumClassTypes == (short) intNumClassTypes, "Num class types overflow"); - typeIDDepth = (short) intDepth; - numClassTypes = (short) intNumClassTypes; - } - - /* - * Compute type check data, which might be based on interface hashing. - */ - DynamicHubTypeCheckUtil.TypeCheckData typeCheckData = computeTypeCheckData(typeID, isInterface, numClassTypes, numInterfacesTypes, superHub, iTableStartingIndices, transitiveSuperInterfaces); - - int[] openTypeWorldTypeCheckSlots = typeCheckData.openTypeWorldTypeCheckSlots(); - int[] openTypeWorldInterfaceHashTable = typeCheckData.openTypeWorldInterfaceHashTable(); - int openTypeWorldInterfaceHashParam = typeCheckData.openTypeWorldInterfaceHashParam(); - // number of interfaces which are not covered by hashing and need to be iterated - short numIterableInterfaces = typeCheckData.numIterableInterfaces(); - - int afterFieldsOffset; - if (isInterface) { - afterFieldsOffset = 0; - } else { - int superAfterFieldsOffset = CremaSupport.singleton().getAfterFieldsOffset(superHub); - afterFieldsOffset = dispatchTable.afterFieldsOffset(superAfterFieldsOffset); - } - boolean isValueBased = (parsed.getFlags() & ACC_VALUE_BASED) != 0; + checkNotHybrid(parsed); // GR-62339 Module module; @@ -416,17 +261,8 @@ private Class createClass(ParserKlass parsed, ClassDefinitionInfo info, Symbo module = classLoader.getUnnamedModule(); } - checkNotHybrid(parsed); - - DynamicHub hub = DynamicHub.allocate(externalName, superHub, interfacesEncoding, null, - sourceFile, modifiers, flags, classLoader, simpleBinaryName, module, declaringClass, classSignature, - typeID, interfaceID, - hasClassInitializer(parsed), numClassTypes, typeIDDepth, numIterableInterfaces, openTypeWorldTypeCheckSlots, openTypeWorldInterfaceHashTable, openTypeWorldInterfaceHashParam, - dispatchTableLength, - dispatchTable.getDeclaredInstanceReferenceFieldOffsets(), afterFieldsOffset, isValueBased, info); - - CremaSupport.singleton().fillDynamicHubInfo(hub, dispatchTable, transitiveSuperInterfaces, iTableStartingIndices); - + String externalName = getExternalName(parsed, info); + DynamicHub hub = CremaSupport.singleton().createHub(parsed, info, typeID, externalName, module, classLoader, superClass, superInterfaces); return DynamicHub.toClass(hub); } @@ -447,61 +283,6 @@ private static void checkNotHybrid(ParserKlass parsed) { } - private static boolean hasClassInitializer(ParserKlass parsed) { - for (ParserMethod method : parsed.getMethods()) { - if (method.getName() == ParserNames._clinit_) { - return true; - } - } - return false; - } - - private static boolean declaresDefaultMethods(ParserKlass parsed) { - for (ParserMethod method : parsed.getMethods()) { - int flags = method.getFlags(); - if (!Modifier.isAbstract(flags) && !Modifier.isStatic(flags)) { - return true; - } - } - return false; - } - - private static boolean isSealed(ParserKlass parsed) { - PermittedSubclassesAttribute permittedSubclasses = (PermittedSubclassesAttribute) parsed.getAttribute(PermittedSubclassesAttribute.NAME); - return permittedSubclasses != null && permittedSubclasses.getClasses().length > 0; - } - - private static boolean hasInheritedDefaultMethods(Class superClass, Class[] superInterfaces) { - if (DynamicHub.fromClass(superClass).hasDefaultMethods()) { - return true; - } - for (Class superInterface : superInterfaces) { - if (DynamicHub.fromClass(superInterface).hasDefaultMethods()) { - return true; - } - } - return false; - } - - private static int getClassModifiers(ParserKlass parsed) { - int modifiers = parsed.getFlags(); - InnerClassesAttribute innerClassesAttribute = (InnerClassesAttribute) parsed.getAttribute(InnerClassesAttribute.NAME); - if (innerClassesAttribute != null) { - ParserConstantPool pool = parsed.getConstantPool(); - for (int i = 0; i < innerClassesAttribute.entryCount(); i++) { - InnerClassesAttribute.Entry entry = innerClassesAttribute.entryAt(i); - if (entry.innerClassIndex != 0) { - Symbol innerClassName = pool.className(entry.innerClassIndex); - if (innerClassName.equals(parsed.getName())) { - modifiers = entry.innerClassAccessFlags; - break; - } - } - } - } - return modifiers & ~ACC_SUPER & JVM_ACC_WRITTEN_FLAGS; - } - private static String getExternalName(ParserKlass parsed, ClassDefinitionInfo info) { String externalName = MetaUtil.internalNameToJava(parsed.getType().toString(), true, true); if (info.isHidden()) { @@ -513,47 +294,6 @@ private static String getExternalName(ParserKlass parsed, ClassDefinitionInfo in return externalName; } - private static String getSimpleBinaryName(ParserKlass parsed) { - InnerClassesAttribute innerClassesAttribute = (InnerClassesAttribute) parsed.getAttribute(InnerClassesAttribute.NAME); - if (innerClassesAttribute == null) { - return null; - } - ParserConstantPool pool = parsed.getConstantPool(); - for (int i = 0; i < innerClassesAttribute.entryCount(); i++) { - InnerClassesAttribute.Entry entry = innerClassesAttribute.entryAt(i); - int innerClassIndex = entry.innerClassIndex; - if (innerClassIndex != 0) { - if (pool.className(innerClassIndex) == parsed.getName()) { - if (entry.innerNameIndex == 0) { - break; - } else { - Symbol innerName = pool.utf8At(entry.innerNameIndex, "inner class name"); - return innerName.toString(); - } - } - } - } - return null; - } - - private static String getSourceFile(ParserKlass parsed) { - String sourceFile = null; - SourceFileAttribute sourceFileAttribute = (SourceFileAttribute) parsed.getAttribute(ParserNames.SourceFile); - if (sourceFileAttribute != null) { - sourceFile = parsed.getConstantPool().utf8At(sourceFileAttribute.getSourceFileIndex()).toString(); - } - return sourceFile; - } - - private static String getClassSignature(ParserKlass parsed) { - String sourceFile = null; - SignatureAttribute signatureAttribute = (SignatureAttribute) parsed.getAttribute(ParserNames.Signature); - if (signatureAttribute != null) { - sourceFile = parsed.getConstantPool().utf8At(signatureAttribute.getSignatureIndex()).toString(); - } - return sourceFile; - } - public final Class loadSuperType(Symbol name, Symbol superName) { Placeholder placeholder = new Placeholder(); var prev = runtimeClasses.putIfAbsent(name, placeholder); @@ -674,25 +414,4 @@ public void addSuperProbingThread() { } } } - - private static DynamicHubTypeCheckUtil.TypeCheckData computeTypeCheckData(int typeID, boolean typeIsInterface, short numClassTypes, short numInterfacesTypes, DynamicHub superHub, - int[] iTableStartingIndices, List> transitiveSuperInterfaces) { - int[] interfaceIDs = new int[numInterfacesTypes]; - for (int i = 0; i < numInterfacesTypes; i++) { - interfaceIDs[i] = DynamicHub.fromClass(transitiveSuperInterfaces.get(i)).getInterfaceID(); - } - - int[] typeHierarchy = new int[numClassTypes]; - System.arraycopy(superHub.getOpenTypeWorldTypeCheckSlots(), 0, typeHierarchy, 0, superHub.getNumClassTypes()); - - if (!typeIsInterface) { - // typeID is not yet in the type hierarchy derived from the super type. - typeHierarchy[numClassTypes - 1] = typeID; - } - - long vTableBaseOffset = KnownOffsets.singleton().getVTableBaseOffset(); - long vTableEntrySize = KnownOffsets.singleton().getVTableEntrySize(); - - return DynamicHubTypeCheckUtil.computeOpenTypeWorldTypeCheckData(!typeIsInterface, typeHierarchy, interfaceIDs, iTableStartingIndices, vTableBaseOffset, vTableEntrySize); - } } 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 a0bac6df8b6f..d644420351f2 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 @@ -24,6 +24,7 @@ */ package com.oracle.svm.interpreter; +import static com.oracle.svm.core.hub.registry.AbstractRuntimeClassRegistry.UNINITIALIZED_DECLARING_CLASS_SENTINEL; import static com.oracle.svm.core.methodhandles.Target_java_lang_invoke_MethodHandleNatives_Constants.MN_CALLER_SENSITIVE; import static com.oracle.svm.core.methodhandles.Target_java_lang_invoke_MethodHandleNatives_Constants.MN_IS_CONSTRUCTOR; import static com.oracle.svm.core.methodhandles.Target_java_lang_invoke_MethodHandleNatives_Constants.MN_IS_FIELD; @@ -38,6 +39,8 @@ import static com.oracle.svm.core.methodhandles.Target_java_lang_invoke_MethodHandleNatives_Constants.REF_newInvokeSpecial; import static com.oracle.svm.core.methodhandles.Target_java_lang_invoke_MethodHandleNatives_Constants.REF_putField; import static com.oracle.svm.core.methodhandles.Target_java_lang_invoke_MethodHandleNatives_Constants.REF_putStatic; +import static com.oracle.svm.espresso.classfile.Constants.ACC_SUPER; +import static com.oracle.svm.espresso.classfile.Constants.JVM_ACC_WRITTEN_FLAGS; import static com.oracle.svm.espresso.shared.meta.SignaturePolymorphicIntrinsic.InvokeGeneric; import static com.oracle.svm.interpreter.InterpreterStubSection.getCremaStubForVTableIndex; @@ -45,6 +48,8 @@ import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Arrays; +import java.util.Comparator; +import java.util.HashSet; import java.util.List; import org.graalvm.collections.EconomicMap; @@ -60,24 +65,35 @@ import com.oracle.graal.pointsto.meta.AnalysisUniverse; import com.oracle.svm.core.graal.meta.KnownOffsets; import com.oracle.svm.core.hub.DynamicHub; +import com.oracle.svm.core.hub.DynamicHubTypeCheckUtil; +import com.oracle.svm.core.hub.DynamicHubTypeCheckUtil.TypeCheckData; +import com.oracle.svm.core.hub.RuntimeClassLoading.ClassDefinitionInfo; 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.hub.registry.TypeIDs; import com.oracle.svm.core.invoke.Target_java_lang_invoke_MemberName; import com.oracle.svm.core.log.Log; import com.oracle.svm.core.meta.MethodPointer; import com.oracle.svm.core.util.BasedOnJDKFile; import com.oracle.svm.core.util.VMError; import com.oracle.svm.espresso.classfile.ConstantPool; +import com.oracle.svm.espresso.classfile.Constants; import com.oracle.svm.espresso.classfile.JavaKind; +import com.oracle.svm.espresso.classfile.ParserConstantPool; 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; import com.oracle.svm.espresso.classfile.attributes.ConstantValueAttribute; +import com.oracle.svm.espresso.classfile.attributes.InnerClassesAttribute; +import com.oracle.svm.espresso.classfile.attributes.PermittedSubclassesAttribute; +import com.oracle.svm.espresso.classfile.attributes.RecordAttribute; +import com.oracle.svm.espresso.classfile.attributes.SignatureAttribute; +import com.oracle.svm.espresso.classfile.attributes.SourceFileAttribute; import com.oracle.svm.espresso.classfile.descriptors.ByteSequence; import com.oracle.svm.espresso.classfile.descriptors.Name; import com.oracle.svm.espresso.classfile.descriptors.ParserSymbols; @@ -215,75 +231,255 @@ private static void buildInterpreterFieldsFromArray(AnalysisUniverse analysisUni } @Override - public void fillDynamicHubInfo(DynamicHub hub, CremaDispatchTable dispatchTable, List> transitiveSuperInterfaces, int[] interfaceIndicies) { - CremaDispatchTableImpl table = (CremaDispatchTableImpl) dispatchTable; + public DynamicHub createHub(ParserKlass parsed, ClassDefinitionInfo info, int typeID, String externalName, Module module, ClassLoader classLoader, Class superClass, + Class[] superInterfaces) { + String simpleBinaryName = getSimpleBinaryName(parsed); + String sourceFile = getSourceFile(parsed); + // The declaring class must be computed lazily + Object declaringClass = UNINITIALIZED_DECLARING_CLASS_SENTINEL; + String classSignature = getClassSignature(parsed); + boolean isValueBased = (parsed.getFlags() & Constants.ACC_VALUE_BASED) != 0; + int modifiers = getClassModifiers(parsed); - assert hub.getSuperHub() == DynamicHub.fromClass(table.superType()); - InterpreterResolvedObjectType superType = (InterpreterResolvedObjectType) hub.getSuperHub().getInterpreterType(); - InterpreterResolvedObjectType[] interfaces = new InterpreterResolvedObjectType[hub.getInterfaces().length]; - for (int i = 0; i < interfaces.length; i++) { - interfaces[i] = (InterpreterResolvedObjectType) hub.getInterfaces()[i].getInterpreterType(); + /* + * The TypeCheckBuilder considers interface arrays as interfaces. Since we are dealing with + * loading from class files, interface arrays need not be considered. + */ + boolean isInterface = Modifier.isInterface(modifiers); + boolean isRecord = Modifier.isFinal(modifiers) && superClass == Record.class && parsed.getAttribute(RecordAttribute.NAME) != null; + // GR-62320 This should be set based on build-time and run-time arguments. + boolean assertionsEnabled = true; + boolean isSealed = isSealed(parsed); + boolean declaresDefaultMethods = isInterface && declaresDefaultMethods(parsed); + boolean hasDefaultMethods = declaresDefaultMethods || hasInheritedDefaultMethods(superClass, superInterfaces); + boolean isLambdaFormHidden = false; + boolean isProxyClass = false; + short hubFlags = DynamicHub.makeFlags(false, isInterface, info.isHidden(), isRecord, assertionsEnabled, hasDefaultMethods, declaresDefaultMethods, isSealed, false, isLambdaFormHidden, false, + isProxyClass); + + Object interfacesEncoding = getInterfaceEncodings(superInterfaces); + + Class[] transitiveSuperInterfaces = getSortedTransitiveSuperInterfaces(superClass, superInterfaces); + AbstractCremaDispatchTable dispatchTable = createDispatchTable(parsed, superClass, transitiveSuperInterfaces); + + /* + * Compute the type check slots depending on the kind of type + * @formatter:off + * ## Instance types + * [Object.id, Super1.id, ..., Current.id, I1.id, off1, I2.id, off2, ...] + * - display with all super classes from Object to self (included) + * - followed by transitive interfaces (ordered by type id) + * - each interface is followed by its itable offset + * ## Interface types + * a) Without interface hashing + * [Object.id, I1.id, bad, I2.id, bad] + * - display with Object + * - followed by transitive interfaces (ordered by type id, including self) + * - using 0xBADD0D1DL as interface starting index + * b) With interface hashing + * - Interfaces with interfaceIDs <= THRESHOLD are covered in per-type hash tables. + * hashTableEntry = interfaceID < 16 | iTableOffset + * - Interfaces with interfaceIDs > THRESHOLD are covered by the type check slot array above. + * @formatter:on + */ + DynamicHub superHub = DynamicHub.fromClass(superClass); + int interfaceID = isInterface ? TypeIDs.singleton().nextInterfaceId() : DynamicHub.NO_INTERFACE_ID; + short numInterfacesTypes = (short) transitiveSuperInterfaces.length; + short numClassTypes; + short typeIDDepth; + if (isInterface) { + assert superHub.getNumClassTypes() == 1; + typeIDDepth = -1; + numClassTypes = 1; + } else { + int intDepth = superHub.getTypeIDDepth() + 1; + int intNumClassTypes = superHub.getNumClassTypes() + 1; + VMError.guarantee(intDepth == (short) intDepth, "Type depth overflow"); + VMError.guarantee(intNumClassTypes == (short) intNumClassTypes, "Num class types overflow"); + typeIDDepth = (short) intDepth; + numClassTypes = (short) intNumClassTypes; + } + + /* Compute type check data, which might be based on interface hashing. */ + DynamicHubTypeCheckUtil.TypeCheckData typeCheckData = computeTypeCheckData(typeID, isInterface, numClassTypes, numInterfacesTypes, superHub, dispatchTable, transitiveSuperInterfaces); + + int[] openTypeWorldTypeCheckSlots = typeCheckData.openTypeWorldTypeCheckSlots(); + int[] openTypeWorldInterfaceHashTable = typeCheckData.openTypeWorldInterfaceHashTable(); + int openTypeWorldInterfaceHashParam = typeCheckData.openTypeWorldInterfaceHashParam(); + // number of interfaces which are not covered by hashing and need to be iterated + short numIterableInterfaces = typeCheckData.numIterableInterfaces(); + + int afterFieldsOffset; + if (isInterface) { + afterFieldsOffset = 0; + } else { + afterFieldsOffset = dispatchTable.afterFieldsOffset(); } + /* Allocate DynamicHub. */ + int hubNumVTableEntries = dispatchTable.vtableLength(); + DynamicHub hub = DynamicHub.allocate(externalName, superHub, interfacesEncoding, null, + sourceFile, modifiers, hubFlags, classLoader, simpleBinaryName, module, declaringClass, classSignature, + typeID, interfaceID, + hasClassInitializer(parsed), numClassTypes, typeIDDepth, numIterableInterfaces, openTypeWorldTypeCheckSlots, openTypeWorldInterfaceHashTable, openTypeWorldInterfaceHashParam, + hubNumVTableEntries, + dispatchTable.getDeclaredInstanceReferenceFieldOffsets(), afterFieldsOffset, isValueBased, info); + + /* Allocate Crema type. */ + assert superHub == DynamicHub.fromClass(dispatchTable.superType()); + InterpreterResolvedObjectType superType = (InterpreterResolvedObjectType) superHub.getInterpreterType(); + InterpreterResolvedObjectType[] interfaces = getInterpreterInterfaces(hub); + InterpreterResolvedJavaType componentType = null; DynamicHub componentHub = hub.getComponentHub(); if (componentHub != null) { componentType = (InterpreterResolvedJavaType) componentHub.getInterpreterType(); } CremaResolvedObjectType thisType = InterpreterResolvedObjectType.createForCrema( - table.getParserKlass(), + dispatchTable.getParserKlass(), hub.getModifiers(), componentType, superType, interfaces, DynamicHub.toClass(hub), - table.layout.getStaticReferenceFieldCount(), table.layout.getStaticPrimitiveFieldSize()); + dispatchTable.layout.getStaticReferenceFieldCount(), dispatchTable.layout.getStaticPrimitiveFieldSize()); - ParserKlass parserKlass = table.partialType.parserKlass; + ParserKlass parserKlass = dispatchTable.partialType.parserKlass; thisType.setConstantPool(new RuntimeInterpreterConstantPool(thisType, parserKlass)); - table.registerClass(thisType); + dispatchTable.registerClass(thisType); - // Methods - thisType.setDeclaredMethods(table.declaredMethods()); + /* Set methods and vtable. */ + thisType.setDeclaredMethods(dispatchTable.declaredMethods()); - List completeTable = table.cremaVTable(transitiveSuperInterfaces); - thisType.setVtable(completeTable.toArray(InterpreterResolvedJavaMethod.EMPTY_ARRAY)); - - long vTableBaseOffset = KnownOffsets.singleton().getVTableBaseOffset(); - long vTableEntrySize = KnownOffsets.singleton().getVTableEntrySize(); - int i = 0; - for (InterpreterResolvedJavaMethod method : completeTable) { - long offset = vTableBaseOffset + i * vTableEntrySize; - WordBase entry; - if (method.hasNativeEntryPoint()) { - entry = method.getNativeEntryPoint(); - } else { - entry = getCremaStubForVTableIndex(i); - } - Word.objectToUntrackedPointer(hub).writeWord(Math.toIntExact(offset), entry); - i++; - } + InterpreterResolvedJavaMethod[] completeVTable = dispatchTable.cremaVTable(transitiveSuperInterfaces).toArray(InterpreterResolvedJavaMethod.EMPTY_ARRAY); + assert completeVTable.length == hubNumVTableEntries; + thisType.setVtable(completeVTable); + fillVTable(hub, completeVTable); // Fields - ParserField[] fields = table.getParserKlass().getFields(); + ParserField[] fields = dispatchTable.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)); + declaredFields[j] = CremaResolvedJavaFieldImpl.createAtRuntime(thisType, f, dispatchTable.layout.getOffset(j)); } - thisType.setAfterFieldsOffset(table.layout().afterInstanceFieldsOffset()); + thisType.setAfterFieldsOffset(dispatchTable.layout().afterInstanceFieldsOffset()); thisType.setDeclaredFields(declaredFields); - initStaticFields(thisType, table.getParserKlass().getFields()); + initStaticFields(thisType, dispatchTable.getParserKlass().getFields()); // Done hub.setInterpreterType(thisType); hub.getCompanion().setHubMetadata(new RuntimeDynamicHubMetadata(thisType)); hub.getCompanion().setReflectionMetadata(new RuntimeReflectionMetadata(thisType)); + + return hub; } - @Override - public CremaDispatchTable getDispatchTable(ParserKlass parsed, Class superClass, List> transitiveSuperInterfaces) { + private static InterpreterResolvedObjectType[] getInterpreterInterfaces(DynamicHub hub) { + InterpreterResolvedObjectType[] interfaces = new InterpreterResolvedObjectType[hub.getInterfaces().length]; + for (int i = 0; i < interfaces.length; i++) { + interfaces[i] = (InterpreterResolvedObjectType) hub.getInterfaces()[i].getInterpreterType(); + } + return interfaces; + } + + private static Object getInterfaceEncodings(Class[] superInterfaces) { + Object interfacesEncoding = null; + if (superInterfaces.length == 1) { + interfacesEncoding = DynamicHub.fromClass(superInterfaces[0]); + } else if (superInterfaces.length > 1) { + DynamicHub[] superHubs = new DynamicHub[superInterfaces.length]; + for (int i = 0; i < superHubs.length; ++i) { + superHubs[i] = DynamicHub.fromClass(superInterfaces[i]); + } + interfacesEncoding = superHubs; + } + return interfacesEncoding; + } + + private static Class[] getSortedTransitiveSuperInterfaces(Class superClass, Class[] superInterfaces) { + HashSet> map = new HashSet<>(); + Class current = superClass; + while (current != null) { + for (Class interfaceClass : current.getInterfaces()) { + collectInterfaces(interfaceClass, map); + } + current = current.getSuperclass(); + } + for (Class interfaceClass : superInterfaces) { + collectInterfaces(interfaceClass, map); + } + + Class[] result = map.toArray(new Class[0]); + Arrays.sort(result, Comparator.comparing(c -> DynamicHub.fromClass(c).getInterfaceID())); + return result; + } + + private static void collectInterfaces(Class interfaceClass, HashSet> result) { + // note that this is and must be called only _after_ class circularity detection + if (result.add(interfaceClass)) { + for (Class superInterface : interfaceClass.getInterfaces()) { + collectInterfaces(superInterface, result); + } + } + } + + private static TypeCheckData computeTypeCheckData(int typeID, boolean typeIsInterface, short numClassTypes, short numInterfacesTypes, DynamicHub superHub, + AbstractCremaDispatchTable dispatchTable, Class[] transitiveSuperInterfaces) { + /* + * The dispatch table will look like: + * @formatter:off + * [vtable..., itable(I1)..., itable(I2)...] + * ^ idx1 ^ idx2 + * @formatter:on + * First compute idx* in iTableStartingIndices + */ + int dispatchTableLength = dispatchTable.vtableLength(); + int[] iTableStartingIndices = new int[transitiveSuperInterfaces.length]; + for (int i = 0; i < transitiveSuperInterfaces.length; i++) { + Class iface = transitiveSuperInterfaces[i]; + iTableStartingIndices[i] = dispatchTableLength; + dispatchTableLength += dispatchTable.itableLength(iface); + } + + int[] interfaceIDs = new int[numInterfacesTypes]; + for (int i = 0; i < numInterfacesTypes; i++) { + interfaceIDs[i] = DynamicHub.fromClass(transitiveSuperInterfaces[i]).getInterfaceID(); + } + + int[] typeHierarchy = new int[numClassTypes]; + System.arraycopy(superHub.getOpenTypeWorldTypeCheckSlots(), 0, typeHierarchy, 0, superHub.getNumClassTypes()); + + if (!typeIsInterface) { + // typeID is not yet in the type hierarchy derived from the super type. + typeHierarchy[numClassTypes - 1] = typeID; + } + + long vTableBaseOffset = KnownOffsets.singleton().getVTableBaseOffset(); + long vTableEntrySize = KnownOffsets.singleton().getVTableEntrySize(); + + return DynamicHubTypeCheckUtil.computeOpenTypeWorldTypeCheckData(!typeIsInterface, typeHierarchy, interfaceIDs, iTableStartingIndices, vTableBaseOffset, vTableEntrySize); + } + + private static void fillVTable(DynamicHub hub, InterpreterResolvedJavaMethod[] vtable) { + long vTableBaseOffset = KnownOffsets.singleton().getVTableBaseOffset(); + long vTableEntrySize = KnownOffsets.singleton().getVTableEntrySize(); + int i = 0; + for (InterpreterResolvedJavaMethod method : vtable) { + long offset = vTableBaseOffset + i * vTableEntrySize; + WordBase entry; + if (method.hasNativeEntryPoint()) { + entry = method.getNativeEntryPoint(); + } else { + entry = getCremaStubForVTableIndex(i); + } + Word.objectToUntrackedPointer(hub).writeWord(Math.toIntExact(offset), entry); + i++; + } + } + + private static AbstractCremaDispatchTable createDispatchTable(ParserKlass parsed, Class superClass, Class[] transitiveSuperInterfaces) { CremaPartialType partialType = new CremaPartialType(parsed, superClass, transitiveSuperInterfaces); try { if (Modifier.isInterface(parsed.getFlags())) { @@ -382,6 +578,102 @@ private static void initStaticFields(CremaResolvedObjectType type, ParserField[] } } + private static String getSimpleBinaryName(ParserKlass parsed) { + InnerClassesAttribute innerClassesAttribute = (InnerClassesAttribute) parsed.getAttribute(InnerClassesAttribute.NAME); + if (innerClassesAttribute == null) { + return null; + } + ParserConstantPool pool = parsed.getConstantPool(); + for (int i = 0; i < innerClassesAttribute.entryCount(); i++) { + InnerClassesAttribute.Entry entry = innerClassesAttribute.entryAt(i); + int innerClassIndex = entry.innerClassIndex; + if (innerClassIndex != 0) { + if (pool.className(innerClassIndex) == parsed.getName()) { + if (entry.innerNameIndex == 0) { + break; + } else { + Symbol innerName = pool.utf8At(entry.innerNameIndex, "inner class name"); + return innerName.toString(); + } + } + } + } + return null; + } + + private static String getSourceFile(ParserKlass parsed) { + String sourceFile = null; + SourceFileAttribute sourceFileAttribute = (SourceFileAttribute) parsed.getAttribute(ParserSymbols.ParserNames.SourceFile); + if (sourceFileAttribute != null) { + sourceFile = parsed.getConstantPool().utf8At(sourceFileAttribute.getSourceFileIndex()).toString(); + } + return sourceFile; + } + + private static String getClassSignature(ParserKlass parsed) { + String sourceFile = null; + SignatureAttribute signatureAttribute = (SignatureAttribute) parsed.getAttribute(ParserSymbols.ParserNames.Signature); + if (signatureAttribute != null) { + sourceFile = parsed.getConstantPool().utf8At(signatureAttribute.getSignatureIndex()).toString(); + } + return sourceFile; + } + + private static boolean hasClassInitializer(ParserKlass parsed) { + for (ParserMethod method : parsed.getMethods()) { + if (method.getName() == ParserSymbols.ParserNames._clinit_) { + return true; + } + } + return false; + } + + private static boolean declaresDefaultMethods(ParserKlass parsed) { + for (ParserMethod method : parsed.getMethods()) { + int flags = method.getFlags(); + if (!Modifier.isAbstract(flags) && !Modifier.isStatic(flags)) { + return true; + } + } + return false; + } + + private static boolean isSealed(ParserKlass parsed) { + PermittedSubclassesAttribute permittedSubclasses = (PermittedSubclassesAttribute) parsed.getAttribute(PermittedSubclassesAttribute.NAME); + return permittedSubclasses != null && permittedSubclasses.getClasses().length > 0; + } + + private static boolean hasInheritedDefaultMethods(Class superClass, Class[] superInterfaces) { + if (DynamicHub.fromClass(superClass).hasDefaultMethods()) { + return true; + } + for (Class superInterface : superInterfaces) { + if (DynamicHub.fromClass(superInterface).hasDefaultMethods()) { + return true; + } + } + return false; + } + + private static int getClassModifiers(ParserKlass parsed) { + int modifiers = parsed.getFlags(); + InnerClassesAttribute innerClassesAttribute = (InnerClassesAttribute) parsed.getAttribute(InnerClassesAttribute.NAME); + if (innerClassesAttribute != null) { + ParserConstantPool pool = parsed.getConstantPool(); + for (int i = 0; i < innerClassesAttribute.entryCount(); i++) { + InnerClassesAttribute.Entry entry = innerClassesAttribute.entryAt(i); + if (entry.innerClassIndex != 0) { + Symbol innerClassName = pool.className(entry.innerClassIndex); + if (innerClassName.equals(parsed.getName())) { + modifiers = entry.innerClassAccessFlags; + break; + } + } + } + } + return modifiers & ~ACC_SUPER & JVM_ACC_WRITTEN_FLAGS; + } + static final class CremaPartialType implements PartialType { private final Class superClass; private final ParserKlass parserKlass; @@ -392,7 +684,7 @@ static final class CremaPartialType implements PartialType superClass, List> superInterfaces) { + CremaPartialType(ParserKlass parsed, Class superClass, Class[] superInterfaces) { this.superClass = superClass; this.parserKlass = parsed; parentTable = computeParentTable(superClass); @@ -531,25 +823,27 @@ public InterpreterResolvedJavaMethod asMethodAccess() { } } - private abstract static class CremaDispatchTableImpl implements CremaDispatchTable { + private abstract static class AbstractCremaDispatchTable { protected final CremaPartialType partialType; private final FieldLayout layout; - CremaDispatchTableImpl(CremaPartialType partialType) { + AbstractCremaDispatchTable(CremaPartialType partialType) { this.partialType = partialType; this.layout = FieldLayout.build(getParserKlass().getFields(), getSuperResolvedType().getAfterFieldsOffset()); } + public abstract int vtableLength(); + + public abstract int itableLength(Class iface); + public final void registerClass(InterpreterResolvedObjectType thisType) { partialType.thisJavaType = thisType; } - @Override - public int afterFieldsOffset(int superAfterFieldsOffset) { + public int afterFieldsOffset() { return layout.afterInstanceFieldsOffset(); } - @Override public int[] getDeclaredInstanceReferenceFieldOffsets() { return layout().getReferenceFieldsOffsets(); } @@ -588,17 +882,17 @@ protected static List toSimpleTable(List cremaVTable(List> intfList); + public abstract List cremaVTable(Class[] interfaces); } - private static final class CremaInterfaceDispatchTableImpl extends CremaDispatchTableImpl { + private static final class CremaInterfaceDispatchTableImpl extends AbstractCremaDispatchTable { CremaInterfaceDispatchTableImpl(CremaPartialType partialType) { super(partialType); } @Override - public List cremaVTable(List> intfList) { + public List cremaVTable(Class[] interfaces) { List itable = new ArrayList<>(); for (CremaPartialMethod method : partialType.getDeclaredMethodsList()) { if (VTable.isVirtualEntry(method)) { @@ -619,7 +913,7 @@ public int itableLength(Class iface) { } } - private static final class CremaInstanceDispatchTableImpl extends CremaDispatchTableImpl { + private static final class CremaInstanceDispatchTableImpl extends AbstractCremaDispatchTable { private final Tables table; CremaInstanceDispatchTableImpl(Tables table, CremaPartialType partialType) { @@ -628,10 +922,10 @@ private static final class CremaInstanceDispatchTableImpl extends CremaDispatchT } @Override - public List cremaVTable(List> intfList) { + public List cremaVTable(Class[] interfaces) { List vtable = toSimpleTable(table.getVtable()); List result = new ArrayList<>(vtable); - for (Class intf : intfList) { + for (Class intf : interfaces) { List itable = toSimpleTable(getItableFor(intf)); result.addAll(itable); } @@ -653,11 +947,6 @@ private List toClass(ResolvedJavaType resolvedJavaType) { /* From c574345975a2b306fcc38551cecb3f3cc7d06772 Mon Sep 17 00:00:00 2001 From: Christian Haeubl Date: Thu, 6 Nov 2025 08:37:18 +0100 Subject: [PATCH 4/4] Crema vtable fixes. --- .../svm/interpreter/CremaSupportImpl.java | 56 ++++++++++++++----- 1 file changed, 42 insertions(+), 14 deletions(-) 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 d644420351f2..3f8a3bf465c6 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 @@ -56,6 +56,7 @@ import org.graalvm.collections.Equivalence; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; +import org.graalvm.word.Pointer; import org.graalvm.word.WordBase; import com.oracle.graal.pointsto.constraints.UnsupportedPlatformException; @@ -63,10 +64,12 @@ 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.config.ConfigurationValues; import com.oracle.svm.core.graal.meta.KnownOffsets; import com.oracle.svm.core.hub.DynamicHub; import com.oracle.svm.core.hub.DynamicHubTypeCheckUtil; import com.oracle.svm.core.hub.DynamicHubTypeCheckUtil.TypeCheckData; +import com.oracle.svm.core.hub.LayoutEncoding; import com.oracle.svm.core.hub.RuntimeClassLoading.ClassDefinitionInfo; import com.oracle.svm.core.hub.RuntimeDynamicHubMetadata; import com.oracle.svm.core.hub.RuntimeReflectionMetadata; @@ -317,7 +320,7 @@ public DynamicHub createHub(ParserKlass parsed, ClassDefinitionInfo info, int ty } /* Allocate DynamicHub. */ - int hubNumVTableEntries = dispatchTable.vtableLength(); + int hubNumVTableEntries = dispatchTable.cremaVTableLength(transitiveSuperInterfaces); DynamicHub hub = DynamicHub.allocate(externalName, superHub, interfacesEncoding, null, sourceFile, modifiers, hubFlags, classLoader, simpleBinaryName, module, declaringClass, classSignature, typeID, interfaceID, @@ -463,19 +466,20 @@ private static TypeCheckData computeTypeCheckData(int typeID, boolean typeIsInte } private static void fillVTable(DynamicHub hub, InterpreterResolvedJavaMethod[] vtable) { - long vTableBaseOffset = KnownOffsets.singleton().getVTableBaseOffset(); - long vTableEntrySize = KnownOffsets.singleton().getVTableEntrySize(); - int i = 0; - for (InterpreterResolvedJavaMethod method : vtable) { - long offset = vTableBaseOffset + i * vTableEntrySize; - WordBase entry; - if (method.hasNativeEntryPoint()) { - entry = method.getNativeEntryPoint(); - } else { - entry = getCremaStubForVTableIndex(i); - } - Word.objectToUntrackedPointer(hub).writeWord(Math.toIntExact(offset), entry); - i++; + int wordSize = ConfigurationValues.getTarget().wordSize; + assert KnownOffsets.singleton().getVTableEntrySize() == wordSize : "only word size is implemented at the moment"; + + Pointer hubStart = Word.objectToUntrackedPointer(hub); + Pointer hubEnd = LayoutEncoding.getMetaspaceObjectEnd(hub); + + Pointer pos = hubStart.add(KnownOffsets.singleton().getVTableBaseOffset()); + for (int i = 0; i < vtable.length; i++) { + InterpreterResolvedJavaMethod method = vtable[i]; + WordBase entry = method.hasNativeEntryPoint() ? method.getNativeEntryPoint() : getCremaStubForVTableIndex(i); + + pos.writeWord(0, entry); + pos = pos.add(wordSize); + assert pos.belowOrEqual(hubEnd); } } @@ -882,6 +886,8 @@ protected static List toSimpleTable(List[] interfaces); + public abstract List cremaVTable(Class[] interfaces); } @@ -891,6 +897,17 @@ private static final class CremaInterfaceDispatchTableImpl extends AbstractCrema super(partialType); } + @Override + public int cremaVTableLength(Class[] interfaces) { + int result = 0; + for (CremaPartialMethod method : partialType.getDeclaredMethodsList()) { + if (VTable.isVirtualEntry(method)) { + result++; + } + } + return result; + } + @Override public List cremaVTable(Class[] interfaces) { List itable = new ArrayList<>(); @@ -899,6 +916,7 @@ public List cremaVTable(Class[] interfaces) { itable.add(method.withITableIndex(itable.size()).asMethodAccess()); } } + assert itable.size() == cremaVTableLength(interfaces); return itable; } @@ -921,6 +939,15 @@ private static final class CremaInstanceDispatchTableImpl extends AbstractCremaD this.table = table; } + @Override + public int cremaVTableLength(Class[] interfaces) { + int result = table.getVtable().size(); + for (Class intf : interfaces) { + result += getItableFor(intf).size(); + } + return result; + } + @Override public List cremaVTable(Class[] interfaces) { List vtable = toSimpleTable(table.getVtable()); @@ -929,6 +956,7 @@ public List cremaVTable(Class[] interfaces) { List itable = toSimpleTable(getItableFor(intf)); result.addAll(itable); } + assert cremaVTableLength(interfaces) == result.size(); return result; }