From 19ab19674dee49a75c18a96aa7499b21b00a33d0 Mon Sep 17 00:00:00 2001 From: Tom Shull Date: Tue, 13 Aug 2024 15:41:13 +0200 Subject: [PATCH 1/3] Change open type world dispatch table slot assignment calculation. Also add validation for dispatch table creation within layered images. --- .../com/oracle/graal/pointsto/api/HostVM.java | 5 + .../graal/pointsto/heap/ImageLayerWriter.java | 6 +- .../graal/pointsto/meta/AnalysisMethod.java | 73 ++++ .../graal/pointsto/meta/AnalysisType.java | 91 ++++ .../oracle/svm/core/config/ObjectLayout.java | 62 ++- .../svm/core/graal/meta/KnownOffsets.java | 57 +++ .../imagelayer/ImageLayerBuildingSupport.java | 15 + .../svm/hosted/OpenTypeWorldFeature.java | 404 +++++++++++++++++- .../src/com/oracle/svm/hosted/SVMHost.java | 12 +- .../svm/hosted/heap/SVMImageLayerLoader.java | 2 +- .../svm/hosted/heap/SVMImageLayerWriter.java | 17 + .../oracle/svm/hosted/image/NativeImage.java | 2 +- .../imagelayer/HostedDynamicLayerInfo.java | 6 +- .../oracle/svm/hosted/meta/HostedType.java | 21 + .../oracle/svm/hosted/meta/VTableBuilder.java | 100 +++-- .../OpenTypeWorldConvertCallTargetPhase.java | 62 +++ 16 files changed, 884 insertions(+), 51 deletions(-) create mode 100644 substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/OpenTypeWorldConvertCallTargetPhase.java diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/api/HostVM.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/api/HostVM.java index c49b4506370c..d33dced7261c 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/api/HostVM.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/api/HostVM.java @@ -33,6 +33,7 @@ import java.util.Comparator; import java.util.List; import java.util.Optional; +import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.BiConsumer; import java.util.function.Function; @@ -436,4 +437,8 @@ public boolean allowConstantFolding(AnalysisMethod method) { */ return method.isOriginalMethod(); } + + public Set loadOpenTypeWorldDispatchTableMethods(@SuppressWarnings("unused") AnalysisType type) { + return Set.of(); + } } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageLayerWriter.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageLayerWriter.java index 0e5f0f9372d6..d09f0d1e9604 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageLayerWriter.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageLayerWriter.java @@ -204,6 +204,10 @@ public void persistImageHeapSize(long imageHeapSize) { jsonMap.put(IMAGE_HEAP_SIZE_TAG, String.valueOf(imageHeapSize)); } + protected boolean shouldPersistMethod(AnalysisMethod method) { + return method.isReachable(); + } + public void persistAnalysisInfo() { persistHook(); @@ -223,7 +227,7 @@ public void persistAnalysisInfo() { } jsonMap.put(TYPES_TAG, typesMap); - for (AnalysisMethod method : aUniverse.getMethods().stream().filter(AnalysisMethod::isReachable).toList()) { + for (AnalysisMethod method : aUniverse.getMethods().stream().filter(this::shouldPersistMethod).toList()) { persistMethod(method); } jsonMap.put(METHODS_TAG, methodsMap); diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisMethod.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisMethod.java index 7c6f8614b7ef..6181fffcbcb2 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisMethod.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisMethod.java @@ -117,6 +117,9 @@ public record Signature(String name, AnalysisType[] parameterTypes) { public final ResolvedJavaMethod wrapped; + private AnalysisMethod indirectCallTarget = null; + public boolean invalidIndirectCallTarget = false; + private final int id; /** Marks a method loaded from a base layer. */ private final boolean isInBaseLayer; @@ -329,6 +332,76 @@ public AnalysisUniverse getUniverse() { return declaringClass.getUniverse(); } + private static boolean matchingSignature(AnalysisMethod o1, AnalysisMethod o2) { + if (o1.equals(o2)) { + return true; + } + + if (!o1.getName().equals(o2.getName())) { + return false; + } + + return o1.getSignature().equals(o2.getSignature()); + } + + private AnalysisMethod setIndirectCallTarget(AnalysisMethod method, boolean foundMatch) { + indirectCallTarget = method; + invalidIndirectCallTarget = !foundMatch; + return indirectCallTarget; + } + + /** + * For methods where its {@link #getDeclaringClass()} does not explicitly declare the method, + * find an alternative explicit declaration for the method which can be used as an indirect call + * target. This logic is currently used for deciding the target of virtual/interface calls when + * using the open type world. + */ + public AnalysisMethod getIndirectCallTarget() { + if (indirectCallTarget != null) { + return indirectCallTarget; + } + if (isStatic()) { + /* + * Static methods must always be explicitly declared. + */ + return setIndirectCallTarget(this, true); + } + + var dispatchTableMethods = declaringClass.getOrCalculateOpenTypeWorldDispatchTableMethods(); + + if (isConstructor()) { + /* + * Constructors can only be found in their declaring class. + */ + return setIndirectCallTarget(this, dispatchTableMethods.contains(this)); + } + + if (dispatchTableMethods.contains(this)) { + return setIndirectCallTarget(this, true); + } + + for (AnalysisType interfaceType : declaringClass.getAllInterfaces()) { + if (interfaceType.equals(declaringClass)) { + // already checked + continue; + } + dispatchTableMethods = interfaceType.getOrCalculateOpenTypeWorldDispatchTableMethods(); + for (AnalysisMethod candidate : dispatchTableMethods) { + if (matchingSignature(candidate, this)) { + return setIndirectCallTarget(candidate, true); + } + } + } + + /* + * For some methods (e.g., methods labeled as @PolymorphicSignature or @Delete), we + * currently do not find matches. However, these methods will not be indirect calls within + * our generated code, so it is not necessary to determine an accurate virtual/interface + * call target. + */ + return setIndirectCallTarget(this, false); + } + public void cleanupAfterAnalysis() { if (parsedGraphCacheState.get() instanceof AnalysisParsedGraph) { parsedGraphCacheState.set(GRAPH_CACHE_CLEARED); diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisType.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisType.java index aa9fb5f004b6..e5a4331157a3 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisType.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisType.java @@ -163,6 +163,9 @@ public abstract class AnalysisType extends AnalysisElement implements WrappedJav private final AnalysisType[] interfaces; private AnalysisMethod[] declaredMethods; + private Set dispatchTableMethods; + + private AnalysisType[] allInterfaces; /* isArray is an expensive operation so we eagerly compute it */ private final boolean isArray; @@ -364,6 +367,36 @@ public int getArrayDimension() { return dimension; } + /** + * @return All interfaces this type inherits (including itself if it is an interface). + */ + public AnalysisType[] getAllInterfaces() { + if (allInterfaces != null) { + return allInterfaces; + } + + Set allInterfaceSet = new HashSet<>(); + + if (isInterface()) { + allInterfaceSet.add(this); + } + + if (this.superClass != null) { + allInterfaceSet.addAll(Arrays.asList(this.superClass.getAllInterfaces())); + } + + for (AnalysisType i : interfaces) { + allInterfaceSet.addAll(Arrays.asList(i.getAllInterfaces())); + } + + var result = allInterfaceSet.toArray(AnalysisType[]::new); + + // ensure result is fully visible across threads + VarHandle.storeStoreFence(); + allInterfaces = result; + return allInterfaces; + } + public void cleanupAfterAnalysis() { instantiatedTypes = null; instantiatedTypesNonNull = null; @@ -1270,6 +1303,64 @@ public AnalysisMethod findConstructor(Signature signature) { return null; } + public Set getOpenTypeWorldDispatchTableMethods() { + AnalysisError.guarantee(dispatchTableMethods != null); + return dispatchTableMethods; + } + + /* + * Calculates all methods in this class which should be included in its dispatch table. + */ + public Set getOrCalculateOpenTypeWorldDispatchTableMethods() { + if (dispatchTableMethods != null) { + return dispatchTableMethods; + } + if (isPrimitive()) { + dispatchTableMethods = Set.of(); + return dispatchTableMethods; + } + if (getWrapped() instanceof BaseLayerType) { + var result = universe.hostVM.loadOpenTypeWorldDispatchTableMethods(this); + + // ensure result is fully visible across threads + VarHandle.storeStoreFence(); + dispatchTableMethods = result; + return dispatchTableMethods; + } + + Set wrappedMethods = new HashSet<>(); + wrappedMethods.addAll(Arrays.asList(getWrapped().getDeclaredMethods(false))); + wrappedMethods.addAll(Arrays.asList(getWrapped().getDeclaredConstructors(false))); + + var resultSet = new HashSet(); + for (ResolvedJavaMethod m : wrappedMethods) { + if (m.isStatic()) { + /* Only looking at member methods and constructors */ + continue; + } + try { + AnalysisMethod aMethod = universe.lookup(m); + resultSet.add(aMethod); + } catch (UnsupportedFeatureException t) { + /* + * Methods which are deleted or not available on this platform will throw an error + * during lookup - ignore and continue execution + * + * Note it is not simple to create a check to determine whether calling + * universe#lookup will trigger an error by creating an analysis object for a type + * not supported on this platform, as creating a method requires, in addition to the + * types of its return type and parameters, all of the super types of its return and + * parameters to be created as well. + */ + } + } + + // ensure result is fully visible across threads + VarHandle.storeStoreFence(); + dispatchTableMethods = resultSet; + return dispatchTableMethods; + } + @Override public AnalysisMethod findMethod(String name, Signature signature) { for (AnalysisMethod method : getDeclaredMethods(false)) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/config/ObjectLayout.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/config/ObjectLayout.java index f21057e781cb..326e5c5381cf 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/config/ObjectLayout.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/config/ObjectLayout.java @@ -24,13 +24,24 @@ */ package com.oracle.svm.core.config; -import jdk.vm.ci.meta.UnresolvedJavaType; +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.EnumSet; +import java.util.function.Predicate; + import org.graalvm.nativeimage.AnnotationAccess; +import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.c.constant.CEnum; import org.graalvm.word.WordBase; import com.oracle.svm.core.SubstrateTargetDescription; import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.imagelayer.ImageLayerBuildingSupport; +import com.oracle.svm.core.layeredimagesingleton.ImageSingletonLoader; +import com.oracle.svm.core.layeredimagesingleton.ImageSingletonWriter; +import com.oracle.svm.core.layeredimagesingleton.LayeredImageSingleton; +import com.oracle.svm.core.layeredimagesingleton.LayeredImageSingletonBuilderFlags; +import com.oracle.svm.core.util.VMError; import jdk.graal.compiler.api.directives.GraalDirectives; import jdk.graal.compiler.core.common.NumUtil; @@ -41,6 +52,7 @@ import jdk.vm.ci.meta.JavaType; import jdk.vm.ci.meta.MetaAccessProvider; import jdk.vm.ci.meta.ResolvedJavaType; +import jdk.vm.ci.meta.UnresolvedJavaType; /** * Immutable class that holds all sizes and offsets that contribute to the object layout. @@ -92,6 +104,29 @@ public ObjectLayout(SubstrateTargetDescription target, int referenceSize, int ob this.arrayBaseOffset = arrayBaseOffset; this.objectHeaderIdentityHashOffset = headerIdentityHashOffset; this.identityHashMode = identityHashMode.value; + + if (ImageLayerBuildingSupport.buildingImageLayer()) { + int[] currentValues = { + /* this.target, */ + this.referenceSize, + this.objectAlignment, + this.alignmentMask, + this.hubOffset, + this.firstFieldOffset, + this.arrayLengthOffset, + this.arrayBaseOffset, + this.objectHeaderIdentityHashOffset, + this.identityHashMode, + }; + var numFields = Arrays.stream(ObjectLayout.class.getDeclaredFields()).filter(Predicate.not(Field::isSynthetic)).count(); + VMError.guarantee(numFields - 1 == currentValues.length, "Missing fields"); + + if (ImageLayerBuildingSupport.buildingInitialLayer()) { + ImageSingletons.add(PriorObjectLayout.class, new PriorObjectLayout(currentValues)); + } else { + VMError.guarantee(Arrays.equals(currentValues, ImageSingletons.lookup(PriorObjectLayout.class).priorValues)); + } + } } /** The minimum alignment of objects (instances and arrays). */ @@ -276,4 +311,29 @@ public enum IdentityHashMode { this.value = value; } } + + static class PriorObjectLayout implements LayeredImageSingleton { + final int[] priorValues; + + PriorObjectLayout(int[] priorValues) { + this.priorValues = priorValues; + } + + @Override + public EnumSet getImageBuilderFlags() { + return LayeredImageSingletonBuilderFlags.BUILDTIME_ACCESS_ONLY; + } + + @Override + public PersistFlags preparePersist(ImageSingletonWriter writer) { + writer.writeIntList("priorValues", Arrays.stream(priorValues).boxed().toList()); + return PersistFlags.CREATE; + } + + @SuppressWarnings("unused") + public static Object createFromLoader(ImageSingletonLoader loader) { + int[] priorValues = loader.readIntList("priorValues").stream().mapToInt(e -> e).toArray(); + return new PriorObjectLayout(priorValues); + } + } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/meta/KnownOffsets.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/meta/KnownOffsets.java index 798117830974..3b671896f2dd 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/meta/KnownOffsets.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/meta/KnownOffsets.java @@ -24,6 +24,11 @@ */ package com.oracle.svm.core.graal.meta; +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.EnumSet; +import java.util.function.Predicate; + import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; @@ -31,6 +36,12 @@ import com.oracle.svm.core.BuildPhaseProvider.ReadyForCompilation; import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.heap.UnknownPrimitiveField; +import com.oracle.svm.core.imagelayer.ImageLayerBuildingSupport; +import com.oracle.svm.core.layeredimagesingleton.ImageSingletonLoader; +import com.oracle.svm.core.layeredimagesingleton.ImageSingletonWriter; +import com.oracle.svm.core.layeredimagesingleton.LayeredImageSingleton; +import com.oracle.svm.core.layeredimagesingleton.LayeredImageSingletonBuilderFlags; +import com.oracle.svm.core.util.VMError; import jdk.graal.compiler.api.replacements.Fold; @@ -72,6 +83,27 @@ public void setLazyState(int vtableBaseOffset, int vtableEntrySize, int typeIDSl this.imageCodeInfoCodeStartOffset = imageCodeInfoCodeStartOffset; assert isFullyInitialized(); + + if (ImageLayerBuildingSupport.buildingImageLayer()) { + int[] currentValues = { + vtableBaseOffset, + vtableEntrySize, + typeIDSlotsOffset, + componentHubOffset, + javaFrameAnchorLastSPOffset, + javaFrameAnchorLastIPOffset, + vmThreadStatusOffset, + imageCodeInfoCodeStartOffset, + }; + var numFields = Arrays.stream(KnownOffsets.class.getDeclaredFields()).filter(Predicate.not(Field::isSynthetic)).count(); + VMError.guarantee(numFields == currentValues.length, "Missing fields"); + + if (ImageLayerBuildingSupport.buildingInitialLayer()) { + ImageSingletons.add(PriorKnownOffsets.class, new PriorKnownOffsets(currentValues)); + } else { + VMError.guarantee(Arrays.equals(currentValues, ImageSingletons.lookup(PriorKnownOffsets.class).priorValues)); + } + } } private boolean isFullyInitialized() { @@ -122,4 +154,29 @@ public int getImageCodeInfoCodeStartOffset() { assert isFullyInitialized(); return imageCodeInfoCodeStartOffset; } + + static class PriorKnownOffsets implements LayeredImageSingleton { + final int[] priorValues; + + PriorKnownOffsets(int[] priorValues) { + this.priorValues = priorValues; + } + + @Override + public EnumSet getImageBuilderFlags() { + return LayeredImageSingletonBuilderFlags.BUILDTIME_ACCESS_ONLY; + } + + @Override + public PersistFlags preparePersist(ImageSingletonWriter writer) { + writer.writeIntList("priorValues", Arrays.stream(priorValues).boxed().toList()); + return PersistFlags.CREATE; + } + + @SuppressWarnings("unused") + public static Object createFromLoader(ImageSingletonLoader loader) { + int[] priorValues = loader.readIntList("priorValues").stream().mapToInt(e -> e).toArray(); + return new PriorKnownOffsets(priorValues); + } + } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/imagelayer/ImageLayerBuildingSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/imagelayer/ImageLayerBuildingSupport.java index d0b1001dddf6..45fb0eb31eaa 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/imagelayer/ImageLayerBuildingSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/imagelayer/ImageLayerBuildingSupport.java @@ -112,26 +112,41 @@ public static boolean lastImageBuild() { @Fold public static boolean buildingImageLayer() { + if (!ImageSingletons.contains(ImageLayerBuildingSupport.class)) { + return false; + } return singleton().buildingImageLayer; } @Fold public static boolean buildingInitialLayer() { + if (!ImageSingletons.contains(ImageLayerBuildingSupport.class)) { + return true; + } return singleton().buildingInitialLayer; } @Fold public static boolean buildingApplicationLayer() { + if (!ImageSingletons.contains(ImageLayerBuildingSupport.class)) { + return false; + } return singleton().buildingApplicationLayer; } @Fold public static boolean buildingExtensionLayer() { + if (!ImageSingletons.contains(ImageLayerBuildingSupport.class)) { + return false; + } return singleton().buildingImageLayer && !singleton().buildingInitialLayer; } @Fold public static boolean buildingSharedLayer() { + if (!ImageSingletons.contains(ImageLayerBuildingSupport.class)) { + return false; + } return singleton().buildingImageLayer && !singleton().buildingApplicationLayer; } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/OpenTypeWorldFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/OpenTypeWorldFeature.java index d24938049787..654d23130c67 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/OpenTypeWorldFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/OpenTypeWorldFeature.java @@ -24,20 +24,31 @@ */ package com.oracle.svm.hosted; +import static com.oracle.svm.hosted.image.NativeImage.localSymbolNameForMethod; + import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Comparator; import java.util.EnumSet; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Set; +import java.util.function.Predicate; +import org.graalvm.collections.Pair; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.hosted.Feature; +import com.oracle.graal.pointsto.heap.ImageLayerLoader; +import com.oracle.graal.pointsto.meta.AnalysisMethod; import com.oracle.graal.pointsto.meta.AnalysisType; +import com.oracle.graal.pointsto.meta.BaseLayerType; import com.oracle.svm.core.SubstrateOptions; +import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; import com.oracle.svm.core.feature.InternalFeature; import com.oracle.svm.core.hub.DynamicHub; @@ -47,12 +58,25 @@ import com.oracle.svm.core.layeredimagesingleton.ImageSingletonWriter; import com.oracle.svm.core.layeredimagesingleton.LayeredImageSingleton; import com.oracle.svm.core.layeredimagesingleton.LayeredImageSingletonBuilderFlags; +import com.oracle.svm.core.option.HostedOptionKey; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.hosted.image.NativeImage; +import com.oracle.svm.hosted.meta.HostedMethod; import com.oracle.svm.hosted.meta.HostedType; +import com.oracle.svm.hosted.meta.VTableBuilder; import jdk.graal.compiler.debug.Assertions; +import jdk.graal.compiler.options.Option; @AutomaticallyRegisteredFeature public class OpenTypeWorldFeature implements InternalFeature { + public static final class Options { + @Option(help = "Log discrepancies between layered open world type information. This is an experimental option which will be removed.")// + public static final HostedOptionKey LogOpenTypeWorldDiscrepancies = new HostedOptionKey<>(false); + + @Option(help = "Throw an error when there are discrepancies between layered open world type information. This is an experimental option which will be removed.")// + public static final HostedOptionKey ErrorOnOpenTypeWorldDiscrepancies = new HostedOptionKey<>(false); + } @Override public boolean isInConfiguration(Feature.IsInConfigurationAccess access) { @@ -68,6 +92,36 @@ public void beforeUniverseBuilding(BeforeUniverseBuildingAccess access) { } } + private final Set triggeredTypes = new HashSet<>(); + private final Set triggeredMethods = new HashSet<>(); + + @Override + public void duringSetup(DuringSetupAccess access) { + if (ImageLayerBuildingSupport.buildingExtensionLayer()) { + var loader = ((FeatureImpl.DuringSetupAccessImpl) access).getUniverse().getImageLayerLoader(); + ImageSingletons.lookup(LayerTypeInfo.class).loader = loader; + } + } + + @Override + public void duringAnalysis(DuringAnalysisAccess access) { + var config = (FeatureImpl.DuringAnalysisAccessImpl) access; + for (AnalysisType aType : config.getUniverse().getTypes()) { + if (triggeredTypes.add(aType)) { + aType.getOrCalculateOpenTypeWorldDispatchTableMethods(); + config.requireAnalysisIteration(); + } + } + for (AnalysisMethod aMethod : config.getUniverse().getMethods()) { + if (triggeredMethods.add(aMethod)) { + if (!aMethod.isStatic()) { + aMethod.getIndirectCallTarget(); + config.requireAnalysisIteration(); + } + } + } + } + @Override public void beforeCompilation(BeforeCompilationAccess access) { var impl = (FeatureImpl.BeforeCompilationAccessImpl) access; @@ -75,6 +129,10 @@ public void beforeCompilation(BeforeCompilationAccess access) { DynamicHub hub = type.getHub(); impl.registerAsImmutable(hub.getOpenTypeWorldTypeCheckSlots()); } + + if (ImageLayerBuildingSupport.buildingSharedLayer()) { + ImageSingletons.lookup(LayerTypeInfo.class).persistDispatchTableMethods(impl.getUniverse().getTypes()); + } } public static int loadTypeInfo(Collection types) { @@ -134,8 +192,90 @@ public int hashCode() { } } + public record DispatchInfo(int[] typeCheckInterfaceOrder, int[] itableStartingOffsets, String[] dispatchTables) { + + private Pair, List> generateLists() { + assert typeCheckInterfaceOrder.length == itableStartingOffsets.length : Assertions.errorMessage(typeCheckInterfaceOrder, itableStartingOffsets); + + ArrayList list = new ArrayList<>(); + list.add(typeCheckInterfaceOrder.length); + Arrays.stream(typeCheckInterfaceOrder).forEach(list::add); + Arrays.stream(itableStartingOffsets).forEach(list::add); + + return Pair.create(list, Arrays.asList(dispatchTables)); + } + + private static DispatchInfo fromLists(List list, List dispatchTables) { + int numInterfaces = list.get(0); + int startingOffset = 1; + int[] typeCheckInterfaceOrder = list.subList(startingOffset, startingOffset + numInterfaces).stream().mapToInt(i -> i).toArray(); + startingOffset += numInterfaces; + int[] itableStartingOffsets = list.subList(startingOffset, startingOffset + numInterfaces).stream().mapToInt(i -> i).toArray(); + + return new DispatchInfo(typeCheckInterfaceOrder, itableStartingOffsets, dispatchTables.toArray(new String[0])); + } + + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (object == null || getClass() != object.getClass()) { + return false; + } + DispatchInfo that = (DispatchInfo) object; + return Arrays.equals(typeCheckInterfaceOrder, that.typeCheckInterfaceOrder) && Arrays.equals(itableStartingOffsets, that.itableStartingOffsets) && + Arrays.equals(dispatchTables, that.dispatchTables); + } + + @Override + public int hashCode() { + int result = Arrays.hashCode(typeCheckInterfaceOrder); + result = 31 * result + Arrays.hashCode(itableStartingOffsets); + result = 31 * result + Arrays.hashCode(dispatchTables); + return result; + } + + @Override + public String toString() { + return "DispatchInfo{" + + "typeCheckInterfaceOrder=" + Arrays.toString(typeCheckInterfaceOrder) + + ", itableStartingOffsets=" + Arrays.toString(itableStartingOffsets) + + ", dispatchTables=" + Arrays.toString(dispatchTables) + + '}'; + } + } + + public static void persistDispatchInfo(Collection types) { + if (ImageSingletons.contains(LayerTypeInfo.class)) { + ImageSingletons.lookup(LayerTypeInfo.class).persistDispatchInfo(types); + } + } + + public static void persistMethodInfo(Collection methods) { + if (ImageSingletons.contains(LayerTypeInfo.class)) { + ImageSingletons.lookup(LayerTypeInfo.class).persistMethodInfo(methods); + } + } + + public static void persistDispatchTable(HostedType type, List table) { + if (ImageSingletons.contains(LayerTypeInfo.class)) { + ImageSingletons.lookup(LayerTypeInfo.class).persistDispatchTable(type, table); + } + } + + public static Set loadDispatchTable(AnalysisType type) { + return ImageSingletons.lookup(LayerTypeInfo.class).loadDispatchTableMethods(type); + } + private static class LayerTypeInfo implements LayeredImageSingleton { Map identifierToTypeInfo = new HashMap<>(); + Map identifierToDispatchInfo = new HashMap<>(); + Map> identifierToSymDispatchTable = new HashMap<>(); + Map> identifierToIdDispatchTable = new HashMap<>(); + Map symbolToVTableIdx = new HashMap<>(); + Map> currentDispatchTableMaps = new HashMap<>(); + ImageLayerLoader loader; int maxTypeID = 0; public int loadTypeID(Collection types) { @@ -152,10 +292,48 @@ public int loadTypeID(Collection types) { return maxTypeID; } + public void persistDispatchTableMethods(Collection types) { + for (HostedType type : types) { + if (isTypeReachable(type)) { + AnalysisType aType = type.getWrapped(); + Set dispatchTable = aType.getOpenTypeWorldDispatchTableMethods(); + var previous = currentDispatchTableMaps.put(aType, dispatchTable); + assert previous == null; + } + } + } + + public Set loadDispatchTableMethods(AnalysisType type) { + assert type.getWrapped() instanceof BaseLayerType : type; + /* + * If there was a race for the identifier id, it is possible no map exists for the base + * layer type. + */ + List methodIDs = identifierToIdDispatchTable.get(type.getId()); + if (methodIDs == null) { + return Set.of(); + } + + return Set.of(methodIDs.stream().map(mid -> loader.getAnalysisMethod(mid)).toArray(AnalysisMethod[]::new)); + } + + private static boolean logErrorMessages() { + return SubstrateUtil.assertionsEnabled() || Options.LogOpenTypeWorldDiscrepancies.getValue(); + } + + private static boolean generateErrorMessage() { + return logErrorMessages() || Options.ErrorOnOpenTypeWorldDiscrepancies.getValue(); + } + + private static boolean isTypeReachable(HostedType type) { + var result = type.getWrapped().isReachable(); + assert type.getTypeID() != -1 : type; + return result; + } + public void persistTypeInfo(Collection types) { for (HostedType type : types) { - AnalysisType analysisType = type.getWrapped(); - if (type.getTypeID() != -1 && analysisType.isReachable()) { + if (isTypeReachable(type)) { int identifierID = type.getWrapped().getId(); int typeID = type.getTypeID(); int numClassTypes = type.getNumClassTypes(); @@ -173,11 +351,144 @@ public void persistTypeInfo(Collection types) { } } + public void persistDispatchTable(HostedType type, List table) { + if (isTypeReachable(type)) { + int identifierID = type.getWrapped().getId(); + List newTable = table.stream().map(NativeImage::localSymbolNameForMethod).toList(); + var priorTable = identifierToSymDispatchTable.get(identifierID); + if (priorTable == null) { + identifierToSymDispatchTable.put(identifierID, newTable); + } else { + if (!newTable.equals(priorTable)) { + if (generateErrorMessage()) { + StringBuilder sb = new StringBuilder(); + Set priorSet = new HashSet<>(priorTable); + Set newSet = new HashSet<>(newTable); + sb.append(String.format("%n%nMismatch in dispatch table for %s: prior-size: %s new size: %s%n", type.getName(), priorSet.size(), newSet.size())); + sb.append("Methods present in priorTable not present in newTable\n"); + priorSet.stream().filter(Predicate.not(newSet::contains)).forEach(sym -> sb.append(sym).append("\n")); + sb.append("Methods present in newTable not present in priorTable\n"); + newSet.stream().filter(Predicate.not(priorSet::contains)).forEach(sym -> sb.append(sym).append("\n")); + sb.append("End Results\n"); + String errorMessage = sb.toString(); + if (logErrorMessages()) { + System.out.println(errorMessage); + } + if (Options.ErrorOnOpenTypeWorldDiscrepancies.getValue()) { + throw VMError.shouldNotReachHere(errorMessage); + } + } + } + } + } + } + + private static void printDispatchTableDifference(DispatchInfo priorInfo, DispatchInfo newInfo) { + System.out.println("Describing difference"); + var priorTables = priorInfo.dispatchTables; + var newTables = newInfo.dispatchTables; + + if (priorTables.length != newTables.length) { + System.out.printf("Different length %s %s%n%n", priorTables.length, newTables.length); + return; + } + + for (int i = 0; i < priorTables.length; i++) { + var priorTarget = priorTables[i]; + var newTarget = newTables[i]; + if (!priorTarget.equals(newTarget)) { + System.out.printf("Difference at index %s: prior: %s new: %s%n", i, priorTarget, newTarget); + } + } + System.out.println("\n"); + } + + public void persistDispatchInfo(Collection types) { + int numErrors = 0; + for (HostedType type : types) { + if (isTypeReachable(type)) { + if (VTableBuilder.needsDispatchTable(type)) { + int identifierID = type.getWrapped().getId(); + var newDispatchInfo = type.generateDispatchInfo(); + var priorInfo = identifierToDispatchInfo.get(identifierID); + if (priorInfo == null) { + identifierToDispatchInfo.put(identifierID, newDispatchInfo); + } else { + if (!newDispatchInfo.equals(priorInfo)) { + numErrors++; + if (generateErrorMessage()) { + String message = String.format("%n%nError %s%nDispatch Info Mismatch: %s%nprior: %s%n%nnew: %s", numErrors, type.getName(), priorInfo, newDispatchInfo); + if (logErrorMessages()) { + System.out.println(message); + printDispatchTableDifference(priorInfo, newDispatchInfo); + } + if (Options.ErrorOnOpenTypeWorldDiscrepancies.getValue()) { + throw VMError.shouldNotReachHere(message); + } + } + } + } + } + } + } + if (numErrors > 0 && generateErrorMessage()) { + /* + * Note to get here ErrorOnOpenTypeWorldDiscrepancies cannot be true. + */ + System.out.println("Total num errors: " + numErrors); + } + } + + public void persistMethodInfo(Collection methods) { + for (HostedMethod method : methods) { + if (method.hasVTableIndex() && isTypeReachable(method.getDeclaringClass())) { + int vTableIndex = method.getVTableIndex(); + String key = localSymbolNameForMethod(method); + var priorIdx = symbolToVTableIdx.get(key); + if (priorIdx == null) { + symbolToVTableIdx.put(key, vTableIndex); + } else { + if (priorIdx != vTableIndex) { + if (generateErrorMessage()) { + String message = String.format("VTable Index Mismatch %s. prior: %s new: %s", method.format("%H.%n"), priorIdx, vTableIndex); + if (logErrorMessages()) { + System.out.println(message); + } + if (Options.ErrorOnOpenTypeWorldDiscrepancies.getValue()) { + throw VMError.shouldNotReachHere(message); + } + } + } + } + } + } + } + @Override public EnumSet getImageBuilderFlags() { return LayeredImageSingletonBuilderFlags.BUILDTIME_ACCESS_ONLY; } + private static String getTypeInfoKey(int id) { + return String.format("TypeInfo-%s", id); + } + + private static String getDispatchInfoKey(int id) { + return String.format("DispatchInfo-%s", id); + } + + private static String getSymbolInfoKey(int id) { + return String.format("SymbolInfo-%s", id); + } + + private static String getSymDispatchTableId(int id) { + return String.format("SymDispatchTable-%s", id); + } + + private static String getIdDispatchTableId(int id) { + return String.format("IdDispatchTable-%s", id); + } + @Override public PersistFlags preparePersist(ImageSingletonWriter writer) { /* @@ -185,13 +496,56 @@ public PersistFlags preparePersist(ImageSingletonWriter writer) { * (identifierID -> typeID) mappings. In the future we can compact the amount of * information we store. */ - var identifierIDs = identifierToTypeInfo.keySet().stream().sorted().toList(); - writer.writeIntList("identifierIDs", identifierIDs); + var typeIdentifierIds = identifierToTypeInfo.keySet().stream().sorted().toList(); + writer.writeIntList("typeIdentifierIds", typeIdentifierIds); writer.writeInt("maxTypeID", DynamicHubSupport.singleton().getMaxTypeId()); - for (int identifierID : identifierIDs) { + for (int identifierID : typeIdentifierIds) { var typeInfo = identifierToTypeInfo.get(identifierID); - writer.writeIntList(Integer.toString(identifierID), typeInfo.toIntList()); + assert typeInfo != null; + writer.writeIntList(getTypeInfoKey(identifierID), typeInfo.toIntList()); + } + + /* + * Currently we keep track of dispatch tables, vtable index assignments, and per-type + * dispatch tables to validate open type world virtual dispatch implementation. As the + * layered analysis information becomes more stable, much of this tracking can be + * removed (GR-57248). + */ + + var dispatchIdentifierIds = identifierToDispatchInfo.keySet().stream().sorted().toList(); + writer.writeIntList("dispatchIdentifierIds", dispatchIdentifierIds); + + for (int identifierID : dispatchIdentifierIds) { + var dispatchInfo = identifierToDispatchInfo.get(identifierID); + assert dispatchInfo != null; + var lists = dispatchInfo.generateLists(); + writer.writeIntList(getDispatchInfoKey(identifierID), lists.getLeft()); + writer.writeStringList(getSymbolInfoKey(identifierID), lists.getRight()); + } + + ArrayList symNames = new ArrayList<>(); + ArrayList vtableIdxs = new ArrayList<>(); + for (var entry : symbolToVTableIdx.entrySet()) { + symNames.add(entry.getKey()); + vtableIdxs.add(entry.getValue()); + } + writer.writeStringList("symNames", symNames); + writer.writeIntList("vtableIdx", vtableIdxs); + + var symDispatchTableIds = identifierToSymDispatchTable.keySet().stream().sorted().toList(); + writer.writeIntList("symDispatchTableIds", symDispatchTableIds); + for (int identifierID : symDispatchTableIds) { + var dispatchTable = identifierToSymDispatchTable.get(identifierID); + assert dispatchTable != null; + writer.writeStringList(getSymDispatchTableId(identifierID), dispatchTable); + } + + var idDispatchTableKeys = currentDispatchTableMaps.keySet().stream().sorted(Comparator.comparingInt(AnalysisType::getId)).toList(); + writer.writeIntList("idDispatchTableIds", idDispatchTableKeys.stream().map(AnalysisType::getId).toList()); + for (AnalysisType aType : idDispatchTableKeys) { + var dispatchTable = currentDispatchTableMaps.get(aType).stream().map(AnalysisMethod::getId).sorted().toList(); + writer.writeIntList(getIdDispatchTableId(aType.getId()), dispatchTable); } return PersistFlags.CREATE; @@ -201,11 +555,43 @@ public PersistFlags preparePersist(ImageSingletonWriter writer) { public static Object createFromLoader(ImageSingletonLoader loader) { var info = new LayerTypeInfo(); info.maxTypeID = loader.readInt("maxTypeID"); - List identifierIDs = loader.readIntList("identifierIDs"); - for (var identifierID : identifierIDs) { - var previous = info.identifierToTypeInfo.put(identifierID, TypeInfo.fromIntList(loader.readIntList(Integer.toString(identifierID)))); + List typeIdentifierIds = loader.readIntList("typeIdentifierIds"); + for (var identifierID : typeIdentifierIds) { + Object previous = info.identifierToTypeInfo.put(identifierID, TypeInfo.fromIntList(loader.readIntList(getTypeInfoKey(identifierID)))); assert previous == null : previous; } + + List dispatchIdentifierIds = loader.readIntList("dispatchIdentifierIds"); + for (var identifierID : dispatchIdentifierIds) { + Object previous = info.identifierToDispatchInfo.put(identifierID, + DispatchInfo.fromLists(loader.readIntList(getDispatchInfoKey(identifierID)), loader.readStringList(getSymbolInfoKey(identifierID)))); + assert previous == null : previous; + } + + var symNameIterator = loader.readStringList("symNames").iterator(); + var vtableIdxIterator = loader.readIntList("vtableIdx").iterator(); + while (symNameIterator.hasNext()) { + String symName = symNameIterator.next(); + int vtableIndex = vtableIdxIterator.next(); + + var previous = info.symbolToVTableIdx.put(symName, vtableIndex); + assert previous == null : previous; + } + + List symDispatchTableIds = loader.readIntList("symDispatchTableIds"); + for (var identifierID : symDispatchTableIds) { + List dispatchTable = loader.readStringList(getSymDispatchTableId(identifierID)); + var previous = info.identifierToSymDispatchTable.put(identifierID, dispatchTable); + assert previous == null : previous; + } + + List idDispatchTableIds = loader.readIntList("idDispatchTableIds"); + for (var identifierID : idDispatchTableIds) { + List dispatchTable = loader.readIntList(getIdDispatchTableId(identifierID)); + var previous = info.identifierToIdDispatchTable.put(identifierID, dispatchTable); + assert previous == null : previous; + } + return info; } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SVMHost.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SVMHost.java index 24f3cce3cc9b..bbd4630f8523 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SVMHost.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SVMHost.java @@ -125,6 +125,7 @@ import com.oracle.svm.hosted.phases.InlineBeforeAnalysisGraphDecoderImpl; import com.oracle.svm.hosted.phases.InlineBeforeAnalysisPolicyImpl; import com.oracle.svm.hosted.phases.InlineBeforeAnalysisPolicyUtils; +import com.oracle.svm.hosted.phases.OpenTypeWorldConvertCallTargetPhase; import com.oracle.svm.hosted.substitute.AnnotationSubstitutionProcessor; import com.oracle.svm.hosted.substitute.AutomaticUnsafeTransformationSupport; import com.oracle.svm.hosted.util.IdentityHashCodeUtil; @@ -253,7 +254,7 @@ public boolean useBaseLayer() { @Override public boolean analyzedInPriorLayer(AnalysisMethod method) { ImageLayerLoader imageLayerLoader = HostedImageLayerBuildingSupport.singleton().getLoader(); - return imageLayerLoader.hasAnalysisParsedGraph(method) || HostedDynamicLayerInfo.singleton().isCompiled(method); + return imageLayerLoader.hasAnalysisParsedGraph(method) || HostedDynamicLayerInfo.singleton().compiledInPriorLayer(method); } protected InlineBeforeAnalysisPolicyUtils getInlineBeforeAnalysisPolicyUtils() { @@ -687,6 +688,10 @@ public void methodAfterParsingHook(BigBang bb, AnalysisMethod method, Structured } protected void optimizeAfterParsing(BigBang bb, AnalysisMethod method, StructuredGraph graph) { + if (!SubstrateOptions.closedTypeWorld()) { + new OpenTypeWorldConvertCallTargetPhase().apply(graph, getProviders(method.getMultiMethodKey())); + } + if (PointstoOptions.EscapeAnalysisBeforeAnalysis.getValue(bb.getOptions())) { if (method.isOriginalMethod()) { /* @@ -1065,4 +1070,9 @@ public boolean allowConstantFolding(AnalysisMethod method) { public SimulateClassInitializerSupport createSimulateClassInitializerSupport(AnalysisMetaAccess aMetaAccess) { return new SimulateClassInitializerSupport(aMetaAccess, this); } + + @Override + public Set loadOpenTypeWorldDispatchTableMethods(AnalysisType type) { + return OpenTypeWorldFeature.loadDispatchTable(type); + } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageLayerLoader.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageLayerLoader.java index 2bac473f2f5e..2ffeecc6f98d 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageLayerLoader.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageLayerLoader.java @@ -114,7 +114,7 @@ protected Annotation[] getAnnotations(EconomicMap elementData) { @Override protected void initializeBaseLayerMethod(AnalysisMethod analysisMethod, EconomicMap methodData) { - if (!HostedDynamicLayerInfo.singleton().isCompiled(analysisMethod) && hasAnalysisParsedGraph(analysisMethod)) { + if (!HostedDynamicLayerInfo.singleton().compiledInPriorLayer(analysisMethod) && hasAnalysisParsedGraph(analysisMethod)) { /* * GR-55294: When the analysis elements from the base layer will be able to be * materialized after the analysis, this will not be needed anymore. diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageLayerWriter.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageLayerWriter.java index 7dc1ee7f6327..b59cec23badc 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageLayerWriter.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageLayerWriter.java @@ -162,6 +162,23 @@ private static void handleNameConflict(String message) { } } + @Override + protected boolean shouldPersistMethod(AnalysisMethod method) { + if (super.shouldPersistMethod(method)) { + return true; + } + + /* + * If the method is present in a dispatch table of a persisted type, then it also should be + * persisted. + */ + AnalysisType type = method.getDeclaringClass(); + if (type.isReachable()) { + return type.getOpenTypeWorldDispatchTableMethods().contains(method); + } + return false; + } + @Override protected void persistField(AnalysisField field, EconomicMap fieldMap) { HostedField hostedField = hUniverse.lookup(field); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImage.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImage.java index a24f93076b70..24d1f2ebc3cb 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImage.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImage.java @@ -627,7 +627,7 @@ private void markFunctionRelocationSite(final ProgbitsSectionImpl sectionImpl, f MethodPointer methodPointer = (MethodPointer) info.getTargetObject(); ResolvedJavaMethod method = methodPointer.getMethod(); HostedMethod target = (method instanceof HostedMethod) ? (HostedMethod) method : heap.hUniverse.lookup(method); - if (!target.isCompiled() && !target.wrapped.isInBaseLayer()) { + if (!target.isCompiled() && !target.isCompiledInPriorLayer()) { target = metaAccess.lookupJavaMethod(InvalidMethodPointerHandler.METHOD_POINTER_NOT_COMPILED_HANDLER_METHOD); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/HostedDynamicLayerInfo.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/HostedDynamicLayerInfo.java index 7507fcdf1b2f..dc8cfd9d4d8c 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/HostedDynamicLayerInfo.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/HostedDynamicLayerInfo.java @@ -63,7 +63,7 @@ public class HostedDynamicLayerInfo extends DynamicImageLayerInfo implements LayeredImageSingleton { private final Map methodIdToOffsetMap; private final CGlobalData cGlobalData; - private Set priorLayerHostedMethods = new HashSet<>(); + private final Set priorLayerHostedMethods = new HashSet<>(); HostedDynamicLayerInfo() { this(0, null, new HashMap<>()); @@ -90,7 +90,7 @@ public PriorLayerMethodLocation getPriorLayerMethodLocation(SharedMethod sMethod return new PriorLayerMethodLocation(basePointer, offset); } - public boolean isCompiled(AnalysisMethod aMethod) { + public boolean compiledInPriorLayer(AnalysisMethod aMethod) { assert !BuildPhaseProvider.isCompileQueueFinished(); return methodIdToOffsetMap.containsKey(aMethod.getId()); } @@ -107,7 +107,7 @@ void registerOffset(HostedMethod method) { public void registerHostedMethod(HostedMethod hMethod) { assert !BuildPhaseProvider.isHostedUniverseBuilt(); AnalysisMethod aMethod = hMethod.getWrapped(); - if (isCompiled(aMethod)) { + if (compiledInPriorLayer(aMethod)) { assert aMethod.isInBaseLayer() : hMethod; priorLayerHostedMethods.add(hMethod); hMethod.setCompiledInPriorLayer(); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedType.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedType.java index 30effeff03d4..b0056849bc7b 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedType.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedType.java @@ -24,6 +24,8 @@ */ package com.oracle.svm.hosted.meta; +import java.util.Arrays; + import org.graalvm.word.WordBase; import com.oracle.graal.pointsto.infrastructure.OriginalClassProvider; @@ -32,8 +34,11 @@ import com.oracle.graal.pointsto.meta.AnalysisType; import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.hub.DynamicHub; +import com.oracle.svm.core.imagelayer.ImageLayerBuildingSupport; import com.oracle.svm.core.meta.SharedType; import com.oracle.svm.core.util.VMError; +import com.oracle.svm.hosted.OpenTypeWorldFeature; +import com.oracle.svm.hosted.image.NativeImage; import jdk.vm.ci.meta.Assumptions.AssumptionResult; import jdk.vm.ci.meta.JavaConstant; @@ -95,7 +100,14 @@ public abstract class HostedType extends HostedElement implements SharedType, Wr // region open-world only fields protected HostedType[] typeCheckInterfaceOrder; + /** + * Flattened array of all dispatch tables installed in the hub for this type. + */ protected HostedMethod[] openTypeWorldDispatchTables; + /** + * Used for debugging to ensure the dispatch call resolution is correct. + */ + protected HostedMethod[] openTypeWorldDispatchTableSlotTargets; protected int[] itableStartingOffsets; /** @@ -545,4 +557,13 @@ public ResolvedJavaType unwrapTowardsOriginalType() { public Class getJavaClass() { return OriginalClassProvider.getJavaClass(this); } + + public OpenTypeWorldFeature.DispatchInfo generateDispatchInfo() { + assert ImageLayerBuildingSupport.buildingImageLayer(); + + int[] interfaceOrder = Arrays.stream(typeCheckInterfaceOrder).mapToInt(t -> t.getWrapped().getId()).toArray(); + String[] dispatchTables = Arrays.stream(openTypeWorldDispatchTables).map(NativeImage::localSymbolNameForMethod).toArray(String[]::new); + return new OpenTypeWorldFeature.DispatchInfo(interfaceOrder, itableStartingOffsets, dispatchTables); + } + } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/VTableBuilder.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/VTableBuilder.java index d410a80790cd..a69bd4a0f75e 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/VTableBuilder.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/VTableBuilder.java @@ -25,13 +25,13 @@ package com.oracle.svm.hosted.meta; import java.util.ArrayList; -import java.util.Arrays; import java.util.BitSet; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Predicate; import org.graalvm.collections.Pair; @@ -39,6 +39,8 @@ import com.oracle.svm.core.InvalidMethodPointerHandler; import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.SubstrateUtil; +import com.oracle.svm.core.imagelayer.ImageLayerBuildingSupport; +import com.oracle.svm.hosted.OpenTypeWorldFeature; import jdk.graal.compiler.debug.Assertions; @@ -49,7 +51,6 @@ public final class VTableBuilder { private VTableBuilder(HostedUniverse hUniverse, HostedMetaAccess hMetaAccess) { this.hUniverse = hUniverse; this.hMetaAccess = hMetaAccess; - } public static void buildTables(HostedUniverse hUniverse, HostedMetaAccess hMetaAccess) { @@ -59,44 +60,52 @@ public static void buildTables(HostedUniverse hUniverse, HostedMetaAccess hMetaA } else { builder.buildOpenTypeWorldDispatchTables(); assert builder.verifyOpenTypeWorldDispatchTables(); + OpenTypeWorldFeature.persistDispatchInfo(hUniverse.getTypes()); + OpenTypeWorldFeature.persistMethodInfo(hUniverse.methods.values()); } } private boolean verifyOpenTypeWorldDispatchTables() { HostedMethod invalidVTableEntryHandler = hMetaAccess.lookupJavaMethod(InvalidMethodPointerHandler.INVALID_VTABLE_ENTRY_HANDLER_METHOD); for (HostedType type : hUniverse.getTypes()) { - if (type.isInterface() || type.isAbstract()) { - // don't need to check these types - they don't have/need real dynamic hubs + if (!type.isInstantiated()) { + /* + * Don't check uninstantiated types. They do not have their methods resolved. + */ continue; } - for (int i = 0; i < type.openTypeWorldDispatchTables.length; i++) { - HostedMethod method = type.openTypeWorldDispatchTables[i]; - if (method.equals(invalidVTableEntryHandler)) { - continue; + for (int i = 0; i < type.openTypeWorldDispatchTableSlotTargets.length; i++) { + HostedMethod slotMethod = type.openTypeWorldDispatchTableSlotTargets[i]; + + var resolvedMethod = (HostedMethod) type.resolveConcreteMethod(slotMethod, type); + if (resolvedMethod == null) { + resolvedMethod = invalidVTableEntryHandler; } + HostedMethod tableResolvedMethod = type.openTypeWorldDispatchTables[i]; + assert tableResolvedMethod.equals(resolvedMethod) : Assertions.errorMessage(type, slotMethod, tableResolvedMethod, resolvedMethod); // retrieve method from open world - if (method.getDeclaringClass().isInterface()) { - int interfaceTypeID = method.getDeclaringClass().getTypeID(); + if (slotMethod.getDeclaringClass().isInterface()) { + int interfaceTypeID = slotMethod.getDeclaringClass().getTypeID(); int[] typeCheckSlots = type.getOpenTypeWorldTypeCheckSlots(); boolean found = false; for (int itableIdx = 0; itableIdx < type.getNumInterfaceTypes(); itableIdx++) { if (typeCheckSlots[type.getNumClassTypes() + itableIdx] == interfaceTypeID) { - HostedMethod dispatchResult = type.openTypeWorldDispatchTables[type.itableStartingOffsets[itableIdx] + method.getVTableIndex()]; - assert dispatchResult.equals(method) : Assertions.errorMessage(method, dispatchResult); + HostedMethod dispatchResult = type.openTypeWorldDispatchTables[type.itableStartingOffsets[itableIdx] + slotMethod.getVTableIndex()]; + assert dispatchResult.equals(resolvedMethod) : Assertions.errorMessage(slotMethod, dispatchResult, resolvedMethod); found = true; break; } } - assert found : Assertions.errorMessage(method, type); + assert found : Assertions.errorMessage(slotMethod, type, resolvedMethod); } else { /* * The class vtable starts at position 0 within the openTypeWorldDispatchTables, * so it is unnecessary to check the itableStartingOffset. */ - HostedMethod openTypeWorldMethod = type.openTypeWorldDispatchTables[method.getVTableIndex()]; - assert openTypeWorldMethod.equals(method) : Assertions.errorMessage(method, openTypeWorldMethod); + HostedMethod openTypeWorldMethod = type.openTypeWorldDispatchTables[slotMethod.getVTableIndex()]; + assert openTypeWorldMethod.equals(resolvedMethod) : Assertions.errorMessage(slotMethod, openTypeWorldMethod, resolvedMethod); } } @@ -104,27 +113,31 @@ private boolean verifyOpenTypeWorldDispatchTables() { return true; } - private static List generateITable(HostedType type) { + private List generateITable(HostedType type) { return generateDispatchTable(type, 0); } - private static List generateDispatchTable(HostedType type, int startingIndex) { - var table = Arrays.stream(type.getAllDeclaredMethods()).filter(method -> { - if (method.wrapped.isInvoked() || method.wrapped.isImplementationInvoked()) { - if (method.implementations.length >= 1 || method.wrapped.isVirtualRootMethod()) { - return true; - } - } - return false; - }).toList(); + private List generateDispatchTable(HostedType type, int startingIndex) { + Predicate includeMethod; + if (ImageLayerBuildingSupport.buildingImageLayer()) { + // include all methods + includeMethod = m -> true; + } else { + // include only methods which will be indirect calls + includeMethod = m -> m.implementations.length > 1 || m.wrapped.isVirtualRootMethod(); + } + var table = type.getWrapped().getOpenTypeWorldDispatchTableMethods().stream().map(hUniverse::lookup).filter(includeMethod).sorted(HostedUniverse.METHOD_COMPARATOR).toList(); int index = startingIndex; - for (HostedMethod method : table) { - assert method.vtableIndex == -1 : method.vtableIndex; - method.vtableIndex = index; + for (HostedMethod typeMethod : table) { + assert typeMethod.getDeclaringClass().equals(type) : typeMethod; + assert typeMethod.vtableIndex == -1 : typeMethod.vtableIndex; + typeMethod.vtableIndex = index; index++; } + OpenTypeWorldFeature.persistDispatchTable(type, table); + return table; } @@ -160,23 +173,36 @@ private void generateOpenTypeWorldDispatchTable(HostedInstanceClass type, Map { + @Override + protected void run(StructuredGraph graph, CoreProviders context) { + for (MethodCallTargetNode callTarget : graph.getNodes(MethodCallTargetNode.TYPE).snapshot()) { + if (callTarget.invokeKind().isIndirect()) { + maybeConvertCallTarget(callTarget); + } + } + } + + public void maybeConvertCallTarget(MethodCallTargetNode target) { + if (target.targetMethod() instanceof AnalysisMethod aMethod) { + AnalysisMethod indirectTarget = aMethod.getIndirectCallTarget(); + if (!indirectTarget.equals(aMethod)) { + target.setTargetMethod(indirectTarget); + } + } + } +} From 6d9fe89b50084a68a9ef08bfaf8a7f8e5956e165 Mon Sep 17 00:00:00 2001 From: Tom Shull Date: Mon, 26 Aug 2024 12:52:18 +0200 Subject: [PATCH 2/3] temporarily disable force persisting methods within dispatch tables --- .../src/com/oracle/svm/hosted/SVMHost.java | 6 +++++- .../src/com/oracle/svm/hosted/heap/SVMImageLayerWriter.java | 4 ++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SVMHost.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SVMHost.java index bbd4630f8523..738795244514 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SVMHost.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SVMHost.java @@ -1073,6 +1073,10 @@ public SimulateClassInitializerSupport createSimulateClassInitializerSupport(Ana @Override public Set loadOpenTypeWorldDispatchTableMethods(AnalysisType type) { - return OpenTypeWorldFeature.loadDispatchTable(type); + /* + * Will be enabled as part of GR-57248 + */ + // return OpenTypeWorldFeature.loadDispatchTable(type); + return Set.of(); } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageLayerWriter.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageLayerWriter.java index b59cec23badc..e4c654067d75 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageLayerWriter.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageLayerWriter.java @@ -171,11 +171,15 @@ protected boolean shouldPersistMethod(AnalysisMethod method) { /* * If the method is present in a dispatch table of a persisted type, then it also should be * persisted. + * + * Will be enabled as part of GR-57248 */ + /*- AnalysisType type = method.getDeclaringClass(); if (type.isReachable()) { return type.getOpenTypeWorldDispatchTableMethods().contains(method); } + */ return false; } From 14d8f93c1b06ff253b0dbee58d45f15d3b658881 Mon Sep 17 00:00:00 2001 From: Tom Shull Date: Mon, 26 Aug 2024 15:18:45 +0200 Subject: [PATCH 3/3] ensure imagelayerbuildingsupport is always installed --- .../imagelayer/ImageLayerBuildingSupport.java | 15 --------------- .../svm/hosted/ImageSingletonsSupportImpl.java | 7 +++++++ 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/imagelayer/ImageLayerBuildingSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/imagelayer/ImageLayerBuildingSupport.java index 45fb0eb31eaa..d0b1001dddf6 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/imagelayer/ImageLayerBuildingSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/imagelayer/ImageLayerBuildingSupport.java @@ -112,41 +112,26 @@ public static boolean lastImageBuild() { @Fold public static boolean buildingImageLayer() { - if (!ImageSingletons.contains(ImageLayerBuildingSupport.class)) { - return false; - } return singleton().buildingImageLayer; } @Fold public static boolean buildingInitialLayer() { - if (!ImageSingletons.contains(ImageLayerBuildingSupport.class)) { - return true; - } return singleton().buildingInitialLayer; } @Fold public static boolean buildingApplicationLayer() { - if (!ImageSingletons.contains(ImageLayerBuildingSupport.class)) { - return false; - } return singleton().buildingApplicationLayer; } @Fold public static boolean buildingExtensionLayer() { - if (!ImageSingletons.contains(ImageLayerBuildingSupport.class)) { - return false; - } return singleton().buildingImageLayer && !singleton().buildingInitialLayer; } @Fold public static boolean buildingSharedLayer() { - if (!ImageSingletons.contains(ImageLayerBuildingSupport.class)) { - return false; - } return singleton().buildingImageLayer && !singleton().buildingApplicationLayer; } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ImageSingletonsSupportImpl.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ImageSingletonsSupportImpl.java index 06d0197976a7..4ad1ce4bcb19 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ImageSingletonsSupportImpl.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ImageSingletonsSupportImpl.java @@ -133,6 +133,13 @@ public static void install(HostedManagement vmConfig, HostedImageLayerBuildingSu * to prevent circular dependency complications. */ singletonDuringImageBuild.doAddInternal(ImageLayerBuildingSupport.class, support); + } else { + /* + * Create a placeholder ImageLayerBuilding support to indicate this is not a layered + * build. + */ + singletonDuringImageBuild.doAddInternal(ImageLayerBuildingSupport.class, new ImageLayerBuildingSupport(false, false, false) { + }); } if (support != null && support.getLoader() != null) { /*