diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/SubstrateAllocationSnippets.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/SubstrateAllocationSnippets.java index 9ff04f324eae..e43067f34dd7 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/SubstrateAllocationSnippets.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/SubstrateAllocationSnippets.java @@ -122,9 +122,10 @@ public class SubstrateAllocationSnippets extends AllocationSnippets { public static final LocationIdentity[] GC_LOCATIONS = new LocationIdentity[]{TLAB_TOP_IDENTITY, TLAB_END_IDENTITY, IdentityHashCodeSupport.IDENTITY_HASHCODE_SALT_LOCATION}; private static final SubstrateForeignCallDescriptor NEW_MULTI_ARRAY = SnippetRuntime.findForeignCall(SubstrateAllocationSnippets.class, "newMultiArrayStub", NO_SIDE_EFFECT); - private static final SubstrateForeignCallDescriptor INSTANCE_HUB_ERROR = SnippetRuntime.findForeignCall(SubstrateAllocationSnippets.class, "instanceHubErrorStub", NO_SIDE_EFFECT); + private static final SubstrateForeignCallDescriptor SLOW_PATH_HUB_OR_UNSAFE_INSTANTIATE_ERROR = SnippetRuntime.findForeignCall(SubstrateAllocationSnippets.class, + "slowPathHubOrUnsafeInstantiationError", NO_SIDE_EFFECT); private static final SubstrateForeignCallDescriptor ARRAY_HUB_ERROR = SnippetRuntime.findForeignCall(SubstrateAllocationSnippets.class, "arrayHubErrorStub", NO_SIDE_EFFECT); - private static final SubstrateForeignCallDescriptor[] FOREIGN_CALLS = new SubstrateForeignCallDescriptor[]{NEW_MULTI_ARRAY, INSTANCE_HUB_ERROR, ARRAY_HUB_ERROR}; + private static final SubstrateForeignCallDescriptor[] FOREIGN_CALLS = new SubstrateForeignCallDescriptor[]{NEW_MULTI_ARRAY, SLOW_PATH_HUB_OR_UNSAFE_INSTANTIATE_ERROR, ARRAY_HUB_ERROR}; public void registerForeignCalls(SubstrateForeignCallsProvider foreignCalls) { foreignCalls.register(FOREIGN_CALLS); @@ -272,12 +273,12 @@ protected Object newmultiarray(DynamicHub hub, @ConstantParameter int rank, @Con public DynamicHub validateNewInstanceClass(DynamicHub hub) { if (probability(EXTREMELY_FAST_PATH_PROBABILITY, hub != null)) { DynamicHub nonNullHub = (DynamicHub) PiNode.piCastNonNull(hub, SnippetAnchorNode.anchor()); - if (probability(EXTREMELY_FAST_PATH_PROBABILITY, nonNullHub.canUnsafeInstantiateAsInstance())) { + if (probability(EXTREMELY_FAST_PATH_PROBABILITY, nonNullHub.canUnsafeInstantiateAsInstanceFastPath())) { return nonNullHub; } } - callInstanceHubErrorWithExceptionStub(INSTANCE_HUB_ERROR, DynamicHub.toClass(hub)); - throw UnreachableNode.unreachable(); + DynamicHub slowPathStub = slowPathHubOrUnsafeInstantiationErrorStub(SLOW_PATH_HUB_OR_UNSAFE_INSTANTIATE_ERROR, DynamicHub.toClass(hub)); + return (DynamicHub) PiNode.piCastNonNull(slowPathStub, SnippetAnchorNode.anchor()); } /** Foreign call: {@link #NEW_MULTI_ARRAY}. */ @@ -308,25 +309,28 @@ private static Object newMultiArrayRecursion(DynamicHub hub, int rank, Word dime } @NodeIntrinsic(value = ForeignCallWithExceptionNode.class) - private static native void callInstanceHubErrorWithExceptionStub(@ConstantNodeParameter ForeignCallDescriptor descriptor, Class hub); + private static native DynamicHub slowPathHubOrUnsafeInstantiationErrorStub(@ConstantNodeParameter ForeignCallDescriptor descriptor, Class hub); - /** Foreign call: {@link #INSTANCE_HUB_ERROR}. */ + /** Foreign call: {@link #SLOW_PATH_HUB_OR_UNSAFE_INSTANTIATE_ERROR}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) - private static void instanceHubErrorStub(DynamicHub hub) throws InstantiationException { + private static DynamicHub slowPathHubOrUnsafeInstantiationError(DynamicHub hub) throws InstantiationException { if (hub == null) { throw new NullPointerException("Allocation type is null."); } else if (!hub.isInstanceClass() || LayoutEncoding.isSpecial(hub.getLayoutEncoding())) { - throw new InstantiationException("Can only allocate instance objects for concrete classes."); - } else if (!hub.canUnsafeInstantiateAsInstance()) { - if (MissingRegistrationUtils.throwMissingRegistrationErrors()) { - MissingReflectionRegistrationUtils.forClass(hub.getTypeName()); - } - throw new IllegalArgumentException("Type " + DynamicHub.toClass(hub).getTypeName() + " is instantiated reflectively but was never registered." + - " Register the type by adding \"unsafeAllocated\" for the type in " + ConfigurationFile.REFLECTION.getFileName() + "."); + throw new InstantiationException("Can only allocate instance objects for concrete classes: " + DynamicHub.toClass(hub).getTypeName()); } else if (LayoutEncoding.isHybrid(hub.getLayoutEncoding())) { - throw new InstantiationException("Cannot allocate objects of special hybrid types."); + throw new InstantiationException("Cannot allocate objects of special hybrid types: " + DynamicHub.toClass(hub).getTypeName()); } else { - throw VMError.shouldNotReachHereUnexpectedInput(hub); // ExcludeFromJacocoGeneratedReport + if (hub.canUnsafeInstantiateAsInstanceSlowPath()) { + hub.getCompanion().setUnsafeAllocate(); + return hub; + } else { + if (MissingRegistrationUtils.throwMissingRegistrationErrors()) { + MissingReflectionRegistrationUtils.forUnsafeAllocation(hub.getTypeName()); + } + throw new IllegalArgumentException("Type " + DynamicHub.toClass(hub).getTypeName() + " is instantiated reflectively but was never registered." + + " Register the type by adding \"unsafeAllocated\" for the type in " + ConfigurationFile.REFLECTION.getFileName() + "."); + } } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/ClassForNameSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/ClassForNameSupport.java index 18fd7e0f1cb0..84ca0c021e61 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/ClassForNameSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/ClassForNameSupport.java @@ -50,6 +50,8 @@ public static ClassForNameSupport singleton() { /** The map used to collect registered classes. */ private final EconomicMap> knownClasses = ImageHeapMap.create(); + /** The map used to collect unsafe allocated classes. */ + private final EconomicMap, RuntimeConditionSet> unsafeInstantiatedClasses = ImageHeapMap.create(); private static final Object NEGATIVE_QUERY = new Object(); @@ -138,6 +140,16 @@ public void registerNegativeQuery(ConfigurationCondition condition, String class updateCondition(condition, className, NEGATIVE_QUERY); } + @Platforms(Platform.HOSTED_ONLY.class) + public void registerUnsafeAllocated(ConfigurationCondition condition, Class clazz) { + if (!clazz.isArray()) { + var conditionSet = unsafeInstantiatedClasses.putIfAbsent(clazz, RuntimeConditionSet.createHosted(condition)); + if (conditionSet != null) { + conditionSet.addCondition(condition); + } + } + } + private void updateCondition(ConfigurationCondition condition, String className, Object value) { synchronized (knownClasses) { var runtimeConditions = knownClasses.putIfAbsent(className, new ConditionalRuntimeValue<>(RuntimeConditionSet.createHosted(condition), value)); @@ -213,4 +225,13 @@ public RuntimeConditionSet getConditionFor(Class jClass) { return conditionalClass.getConditions(); } } + + /** + * Checks whether {@code hub} can be instantiated with {@code Unsafe.allocateInstance}. Note + * that arrays can't be instantiated and this function will always return false for array types. + */ + public boolean canUnsafeInstantiateAsInstance(DynamicHub hub) { + var conditionSet = unsafeInstantiatedClasses.get(DynamicHub.toClass(hub)); + return conditionSet != null && conditionSet.satisfied(); + } } 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 4e3e3ecb6b2c..c8a604b3f0ce 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 @@ -312,10 +312,6 @@ public final class DynamicHub implements AnnotatedElement, java.lang.reflect.Typ /** Indicates whether the type has been discovered as instantiated by the static analysis. */ private static final int IS_INSTANTIATED_BIT = 0; - /** Can this class be instantiated as an instance. */ - private static final int CAN_UNSAFE_INSTANTIATE_AS_INSTANCE_BIT = 1; - - private static final int IS_REGISTERED_FOR_SERIALIZATION = 2; /** * The {@link Modifier modifiers} of this class. @@ -492,8 +488,7 @@ public void setClassInitializationInfo(ClassInitializationInfo classInitializati @Platforms(Platform.HOSTED_ONLY.class) public void setSharedData(int layoutEncoding, int monitorOffset, int identityHashOffset, long referenceMapIndex, - boolean isInstantiated, boolean canUnsafeInstantiateAsInstance) { - assert !(!isInstantiated && canUnsafeInstantiateAsInstance); + boolean isInstantiated) { VMError.guarantee(monitorOffset == (char) monitorOffset, "Class %s has an invalid monitor field offset. Most likely, its objects are larger than supported.", name); VMError.guarantee(identityHashOffset == (char) identityHashOffset, "Class %s has an invalid identity hash code field offset. Most likely, its objects are larger than supported.", name); @@ -505,8 +500,7 @@ public void setSharedData(int layoutEncoding, int monitorOffset, int identityHas throw VMError.shouldNotReachHere("Reference map index not within integer range, need to switch field from int to long"); } this.referenceMapIndex = (int) referenceMapIndex; - this.additionalFlags = NumUtil.safeToUByte(makeFlag(IS_INSTANTIATED_BIT, isInstantiated) | - makeFlag(CAN_UNSAFE_INSTANTIATE_AS_INSTANCE_BIT, canUnsafeInstantiateAsInstance)); + this.additionalFlags = NumUtil.safeToUByte(makeFlag(IS_INSTANTIATED_BIT, isInstantiated)); } @Platforms(Platform.HOSTED_ONLY.class) @@ -747,8 +741,17 @@ public boolean isInstantiated() { return isFlagSet(additionalFlags, IS_INSTANTIATED_BIT); } - public boolean canUnsafeInstantiateAsInstance() { - return isFlagSet(additionalFlags, CAN_UNSAFE_INSTANTIATE_AS_INSTANCE_BIT); + public boolean canUnsafeInstantiateAsInstanceFastPath() { + return companion.canUnsafeAllocate(); + } + + public boolean canUnsafeInstantiateAsInstanceSlowPath() { + if (ClassForNameSupport.singleton().canUnsafeInstantiateAsInstance(this)) { + companion.setUnsafeAllocate(); + return true; + } else { + return false; + } } public boolean isProxyClass() { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHubCompanion.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHubCompanion.java index ace302e3dc4e..d3748d52e5c6 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHubCompanion.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHubCompanion.java @@ -59,6 +59,7 @@ public final class DynamicHubCompanion { private Constructor cachedConstructor; private Class newInstanceCallerCache; private Object jfrEventConfiguration; + private boolean canUnsafeAllocate; @Platforms(Platform.HOSTED_ONLY.class) DynamicHubCompanion(Class hostedJavaClass, ClassLoader classLoader) { @@ -141,4 +142,12 @@ public void setJfrEventConfiguration(Object configuration) { public Object getJfrEventConfiguration() { return jfrEventConfiguration; } + + public boolean canUnsafeAllocate() { + return canUnsafeAllocate; + } + + public void setUnsafeAllocate() { + canUnsafeAllocate = true; + } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/MissingReflectionRegistrationUtils.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/MissingReflectionRegistrationUtils.java index 47337d528d4d..e12aebeb78e1 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/MissingReflectionRegistrationUtils.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/MissingReflectionRegistrationUtils.java @@ -49,6 +49,12 @@ public static void forClass(String className) { report(exception); } + public static void forUnsafeAllocation(String className) { + MissingReflectionRegistrationError exception = new MissingReflectionRegistrationError(errorMessage("unsafe instantiate class", className), + Class.class, null, className, null); + report(exception); + } + public static void forField(Class declaringClass, String fieldName) { MissingReflectionRegistrationError exception = new MissingReflectionRegistrationError(errorMessage("access field", declaringClass.getTypeName() + "#" + fieldName), diff --git a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/FieldsOffsetsFeature.java b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/FieldsOffsetsFeature.java index 2e105e1758a0..4178cd1c4b19 100644 --- a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/FieldsOffsetsFeature.java +++ b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/FieldsOffsetsFeature.java @@ -191,7 +191,7 @@ private static void registerFields(FieldIntrospection introspection, BeforeAn /* The partial evaluator allocates Node classes via Unsafe. */ AnalysisType nodeType = config.getMetaAccess().lookupJavaType(nodeClass.getJavaClass()); - nodeType.registerInstantiatedCallback(unused -> nodeType.registerAsUnsafeAllocated("Graal node class")); + nodeType.registerInstantiatedCallback(unused -> config.registerAsUnsafeAllocated(nodeType)); Fields dataFields = nodeClass.getData(); registerFields(dataFields, config, "Graal node data field"); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/FeatureImpl.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/FeatureImpl.java index aa212b3c819e..fbc6f9effcf2 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/FeatureImpl.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/FeatureImpl.java @@ -51,6 +51,7 @@ import org.graalvm.nativeimage.hosted.Feature.DuringAnalysisAccess; import org.graalvm.nativeimage.hosted.FieldValueTransformer; import org.graalvm.nativeimage.hosted.RuntimeReflection; +import org.graalvm.nativeimage.impl.ConfigurationCondition; import com.oracle.graal.pointsto.BigBang; import com.oracle.graal.pointsto.ObjectScanner; @@ -69,6 +70,7 @@ import com.oracle.svm.core.annotate.Delete; import com.oracle.svm.core.feature.InternalFeature; import com.oracle.svm.core.graal.meta.RuntimeConfiguration; +import com.oracle.svm.core.hub.ClassForNameSupport; import com.oracle.svm.core.meta.SharedField; import com.oracle.svm.core.meta.SharedMethod; import com.oracle.svm.core.meta.SharedType; @@ -349,12 +351,15 @@ public static class BeforeAnalysisAccessImpl extends AnalysisAccessBase implemen private final NativeLibraries nativeLibraries; private final boolean concurrentReachabilityHandlers; private final ReachabilityHandler reachabilityHandler; + private ClassForNameSupport classForNameSupport; - public BeforeAnalysisAccessImpl(FeatureHandler featureHandler, ImageClassLoader imageClassLoader, Inflation bb, NativeLibraries nativeLibraries, DebugContext debugContext) { + public BeforeAnalysisAccessImpl(FeatureHandler featureHandler, ImageClassLoader imageClassLoader, Inflation bb, NativeLibraries nativeLibraries, + DebugContext debugContext) { super(featureHandler, imageClassLoader, bb, debugContext); this.nativeLibraries = nativeLibraries; this.concurrentReachabilityHandlers = SubstrateOptions.RunReachabilityHandlersConcurrently.getValue(bb.getOptions()); this.reachabilityHandler = concurrentReachabilityHandlers ? ConcurrentReachabilityHandler.singleton() : ReachabilityHandlerFeature.singleton(); + this.classForNameSupport = ClassForNameSupport.singleton(); } public NativeLibraries getNativeLibraries() { @@ -397,6 +402,7 @@ public void registerAsUnsafeAllocated(AnalysisType aType) { throw UserError.abort("Cannot register an abstract class as instantiated: " + aType.toJavaName(true)); } aType.registerAsUnsafeAllocated("From feature"); + classForNameSupport.registerUnsafeAllocated(ConfigurationCondition.alwaysTrue(), aType.getJavaClass()); } @Override 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 0733c9b4fee6..600ce9024967 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 @@ -47,7 +47,6 @@ import java.util.function.BiPredicate; import java.util.function.Function; -import com.oracle.svm.core.interpreter.InterpreterSupport; import org.graalvm.nativeimage.AnnotationAccess; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; @@ -93,6 +92,7 @@ import com.oracle.svm.core.hub.PredefinedClassesSupport; import com.oracle.svm.core.hub.ReferenceType; import com.oracle.svm.core.imagelayer.ImageLayerBuildingSupport; +import com.oracle.svm.core.interpreter.InterpreterSupport; import com.oracle.svm.core.jdk.InternalVMMethod; import com.oracle.svm.core.jdk.LambdaFormHiddenMethod; import com.oracle.svm.core.option.HostedOptionKey; @@ -360,7 +360,8 @@ public void onTypeInstantiated(BigBang bb, AnalysisType type) { if (optionAllowUnsafeAllocationOfAllInstantiatedTypes != null) { if (optionAllowUnsafeAllocationOfAllInstantiatedTypes) { - type.registerAsUnsafeAllocated("All types are registered as Unsafe allocated via option AllowUnsafeAllocationOfAllInstantiatedTypes"); + type.registerAsUnsafeAllocated("All types are registered as Unsafe allocated via option -H:+AllowUnsafeAllocationOfAllInstantiatedTypes"); + typeToHub.get(type).getCompanion().setUnsafeAllocate(); } else { /* * No default registration for unsafe allocation, setting the explicit option has @@ -369,6 +370,7 @@ public void onTypeInstantiated(BigBang bb, AnalysisType type) { } } else if (!missingRegistrationSupport.reportMissingRegistrationErrors(type.getJavaClass())) { type.registerAsUnsafeAllocated("Type is not listed as ThrowMissingRegistrationError and therefore registered as Unsafe allocated automatically for compatibility reasons"); + typeToHub.get(type).getCompanion().setUnsafeAllocate(); } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/UniverseBuilder.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/UniverseBuilder.java index 39d79568a7ae..25c10e49d801 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/UniverseBuilder.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/UniverseBuilder.java @@ -865,7 +865,6 @@ private void buildHubs() { hUniverse.hostVM().recordActivity(); int layoutHelper; - boolean canUnsafeInstantiateAsInstance = false; int monitorOffset = 0; int identityHashOffset = 0; if (type.isInstanceClass()) { @@ -879,10 +878,8 @@ private void buildHubs() { JavaKind storageKind = hybridLayout.getArrayElementStorageKind(); boolean isObject = (storageKind == JavaKind.Object); layoutHelper = LayoutEncoding.forHybrid(type, isObject, hybridLayout.getArrayBaseOffset(), ol.getArrayIndexShift(storageKind)); - canUnsafeInstantiateAsInstance = type.wrapped.isUnsafeAllocated() && HybridLayout.canInstantiateAsInstance(type); } else { layoutHelper = LayoutEncoding.forPureInstance(type, ConfigurationValues.getObjectLayout().alignUp(instanceClass.getInstanceSize())); - canUnsafeInstantiateAsInstance = type.wrapped.isUnsafeAllocated(); } monitorOffset = instanceClass.getMonitorFieldOffset(); identityHashOffset = instanceClass.getIdentityHashOffset(); @@ -909,7 +906,7 @@ private void buildHubs() { DynamicHub hub = type.getHub(); hub.setSharedData(layoutHelper, monitorOffset, identityHashOffset, - referenceMapIndex, type.isInstantiated(), canUnsafeInstantiateAsInstance); + referenceMapIndex, type.isInstantiated()); if (SubstrateOptions.closedTypeWorld()) { CFunctionPointer[] vtable = new CFunctionPointer[type.closedTypeWorldVTable.length]; diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java index c9af40e0aac1..194fa641c528 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java @@ -108,7 +108,7 @@ public class ReflectionDataBuilder extends ConditionalConfigurationRegistry impl private AnalysisUniverse universe; private final SubstrateAnnotationExtractor annotationExtractor; private BeforeAnalysisAccessImpl analysisAccess; - private ClassForNameSupport classForNameSupport; + private final ClassForNameSupport classForNameSupport; private boolean sealed; @@ -256,7 +256,8 @@ private void registerClass(ConfigurationCondition condition, Class clazz, boo AnalysisType type = metaAccess.lookupJavaType(clazz); type.registerAsReachable("Is registered for reflection."); if (unsafeInstantiated) { - type.registerAsUnsafeAllocated("Is registered for reflection."); + type.registerAsUnsafeAllocated("Is registered via reflection metadata."); + classForNameSupport.registerUnsafeAllocated(condition, clazz); } if (allowForName) {