diff --git a/substratevm/src/com.oracle.graal.pointsto.standalone/src/com/oracle/graal/pointsto/standalone/StandaloneHost.java b/substratevm/src/com.oracle.graal.pointsto.standalone/src/com/oracle/graal/pointsto/standalone/StandaloneHost.java index 26ac64d8a6a1..cf0e0990c0dd 100644 --- a/substratevm/src/com.oracle.graal.pointsto.standalone/src/com/oracle/graal/pointsto/standalone/StandaloneHost.java +++ b/substratevm/src/com.oracle.graal.pointsto.standalone/src/com/oracle/graal/pointsto/standalone/StandaloneHost.java @@ -29,18 +29,18 @@ import java.util.Comparator; import java.util.concurrent.ConcurrentHashMap; -import jdk.graal.compiler.java.GraphBuilderPhase; -import jdk.graal.compiler.nodes.graphbuilderconf.GraphBuilderConfiguration; -import jdk.graal.compiler.nodes.graphbuilderconf.IntrinsicContext; -import jdk.graal.compiler.options.OptionValues; -import jdk.graal.compiler.phases.OptimisticOptimizations; - +import com.oracle.graal.pointsto.BigBang; import com.oracle.graal.pointsto.api.HostVM; import com.oracle.graal.pointsto.meta.AnalysisType; import com.oracle.graal.pointsto.meta.HostedProviders; import com.oracle.graal.pointsto.standalone.plugins.StandaloneGraphBuilderPhase; import com.oracle.graal.pointsto.util.AnalysisError; +import jdk.graal.compiler.java.GraphBuilderPhase; +import jdk.graal.compiler.nodes.graphbuilderconf.GraphBuilderConfiguration; +import jdk.graal.compiler.nodes.graphbuilderconf.IntrinsicContext; +import jdk.graal.compiler.options.OptionValues; +import jdk.graal.compiler.phases.OptimisticOptimizations; import jdk.vm.ci.meta.ResolvedJavaType; public class StandaloneHost extends HostVM { @@ -72,7 +72,7 @@ public boolean isInitialized(AnalysisType type) { } @Override - public void onTypeReachable(AnalysisType type) { + public void onTypeReachable(BigBang bb, AnalysisType type) { if (!type.isReachable()) { AnalysisError.shouldNotReachHere("Registering and initializing a type that was not yet marked as reachable: " + type); } 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 4ed6438b42bc..9ec95cd96b31 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 @@ -41,11 +41,9 @@ import com.oracle.graal.pointsto.BigBang; import com.oracle.graal.pointsto.PointsToAnalysis; import com.oracle.graal.pointsto.flow.InvokeTypeFlow; -import com.oracle.graal.pointsto.meta.AnalysisField; import com.oracle.graal.pointsto.meta.AnalysisMethod; import com.oracle.graal.pointsto.meta.AnalysisType; import com.oracle.graal.pointsto.meta.AnalysisUniverse; -import com.oracle.graal.pointsto.meta.FieldValueComputer; import com.oracle.graal.pointsto.meta.HostedProviders; import com.oracle.graal.pointsto.phases.InlineBeforeAnalysisGraphDecoder; import com.oracle.graal.pointsto.phases.InlineBeforeAnalysisPolicy; @@ -155,7 +153,7 @@ public void checkType(ResolvedJavaType type, AnalysisUniverse universe) { * * @param newValue the type to initialize */ - public abstract void onTypeReachable(AnalysisType newValue); + public abstract void onTypeReachable(BigBang bb, AnalysisType newValue); /** * Check if an {@link AnalysisType} is initialized. @@ -420,8 +418,4 @@ public boolean allowConstantFolding(AnalysisMethod method) { */ return method.isOriginalMethod(); } - - public FieldValueComputer createFieldValueComputer(@SuppressWarnings("unused") AnalysisField field) { - return null; - } } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageHeapScanner.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageHeapScanner.java index 5ed293358b20..fa757545e9ac 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageHeapScanner.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageHeapScanner.java @@ -545,7 +545,7 @@ private void updateInstanceField(AnalysisField field, ImageHeapInstance imageHea } public boolean isValueAvailable(AnalysisField field) { - return field.isValueAvailable(); + return true; } protected String formatReason(String message, ScanReason reason) { diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/value/LazyValueSupplier.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/value/LazyValueSupplier.java index b39cea55ef7b..2c7bb89e498d 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/value/LazyValueSupplier.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/value/LazyValueSupplier.java @@ -24,6 +24,7 @@ */ package com.oracle.graal.pointsto.heap.value; +import java.util.Objects; import java.util.function.BooleanSupplier; import java.util.function.Supplier; @@ -47,6 +48,6 @@ public boolean isAvailable() { @Override public V get() { AnalysisError.guarantee(isAvailable(), "Value is not yet available."); - return valueSupplier.get(); + return Objects.requireNonNull(valueSupplier.get()); } } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/value/ValueSupplier.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/value/ValueSupplier.java index febb337f8be7..181b9e691b74 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/value/ValueSupplier.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/value/ValueSupplier.java @@ -24,6 +24,7 @@ */ package com.oracle.graal.pointsto.heap.value; +import java.util.Objects; import java.util.function.BooleanSupplier; import java.util.function.Supplier; @@ -42,7 +43,7 @@ public interface ValueSupplier { static ValueSupplier eagerValue(V value) { - return new EagerValueSupplier<>(value); + return new EagerValueSupplier<>(Objects.requireNonNull(value)); } static ValueSupplier lazyValue(Supplier valueSupplier, BooleanSupplier isAvailable) { diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/infrastructure/OriginalClassProvider.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/infrastructure/OriginalClassProvider.java index b8caed018e82..950061247e93 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/infrastructure/OriginalClassProvider.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/infrastructure/OriginalClassProvider.java @@ -28,16 +28,21 @@ import com.oracle.graal.pointsto.util.GraalAccess; +import jdk.vm.ci.meta.JavaType; import jdk.vm.ci.meta.ResolvedJavaType; public interface OriginalClassProvider { - static Class getJavaClass(ResolvedJavaType javaType) { + static Class getJavaClass(JavaType javaType) { Class result; if (javaType instanceof OriginalClassProvider) { result = ((OriginalClassProvider) javaType).getJavaClass(); } else { - result = GraalAccess.getOriginalSnippetReflection().originalClass(javaType); + /* + * The static analysis and the image generator never use unresolved types. The JavaType + * in the method signature is just to avoid casts in the callers. + */ + result = GraalAccess.getOriginalSnippetReflection().originalClass((ResolvedJavaType) javaType); } /* diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/infrastructure/OriginalFieldProvider.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/infrastructure/OriginalFieldProvider.java index 646843e5f87e..e88434b5c340 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/infrastructure/OriginalFieldProvider.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/infrastructure/OriginalFieldProvider.java @@ -41,21 +41,28 @@ */ public interface OriginalFieldProvider { + static ResolvedJavaField getOriginalField(ResolvedJavaField field) { + ResolvedJavaField cur = field; + while (cur instanceof OriginalFieldProvider originalFieldProvider) { + cur = originalFieldProvider.unwrapTowardsOriginalField(); + } + return cur; + } + static Field getJavaField(ResolvedJavaField field) { - if (field instanceof OriginalFieldProvider) { - return ((OriginalFieldProvider) field).getJavaField(); - } else { + ResolvedJavaField originalField = getOriginalField(field); + if (originalField != null) { try { - return GraalAccess.getOriginalSnippetReflection().originalField(field); + return GraalAccess.getOriginalSnippetReflection().originalField(originalField); } catch (LinkageError ignored) { /* * Ignore any linking problems and incompatible class change errors. Looking up a * reflective representation of a JVMCI field is always a best effort operation. */ - return null; } } + return null; } - Field getJavaField(); + ResolvedJavaField unwrapTowardsOriginalField(); } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisField.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisField.java index b78da63d0585..453fd6234529 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisField.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisField.java @@ -24,16 +24,14 @@ */ package com.oracle.graal.pointsto.meta; -import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.ArrayList; +import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; -import jdk.graal.compiler.debug.GraalError; - import com.oracle.graal.pointsto.api.DefaultUnsafePartition; import com.oracle.graal.pointsto.api.HostVM; import com.oracle.graal.pointsto.api.PointstoOptions; @@ -42,10 +40,12 @@ import com.oracle.graal.pointsto.infrastructure.OriginalFieldProvider; import com.oracle.graal.pointsto.infrastructure.WrappedJavaField; import com.oracle.graal.pointsto.typestate.TypeState; +import com.oracle.graal.pointsto.util.AnalysisFuture; import com.oracle.graal.pointsto.util.AtomicUtils; import com.oracle.graal.pointsto.util.ConcurrentLightHashSet; import com.oracle.svm.util.UnsafePartitionKind; +import jdk.graal.compiler.debug.GraalError; import jdk.vm.ci.code.BytecodePosition; import jdk.vm.ci.meta.JavaConstant; import jdk.vm.ci.meta.JavaKind; @@ -123,9 +123,10 @@ public abstract class AnalysisField extends AnalysisElement implements WrappedJa /** * Marks a field whose value is computed during image building, in general derived from other - * values, and it cannot be constant-folded or otherwise optimized. + * values. The actual meaning of the field value is out of scope of the static analysis, but the + * value is stored here to allow fast access. */ - protected final FieldValueComputer fieldValueComputer; + protected Object fieldValueInterceptor; @SuppressWarnings("this-escape") public AnalysisField(AnalysisUniverse universe, ResolvedJavaField wrappedField) { @@ -152,8 +153,6 @@ public AnalysisField(AnalysisUniverse universe, ResolvedJavaField wrappedField) this.instanceFieldFlow = new ContextInsensitiveFieldTypeFlow(this, getType()); this.initialInstanceFieldFlow = new FieldTypeFlow(this, getType()); } - - fieldValueComputer = universe.hostVM().createFieldValueComputer(this); } @Override @@ -260,6 +259,8 @@ public void cleanupAfterAnalysis() { } public boolean registerAsAccessed(Object reason) { + getDeclaringClass().registerAsReachable(this); + assert isValidReason(reason) : "Registering a field as accessed needs to provide a valid reason."; boolean firstAttempt = AtomicUtils.atomicSet(this, reason, isAccessedUpdater); notifyUpdateAccessInfo(); @@ -275,6 +276,8 @@ public boolean registerAsAccessed(Object reason) { * @param reason the reason why this field is read, non-null */ public boolean registerAsRead(Object reason) { + getDeclaringClass().registerAsReachable(this); + assert isValidReason(reason) : "Registering a field as read needs to provide a valid reason."; boolean firstAttempt = AtomicUtils.atomicSet(this, reason, isReadUpdater); notifyUpdateAccessInfo(); @@ -295,6 +298,8 @@ public boolean registerAsRead(Object reason) { * @param reason the reason why this field is written, non-null */ public boolean registerAsWritten(Object reason) { + getDeclaringClass().registerAsReachable(this); + assert isValidReason(reason) : "Registering a field as written needs to provide a valid reason."; boolean firstAttempt = AtomicUtils.atomicSet(this, reason, isWrittenUpdater); notifyUpdateAccessInfo(); @@ -311,6 +316,8 @@ public boolean registerAsWritten(Object reason) { } public void registerAsFolded(Object reason) { + getDeclaringClass().registerAsReachable(this); + assert isValidReason(reason) : "Registering a field as folded needs to provide a valid reason."; if (AtomicUtils.atomicSet(this, reason, isFoldedUpdater)) { assert getDeclaringClass().isReachable() : this; @@ -442,23 +449,12 @@ public void onReachable() { notifyReachabilityCallbacks(declaringClass.getUniverse(), new ArrayList<>()); } - public boolean isValueAvailable() { - if (fieldValueComputer != null) { - return fieldValueComputer.isAvailable(); - } - return true; - } - - public boolean isComputedValue() { - return fieldValueComputer != null; - } - - public Class[] computedValueTypes() { - return fieldValueComputer.types(); + public Object getFieldValueInterceptor() { + return fieldValueInterceptor; } - public boolean computedValueCanBeNull() { - return fieldValueComputer.canBeNull(); + public void setFieldValueInterceptor(Object fieldValueInterceptor) { + this.fieldValueInterceptor = fieldValueInterceptor; } public void setCanBeNull(boolean canBeNull) { @@ -531,8 +527,8 @@ public String toString() { } @Override - public Field getJavaField() { - return OriginalFieldProvider.getJavaField(wrapped); + public ResolvedJavaField unwrapTowardsOriginalField() { + return wrapped; } @Override @@ -540,6 +536,25 @@ public JavaConstant getConstantValue() { return getUniverse().lookup(getWrapped().getConstantValue()); } + /** + * Ensure that all reachability handler that were present at the time the declaring type was + * marked as reachable are executed before accessing field values. This allows field value + * transformer to be installed reliably in reachability handler. + */ + public void beforeFieldValueAccess() { + declaringClass.registerAsReachable(this); + declaringClass.ensureOnTypeReachableTaskDone(); + + List> notifications = declaringClass.scheduledTypeReachableNotifications; + if (notifications != null) { + for (var notification : notifications) { + notification.ensureDone(); + } + /* Now we know all the handlers have been executed, no checks are necessary anymore. */ + declaringClass.scheduledTypeReachableNotifications = null; + } + } + public void addAnalysisFieldObserver(AnalysisFieldObserver observer) { ConcurrentLightHashSet.addElement(this, OBSERVERS_UPDATER, observer); } 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 d7f368549859..1a378ff0ca68 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 @@ -681,13 +681,18 @@ private static void forAllSuperTypes(AnalysisType elementType, int arrayDimensio if (elementType == null) { return; } - if (processType) { - superTypeConsumer.accept(elementType.getArrayClass(arrayDimension)); - } for (AnalysisType interf : elementType.getInterfaces()) { forAllSuperTypes(interf, arrayDimension, true, superTypeConsumer); } forAllSuperTypes(elementType.getSuperclass(), arrayDimension, true, superTypeConsumer); + /* + * Process the type itself only after visiting all supertypes. This ensures that, e.g., a + * type is never seen as reachable by another thread before all of its supertypes are + * already marked as reachable too. + */ + if (processType) { + superTypeConsumer.accept(elementType.getArrayClass(arrayDimension)); + } } protected synchronized void addAssignableType(BigBang bb, TypeState typeState) { diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisUniverse.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisUniverse.java index 4623d41da59a..285b97891da2 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisUniverse.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisUniverse.java @@ -57,7 +57,6 @@ import com.oracle.graal.pointsto.infrastructure.WrappedJavaType; import com.oracle.graal.pointsto.meta.AnalysisType.UsageKind; import com.oracle.graal.pointsto.util.AnalysisError; -import com.oracle.graal.pointsto.util.AnalysisFuture; import com.oracle.graal.pointsto.util.GraalAccess; import jdk.graal.compiler.api.replacements.SnippetReflectionProvider; @@ -357,43 +356,6 @@ public JavaField lookupAllowUnresolved(JavaField rawField) { ResolvedJavaField field = (ResolvedJavaField) rawField; - if (!sealed) { - /* - * Trigger computation of automatic substitutions. There might be an automatic - * substitution for the current field and we want to register it before the analysis - * field is created. This also ensures that the class is initialized (if the class is - * registered for initialization at build time) before any constant folding of static - * fields is attempted. Calling ensureInitialized() here at field lookup avoids calling - * it during constant folding. - */ - AnalysisType declaringType = lookup(field.getDeclaringClass()); - declaringType.registerAsReachable(field); - declaringType.ensureOnTypeReachableTaskDone(); - - /* - * Ensure that all reachability handler that were present at the time the type was - * marked as reachable are executed before creating the field. This allows field value - * transformer to be installed reliably in reachability handler. - * - * This is necessary because field value transformer are currently implemented via - * ComputedValueField that are injected into the substitution universe. A - * ComputedValueField added after the AnalysisField is created would be ignored. In the - * future, we want a better implementation of field value transformer that do not rely - * on the substitution universe, then this code can be removed. - */ - List> notifications = declaringType.scheduledTypeReachableNotifications; - if (notifications != null) { - for (var notification : notifications) { - notification.ensureDone(); - } - /* - * Now we know all the handlers have been executed, so subsequent field lookups do - * not need to check anymore. - */ - declaringType.scheduledTypeReachableNotifications = null; - } - } - field = substitutions.lookup(field); AnalysisField result = fields.get(field); if (result == null) { @@ -732,7 +694,7 @@ public void onTypeInstantiated(AnalysisType type, UsageKind usage) { } public void onTypeReachable(AnalysisType type) { - hostVM.onTypeReachable(type); + hostVM.onTypeReachable(bb, type); if (bb != null) { bb.onTypeReachable(type); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/ParsingReason.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/ParsingReason.java index 0d01c3d11de8..5a77ab3f3205 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/ParsingReason.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/ParsingReason.java @@ -30,7 +30,7 @@ public enum ParsingReason { AOTCompilation, JITCompilation, EarlyClassInitializerAnalysis, - UnsafeSubstitutionAnalysis; + AutomaticUnsafeTransformation; public boolean isForHosted() { return this != JITCompilation; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/fieldvaluetransformer/ArrayBaseOffsetFieldValueTransformer.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/fieldvaluetransformer/ArrayBaseOffsetFieldValueTransformer.java new file mode 100644 index 000000000000..6438f1436e0a --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/fieldvaluetransformer/ArrayBaseOffsetFieldValueTransformer.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.fieldvaluetransformer; + +import org.graalvm.nativeimage.hosted.FieldValueTransformer; + +import com.oracle.svm.core.config.ConfigurationValues; + +import jdk.vm.ci.meta.JavaKind; + +public final class ArrayBaseOffsetFieldValueTransformer extends BoxingTransformer implements FieldValueTransformer { + private final Class targetClass; + + public ArrayBaseOffsetFieldValueTransformer(Class targetClass, Class returnType) { + super(returnType); + this.targetClass = targetClass; + } + + @Override + public Object transform(Object receiver, Object originalValue) { + return box(ConfigurationValues.getObjectLayout().getArrayBaseOffset(JavaKind.fromJavaClass(targetClass.getComponentType()))); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/fieldvaluetransformer/ArrayIndexScaleFieldValueTransformer.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/fieldvaluetransformer/ArrayIndexScaleFieldValueTransformer.java new file mode 100644 index 000000000000..265a1cc62f65 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/fieldvaluetransformer/ArrayIndexScaleFieldValueTransformer.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.fieldvaluetransformer; + +import org.graalvm.nativeimage.hosted.FieldValueTransformer; + +import com.oracle.svm.core.config.ConfigurationValues; + +import jdk.vm.ci.meta.JavaKind; + +public final class ArrayIndexScaleFieldValueTransformer extends BoxingTransformer implements FieldValueTransformer { + private final Class targetClass; + + public ArrayIndexScaleFieldValueTransformer(Class targetClass, Class returnType) { + super(returnType); + this.targetClass = targetClass; + } + + @Override + public Object transform(Object receiver, Object originalValue) { + return box(ConfigurationValues.getObjectLayout().getArrayIndexScale(JavaKind.fromJavaClass(targetClass.getComponentType()))); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/fieldvaluetransformer/ArrayIndexShiftFieldValueTransformer.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/fieldvaluetransformer/ArrayIndexShiftFieldValueTransformer.java new file mode 100644 index 000000000000..78b9da2d9bcd --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/fieldvaluetransformer/ArrayIndexShiftFieldValueTransformer.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.fieldvaluetransformer; + +import org.graalvm.nativeimage.hosted.FieldValueTransformer; + +import com.oracle.svm.core.config.ConfigurationValues; + +import jdk.vm.ci.meta.JavaKind; + +public final class ArrayIndexShiftFieldValueTransformer extends BoxingTransformer implements FieldValueTransformer { + private final Class targetClass; + + public ArrayIndexShiftFieldValueTransformer(Class targetClass, Class returnType) { + super(returnType); + this.targetClass = targetClass; + } + + @Override + public Object transform(Object receiver, Object originalValue) { + return box(ConfigurationValues.getObjectLayout().getArrayIndexShift(JavaKind.fromJavaClass(targetClass.getComponentType()))); + } +} diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/FieldValueComputer.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/fieldvaluetransformer/BoxingTransformer.java similarity index 66% rename from substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/FieldValueComputer.java rename to substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/fieldvaluetransformer/BoxingTransformer.java index 39be8a4041ad..c64177c8f903 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/FieldValueComputer.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/fieldvaluetransformer/BoxingTransformer.java @@ -22,25 +22,24 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ -package com.oracle.graal.pointsto.meta; +package com.oracle.svm.core.fieldvaluetransformer; -import org.graalvm.nativeimage.Platform; -import org.graalvm.nativeimage.Platforms; +import com.oracle.svm.core.util.VMError; -@Platforms(Platform.HOSTED_ONLY.class) -public interface FieldValueComputer { +abstract class BoxingTransformer { + private final Class returnType; - Class[] EMPTY_TYPES = new Class[0]; - - default boolean isAvailable() { - return true; - } - - default Class[] types() { - return EMPTY_TYPES; + BoxingTransformer(Class returnType) { + this.returnType = returnType; } - default boolean canBeNull() { - return false; + Object box(int value) { + if (returnType == int.class || returnType == Integer.class) { + return Integer.valueOf(value); + } else if (returnType == long.class || returnType == Long.class) { + return Long.valueOf(value); + } else { + throw VMError.shouldNotReachHere("Unexpected type: " + returnType); + } } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/fieldvaluetransformer/FieldOffsetFieldValueTransformer.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/fieldvaluetransformer/FieldOffsetFieldValueTransformer.java new file mode 100644 index 000000000000..bb53ef42e85c --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/fieldvaluetransformer/FieldOffsetFieldValueTransformer.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.fieldvaluetransformer; + +import java.lang.reflect.Field; + +import com.oracle.svm.core.reflect.target.ReflectionSubstitutionSupport; +import com.oracle.svm.core.util.VMError; + +public final class FieldOffsetFieldValueTransformer extends BoxingTransformer implements FieldValueTransformerWithAvailability { + private final Field targetField; + + public FieldOffsetFieldValueTransformer(Field targetField, Class returnType) { + super(returnType); + this.targetField = targetField; + } + + @Override + public ValueAvailability valueAvailability() { + return ValueAvailability.AfterAnalysis; + } + + @Override + public Object transform(Object receiver, Object originalValue) { + int offset = ReflectionSubstitutionSupport.singleton().getFieldOffset(targetField, true); + if (offset <= 0) { + throw VMError.shouldNotReachHere("Field is not marked as unsafe accessed: " + targetField); + } + return box(offset); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/fieldvaluetransformer/StaticFieldBaseFieldValueTransformer.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/fieldvaluetransformer/StaticFieldBaseFieldValueTransformer.java new file mode 100644 index 000000000000..515a3b50db13 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/fieldvaluetransformer/StaticFieldBaseFieldValueTransformer.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.fieldvaluetransformer; + +import java.lang.reflect.Field; + +import com.oracle.svm.core.StaticFieldsSupport; + +public final class StaticFieldBaseFieldValueTransformer implements FieldValueTransformerWithAvailability { + private final Field targetField; + + public StaticFieldBaseFieldValueTransformer(Field targetField) { + this.targetField = targetField; + } + + @Override + public ValueAvailability valueAvailability() { + return ValueAvailability.AfterAnalysis; + } + + @Override + public Object transform(Object receiver, Object originalValue) { + return targetField.getType().isPrimitive() ? StaticFieldsSupport.getStaticPrimitiveFields() : StaticFieldsSupport.getStaticObjectFields(); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHubSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHubSupport.java index 71befa3164c8..8261e019ec48 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHubSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHubSupport.java @@ -34,11 +34,12 @@ import com.oracle.svm.core.c.NonmovableArrays; import com.oracle.svm.core.feature.AutomaticallyRegisteredImageSingleton; import com.oracle.svm.core.heap.UnknownObjectField; +import com.oracle.svm.core.heap.UnknownPrimitiveField; @AutomaticallyRegisteredImageSingleton public final class DynamicHubSupport { - @UnknownObjectField(availability = AfterHostedUniverse.class) private int maxTypeId; + @UnknownPrimitiveField(availability = AfterHostedUniverse.class) private int maxTypeId; @UnknownObjectField(availability = AfterHostedUniverse.class) private byte[] referenceMapEncoding; @Platforms(Platform.HOSTED_ONLY.class) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/VarHandleFeature.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/VarHandleFeature.java index 92191bebe238..9e74855377c7 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/VarHandleFeature.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/VarHandleFeature.java @@ -251,7 +251,7 @@ public ValueAvailability valueAvailability() { @Override public Object transform(Object receiver, Object originalValue) { Field field = ImageSingletons.lookup(VarHandleFeature.class).findVarHandleField(receiver); - int offset = ImageSingletons.lookup(ReflectionSubstitutionSupport.class).getFieldOffset(field, true); + int offset = ReflectionSubstitutionSupport.singleton().getFieldOffset(field, true); if (offset <= 0) { throw VMError.shouldNotReachHere("Field is not marked as unsafe accessed: " + field); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/ExecutableAccessorComputer.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/ExecutableAccessorComputer.java index c4093cf3ec9e..782f967a9819 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/ExecutableAccessorComputer.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/ExecutableAccessorComputer.java @@ -26,7 +26,6 @@ import java.lang.reflect.Executable; -import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.hosted.FieldValueTransformer; import com.oracle.svm.core.annotate.RecomputeFieldValue; @@ -40,6 +39,6 @@ public final class ExecutableAccessorComputer implements FieldValueTransformer { @Override public Object transform(Object receiver, Object originalValue) { - return ImageSingletons.lookup(ReflectionSubstitutionSupport.class).getOrCreateAccessor((Executable) receiver); + return ReflectionSubstitutionSupport.singleton().getOrCreateAccessor((Executable) receiver); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/FieldOffsetComputer.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/FieldOffsetComputer.java index 25a921c6d472..cffd02e1b145 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/FieldOffsetComputer.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/FieldOffsetComputer.java @@ -26,8 +26,6 @@ import java.lang.reflect.Field; -import org.graalvm.nativeimage.ImageSingletons; - import com.oracle.svm.core.fieldvaluetransformer.FieldValueTransformerWithAvailability; public class FieldOffsetComputer implements FieldValueTransformerWithAvailability { @@ -39,6 +37,6 @@ public ValueAvailability valueAvailability() { @Override public Object transform(Object receiver, Object originalValue) { - return ImageSingletons.lookup(ReflectionSubstitutionSupport.class).getFieldOffset((Field) receiver, true); + return ReflectionSubstitutionSupport.singleton().getFieldOffset((Field) receiver, true); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/ReflectionSubstitutionSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/ReflectionSubstitutionSupport.java index ca9719de5884..75833e9f31ad 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/ReflectionSubstitutionSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/ReflectionSubstitutionSupport.java @@ -27,10 +27,17 @@ import java.lang.reflect.Executable; import java.lang.reflect.Field; +import org.graalvm.nativeimage.ImageSingletons; + import com.oracle.svm.core.annotate.Delete; import com.oracle.svm.core.reflect.SubstrateAccessor; public interface ReflectionSubstitutionSupport { + + static ReflectionSubstitutionSupport singleton() { + return ImageSingletons.lookup(ReflectionSubstitutionSupport.class); + } + SubstrateAccessor getOrCreateAccessor(Executable member); /** Offset of the field or -1 if the field was not registered for unsafe access. */ diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/Target_java_lang_reflect_Field.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/Target_java_lang_reflect_Field.java index 05385d4c63ab..859d73ad5d42 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/Target_java_lang_reflect_Field.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/Target_java_lang_reflect_Field.java @@ -133,7 +133,7 @@ public ValueAvailability valueAvailability() { @Override public Object transform(Object receiver, Object originalValue) { - return ImageSingletons.lookup(ReflectionSubstitutionSupport.class).getDeletionReason((Field) receiver); + return ReflectionSubstitutionSupport.singleton().getDeletionReason((Field) receiver); } } 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 b9f0c0238583..8daa17a3dc08 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 @@ -72,6 +72,7 @@ import com.oracle.svm.core.meta.SharedType; import com.oracle.svm.core.option.SubstrateOptionsParser; import com.oracle.svm.core.util.VMError; +import com.oracle.svm.hosted.ameta.FieldValueInterceptionSupport; import com.oracle.svm.hosted.analysis.Inflation; import com.oracle.svm.hosted.c.NativeLibraries; import com.oracle.svm.hosted.code.CEntryPointData; @@ -452,7 +453,7 @@ public boolean concurrentReachabilityHandlers() { @Override public void registerFieldValueTransformer(Field field, FieldValueTransformer transformer) { - bb.getAnnotationSubstitutionProcessor().registerFieldValueTransformer(field, transformer); + FieldValueInterceptionSupport.singleton().registerFieldValueTransformer(field, transformer); } /** diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/HostedConfiguration.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/HostedConfiguration.java index ca531229c685..25eb7f0b7c0e 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/HostedConfiguration.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/HostedConfiguration.java @@ -67,7 +67,7 @@ import com.oracle.svm.hosted.meta.HostedInstanceClass; import com.oracle.svm.hosted.meta.HostedMetaAccess; import com.oracle.svm.hosted.meta.HostedUniverse; -import com.oracle.svm.hosted.substitute.UnsafeAutomaticSubstitutionProcessor; +import com.oracle.svm.hosted.substitute.AnnotationSubstitutionProcessor; import jdk.graal.compiler.api.replacements.SnippetReflectionProvider; import jdk.graal.compiler.core.common.CompressEncoding; @@ -162,9 +162,8 @@ public static ObjectLayout createObjectLayout(JavaKind referenceKind, IdentityHa return new ObjectLayout(target, referenceSize, objectAlignment, hubOffset, firstFieldOffset, arrayLengthOffset, arrayBaseOffset, headerIdentityHashOffset, identityHashMode); } - public SVMHost createHostVM(OptionValues options, ClassLoader classLoader, ClassInitializationSupport classInitializationSupport, - UnsafeAutomaticSubstitutionProcessor automaticSubstitutions, Platform platform) { - return new SVMHost(options, classLoader, classInitializationSupport, automaticSubstitutions, platform); + public SVMHost createHostVM(OptionValues options, ImageClassLoader loader, ClassInitializationSupport classInitializationSupport, AnnotationSubstitutionProcessor annotationSubstitutions) { + return new SVMHost(options, loader, classInitializationSupport, annotationSubstitutions); } public CompileQueue createCompileQueue(DebugContext debug, FeatureHandler featureHandler, HostedUniverse hostedUniverse, RuntimeConfiguration runtimeConfiguration, boolean deoptimizeAll, diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java index 577a0139c5df..5d0b797abe61 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java @@ -253,7 +253,6 @@ import com.oracle.svm.hosted.snippets.SubstrateGraphBuilderPlugins; import com.oracle.svm.hosted.substitute.AnnotationSubstitutionProcessor; import com.oracle.svm.hosted.substitute.DeletedFieldsPlugin; -import com.oracle.svm.hosted.substitute.UnsafeAutomaticSubstitutionProcessor; import com.oracle.svm.hosted.util.CPUTypeAArch64; import com.oracle.svm.hosted.util.CPUTypeAMD64; import com.oracle.svm.hosted.util.CPUTypeRISCV64; @@ -564,11 +563,10 @@ protected void doRun(Map entryPoints, JavaMainSupport j List hostedEntryPoints = new ArrayList<>(); OptionValues options = HostedOptionValues.singleton(); - SnippetReflectionProvider originalSnippetReflection = GraalAccess.getOriginalSnippetReflection(); - try (DebugContext debug = new Builder(options, new GraalDebugHandlersFactory(originalSnippetReflection)).build(); + try (DebugContext debug = new Builder(options, new GraalDebugHandlersFactory(GraalAccess.getOriginalSnippetReflection())).build(); DebugCloseable featureCleanup = () -> featureHandler.forEachFeature(Feature::cleanup)) { - setupNativeImage(options, entryPoints, javaMainSupport, harnessSubstitutions, originalSnippetReflection, debug); + setupNativeImage(options, entryPoints, javaMainSupport, harnessSubstitutions, debug); boolean returnAfterAnalysis = runPointsToAnalysis(imageName, options, debug); if (returnAfterAnalysis) { @@ -863,7 +861,7 @@ protected boolean verifyAssignableTypes() { @SuppressWarnings("try") protected void setupNativeImage(OptionValues options, Map entryPoints, JavaMainSupport javaMainSupport, - SubstitutionProcessor harnessSubstitutions, SnippetReflectionProvider originalSnippetReflection, DebugContext debug) { + SubstitutionProcessor harnessSubstitutions, DebugContext debug) { try (Indent ignored = debug.logAndIndent("setup native-image builder")) { try (StopTimer ignored1 = TimerCollection.createTimerAndStart(TimerCollection.Registry.SETUP)) { SubstrateTargetDescription target = createTarget(); @@ -917,7 +915,7 @@ protected void setupNativeImage(OptionValues options, Map additionalSubstitutions) { - UnsafeAutomaticSubstitutionProcessor automaticSubstitutions = createAutomaticUnsafeSubstitutions(options, originalSnippetReflection, annotationSubstitutions); - SubstitutionProcessor aSubstitutions = createAnalysisSubstitutionProcessor(cEnumProcessor, automaticSubstitutions, annotationSubstitutions, additionalSubstitutions); + SubstitutionProcessor aSubstitutions = createAnalysisSubstitutionProcessor(cEnumProcessor, annotationSubstitutions, additionalSubstitutions); - SVMHost hostVM = HostedConfiguration.instance().createHostVM(options, loader.getClassLoader(), classInitializationSupport, automaticSubstitutions, loader.platform); + SVMHost hostVM = HostedConfiguration.instance().createHostVM(options, loader, classInitializationSupport, annotationSubstitutions); - automaticSubstitutions.init(loader, originalMetaAccess); AnalysisPolicy analysisPolicy = PointstoOptions.AllocationSiteSensitiveHeap.getValue(options) ? new BytecodeSensitiveAnalysisPolicy(options) : new DefaultAnalysisPolicy(options); AnalysisFactory analysisFactory; @@ -1054,17 +1050,12 @@ public static AnnotationSubstitutionProcessor createAnnotationSubstitutionProces return annotationSubstitutions; } - public static UnsafeAutomaticSubstitutionProcessor createAutomaticUnsafeSubstitutions(OptionValues options, SnippetReflectionProvider originalSnippetReflection, - AnnotationSubstitutionProcessor annotationSubstitutions) { - return new UnsafeAutomaticSubstitutionProcessor(options, annotationSubstitutions, originalSnippetReflection); - } - public static SubstitutionProcessor createAnalysisSubstitutionProcessor( - SubstitutionProcessor cEnumProcessor, SubstitutionProcessor automaticSubstitutions, SubstitutionProcessor annotationSubstitutions, + SubstitutionProcessor cEnumProcessor, SubstitutionProcessor annotationSubstitutions, List additionalSubstitutionProcessors) { List allProcessors = new ArrayList<>(); SubstitutionProcessor cFunctionSubstitutions = new CFunctionSubstitutionProcessor(); - allProcessors.addAll(Arrays.asList(annotationSubstitutions, cFunctionSubstitutions, automaticSubstitutions, cEnumProcessor)); + allProcessors.addAll(Arrays.asList(annotationSubstitutions, cFunctionSubstitutions, cEnumProcessor)); allProcessors.addAll(additionalSubstitutionProcessors); return SubstitutionProcessor.chainUpInOrder(allProcessors.toArray(new SubstitutionProcessor[0])); } 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 e1d1f4750401..ce07a00c936c 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 @@ -24,8 +24,6 @@ */ package com.oracle.svm.hosted; -import static com.oracle.graal.pointsto.util.AnalysisError.shouldNotReachHere; - import java.lang.ref.PhantomReference; import java.lang.ref.Reference; import java.lang.ref.SoftReference; @@ -33,8 +31,6 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; -import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.EnumSet; @@ -48,7 +44,6 @@ import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.BiPredicate; -import java.util.function.BooleanSupplier; import java.util.function.Function; import org.graalvm.nativeimage.AnnotationAccess; @@ -67,7 +62,6 @@ import com.oracle.graal.pointsto.meta.AnalysisMethod; import com.oracle.graal.pointsto.meta.AnalysisType; import com.oracle.graal.pointsto.meta.AnalysisUniverse; -import com.oracle.graal.pointsto.meta.FieldValueComputer; import com.oracle.graal.pointsto.meta.HostedProviders; import com.oracle.graal.pointsto.phases.InlineBeforeAnalysisGraphDecoder; import com.oracle.graal.pointsto.phases.InlineBeforeAnalysisPolicy; @@ -87,8 +81,6 @@ import com.oracle.svm.core.heap.StoredContinuation; import com.oracle.svm.core.heap.Target_java_lang_ref_Reference; import com.oracle.svm.core.heap.UnknownClass; -import com.oracle.svm.core.heap.UnknownObjectField; -import com.oracle.svm.core.heap.UnknownPrimitiveField; import com.oracle.svm.core.hub.DynamicHub; import com.oracle.svm.core.hub.HubType; import com.oracle.svm.core.hub.ReferenceType; @@ -99,7 +91,7 @@ import com.oracle.svm.core.thread.ContinuationSupport; import com.oracle.svm.core.util.HostedStringDeduplication; import com.oracle.svm.core.util.VMError; -import com.oracle.svm.hosted.analysis.CustomTypeFieldHandler; +import com.oracle.svm.hosted.ameta.FieldValueInterceptionSupport; import com.oracle.svm.hosted.analysis.SVMParsingSupport; import com.oracle.svm.hosted.classinitialization.ClassInitializationOptions; import com.oracle.svm.hosted.classinitialization.ClassInitializationSupport; @@ -117,7 +109,8 @@ 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.substitute.UnsafeAutomaticSubstitutionProcessor; +import com.oracle.svm.hosted.substitute.AnnotationSubstitutionProcessor; +import com.oracle.svm.hosted.substitute.AutomaticUnsafeTransformationSupport; import com.oracle.svm.util.LogUtils; import com.oracle.svm.util.ReflectionUtil; @@ -157,7 +150,7 @@ public class SVMHost extends HostVM { private final ClassInitializationSupport classInitializationSupport; private final LinkAtBuildTimeSupport linkAtBuildTimeSupport; private final HostedStringDeduplication stringTable; - private final UnsafeAutomaticSubstitutionProcessor automaticSubstitutions; + private final AutomaticUnsafeTransformationSupport automaticUnsafeTransformations; /** * Optionally keep the Graal graphs alive during analysis. This increases the memory footprint @@ -179,15 +172,16 @@ public class SVMHost extends HostVM { private final SVMParsingSupport parsingSupport; private final InlineBeforeAnalysisPolicy inlineBeforeAnalysisPolicy; + private final FieldValueInterceptionSupport fieldValueInterceptionSupport; + @SuppressWarnings("this-escape") - public SVMHost(OptionValues options, ClassLoader classLoader, ClassInitializationSupport classInitializationSupport, - UnsafeAutomaticSubstitutionProcessor automaticSubstitutions, Platform platform) { - super(options, classLoader); + public SVMHost(OptionValues options, ImageClassLoader loader, ClassInitializationSupport classInitializationSupport, AnnotationSubstitutionProcessor annotationSubstitutions) { + super(options, loader.getClassLoader()); this.classInitializationSupport = classInitializationSupport; this.stringTable = HostedStringDeduplication.singleton(); this.forbiddenTypes = setupForbiddenTypes(options); - this.automaticSubstitutions = automaticSubstitutions; - this.platform = platform; + this.automaticUnsafeTransformations = new AutomaticUnsafeTransformationSupport(options, annotationSubstitutions, loader); + this.platform = loader.platform; this.linkAtBuildTimeSupport = LinkAtBuildTimeSupport.singleton(); if (ImageSingletons.contains(MultiMethodAnalysisPolicy.class)) { multiMethodAnalysisPolicy = ImageSingletons.lookup(MultiMethodAnalysisPolicy.class); @@ -204,6 +198,8 @@ public SVMHost(OptionValues options, ClassLoader classLoader, ClassInitializatio } else { parsingSupport = null; } + fieldValueInterceptionSupport = new FieldValueInterceptionSupport(annotationSubstitutions); + ImageSingletons.add(FieldValueInterceptionSupport.class, fieldValueInterceptionSupport); } protected InlineBeforeAnalysisPolicyUtils getInlineBeforeAnalysisPolicyUtils() { @@ -303,7 +299,7 @@ public void registerType(AnalysisType analysisType) { } @Override - public void onTypeReachable(AnalysisType analysisType) { + public void onTypeReachable(BigBang bb, AnalysisType analysisType) { if (!analysisType.isReachable()) { throw VMError.shouldNotReachHere("Registering and initializing a type that was not yet marked as reachable: " + analysisType); } @@ -315,7 +311,7 @@ public void onTypeReachable(AnalysisType analysisType) { classInitializationSupport.maybeInitializeAtBuildTime(analysisType); /* Compute the automatic substitutions. */ - automaticSubstitutions.computeSubstitutions(this, GraalAccess.getOriginalProviders().getMetaAccess().lookupJavaType(analysisType.getJavaClass())); + automaticUnsafeTransformations.computeTransformations(bb, this, GraalAccess.getOriginalProviders().getMetaAccess().lookupJavaType(analysisType.getJavaClass())); } @Override @@ -484,10 +480,6 @@ public ClassInitializationSupport getClassInitializationSupport() { return classInitializationSupport; } - public UnsafeAutomaticSubstitutionProcessor getAutomaticSubstitutionProcessor() { - return automaticSubstitutions; - } - private static int computeHubType(AnalysisType type) { if (type.isArray()) { if (type.getComponentType().isPrimitive() || type.getComponentType().isWordType()) { @@ -952,77 +944,6 @@ public boolean allowConstantFolding(AnalysisMethod method) { return method.isOriginalMethod() && !SubstrateCompilationDirectives.singleton().isRegisteredForDeoptTesting(method); } - @Override - public FieldValueComputer createFieldValueComputer(AnalysisField field) { - UnknownObjectField unknownObjectField = field.getAnnotation(UnknownObjectField.class); - if (unknownObjectField != null) { - return createObjectFieldValueComputer(field, unknownObjectField); - } - UnknownPrimitiveField unknownPrimitiveField = field.getAnnotation(UnknownPrimitiveField.class); - if (unknownPrimitiveField != null) { - return createPrimitiveFieldValueComputer(field, unknownPrimitiveField); - } - return null; - } - - private static FieldValueComputer createObjectFieldValueComputer(AnalysisField field, UnknownObjectField unknownValueField) { - return new FieldValueComputer() { - final BooleanSupplier availability = ReflectionUtil.newInstance(unknownValueField.availability()); - - @Override - public boolean isAvailable() { - return availability.getAsBoolean(); - } - - @Override - public Class[] types() { - return extractAnnotationTypes(field, unknownValueField.types(), unknownValueField.fullyQualifiedTypes()); - } - - @Override - public boolean canBeNull() { - return unknownValueField.canBeNull(); - } - }; - } - - private static FieldValueComputer createPrimitiveFieldValueComputer(AnalysisField field, UnknownPrimitiveField unknownValueField) { - return new FieldValueComputer() { - final BooleanSupplier availability = ReflectionUtil.newInstance(unknownValueField.availability()); - - @Override - public boolean isAvailable() { - return availability.getAsBoolean(); - } - - @Override - public Class[] types() { - return new Class[]{field.getType().getJavaClass()}; - } - }; - } - - private static Class[] extractAnnotationTypes(AnalysisField field, Class[] types, String[] fullyQualifiedTypes) { - List> annotationTypes = new ArrayList<>(Arrays.asList(types)); - for (String annotationTypeName : fullyQualifiedTypes) { - try { - Class annotationType = Class.forName(annotationTypeName); - annotationTypes.add(annotationType); - } catch (ClassNotFoundException e) { - throw shouldNotReachHere("Specified computed value type not found " + annotationTypeName); - } - } - - if (annotationTypes.isEmpty()) { - /* If no types are specified fallback to the field declared type. */ - AnalysisType fieldType = field.getType(); - VMError.guarantee(CustomTypeFieldHandler.isConcreteType(fieldType), "Illegal use of @UnknownObjectField annotation on field %s. " + - "The field type must be concrete or the annotation must declare a concrete type.", field); - annotationTypes.add(fieldType.getJavaClass()); - } - return annotationTypes.toArray(new Class[0]); - } - public SimulateClassInitializerSupport createSimulateClassInitializerSupport(AnalysisMetaAccess aMetaAccess) { return new SimulateClassInitializerSupport(aMetaAccess, this); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ameta/AnalysisConstantFieldProvider.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ameta/AnalysisConstantFieldProvider.java index 5b61e6ea32e4..103075939797 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ameta/AnalysisConstantFieldProvider.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ameta/AnalysisConstantFieldProvider.java @@ -45,9 +45,6 @@ public AnalysisConstantFieldProvider(MetaAccessProvider metaAccess, SVMHost host @Override public T readConstantField(ResolvedJavaField f, ConstantFieldTool analysisTool) { AnalysisField field = (AnalysisField) f; - if (!ReadableJavaField.isValueAvailable(field)) { - return null; - } T foldedValue = super.readConstantField(field, analysisTool); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ameta/AnalysisConstantReflectionProvider.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ameta/AnalysisConstantReflectionProvider.java index 5df7adc0a78f..9ba901df5171 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ameta/AnalysisConstantReflectionProvider.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ameta/AnalysisConstantReflectionProvider.java @@ -76,6 +76,7 @@ public class AnalysisConstantReflectionProvider extends SharedConstantReflection private final ClassInitializationSupport classInitializationSupport; private final AnalysisMethodHandleAccessProvider methodHandleAccess; private SimulateClassInitializerSupport simulateClassInitializerSupport; + private final FieldValueInterceptionSupport fieldValueInterceptionSupport = FieldValueInterceptionSupport.singleton(); public AnalysisConstantReflectionProvider(AnalysisUniverse universe, UniverseMetaAccess metaAccess, ClassInitializationSupport classInitializationSupport) { this.universe = universe; @@ -227,7 +228,7 @@ public JavaConstant readValue(UniverseMetaAccess suppliedMetaAccess, AnalysisFie } if (value == null && receiver instanceof ImageHeapConstant heapConstant) { heapConstant.ensureReaderInstalled(); - AnalysisError.guarantee(ReadableJavaField.isValueAvailable(field), "Value not yet available for %s", field); + AnalysisError.guarantee(fieldValueInterceptionSupport.isValueAvailable(field), "Value not yet available for %s", field); ImageHeapInstance heapObject = (ImageHeapInstance) receiver; value = heapObject.readFieldValue(field); } @@ -245,7 +246,7 @@ public JavaConstant readValue(UniverseMetaAccess suppliedMetaAccess, AnalysisFie * heap is a snapshot of the hosted state; simulated values are a level above the shadow heap. */ public ValueSupplier readHostedFieldValue(AnalysisField field, JavaConstant receiver) { - if (ReadableJavaField.isValueAvailable(field)) { + if (fieldValueInterceptionSupport.isValueAvailable(field)) { /* Materialize and return the value. */ return ValueSupplier.eagerValue(doReadValue(field, receiver)); } @@ -258,7 +259,7 @@ public ValueSupplier readHostedFieldValue(AnalysisField field, Jav * during analysis or in a later phase. Attempts to materialize the value before it becomes * available will result in an error. */ - return ValueSupplier.lazyValue(() -> doReadValue(field, receiver), () -> ReadableJavaField.isValueAvailable(field)); + return ValueSupplier.lazyValue(() -> doReadValue(field, receiver), () -> fieldValueInterceptionSupport.isValueAvailable(field)); } /** Returns the hosted field value. The receiver must be a hosted constant. */ @@ -268,7 +269,7 @@ public JavaConstant readHostedFieldValue(UniverseMetaAccess access, AnalysisFiel } private JavaConstant doReadValue(AnalysisField field, JavaConstant receiver) { - return universe.fromHosted(ReadableJavaField.readFieldValue(classInitializationSupport, field.wrapped, receiver)); + return universe.fromHosted(fieldValueInterceptionSupport.readFieldValue(classInitializationSupport, field, receiver)); } /** diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/CustomTypeFieldHandler.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ameta/CustomTypeFieldHandler.java similarity index 87% rename from substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/CustomTypeFieldHandler.java rename to substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ameta/CustomTypeFieldHandler.java index ec793d8018a0..fbaad53baafe 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/CustomTypeFieldHandler.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ameta/CustomTypeFieldHandler.java @@ -22,7 +22,7 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ -package com.oracle.svm.hosted.analysis; +package com.oracle.svm.hosted.ameta; import java.util.ArrayList; import java.util.List; @@ -36,11 +36,12 @@ import com.oracle.graal.pointsto.meta.AnalysisMetaAccess; import com.oracle.graal.pointsto.meta.AnalysisType; import com.oracle.graal.pointsto.meta.PointsToAnalysisField; -import com.oracle.svm.hosted.substitute.ComputedValueField; +import com.oracle.svm.hosted.analysis.FieldValueComputer; public abstract class CustomTypeFieldHandler { protected final BigBang bb; private final AnalysisMetaAccess metaAccess; + private final FieldValueInterceptionSupport fieldValueInterceptionSupport = FieldValueInterceptionSupport.singleton(); private Set processedFields = ConcurrentHashMap.newKeySet(); public CustomTypeFieldHandler(BigBang bb, AnalysisMetaAccess metaAccess) { @@ -49,7 +50,7 @@ public CustomTypeFieldHandler(BigBang bb, AnalysisMetaAccess metaAccess) { } public void handleField(AnalysisField field) { - if (processedFields.contains(field)) { + if (!processedFields.add(field)) { return; } /* @@ -57,21 +58,20 @@ public void handleField(AnalysisField field) { * types as allocated when the field is not yet accessed. */ assert field.isAccessed(); - if (field.wrapped instanceof ComputedValueField computedField) { - if (!computedField.isValueAvailableBeforeAnalysis() && field.getStorageKind().isObject()) { + if (fieldValueInterceptionSupport.hasFieldValueTransformer(field)) { + if (field.getJavaKind().isObject() && !fieldValueInterceptionSupport.isValueAvailable(field)) { injectFieldTypes(field, field.getType()); } else if (bb.trackPrimitiveValues() && field.getStorageKind().isPrimitive() && field instanceof PointsToAnalysisField ptaField) { ptaField.saturatePrimitiveField(); } - } else if (field.isComputedValue()) { + } else if (fieldValueInterceptionSupport.lookupFieldValueInterceptor(field) instanceof FieldValueComputer fieldValueComputer) { if (field.getStorageKind().isObject()) { - field.setCanBeNull(field.computedValueCanBeNull()); - injectFieldTypes(field, transformTypes(field, field.computedValueTypes())); + field.setCanBeNull(fieldValueComputer.canBeNull()); + injectFieldTypes(field, transformTypes(field, fieldValueComputer.types())); } else if (bb.trackPrimitiveValues() && field.getStorageKind().isPrimitive() && field instanceof PointsToAnalysisField ptaField) { ptaField.saturatePrimitiveField(); } } - processedFields.add(field); } private void injectFieldTypes(AnalysisField field, List customTypes) { @@ -87,7 +87,7 @@ private void injectFieldTypes(AnalysisField field, List customType protected abstract void injectFieldTypes(AnalysisField aField, AnalysisType... customTypes); - private List transformTypes(AnalysisField field, Class[] types) { + private List transformTypes(AnalysisField field, List> types) { List customTypes = new ArrayList<>(); AnalysisType declaredType = field.getType(); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ameta/FieldValueInterceptionSupport.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ameta/FieldValueInterceptionSupport.java new file mode 100644 index 000000000000..4975cb5eb161 --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ameta/FieldValueInterceptionSupport.java @@ -0,0 +1,298 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.hosted.ameta; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; + +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.hosted.Feature.BeforeAnalysisAccess; +import org.graalvm.nativeimage.hosted.FieldValueTransformer; + +import com.oracle.graal.pointsto.infrastructure.OriginalClassProvider; +import com.oracle.graal.pointsto.infrastructure.OriginalFieldProvider; +import com.oracle.graal.pointsto.meta.AnalysisField; +import com.oracle.graal.pointsto.meta.AnalysisType; +import com.oracle.graal.pointsto.util.GraalAccess; +import com.oracle.svm.core.BuildPhaseProvider; +import com.oracle.svm.core.annotate.RecomputeFieldValue; +import com.oracle.svm.core.fieldvaluetransformer.FieldValueTransformerWithAvailability; +import com.oracle.svm.core.fieldvaluetransformer.FieldValueTransformerWithAvailability.ValueAvailability; +import com.oracle.svm.core.heap.UnknownObjectField; +import com.oracle.svm.core.heap.UnknownPrimitiveField; +import com.oracle.svm.core.util.UserError; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.hosted.analysis.FieldValueComputer; +import com.oracle.svm.hosted.classinitialization.ClassInitializationSupport; +import com.oracle.svm.hosted.substitute.AnnotationSubstitutionProcessor; +import com.oracle.svm.hosted.substitute.AutomaticUnsafeTransformationSupport; +import com.oracle.svm.hosted.substitute.ComputedValueField; +import com.oracle.svm.hosted.substitute.FieldValueTransformation; +import com.oracle.svm.util.ReflectionUtil; + +import jdk.vm.ci.hotspot.HotSpotResolvedJavaField; +import jdk.vm.ci.meta.JavaConstant; +import jdk.vm.ci.meta.ResolvedJavaField; + +/** + * This class centralizes access to the several ways we have to transform and intercept field + * values: + * + * {@link FieldValueTransformer} are part of the supported API, and the preferred way of + * transformation. The non-API {@link FieldValueTransformerWithAvailability} allows to provide field + * values only after static analysis. The API for registration transformers + * {@link BeforeAnalysisAccess#registerFieldValueTransformer}. Transformers are also registered + * automatically by the {@link AutomaticUnsafeTransformationSupport}. + * + * {@link ComputedValueField} registered via the {@link RecomputeFieldValue} annotation is the + * legacy way of registering a transformer. Eventually, we want to remove {@link ComputedValueField} + * and only use field value transformer, but for now that is a functionally equivalent way of + * transformation and we always need to check for both. + * + * {@link UnknownObjectField} and {@link UnknownPrimitiveField} are internal annotations for fields + * whose value is only available after static analysis. Once the value is available, it is read as + * usual from the hosted HotSpot field without any further transformation. + */ +public final class FieldValueInterceptionSupport { + private static final Object INTERCEPTOR_ACCESSED_MARKER = new Object(); + + private final AnnotationSubstitutionProcessor annotationSubstitutions; + private final Map fieldValueInterceptors = new ConcurrentHashMap<>(); + + public static FieldValueInterceptionSupport singleton() { + return ImageSingletons.lookup(FieldValueInterceptionSupport.class); + } + + public FieldValueInterceptionSupport(AnnotationSubstitutionProcessor annotationSubstitutions) { + this.annotationSubstitutions = annotationSubstitutions; + } + + /** + * Register a field value transformer for the provided field. There can only be one transformer + * per field, if there is already a transformation in place, a {@link UserError} is reported. + */ + public void registerFieldValueTransformer(Field reflectionField, FieldValueTransformer transformer) { + registerFieldValueTransformer(GraalAccess.getOriginalProviders().getMetaAccess().lookupJavaField(reflectionField), transformer); + } + + public void registerFieldValueTransformer(ResolvedJavaField oField, FieldValueTransformer transformer) { + assert oField instanceof HotSpotResolvedJavaField : oField; + if (annotationSubstitutions.isDeleted(oField)) { + throw UserError.abort("Cannot register a field value transformer for field %s: %s", oField.format("%H.%n"), + "The field is marked as deleted, i.e., the field is not available on this platform"); + } + + var substitution = annotationSubstitutions.findSubstitution(oField); + if (substitution.isPresent() && substitution.get() instanceof ComputedValueField computedValueField && computedValueField.getRecomputeValueKind() != RecomputeFieldValue.Kind.None) { + throw UserError.abort("Cannot register a field value transformer for field %s: %s", oField.format("%H.%n"), "The field value is already transformed via an @Alias annotation."); + } + + var transformation = new FieldValueTransformation(OriginalClassProvider.getJavaClass(oField.getType()), Objects.requireNonNull(transformer), false); + var existingInterceptor = fieldValueInterceptors.putIfAbsent(oField, transformation); + + if (existingInterceptor == INTERCEPTOR_ACCESSED_MARKER) { + throw UserError.abort("Cannot register a field value transformer for field %s: %s", oField.format("%H.%n"), + "The field was already accessed by the static analysis. The transformer must be registered earlier, before the static analysis sees a reference to the field for the first time."); + } else if (existingInterceptor != null) { + throw UserError.abort("Cannot register a field value transformer for field %s: %s", oField.format("%H.%n"), + "A field value transformer is already registered for this field."); + } + } + + Object lookupFieldValueInterceptor(AnalysisField field) { + var result = field.getFieldValueInterceptor(); + if (result == null) { + result = computeAndCacheFieldValueInterceptor(field); + } + return result == INTERCEPTOR_ACCESSED_MARKER ? null : result; + } + + private Object computeAndCacheFieldValueInterceptor(AnalysisField field) { + /* + * Trigger computation of automatic substitutions. There might be an automatic substitution + * for the current field, and we must register it before checking if a field value + * transformer exists. + */ + field.beforeFieldValueAccess(); + + ResolvedJavaField oField = OriginalFieldProvider.getOriginalField(field); + assert oField == null || oField instanceof HotSpotResolvedJavaField : oField; + + FieldValueComputer computer = createFieldValueComputer(field); + Object result; + if (computer != null) { + VMError.guarantee(oField != null, "Cannot have a @UnknownObjectField or @UnknownPrimitiveField annotation on synthetic field %s", field); + + var interceptor = fieldValueInterceptors.computeIfAbsent(oField, k -> computer); + /* + * There can be a race with another thread, so `interceptor` might not be the same + * object as `computer`. But that is not a problem because they are equivalent + * `FieldValueComputer`. We only need to check that there was no field value transformer + * registered beforehand. Unfortunately, we do not have a good stack trace for the user + * showing how the field value transformer was created. But we expect this to be a rare + * error, since the `@Unknown*Field` annotations are not public API. + */ + if (!(interceptor instanceof FieldValueComputer)) { + throw UserError.abort("Cannot register a field value transformer for field %s: %s", field.format("%H.%n"), + "The field is annotated with @UnknownObjectField or @UnknownPrimitiveField."); + } + result = interceptor; + + } else if (oField != null) { + /* + * If no field value transformer was registered beforehand, install our marker value so + * that later registration of a field value transformer is reported as an error. + */ + result = fieldValueInterceptors.computeIfAbsent(oField, k -> INTERCEPTOR_ACCESSED_MARKER); + } else { + /* + * This is a synthetic field, so it is not possible to install a field value transformer + * for it. + */ + result = INTERCEPTOR_ACCESSED_MARKER; + } + + Objects.requireNonNull(result, "Must have a non-null value now to avoid repeated invocation of this method"); + /* + * Cache the result for future fast lookups. No need to be atomic here again, that was + * already done via the operations on `fieldValueInterceptors`. Multiple threads might write + * the same value into the cache. + */ + field.setFieldValueInterceptor(result); + return result; + } + + /** + * Check if the value of the provided field is currently available. After this method has been + * called, it is not possible to install a transformer anymore. + */ + public boolean isValueAvailable(AnalysisField field) { + var interceptor = lookupFieldValueInterceptor(field); + if (interceptor instanceof FieldValueTransformation transformation) { + if (transformation.getFieldValueTransformer() instanceof FieldValueTransformerWithAvailability transformerWithAvailability) { + if (!isAvailable(transformerWithAvailability.valueAvailability())) { + return false; + } + } + } else if (interceptor instanceof FieldValueComputer computer) { + if (!computer.isAvailable()) { + return false; + } + } else if (field.wrapped instanceof ReadableJavaField readableField) { + if (!readableField.isValueAvailable()) { + return false; + } + } + return true; + } + + private static boolean isAvailable(ValueAvailability availability) { + /* + * Note that we use isHostedUniverseBuild on purpose to define "available after analysis": + * many field value transformers require field offsets to be available, i.e., the hosted + * universe to be built. This ensures that such field value transformers do not have their + * value available when strengthening graphs after analysis, i.e., when applying analysis + * results back into the IR. + */ + return switch (availability) { + case BeforeAnalysis -> true; + case AfterAnalysis -> BuildPhaseProvider.isHostedUniverseBuilt(); + case AfterCompilation -> BuildPhaseProvider.isCompilationFinished(); + }; + } + + /** + * Returns true if a field value transformer has been registered for this field. After this + * method has been called, it is not possible to install a transformer anymore. + */ + public boolean hasFieldValueTransformer(AnalysisField field) { + var interceptor = lookupFieldValueInterceptor(field); + if (interceptor instanceof FieldValueTransformation) { + return true; + } else if (field.wrapped instanceof ComputedValueField) { + return true; + } + return false; + } + + JavaConstant readFieldValue(ClassInitializationSupport classInitializationSupport, AnalysisField field, JavaConstant receiver) { + assert isValueAvailable(field) : field; + + var interceptor = lookupFieldValueInterceptor(field); + if (interceptor instanceof FieldValueTransformation transformation) { + return transformation.readValue(classInitializationSupport, field.wrapped, receiver); + } else { + /* + * If the wrapped field is ComputedValueField, the ReadableJavaField handling does the + * necessary field value transformations. + */ + return ReadableJavaField.readFieldValue(classInitializationSupport, field.wrapped, receiver); + } + } + + private static FieldValueComputer createFieldValueComputer(AnalysisField field) { + UnknownObjectField unknownObjectField = field.getAnnotation(UnknownObjectField.class); + if (unknownObjectField != null) { + return new FieldValueComputer( + ReflectionUtil.newInstance(unknownObjectField.availability()), + extractAnnotationTypes(field, unknownObjectField.types(), unknownObjectField.fullyQualifiedTypes()), + unknownObjectField.canBeNull()); + } + UnknownPrimitiveField unknownPrimitiveField = field.getAnnotation(UnknownPrimitiveField.class); + if (unknownPrimitiveField != null) { + return new FieldValueComputer( + ReflectionUtil.newInstance(unknownPrimitiveField.availability()), + List.of(field.getType().getJavaClass()), + false); + } + return null; + } + + private static List> extractAnnotationTypes(AnalysisField field, Class[] types, String[] fullyQualifiedTypes) { + List> annotationTypes = new ArrayList<>(Arrays.asList(types)); + for (String annotationTypeName : fullyQualifiedTypes) { + try { + Class annotationType = Class.forName(annotationTypeName); + annotationTypes.add(annotationType); + } catch (ClassNotFoundException e) { + throw UserError.abort("Specified computed value type not found: " + annotationTypeName); + } + } + + if (annotationTypes.isEmpty()) { + /* If no types are specified, fall back to the field declared type. */ + AnalysisType fieldType = field.getType(); + UserError.guarantee(CustomTypeFieldHandler.isConcreteType(fieldType), "Illegal use of @UnknownObjectField annotation on field %s. " + + "The field type must be concrete or the annotation must declare a concrete type.", field); + annotationTypes.add(fieldType.getJavaClass()); + } + return annotationTypes; + } +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ameta/ReadableJavaField.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ameta/ReadableJavaField.java index 8d8588e0151c..4e255fc49bfa 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ameta/ReadableJavaField.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ameta/ReadableJavaField.java @@ -26,8 +26,6 @@ import com.oracle.graal.pointsto.meta.AnalysisField; import com.oracle.graal.pointsto.util.GraalAccess; -import com.oracle.svm.core.BuildPhaseProvider; -import com.oracle.svm.core.fieldvaluetransformer.FieldValueTransformerWithAvailability.ValueAvailability; import com.oracle.svm.core.util.VMError; import com.oracle.svm.hosted.classinitialization.ClassInitializationSupport; import com.oracle.svm.hosted.meta.HostedField; @@ -37,13 +35,6 @@ public interface ReadableJavaField extends ResolvedJavaField { - static boolean isValueAvailable(AnalysisField field) { - if (field.wrapped instanceof ReadableJavaField readableField) { - return readableField.isValueAvailable(); - } - return field.isValueAvailable(); - } - static JavaConstant readFieldValue(ClassInitializationSupport classInitializationSupport, ResolvedJavaField field, JavaConstant receiver) { assert !(field instanceof AnalysisField) && !(field instanceof HostedField) : "must have been unwrapped"; @@ -95,19 +86,7 @@ static JavaConstant readFieldValue(ClassInitializationSupport classInitializatio JavaConstant readValue(ClassInitializationSupport classInitializationSupport, JavaConstant receiver); - /** - * When this method returns true, image heap snapshotting can access the value before analysis. - * If the field is final, then the value can also be constant folded before analysis. - * - * The introduction of this method pre-dates {@link ValueAvailability}, i.e., we could combine - * this method and {@link #isValueAvailable} into a single method that returns the - * {@link ValueAvailability} of the field. - */ - boolean isValueAvailableBeforeAnalysis(); - - default boolean isValueAvailable() { - return isValueAvailableBeforeAnalysis() || BuildPhaseProvider.isAnalysisFinished(); - } + boolean isValueAvailable(); boolean injectFinalForRuntimeCompilation(); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/FieldValueComputer.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/FieldValueComputer.java new file mode 100644 index 000000000000..b27391fa23ce --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/FieldValueComputer.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.hosted.analysis; + +import java.util.List; +import java.util.function.BooleanSupplier; + +public final class FieldValueComputer { + private final BooleanSupplier availability; + private final List> types; + private final boolean canBeNull; + + public FieldValueComputer(BooleanSupplier availability, List> types, boolean canBeNull) { + this.availability = availability; + this.types = types; + this.canBeNull = canBeNull; + } + + public boolean isAvailable() { + return availability.getAsBoolean(); + } + + public List> types() { + return types; + } + + public boolean canBeNull() { + return canBeNull; + } +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/NativeImagePointsToAnalysis.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/NativeImagePointsToAnalysis.java index f984e4946e65..f9eb3ae3524d 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/NativeImagePointsToAnalysis.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/NativeImagePointsToAnalysis.java @@ -41,6 +41,7 @@ import com.oracle.graal.pointsto.util.TimerCollection; import com.oracle.svm.hosted.HostedConfiguration; import com.oracle.svm.hosted.SVMHost; +import com.oracle.svm.hosted.ameta.CustomTypeFieldHandler; import com.oracle.svm.hosted.code.IncompatibleClassChangeFallbackMethod; import com.oracle.svm.hosted.meta.HostedType; import com.oracle.svm.hosted.substitute.AnnotationSubstitutionProcessor; diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/NativeImageReachabilityAnalysisEngine.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/NativeImageReachabilityAnalysisEngine.java index 788139e03492..be0373bf07b4 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/NativeImageReachabilityAnalysisEngine.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/NativeImageReachabilityAnalysisEngine.java @@ -32,6 +32,7 @@ import com.oracle.graal.reachability.ReachabilityAnalysisEngine; import com.oracle.graal.reachability.ReachabilityMethodProcessingHandler; import com.oracle.svm.hosted.SVMHost; +import com.oracle.svm.hosted.ameta.CustomTypeFieldHandler; import com.oracle.svm.hosted.substitute.AnnotationSubstitutionProcessor; import jdk.graal.compiler.api.replacements.SnippetReflectionProvider; diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/PointsToCustomTypeFieldHandler.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/PointsToCustomTypeFieldHandler.java index bc0179a8138b..724a35c005ac 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/PointsToCustomTypeFieldHandler.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/PointsToCustomTypeFieldHandler.java @@ -29,6 +29,7 @@ import com.oracle.graal.pointsto.meta.AnalysisField; import com.oracle.graal.pointsto.meta.AnalysisMetaAccess; import com.oracle.graal.pointsto.meta.AnalysisType; +import com.oracle.svm.hosted.ameta.CustomTypeFieldHandler; public class PointsToCustomTypeFieldHandler extends CustomTypeFieldHandler { public PointsToCustomTypeFieldHandler(BigBang bb, AnalysisMetaAccess metaAccess) { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/flow/SVMMethodTypeFlowBuilder.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/flow/SVMMethodTypeFlowBuilder.java index 7b6da38e8e38..de83d64c6372 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/flow/SVMMethodTypeFlowBuilder.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/flow/SVMMethodTypeFlowBuilder.java @@ -25,18 +25,6 @@ package com.oracle.svm.hosted.analysis.flow; -import jdk.graal.compiler.core.common.type.ObjectStamp; -import jdk.graal.compiler.core.common.type.Stamp; -import jdk.graal.compiler.debug.GraalError; -import jdk.graal.compiler.graph.Node; -import jdk.graal.compiler.nodes.CallTargetNode.InvokeKind; -import jdk.graal.compiler.nodes.ConstantNode; -import jdk.graal.compiler.nodes.FixedNode; -import jdk.graal.compiler.nodes.NodeView; -import jdk.graal.compiler.nodes.StructuredGraph; -import jdk.graal.compiler.nodes.ValueNode; -import jdk.graal.compiler.nodes.java.LoadFieldNode; - import com.oracle.graal.pointsto.AbstractAnalysisEngine; import com.oracle.graal.pointsto.PointsToAnalysis; import com.oracle.graal.pointsto.flow.MethodFlowsGraph; @@ -53,9 +41,20 @@ import com.oracle.svm.core.util.UserError.UserException; import com.oracle.svm.hosted.NativeImageOptions; import com.oracle.svm.hosted.SVMHost; +import com.oracle.svm.hosted.ameta.FieldValueInterceptionSupport; import com.oracle.svm.hosted.code.SubstrateCompilationDirectives; -import com.oracle.svm.hosted.substitute.ComputedValueField; +import jdk.graal.compiler.core.common.type.ObjectStamp; +import jdk.graal.compiler.core.common.type.Stamp; +import jdk.graal.compiler.debug.GraalError; +import jdk.graal.compiler.graph.Node; +import jdk.graal.compiler.nodes.CallTargetNode.InvokeKind; +import jdk.graal.compiler.nodes.ConstantNode; +import jdk.graal.compiler.nodes.FixedNode; +import jdk.graal.compiler.nodes.NodeView; +import jdk.graal.compiler.nodes.StructuredGraph; +import jdk.graal.compiler.nodes.ValueNode; +import jdk.graal.compiler.nodes.java.LoadFieldNode; import jdk.vm.ci.code.BytecodePosition; import jdk.vm.ci.meta.JavaConstant; import jdk.vm.ci.meta.JavaKind; @@ -159,7 +158,7 @@ protected void checkUnsafeOffset(ValueNode base, ValueNode offsetNode) { if (field.isStatic() && getHostVM().getClassInitializationSupport().maybeInitializeAtBuildTime(field.getDeclaringClass()) && !field.getDeclaringClass().unsafeFieldsRecomputed() && - !(field.wrapped instanceof ComputedValueField) && + !FieldValueInterceptionSupport.singleton().hasFieldValueTransformer(field) && !(base.isConstant() && base.asConstant().isDefaultForKind())) { String message = String.format("Field %s is used as an offset in an unsafe operation, but no value recomputation found.%n Wrapped field: %s", field, field.wrapped); message += String.format("%n Location: %s", pos); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/fieldfolding/StaticFinalFieldFoldingNodePlugin.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/fieldfolding/StaticFinalFieldFoldingNodePlugin.java index 8c57bf0f90e2..661230b89b56 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/fieldfolding/StaticFinalFieldFoldingNodePlugin.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/fieldfolding/StaticFinalFieldFoldingNodePlugin.java @@ -26,6 +26,10 @@ import java.util.Arrays; +import com.oracle.graal.pointsto.meta.AnalysisField; +import com.oracle.graal.pointsto.meta.AnalysisMethod; +import com.oracle.svm.hosted.ameta.FieldValueInterceptionSupport; + import jdk.graal.compiler.nodes.ConstantNode; import jdk.graal.compiler.nodes.EndNode; import jdk.graal.compiler.nodes.IfNode; @@ -39,11 +43,6 @@ import jdk.graal.compiler.nodes.graphbuilderconf.GraphBuilderContext; import jdk.graal.compiler.nodes.graphbuilderconf.NodePlugin; import jdk.graal.compiler.nodes.type.StampTool; - -import com.oracle.graal.pointsto.meta.AnalysisField; -import com.oracle.graal.pointsto.meta.AnalysisMethod; -import com.oracle.svm.hosted.ameta.ReadableJavaField; - import jdk.vm.ci.meta.JavaConstant; import jdk.vm.ci.meta.ResolvedJavaField; @@ -53,6 +52,7 @@ final class StaticFinalFieldFoldingNodePlugin implements NodePlugin { private final StaticFinalFieldFoldingFeature feature; + private final FieldValueInterceptionSupport fieldValueInterceptionSupport = FieldValueInterceptionSupport.singleton(); StaticFinalFieldFoldingNodePlugin(StaticFinalFieldFoldingFeature feature) { this.feature = feature; @@ -80,7 +80,7 @@ public boolean handleLoadStaticField(GraphBuilderContext b, ResolvedJavaField fi return false; } - if (!ReadableJavaField.isValueAvailable(aField)) { + if (!fieldValueInterceptionSupport.isValueAvailable(aField)) { /* * Cannot optimize static field whose value is recomputed and is not yet available, * i.e., it may depend on analysis/compilation derived data. diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/HeapDumpHostedUtils.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/HeapDumpHostedUtils.java index ceb2491d81ab..8fd3c0114683 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/HeapDumpHostedUtils.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/HeapDumpHostedUtils.java @@ -27,8 +27,6 @@ import java.io.UnsupportedEncodingException; import java.util.Collection; -import jdk.graal.compiler.core.common.util.TypeConversion; -import jdk.graal.compiler.core.common.util.UnsafeArrayTypeWriter; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; @@ -37,6 +35,8 @@ import com.oracle.svm.core.util.ByteArrayReader; import com.oracle.svm.core.util.VMError; +import jdk.graal.compiler.core.common.util.TypeConversion; +import jdk.graal.compiler.core.common.util.UnsafeArrayTypeWriter; import jdk.vm.ci.meta.ResolvedJavaField; /** Legacy implementation, only used by other legacy code (see GR-44538). */ @@ -80,14 +80,14 @@ public static void writeFieldsInfo(UnsafeArrayTypeWriter writeBuffer, Collection for (ResolvedJavaField resolvedJavaField : inHotSpotFieldOrder(sfields)) { if (resolvedJavaField instanceof SharedField) { final SharedField field = (SharedField) resolvedJavaField; - if (!field.isWritten() && field.isValueAvailable()) { - /* I am only interested in fields that are not constants. */ - continue; - } if (!field.isAccessed()) { /* I am only interested in fields that are used. */ continue; } + if (!field.isWritten() && field.isValueAvailable()) { + /* I am only interested in fields that are not constants. */ + continue; + } writeField(field, writeBuffer); } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageHeapScanner.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageHeapScanner.java index 83970e32cd64..d75382b40d3a 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageHeapScanner.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageHeapScanner.java @@ -51,7 +51,7 @@ import com.oracle.svm.core.util.VMError; import com.oracle.svm.hosted.ImageClassLoader; import com.oracle.svm.hosted.ameta.AnalysisConstantReflectionProvider; -import com.oracle.svm.hosted.ameta.ReadableJavaField; +import com.oracle.svm.hosted.ameta.FieldValueInterceptionSupport; import com.oracle.svm.hosted.classinitialization.SimulateClassInitializerSupport; import com.oracle.svm.hosted.methodhandles.MethodHandleFeature; import com.oracle.svm.hosted.reflect.ReflectionHostedSupport; @@ -75,6 +75,7 @@ public class SVMImageHeapScanner extends ImageHeapScanner { private final MethodHandleFeature methodHandleSupport; private final Class directMethodHandleClass; private final VarHandleFeature varHandleSupport; + private final FieldValueInterceptionSupport fieldValueInterceptionSupport; @SuppressWarnings("this-escape") public SVMImageHeapScanner(BigBang bb, ImageHeap imageHeap, ImageClassLoader loader, AnalysisMetaAccess metaAccess, @@ -92,6 +93,7 @@ public SVMImageHeapScanner(BigBang bb, ImageHeap imageHeap, ImageClassLoader loa methodHandleSupport = ImageSingletons.lookup(MethodHandleFeature.class); directMethodHandleClass = getClass("java.lang.invoke.DirectMethodHandle"); varHandleSupport = ImageSingletons.lookup(VarHandleFeature.class); + fieldValueInterceptionSupport = FieldValueInterceptionSupport.singleton(); } public static ImageHeapScanner instance() { @@ -111,7 +113,7 @@ protected ImageHeapConstant getOrCreateImageHeapConstant(JavaConstant javaConsta @Override public boolean isValueAvailable(AnalysisField field) { - return ReadableJavaField.isValueAvailable(field); + return fieldValueInterceptionSupport.isValueAvailable(field); } /** diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedField.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedField.java index f397d729b7a2..f13a5ef104e3 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedField.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedField.java @@ -24,15 +24,14 @@ */ package com.oracle.svm.hosted.meta; -import java.lang.reflect.Field; - import com.oracle.graal.pointsto.infrastructure.OriginalFieldProvider; import com.oracle.graal.pointsto.infrastructure.WrappedJavaField; import com.oracle.graal.pointsto.meta.AnalysisField; import com.oracle.svm.core.meta.SharedField; -import com.oracle.svm.hosted.ameta.ReadableJavaField; +import com.oracle.svm.hosted.ameta.FieldValueInterceptionSupport; import jdk.vm.ci.meta.JavaKind; +import jdk.vm.ci.meta.ResolvedJavaField; /** * Store the compile-time information for a field in the Substrate VM, such as the field offset. @@ -112,7 +111,7 @@ public boolean isWritten() { @Override public boolean isValueAvailable() { - return ReadableJavaField.isValueAvailable(wrapped); + return FieldValueInterceptionSupport.singleton().isValueAvailable(wrapped); } @Override @@ -166,7 +165,7 @@ public JavaKind getStorageKind() { } @Override - public Field getJavaField() { - return wrapped.getJavaField(); + public ResolvedJavaField unwrapTowardsOriginalField() { + return wrapped; } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/SharedConstantFieldProvider.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/SharedConstantFieldProvider.java index af5e9b8b83c3..4f097d792e9f 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/SharedConstantFieldProvider.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/SharedConstantFieldProvider.java @@ -24,7 +24,6 @@ */ package com.oracle.svm.hosted.meta; -import jdk.graal.compiler.core.common.spi.JavaConstantFieldProvider; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; @@ -32,8 +31,9 @@ import com.oracle.graal.pointsto.meta.AnalysisField; import com.oracle.svm.core.meta.MethodPointer; import com.oracle.svm.hosted.SVMHost; -import com.oracle.svm.hosted.ameta.ReadableJavaField; +import com.oracle.svm.hosted.ameta.FieldValueInterceptionSupport; +import jdk.graal.compiler.core.common.spi.JavaConstantFieldProvider; import jdk.vm.ci.meta.JavaConstant; import jdk.vm.ci.meta.JavaKind; import jdk.vm.ci.meta.MetaAccessProvider; @@ -44,6 +44,7 @@ public abstract class SharedConstantFieldProvider extends JavaConstantFieldProvi protected final UniverseMetaAccess metaAccess; protected final SVMHost hostVM; + protected final FieldValueInterceptionSupport fieldValueInterceptionSupport = FieldValueInterceptionSupport.singleton(); public SharedConstantFieldProvider(MetaAccessProvider metaAccess, SVMHost hostVM) { super(metaAccess); @@ -51,14 +52,6 @@ public SharedConstantFieldProvider(MetaAccessProvider metaAccess, SVMHost hostVM this.hostVM = hostVM; } - @Override - public T readConstantField(ResolvedJavaField field, ConstantFieldTool analysisTool) { - if (!ReadableJavaField.isValueAvailable(asAnalysisField(field))) { - return null; - } - return super.readConstantField(field, analysisTool); - } - protected abstract AnalysisField asAnalysisField(ResolvedJavaField field); @Override @@ -83,10 +76,24 @@ public boolean isStableField(ResolvedJavaField field, ConstantFieldTool tool) } private boolean allowConstantFolding(ResolvedJavaField field) { - if (field.isStatic() && !isClassInitialized(field) && !(asAnalysisField(field).getWrapped() instanceof ReadableJavaField)) { + var aField = asAnalysisField(field); + + /* + * This code should run as late as possible, because it has side effects. So we only do it + * after we have already checked that the field is `final` or `stable`. It marks the + * declaring class of the field as reachable, in order to trigger computation of automatic + * substitutions. It also ensures that the class is initialized (if the class is registered + * for initialization at build time) before any constant folding of static fields is + * attempted. + */ + if (!fieldValueInterceptionSupport.isValueAvailable(aField)) { + return false; + } + + if (field.isStatic() && !isClassInitialized(field) && !fieldValueInterceptionSupport.hasFieldValueTransformer(aField)) { /* * The class is not initialized at image build time, so we do not have a static field - * value to constant fold. Note that a ReadableJavaField is able to provide a field + * value to constant fold. Note that a FieldValueTransformer is able to provide a field * value also for non-initialized classes. */ return false; 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 9aace5aa2b1a..1ceab52617d5 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 @@ -91,7 +91,7 @@ import com.oracle.svm.hosted.FeatureImpl.BeforeAnalysisAccessImpl; import com.oracle.svm.hosted.HostedConfiguration; import com.oracle.svm.hosted.NativeImageOptions; -import com.oracle.svm.hosted.ameta.ReadableJavaField; +import com.oracle.svm.hosted.ameta.FieldValueInterceptionSupport; import com.oracle.svm.hosted.annotation.CustomSubstitutionMethod; import com.oracle.svm.hosted.config.HybridLayout; import com.oracle.svm.hosted.heap.PodSupport; @@ -1133,13 +1133,14 @@ private static boolean excludeFromReferenceMap(HostedField field) { } private void processFieldLocations() { + var fieldValueInterceptionSupport = FieldValueInterceptionSupport.singleton(); for (HostedField hField : hUniverse.fields.values()) { AnalysisField aField = hField.wrapped; if (aField.wrapped instanceof ComputedValueField) { ((ComputedValueField) aField.wrapped).processSubstrate(hMetaAccess); } - if (!hField.hasLocation() && Modifier.isStatic(hField.getModifiers()) && !aField.isWritten() && ReadableJavaField.isValueAvailable(aField)) { + if (hField.isReachable() && !hField.hasLocation() && Modifier.isStatic(hField.getModifiers()) && !aField.isWritten() && fieldValueInterceptionSupport.isValueAvailable(aField)) { hField.setUnmaterializedStaticConstant(); } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/SharedGraphBuilderPhase.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/SharedGraphBuilderPhase.java index 824f7feb9c50..aa017bdf5f4e 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/SharedGraphBuilderPhase.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/SharedGraphBuilderPhase.java @@ -541,7 +541,7 @@ private static Class findResolutionError Class[] searchSignature = signatureToClasses(searchMethod); Class searchReturnType = null; if (searchMethod.getSignature().getReturnType(null) instanceof ResolvedJavaType) { - searchReturnType = OriginalClassProvider.getJavaClass((ResolvedJavaType) searchMethod.getSignature().getReturnType(null)); + searchReturnType = OriginalClassProvider.getJavaClass(searchMethod.getSignature().getReturnType(null)); } Class declaringClass = OriginalClassProvider.getJavaClass(declaringType); @@ -585,7 +585,7 @@ private static Class[] signatureToClasses(JavaMethod method) { for (int i = 0; i < paramCount; i++) { JavaType parameterType = method.getSignature().getParameterType(i, null); if (parameterType instanceof ResolvedJavaType) { - result[i] = OriginalClassProvider.getJavaClass((ResolvedJavaType) parameterType); + result[i] = OriginalClassProvider.getJavaClass(parameterType); } } return result; diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/snippets/ReflectionPlugins.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/snippets/ReflectionPlugins.java index e048e06e2e86..3790e75bbe5d 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/snippets/ReflectionPlugins.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/snippets/ReflectionPlugins.java @@ -644,7 +644,7 @@ private Object unboxObjectConstant(GraphBuilderContext b, JavaConstant argConsta */ @SuppressWarnings("unchecked") private T getIntrinsic(GraphBuilderContext context, T element) { - if (reason == ParsingReason.UnsafeSubstitutionAnalysis || reason == ParsingReason.EarlyClassInitializerAnalysis) { + if (reason == ParsingReason.AutomaticUnsafeTransformation || reason == ParsingReason.EarlyClassInitializerAnalysis) { /* We are analyzing the static initializers and should always intrinsify. */ return element; } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/snippets/SubstrateGraphBuilderPlugins.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/snippets/SubstrateGraphBuilderPlugins.java index 909a466dc167..a36c127bfb6f 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/snippets/SubstrateGraphBuilderPlugins.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/snippets/SubstrateGraphBuilderPlugins.java @@ -1320,7 +1320,7 @@ protected ValueNode getFieldOffset(GraphBuilderContext b, ResolvedJavaField fiel if (field instanceof AnalysisField) { ((AnalysisField) field).registerAsUnsafeAccessed(nonNullReason(b.getGraph().currentNodeSourcePosition())); } - return LazyConstantNode.create(StampFactory.forKind(JavaKind.Long), new FieldOffsetConstantProvider(((OriginalFieldProvider) field).getJavaField()), b); + return LazyConstantNode.create(StampFactory.forKind(JavaKind.Long), new FieldOffsetConstantProvider(OriginalFieldProvider.getJavaField(field)), b); } @Override diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/AnnotatedField.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/AnnotatedField.java index ef199ce0def2..cab58f3f8fb0 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/AnnotatedField.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/AnnotatedField.java @@ -26,9 +26,9 @@ import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; -import java.lang.reflect.Field; import com.oracle.graal.pointsto.infrastructure.OriginalFieldProvider; +import com.oracle.svm.core.BuildPhaseProvider; import com.oracle.svm.hosted.ameta.ReadableJavaField; import com.oracle.svm.hosted.annotation.AnnotationValue; import com.oracle.svm.hosted.annotation.AnnotationWrapper; @@ -66,12 +66,12 @@ public JavaConstant readValue(ClassInitializationSupport classInitializationSupp } @Override - public boolean isValueAvailableBeforeAnalysis() { + public boolean isValueAvailable() { /* * We assume that fields for which this class is used always have altered behavior for which - * constant folding is not valid. + * constant folding before or during analysis is not valid. */ - return false; + return BuildPhaseProvider.isAnalysisFinished(); } @Override @@ -122,8 +122,8 @@ public String toString() { } @Override - public Field getJavaField() { - return OriginalFieldProvider.getJavaField(original); + public ResolvedJavaField unwrapTowardsOriginalField() { + return original; } @Override diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/AnnotationSubstitutionProcessor.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/AnnotationSubstitutionProcessor.java index 8970ee6efc23..053b1b94d5ca 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/AnnotationSubstitutionProcessor.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/AnnotationSubstitutionProcessor.java @@ -50,7 +50,6 @@ import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; -import org.graalvm.nativeimage.hosted.FieldValueTransformer; import com.oracle.graal.pointsto.BigBang; import com.oracle.graal.pointsto.infrastructure.SubstitutionProcessor; @@ -107,7 +106,7 @@ public class AnnotationSubstitutionProcessor extends SubstitutionProcessor { private final Map methodSubstitutions; private final Map polymorphicMethodSubstitutions; private final Map fieldSubstitutions; - private ClassInitializationSupport classInitializationSupport; + private final ClassInitializationSupport classInitializationSupport; public AnnotationSubstitutionProcessor(ImageClassLoader imageClassLoader, MetaAccessProvider metaAccess, ClassInitializationSupport classInitializationSupport) { this.imageClassLoader = imageClassLoader; @@ -121,20 +120,6 @@ public AnnotationSubstitutionProcessor(ImageClassLoader imageClassLoader, MetaAc fieldSubstitutions = new ConcurrentHashMap<>(); } - public void registerFieldValueTransformer(Field reflectionField, FieldValueTransformer transformer) { - ResolvedJavaField field = metaAccess.lookupJavaField(reflectionField); - boolean isFinal = field.isFinal(); - ComputedValueField computedValueField = new ComputedValueField(field, field, Kind.Custom, reflectionField.getType(), transformer, null, null, isFinal, false); - ResolvedJavaField existingSubstitution = fieldSubstitutions.put(field, computedValueField); - - if (existingSubstitution != null) { - String reason = existingSubstitution.equals(field) - ? "The field was already accessed by the static analysis. The transformer must be registered earlier, before the static analysis sees a reference to the field for the first time." - : "A field value transformer is already registered for this field."; - throw UserError.abort("Cannot register a field value transformer for field %s: %s", field, reason); - } - } - @Override public ResolvedJavaType lookup(ResolvedJavaType type) { Delete deleteAnnotation = deleteAnnotations.get(type); @@ -200,12 +185,7 @@ public ResolvedJavaField lookup(ResolvedJavaField field) { throw new DeletedElementException(deleteErrorMessage(field, deleteAnnotation, true)); } - /* - * If there is no substitution registered yet, put in the field itself as a marker that the - * field was used in a lookup. Registering a substitution after a lookup was done is not - * allowed because that means the substitution was missed in the prior lookup. - */ - ResolvedJavaField existing = fieldSubstitutions.putIfAbsent(field, field); + ResolvedJavaField existing = fieldSubstitutions.get(field); return existing != null ? existing : field; } @@ -996,12 +976,12 @@ private ResolvedJavaField fieldValueRecomputation(Class originalClass, Resolv } Class transformedValueAllowedType = getTargetClass(annotatedField.getType()); - return new ComputedValueField(original, annotated, kind, transformedValueAllowedType, null, targetClass, targetName, isFinal, disableCaching); + return ComputedValueField.create(original, annotated, kind, transformedValueAllowedType, null, targetClass, targetName, isFinal, disableCaching); } protected void reinitializeField(Field annotatedField) { ResolvedJavaField annotated = metaAccess.lookupJavaField(annotatedField); - ComputedValueField alias = new ComputedValueField(annotated, annotated, Kind.Reset, annotatedField.getDeclaringClass(), "", false); + ComputedValueField alias = ComputedValueField.create(annotated, annotated, Kind.Reset, annotatedField.getDeclaringClass(), "", false); register(fieldSubstitutions, annotated, annotated, alias); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/UnsafeAutomaticSubstitutionProcessor.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/AutomaticUnsafeTransformationSupport.java similarity index 73% rename from substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/UnsafeAutomaticSubstitutionProcessor.java rename to substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/AutomaticUnsafeTransformationSupport.java index 83754b3fdf1a..088511b32352 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/UnsafeAutomaticSubstitutionProcessor.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/AutomaticUnsafeTransformationSupport.java @@ -37,12 +37,38 @@ import java.util.ArrayList; import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; import java.util.function.Supplier; import java.util.stream.Collectors; +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.hosted.FieldValueTransformer; + +import com.oracle.graal.pointsto.BigBang; +import com.oracle.graal.pointsto.api.DefaultUnsafePartition; +import com.oracle.graal.pointsto.phases.NoClassInitializationPlugin; +import com.oracle.graal.pointsto.util.GraalAccess; +import com.oracle.svm.core.ParsingReason; +import com.oracle.svm.core.annotate.RecomputeFieldValue; +import com.oracle.svm.core.annotate.RecomputeFieldValue.Kind; +import com.oracle.svm.core.fieldvaluetransformer.ArrayBaseOffsetFieldValueTransformer; +import com.oracle.svm.core.fieldvaluetransformer.ArrayIndexScaleFieldValueTransformer; +import com.oracle.svm.core.fieldvaluetransformer.ArrayIndexShiftFieldValueTransformer; +import com.oracle.svm.core.fieldvaluetransformer.FieldOffsetFieldValueTransformer; +import com.oracle.svm.core.fieldvaluetransformer.StaticFieldBaseFieldValueTransformer; +import com.oracle.svm.core.option.HostedOptionKey; +import com.oracle.svm.core.option.SubstrateOptionsParser; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.hosted.FallbackFeature; +import com.oracle.svm.hosted.ImageClassLoader; +import com.oracle.svm.hosted.SVMHost; +import com.oracle.svm.hosted.ameta.FieldValueInterceptionSupport; +import com.oracle.svm.hosted.classinitialization.ClassInitializerGraphBuilderPhase; +import com.oracle.svm.hosted.phases.ConstantFoldLoadFieldPlugin; +import com.oracle.svm.hosted.snippets.ReflectionPlugins; +import com.oracle.svm.util.LogUtils; +import com.oracle.svm.util.ReflectionUtil; + import jdk.graal.compiler.api.replacements.SnippetReflectionProvider; import jdk.graal.compiler.debug.DebugContext; import jdk.graal.compiler.debug.DebugContext.Builder; @@ -72,29 +98,6 @@ import jdk.graal.compiler.phases.common.CanonicalizerPhase; import jdk.graal.compiler.phases.tiers.HighTierContext; import jdk.graal.compiler.phases.util.Providers; -import org.graalvm.nativeimage.ImageSingletons; - -import com.oracle.graal.pointsto.infrastructure.SubstitutionProcessor; -import com.oracle.graal.pointsto.meta.AnalysisType; -import com.oracle.graal.pointsto.phases.NoClassInitializationPlugin; -import com.oracle.graal.pointsto.util.GraalAccess; -import com.oracle.svm.core.ParsingReason; -import com.oracle.svm.core.annotate.RecomputeFieldValue; -import com.oracle.svm.core.annotate.RecomputeFieldValue.Kind; -import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; -import com.oracle.svm.core.feature.InternalFeature; -import com.oracle.svm.core.option.HostedOptionKey; -import com.oracle.svm.core.option.SubstrateOptionsParser; -import com.oracle.svm.core.util.VMError; -import com.oracle.svm.hosted.FallbackFeature; -import com.oracle.svm.hosted.FeatureImpl.DuringAnalysisAccessImpl; -import com.oracle.svm.hosted.ImageClassLoader; -import com.oracle.svm.hosted.SVMHost; -import com.oracle.svm.hosted.classinitialization.ClassInitializerGraphBuilderPhase; -import com.oracle.svm.hosted.phases.ConstantFoldLoadFieldPlugin; -import com.oracle.svm.hosted.snippets.ReflectionPlugins; -import com.oracle.svm.util.LogUtils; - import jdk.vm.ci.meta.JavaConstant; import jdk.vm.ci.meta.JavaKind; import jdk.vm.ci.meta.MetaAccessProvider; @@ -103,82 +106,56 @@ import jdk.vm.ci.meta.ResolvedJavaMethod; import jdk.vm.ci.meta.ResolvedJavaType; -@AutomaticallyRegisteredFeature -class AutomaticSubstitutionFeature implements InternalFeature { - - @Override - public void duringAnalysis(DuringAnalysisAccess access) { - DuringAnalysisAccessImpl accessImpl = (DuringAnalysisAccessImpl) access; - UnsafeAutomaticSubstitutionProcessor automaticSubstitutions = accessImpl.getHostVM().getAutomaticSubstitutionProcessor(); - automaticSubstitutions.processComputedValueFields(accessImpl); - } -} - /** - * This class tries to registered automatic substitutions for field offset, array base, array index + * This class tries to registered field value transformer for field offset, array base, array index * scale and array index shift unsafe computations. */ -public class UnsafeAutomaticSubstitutionProcessor extends SubstitutionProcessor { +public class AutomaticUnsafeTransformationSupport { private static final int BASIC_LEVEL = 1; private static final int INFO_LEVEL = 2; private static final int DEBUG_LEVEL = 3; static class Options { - - @Option(help = "Unsafe automatic substitutions logging level: Disabled=0, Basic=1, Info=2, Debug=3.)")// - static final HostedOptionKey UnsafeAutomaticSubstitutionsLogLevel = new HostedOptionKey<>(BASIC_LEVEL); + @Option(help = "Automatic unsafe field value transformation logging level: Disabled=0, Basic=1, Info=2, Debug=3.)") // + static final HostedOptionKey AutomaticUnsafeTransformationLogLevel = new HostedOptionKey<>(BASIC_LEVEL); } private final AnnotationSubstitutionProcessor annotationSubstitutions; - private final Map fieldSubstitutions; private final List suppressWarnings; - private static ResolvedJavaType resolvedUnsafeClass; - private static ResolvedJavaType resolvedSunMiscUnsafeClass; + private final ResolvedJavaType jdkInternalUnsafeType; + private final ResolvedJavaType sunMiscUnsafeType; - private ResolvedJavaMethod unsafeStaticFieldOffsetMethod; - private ResolvedJavaMethod unsafeStaticFieldBaseMethod; - private ResolvedJavaMethod unsafeObjectFieldOffsetFieldMethod; - private ResolvedJavaMethod sunMiscUnsafeObjectFieldOffsetMethod; - private ResolvedJavaMethod unsafeObjectFieldOffsetClassStringMethod; - private ResolvedJavaMethod unsafeArrayBaseOffsetMethod; - private ResolvedJavaMethod unsafeArrayIndexScaleMethod; - private ResolvedJavaMethod integerNumberOfLeadingZerosMethod; + private final ResolvedJavaMethod unsafeStaticFieldOffsetMethod; + private final ResolvedJavaMethod unsafeStaticFieldBaseMethod; + private final ResolvedJavaMethod unsafeObjectFieldOffsetFieldMethod; + private final ResolvedJavaMethod sunMiscUnsafeObjectFieldOffsetMethod; + private final ResolvedJavaMethod unsafeObjectFieldOffsetClassStringMethod; + private final ResolvedJavaMethod unsafeArrayBaseOffsetMethod; + private final ResolvedJavaMethod unsafeArrayIndexScaleMethod; + private final ResolvedJavaMethod integerNumberOfLeadingZerosMethod; - private HashSet neverInlineSet = new HashSet<>(); - private HashSet noCheckedExceptionsSet = new HashSet<>(); + private final HashSet neverInlineSet = new HashSet<>(); + private final HashSet noCheckedExceptionsSet = new HashSet<>(); - private Plugins plugins; + private final Plugins plugins; private final OptionValues options; - private final SnippetReflectionProvider snippetReflection; - public UnsafeAutomaticSubstitutionProcessor(OptionValues options, AnnotationSubstitutionProcessor annotationSubstitutions, SnippetReflectionProvider snippetReflection) { + public AutomaticUnsafeTransformationSupport(OptionValues options, AnnotationSubstitutionProcessor annotationSubstitutions, ImageClassLoader loader) { this.options = options; - this.snippetReflection = snippetReflection; this.annotationSubstitutions = annotationSubstitutions; - this.fieldSubstitutions = new ConcurrentHashMap<>(); - this.suppressWarnings = new ArrayList<>(); - } - - public void init(ImageClassLoader loader, MetaAccessProvider originalMetaAccess) { - ResolvedJavaMethod atomicIntegerFieldUpdaterNewUpdaterMethod; - ResolvedJavaMethod atomicLongFieldUpdaterNewUpdaterMethod; - ResolvedJavaMethod atomicReferenceFieldUpdaterNewUpdaterMethod; - - ResolvedJavaMethod fieldSetAccessibleMethod; - ResolvedJavaMethod fieldGetMethod; + MetaAccessProvider originalMetaAccess = GraalAccess.getOriginalProviders().getMetaAccess(); try { - Method fieldSetAccessible = Field.class.getMethod("setAccessible", boolean.class); - fieldSetAccessibleMethod = originalMetaAccess.lookupJavaMethod(fieldSetAccessible); + ResolvedJavaMethod fieldSetAccessibleMethod = originalMetaAccess.lookupJavaMethod(fieldSetAccessible); neverInlineSet.add(fieldSetAccessibleMethod); Method fieldGet = Field.class.getMethod("get", Object.class); - fieldGetMethod = originalMetaAccess.lookupJavaMethod(fieldGet); + ResolvedJavaMethod fieldGetMethod = originalMetaAccess.lookupJavaMethod(fieldGet); neverInlineSet.add(fieldGetMethod); /* @@ -186,39 +163,31 @@ public void init(ImageClassLoader loader, MetaAccessProvider originalMetaAccess) * There is no need to analyze these calls because VarHandle accesses are intrinsified * to simple array and field access nodes during inlining before analysis. */ - for (Method method : loader.findClassOrFail("java.lang.invoke.VarHandles").getDeclaredMethods()) { + for (Method method : ReflectionUtil.lookupClass(false, "java.lang.invoke.VarHandles").getDeclaredMethods()) { neverInlineSet.add(originalMetaAccess.lookupJavaMethod(method)); } - Class unsafeClass; - Class sunMiscUnsafeClass; - - try { - sunMiscUnsafeClass = Class.forName("sun.misc.Unsafe"); - unsafeClass = Class.forName("jdk.internal.misc.Unsafe"); - } catch (ClassNotFoundException cnfe) { - throw VMError.shouldNotReachHere(cnfe); - } - - resolvedUnsafeClass = originalMetaAccess.lookupJavaType(unsafeClass); - resolvedSunMiscUnsafeClass = originalMetaAccess.lookupJavaType(sunMiscUnsafeClass); + Class sunMiscUnsafeClass = ReflectionUtil.lookupClass(false, "sun.misc.Unsafe"); + sunMiscUnsafeType = originalMetaAccess.lookupJavaType(sunMiscUnsafeClass); + Class jdkInternalUnsafeClass = jdk.internal.misc.Unsafe.class; + jdkInternalUnsafeType = originalMetaAccess.lookupJavaType(jdkInternalUnsafeClass); - Method unsafeStaticFieldOffset = unsafeClass.getMethod("staticFieldOffset", Field.class); + Method unsafeStaticFieldOffset = jdkInternalUnsafeClass.getMethod("staticFieldOffset", Field.class); unsafeStaticFieldOffsetMethod = originalMetaAccess.lookupJavaMethod(unsafeStaticFieldOffset); noCheckedExceptionsSet.add(unsafeStaticFieldOffsetMethod); neverInlineSet.add(unsafeStaticFieldOffsetMethod); - Method unsafeStaticFieldBase = unsafeClass.getMethod("staticFieldBase", Field.class); + Method unsafeStaticFieldBase = jdkInternalUnsafeClass.getMethod("staticFieldBase", Field.class); unsafeStaticFieldBaseMethod = originalMetaAccess.lookupJavaMethod(unsafeStaticFieldBase); noCheckedExceptionsSet.add(unsafeStaticFieldBaseMethod); neverInlineSet.add(unsafeStaticFieldBaseMethod); - Method unsafeObjectFieldOffset = unsafeClass.getMethod("objectFieldOffset", java.lang.reflect.Field.class); + Method unsafeObjectFieldOffset = jdkInternalUnsafeClass.getMethod("objectFieldOffset", java.lang.reflect.Field.class); unsafeObjectFieldOffsetFieldMethod = originalMetaAccess.lookupJavaMethod(unsafeObjectFieldOffset); noCheckedExceptionsSet.add(unsafeObjectFieldOffsetFieldMethod); neverInlineSet.add(unsafeObjectFieldOffsetFieldMethod); - Method unsafeObjectClassStringOffset = unsafeClass.getMethod("objectFieldOffset", java.lang.Class.class, String.class); + Method unsafeObjectClassStringOffset = jdkInternalUnsafeClass.getMethod("objectFieldOffset", java.lang.Class.class, String.class); unsafeObjectFieldOffsetClassStringMethod = originalMetaAccess.lookupJavaMethod(unsafeObjectClassStringOffset); noCheckedExceptionsSet.add(unsafeObjectFieldOffsetClassStringMethod); neverInlineSet.add(unsafeObjectFieldOffsetClassStringMethod); @@ -233,12 +202,12 @@ public void init(ImageClassLoader loader, MetaAccessProvider originalMetaAccess) noCheckedExceptionsSet.add(sunMiscUnsafeObjectFieldOffsetMethod); neverInlineSet.add(sunMiscUnsafeObjectFieldOffsetMethod); - Method unsafeArrayBaseOffset = unsafeClass.getMethod("arrayBaseOffset", java.lang.Class.class); + Method unsafeArrayBaseOffset = jdkInternalUnsafeClass.getMethod("arrayBaseOffset", java.lang.Class.class); unsafeArrayBaseOffsetMethod = originalMetaAccess.lookupJavaMethod(unsafeArrayBaseOffset); noCheckedExceptionsSet.add(unsafeArrayBaseOffsetMethod); neverInlineSet.add(unsafeArrayBaseOffsetMethod); - Method unsafeArrayIndexScale = unsafeClass.getMethod("arrayIndexScale", java.lang.Class.class); + Method unsafeArrayIndexScale = jdkInternalUnsafeClass.getMethod("arrayIndexScale", java.lang.Class.class); unsafeArrayIndexScaleMethod = originalMetaAccess.lookupJavaMethod(unsafeArrayIndexScale); noCheckedExceptionsSet.add(unsafeArrayIndexScaleMethod); neverInlineSet.add(unsafeArrayIndexScaleMethod); @@ -248,15 +217,15 @@ public void init(ImageClassLoader loader, MetaAccessProvider originalMetaAccess) neverInlineSet.add(integerNumberOfLeadingZerosMethod); Method atomicIntegerFieldUpdaterNewUpdater = java.util.concurrent.atomic.AtomicIntegerFieldUpdater.class.getMethod("newUpdater", Class.class, String.class); - atomicIntegerFieldUpdaterNewUpdaterMethod = originalMetaAccess.lookupJavaMethod(atomicIntegerFieldUpdaterNewUpdater); + ResolvedJavaMethod atomicIntegerFieldUpdaterNewUpdaterMethod = originalMetaAccess.lookupJavaMethod(atomicIntegerFieldUpdaterNewUpdater); neverInlineSet.add(atomicIntegerFieldUpdaterNewUpdaterMethod); Method atomicLongFieldUpdaterNewUpdater = java.util.concurrent.atomic.AtomicLongFieldUpdater.class.getMethod("newUpdater", Class.class, String.class); - atomicLongFieldUpdaterNewUpdaterMethod = originalMetaAccess.lookupJavaMethod(atomicLongFieldUpdaterNewUpdater); + ResolvedJavaMethod atomicLongFieldUpdaterNewUpdaterMethod = originalMetaAccess.lookupJavaMethod(atomicLongFieldUpdaterNewUpdater); neverInlineSet.add(atomicLongFieldUpdaterNewUpdaterMethod); Method atomicReferenceFieldUpdaterNewUpdater = java.util.concurrent.atomic.AtomicReferenceFieldUpdater.class.getMethod("newUpdater", Class.class, Class.class, String.class); - atomicReferenceFieldUpdaterNewUpdaterMethod = originalMetaAccess.lookupJavaMethod(atomicReferenceFieldUpdaterNewUpdater); + ResolvedJavaMethod atomicReferenceFieldUpdaterNewUpdaterMethod = originalMetaAccess.lookupJavaMethod(atomicReferenceFieldUpdaterNewUpdater); neverInlineSet.add(atomicReferenceFieldUpdaterNewUpdaterMethod); } catch (NoSuchMethodException e) { @@ -284,8 +253,8 @@ public void init(ImageClassLoader loader, MetaAccessProvider originalMetaAccess) plugins.setClassInitializationPlugin(classInitializationPlugin); FallbackFeature fallbackFeature = ImageSingletons.contains(FallbackFeature.class) ? ImageSingletons.lookup(FallbackFeature.class) : null; - ReflectionPlugins.registerInvocationPlugins(loader, snippetReflection, annotationSubstitutions, classInitializationPlugin, plugins.getInvocationPlugins(), null, - ParsingReason.UnsafeSubstitutionAnalysis, fallbackFeature); + ReflectionPlugins.registerInvocationPlugins(loader, GraalAccess.getOriginalSnippetReflection(), annotationSubstitutions, classInitializationPlugin, plugins.getInvocationPlugins(), null, + ParsingReason.AutomaticUnsafeTransformation, fallbackFeature); /* * Note: ConstantFoldLoadFieldPlugin should not be installed because it will disrupt @@ -298,58 +267,37 @@ public void init(ImageClassLoader loader, MetaAccessProvider originalMetaAccess) * Analyzing certain classes leads to false errors. We disable reporting for those classes * by default. */ - try { - suppressWarnings.add(originalMetaAccess.lookupJavaType(Class.forName("sun.security.provider.ByteArrayAccess"))); - } catch (ClassNotFoundException e) { - throw VMError.shouldNotReachHere(e); - } + suppressWarnings = List.of(originalMetaAccess.lookupJavaType(ReflectionUtil.lookupClass(false, "sun.security.provider.ByteArrayAccess"))); } /** - * Post-process computed value fields during analysis, e.g, like registering the target field of - * field offset computation as unsafe accessed. Operations that lookup fields/methods/types in - * the analysis universe cannot be executed while the substitution is computed. The call to - * {@link #computeSubstitutions} is made from - * com.oracle.graal.pointsto.meta.AnalysisUniverse#createType(ResolvedJavaType), before the type - * is published. Thus if there is a circular dependency between the processed type and one of - * the fields/methods/types that it needs to access it might lead to a deadlock in - * {@link AnalysisType} creation. The automatic substitutions for an {@link AnalysisType} are - * computed just after the type is created but before it is published to other threads so that - * all threads see the substitutions. + * We use {@link ComputedValueField} to hold information about transformations, because it is a + * convenient holder class to check for equality for manually registered substitutions. But now + * we need to convert it to a {@link FieldValueTransformer}, because that is the only mechanism + * we can install late while the analysis is already running. */ - void processComputedValueFields(DuringAnalysisAccessImpl access) { - for (ResolvedJavaField field : fieldSubstitutions.values()) { - if (field instanceof ComputedValueField) { - ComputedValueField cvField = (ComputedValueField) field; - - switch (cvField.getRecomputeValueKind()) { - case FieldOffset: - Field targetField = cvField.getTargetField(); - if (access.registerAsUnsafeAccessed(access.getMetaAccess().lookupJavaField(targetField), cvField)) { - access.requireAnalysisIteration(); - } - break; - } - } - } + private void addTransformation(BigBang bb, ResolvedJavaField original, ComputedValueField transformation) { + Class returnType = original.getType().getJavaKind().toJavaClass(); + + FieldValueTransformer transformer = switch (transformation.getRecomputeValueKind()) { + case ArrayBaseOffset -> new ArrayBaseOffsetFieldValueTransformer(transformation.getTargetClass(), returnType); + case ArrayIndexScale -> new ArrayIndexScaleFieldValueTransformer(transformation.getTargetClass(), returnType); + case ArrayIndexShift -> new ArrayIndexShiftFieldValueTransformer(transformation.getTargetClass(), returnType); + case FieldOffset -> createFieldOffsetFieldValueTransformer(bb, original, transformation.getTargetField()); + case StaticFieldBase -> new StaticFieldBaseFieldValueTransformer(transformation.getTargetField()); + default -> throw VMError.shouldNotReachHere("Unexpected kind: " + transformation); + }; + + FieldValueInterceptionSupport.singleton().registerFieldValueTransformer(original, transformer); } - private void addSubstitutionField(ResolvedJavaField original, ComputedValueField substitution) { - assert substitution != null; - assert !fieldSubstitutions.containsKey(original); - fieldSubstitutions.put(original, substitution); - } - - @Override - public ResolvedJavaField lookup(ResolvedJavaField field) { - if (fieldSubstitutions.containsKey(field)) { - return fieldSubstitutions.get(field); - } - return field; + private static FieldOffsetFieldValueTransformer createFieldOffsetFieldValueTransformer(BigBang bb, ResolvedJavaField original, Field targetField) { + bb.postTask(debugContext -> bb.registerAsUnsafeAccessed(bb.getMetaAccess().lookupJavaField(targetField), DefaultUnsafePartition.get(), original)); + return new FieldOffsetFieldValueTransformer(targetField, original.getType().getJavaKind().toJavaClass()); } @SuppressWarnings("try") - public void computeSubstitutions(SVMHost hostVM, ResolvedJavaType hostType) { + public void computeTransformations(BigBang bb, SVMHost hostVM, ResolvedJavaType hostType) { if (hostType.isArray()) { return; } @@ -371,7 +319,7 @@ public void computeSubstitutions(SVMHost hostVM, ResolvedJavaType hostType) { if (annotationSubstitutions.findFullSubstitution(hostType).isPresent()) { /* If the class is substituted clinit will be eliminated, so bail early. */ - reportSkippedSubstitution(hostType); + reportSkippedTransformation(hostType); return; } @@ -390,16 +338,16 @@ public void computeSubstitutions(SVMHost hostVM, ResolvedJavaType hostType) { for (Invoke invoke : clinitGraph.getInvokes()) { if (invoke.callTarget() instanceof MethodCallTargetNode) { if (isInvokeTo(invoke, unsafeStaticFieldBaseMethod)) { - processUnsafeFieldComputation(hostType, invoke, StaticFieldBase); + processUnsafeFieldComputation(bb, hostType, invoke, StaticFieldBase); } else if (isInvokeTo(invoke, unsafeObjectFieldOffsetFieldMethod) || isInvokeTo(invoke, sunMiscUnsafeObjectFieldOffsetMethod) || isInvokeTo(invoke, unsafeStaticFieldOffsetMethod)) { - processUnsafeFieldComputation(hostType, invoke, FieldOffset); + processUnsafeFieldComputation(bb, hostType, invoke, FieldOffset); } else if (isInvokeTo(invoke, unsafeObjectFieldOffsetClassStringMethod)) { - processUnsafeObjectFieldOffsetClassStringInvoke(hostType, invoke); + processUnsafeObjectFieldOffsetClassStringInvoke(bb, hostType, invoke); } else if (isInvokeTo(invoke, unsafeArrayBaseOffsetMethod)) { - processUnsafeArrayBaseOffsetInvoke(hostType, invoke); + processUnsafeArrayBaseOffsetInvoke(bb, hostType, invoke); } else if (isInvokeTo(invoke, unsafeArrayIndexScaleMethod)) { - processUnsafeArrayIndexScaleInvoke(hostType, invoke, clinitGraph); + processUnsafeArrayIndexScaleInvoke(bb, hostType, invoke, clinitGraph); } } } @@ -422,7 +370,7 @@ public void computeSubstitutions(SVMHost hostVM, ResolvedJavaType hostType) { *

* static final long fieldOffset = Unsafe.getUnsafe().staticFieldBase(X.class.getDeclaredField("f")); */ - private void processUnsafeFieldComputation(ResolvedJavaType type, Invoke invoke, Kind kind) { + private void processUnsafeFieldComputation(BigBang bb, ResolvedJavaType type, Invoke invoke, Kind kind) { List> unsuccessfulReasons = new ArrayList<>(); Class targetFieldHolder = null; @@ -432,7 +380,7 @@ private void processUnsafeFieldComputation(ResolvedJavaType type, Invoke invoke, ValueNode fieldArgumentNode = invoke.callTarget().arguments().get(1); JavaConstant fieldArgument = nodeAsConstant(fieldArgumentNode); if (fieldArgument != null) { - Field targetField = snippetReflection.asObject(Field.class, fieldArgument); + Field targetField = GraalAccess.getOriginalSnippetReflection().asObject(Field.class, fieldArgument); if (isValidField(invoke, targetField, unsuccessfulReasons, methodFormat)) { targetFieldHolder = targetField.getDeclaringClass(); targetFieldName = targetField.getName(); @@ -440,7 +388,7 @@ private void processUnsafeFieldComputation(ResolvedJavaType type, Invoke invoke, } else { unsuccessfulReasons.add(() -> "The argument of " + methodFormat + " is not a constant value or a field load that can be constant-folded."); } - processUnsafeFieldComputation(type, invoke, kind, unsuccessfulReasons, targetFieldHolder, targetFieldName); + processUnsafeFieldComputation(bb, type, invoke, kind, unsuccessfulReasons, targetFieldHolder, targetFieldName); } /** @@ -448,7 +396,7 @@ private void processUnsafeFieldComputation(ResolvedJavaType type, Invoke invoke, * {@link LoadFieldNode} it attempts to constant fold it. We manually constant fold just * specific nodes instead of globally installing {@link ConstantFoldLoadFieldPlugin} to avoid * folding load filed nodes that could disrupt other patterns that we try to match, e.g., like - * {@link #processArrayIndexShiftFromField(ResolvedJavaType, ResolvedJavaField, Class, StructuredGraph)}. + * {@link #processArrayIndexShiftFromField}. */ private JavaConstant nodeAsConstant(ValueNode node) { if (node.isConstant()) { @@ -500,7 +448,7 @@ private boolean isValidField(Invoke invoke, Field field, List> * * static final long fieldOffset = Unsafe.getUnsafe().objectFieldOffset(X.class, "f"); */ - private void processUnsafeObjectFieldOffsetClassStringInvoke(ResolvedJavaType type, Invoke unsafeObjectFieldOffsetInvoke) { + private void processUnsafeObjectFieldOffsetClassStringInvoke(BigBang bb, ResolvedJavaType type, Invoke unsafeObjectFieldOffsetInvoke) { List> unsuccessfulReasons = new ArrayList<>(); Class targetFieldHolder = null; @@ -508,7 +456,7 @@ private void processUnsafeObjectFieldOffsetClassStringInvoke(ResolvedJavaType ty ValueNode classArgument = unsafeObjectFieldOffsetInvoke.callTarget().arguments().get(1); if (classArgument.isConstant()) { - Class clazz = snippetReflection.asObject(Class.class, classArgument.asJavaConstant()); + Class clazz = GraalAccess.getOriginalSnippetReflection().asObject(Class.class, classArgument.asJavaConstant()); if (clazz == null) { unsuccessfulReasons.add(() -> "The Class argument of Unsafe.objectFieldOffset(Class, String) is a null constant."); } else { @@ -520,7 +468,7 @@ private void processUnsafeObjectFieldOffsetClassStringInvoke(ResolvedJavaType ty ValueNode nameArgument = unsafeObjectFieldOffsetInvoke.callTarget().arguments().get(2); if (nameArgument.isConstant()) { - String fieldName = snippetReflection.asObject(String.class, nameArgument.asJavaConstant()); + String fieldName = GraalAccess.getOriginalSnippetReflection().asObject(String.class, nameArgument.asJavaConstant()); if (fieldName == null) { unsuccessfulReasons.add(() -> "The String argument of Unsafe.objectFieldOffset(Class, String) is a null String."); } else { @@ -529,10 +477,11 @@ private void processUnsafeObjectFieldOffsetClassStringInvoke(ResolvedJavaType ty } else { unsuccessfulReasons.add(() -> "The name argument of Unsafe.objectFieldOffset(Class, String) is not a constant String."); } - processUnsafeFieldComputation(type, unsafeObjectFieldOffsetInvoke, FieldOffset, unsuccessfulReasons, targetFieldHolder, targetFieldName); + processUnsafeFieldComputation(bb, type, unsafeObjectFieldOffsetInvoke, FieldOffset, unsuccessfulReasons, targetFieldHolder, targetFieldName); } - private void processUnsafeFieldComputation(ResolvedJavaType type, Invoke invoke, Kind kind, List> unsuccessfulReasons, Class targetFieldHolder, String targetFieldName) { + private void processUnsafeFieldComputation(BigBang bb, ResolvedJavaType type, Invoke invoke, Kind kind, List> unsuccessfulReasons, Class targetFieldHolder, + String targetFieldName) { assert kind == FieldOffset || kind == StaticFieldBase; /* * If the value returned by the call to Unsafe.objectFieldOffset() is stored into a field @@ -548,11 +497,11 @@ private void processUnsafeFieldComputation(ResolvedJavaType type, Invoke invoke, ResolvedJavaField valueStoreField = result.valueStoreField; /* * If the target field holder and name, and the offset field were found try to register a - * substitution. + * transformation. */ if (targetFieldHolder != null && targetFieldName != null && valueStoreField != null) { - Supplier supplier = () -> new ComputedValueField(valueStoreField, null, kind, targetFieldHolder, targetFieldName, false); - if (tryAutomaticRecomputation(valueStoreField, kind, supplier)) { + Supplier supplier = () -> ComputedValueField.create(valueStoreField, null, kind, targetFieldHolder, targetFieldName, false); + if (tryAutomaticTransformation(bb, valueStoreField, kind, supplier)) { reportSuccessfulAutomaticRecomputation(kind, valueStoreField, targetFieldHolder.getName() + "." + targetFieldName); } } else { @@ -566,7 +515,7 @@ private void processUnsafeFieldComputation(ResolvedJavaType type, Invoke invoke, * * static final long arrayBaseOffsets = Unsafe.getUnsafe().arrayBaseOffset(byte[].class); */ - private void processUnsafeArrayBaseOffsetInvoke(ResolvedJavaType type, Invoke unsafeArrayBaseOffsetInvoke) { + private void processUnsafeArrayBaseOffsetInvoke(BigBang bb, ResolvedJavaType type, Invoke unsafeArrayBaseOffsetInvoke) { SnippetReflectionProvider snippetReflectionProvider = GraalAccess.getOriginalSnippetReflection(); List> unsuccessfulReasons = new ArrayList<>(); @@ -589,8 +538,8 @@ private void processUnsafeArrayBaseOffsetInvoke(ResolvedJavaType type, Invoke un ResolvedJavaField offsetField = result.valueStoreField; if (arrayClass != null && offsetField != null) { Class finalArrayClass = arrayClass; - Supplier supplier = () -> new ComputedValueField(offsetField, null, ArrayBaseOffset, finalArrayClass, null, true); - if (tryAutomaticRecomputation(offsetField, ArrayBaseOffset, supplier)) { + Supplier supplier = () -> ComputedValueField.create(offsetField, null, ArrayBaseOffset, finalArrayClass, null, true); + if (tryAutomaticTransformation(bb, offsetField, ArrayBaseOffset, supplier)) { reportSuccessfulAutomaticRecomputation(ArrayBaseOffset, offsetField, arrayClass.getCanonicalName()); } } else { @@ -607,7 +556,7 @@ private void processUnsafeArrayBaseOffsetInvoke(ResolvedJavaType type, Invoke un * * static final long byteArrayIndexScale = Unsafe.getUnsafe().arrayIndexScale(byte[].class); */ - private void processUnsafeArrayIndexScaleInvoke(ResolvedJavaType type, Invoke unsafeArrayIndexScale, StructuredGraph clinitGraph) { + private void processUnsafeArrayIndexScaleInvoke(BigBang bb, ResolvedJavaType type, Invoke unsafeArrayIndexScale, StructuredGraph clinitGraph) { SnippetReflectionProvider snippetReflectionProvider = GraalAccess.getOriginalSnippetReflection(); List> unsuccessfulReasons = new ArrayList<>(); @@ -634,19 +583,19 @@ private void processUnsafeArrayIndexScaleInvoke(ResolvedJavaType type, Invoke un if (arrayClass != null) { if (indexScaleField != null) { Class finalArrayClass = arrayClass; - Supplier supplier = () -> new ComputedValueField(indexScaleField, null, ArrayIndexScale, finalArrayClass, null, true); - if (tryAutomaticRecomputation(indexScaleField, ArrayIndexScale, supplier)) { + Supplier supplier = () -> ComputedValueField.create(indexScaleField, null, ArrayIndexScale, finalArrayClass, null, true); + if (tryAutomaticTransformation(bb, indexScaleField, ArrayIndexScale, supplier)) { reportSuccessfulAutomaticRecomputation(ArrayIndexScale, indexScaleField, arrayClass.getCanonicalName()); indexScaleComputed = true; - /* Try substitution for the array index shift computation if present. */ - indexShiftComputed = processArrayIndexShiftFromField(type, indexScaleField, arrayClass, clinitGraph); + /* Try transformation for the array index shift computation if present. */ + indexShiftComputed = processArrayIndexShiftFromField(bb, type, indexScaleField, arrayClass, clinitGraph); } } else { /* * The index scale is not stored into a field, it might be used to compute the index * shift. */ - indexShiftComputed = processArrayIndexShiftFromLocal(type, unsafeArrayIndexScale, arrayClass); + indexShiftComputed = processArrayIndexShiftFromLocal(bb, type, unsafeArrayIndexScale, arrayClass); } } if (!indexScaleComputed && !indexShiftComputed) { @@ -676,7 +625,7 @@ private void processUnsafeArrayIndexScaleInvoke(ResolvedJavaType type, Invoke un * It is important that constant folding is not enabled for the byteArrayIndexScale load because * it would break the link between the scale and shift computations. */ - private boolean processArrayIndexShiftFromField(ResolvedJavaType type, ResolvedJavaField indexScaleField, Class arrayClass, StructuredGraph clinitGraph) { + private boolean processArrayIndexShiftFromField(BigBang bb, ResolvedJavaType type, ResolvedJavaField indexScaleField, Class arrayClass, StructuredGraph clinitGraph) { for (LoadFieldNode load : clinitGraph.getNodes().filter(LoadFieldNode.class)) { if (load.field().equals(indexScaleField)) { /* @@ -684,7 +633,7 @@ private boolean processArrayIndexShiftFromField(ResolvedJavaType type, ResolvedJ * field was already found. The case where both scale and shift are computed is * uncommon. */ - if (processArrayIndexShift(type, arrayClass, load, true)) { + if (processArrayIndexShift(bb, type, arrayClass, load, true)) { /* * Return true as soon as an index shift computed from the index scale field is * found. It is very unlikely that there are multiple shift computations from @@ -713,13 +662,15 @@ private boolean processArrayIndexShiftFromField(ResolvedJavaType type, ResolvedJ * } * */ - private boolean processArrayIndexShiftFromLocal(ResolvedJavaType type, Invoke unsafeArrayIndexScale, Class arrayClass) { + private boolean processArrayIndexShiftFromLocal(BigBang bb, ResolvedJavaType type, Invoke unsafeArrayIndexScale, Class arrayClass) { /* Try to compute index shift. Report errors since the index scale field was not found. */ - return processArrayIndexShift(type, arrayClass, unsafeArrayIndexScale.asNode(), false); + return processArrayIndexShift(bb, type, arrayClass, unsafeArrayIndexScale.asNode(), false); } - /** Try to compute the arrayIndexShift. Return true if successful, false otherwise. */ - private boolean processArrayIndexShift(ResolvedJavaType type, Class arrayClass, ValueNode indexScaleValue, boolean silentFailure) { + /** + * Try to compute the arrayIndexShift. Return true if successful, false otherwise. + */ + private boolean processArrayIndexShift(BigBang bb, ResolvedJavaType type, Class arrayClass, ValueNode indexScaleValue, boolean silentFailure) { NodeIterable loadMethodCallTargetUsages = indexScaleValue.usages().filter(MethodCallTargetNode.class); for (MethodCallTargetNode methodCallTarget : loadMethodCallTargetUsages) { /* Iterate over all the calls that use the index scale value. */ @@ -753,8 +704,8 @@ private boolean processArrayIndexShift(ResolvedJavaType type, Class arrayClas if (indexShiftField != null) { ResolvedJavaField finalIndexShiftField = indexShiftField; - Supplier supplier = () -> new ComputedValueField(finalIndexShiftField, null, ArrayIndexShift, arrayClass, null, true); - if (tryAutomaticRecomputation(indexShiftField, ArrayIndexShift, supplier)) { + Supplier supplier = () -> ComputedValueField.create(finalIndexShiftField, null, ArrayIndexShift, arrayClass, null, true); + if (tryAutomaticTransformation(bb, indexShiftField, ArrayIndexShift, supplier)) { reportSuccessfulAutomaticRecomputation(ArrayIndexShift, indexShiftField, arrayClass.getCanonicalName()); return true; } @@ -807,7 +758,9 @@ private static boolean subNodeComputesLog2(SubNode subNode, Invoke numberOfLeadi * static final field where the unsafe value may be stored. */ static final class SearchResult { - /** The field where the value is stored, if found. */ + /** + * The field where the value is stored, if found. + */ final ResolvedJavaField valueStoreField; /** * Uses that can lead to the unsafe value having side effects that we didn't account for are @@ -838,7 +791,7 @@ static SearchResult didNotFindIllegalUse() { * returned. If the field is either not static or not final the method returns null and the * reason is recorded in the unsuccessfulReasons parameter. */ - private static SearchResult extractValueStoreField(ValueNode valueNode, Kind substitutionKind, List> unsuccessfulReasons) { + private SearchResult extractValueStoreField(ValueNode valueNode, Kind recomputeKind, List> unsuccessfulReasons) { ResolvedJavaField valueStoreField = null; boolean illegalUseFound = false; @@ -875,7 +828,7 @@ private static SearchResult extractValueStoreField(ValueNode valueNode, Kind sub return SearchResult.foundField(valueStoreField); } else { ResolvedJavaField valueStoreFieldFinal = valueStoreField; - Supplier message = () -> "The field " + valueStoreFieldFinal.format("%H.%n") + ", where the value produced by the " + kindAsString(substitutionKind) + + Supplier message = () -> "The field " + valueStoreFieldFinal.format("%H.%n") + ", where the value produced by the " + kindAsString(recomputeKind) + " computation is stored, is not" + (!valueStoreFieldFinal.isStatic() ? " static" : "") + (!valueStoreFieldFinal.isFinal() ? " final" : "") + "."; unsuccessfulReasons.add(message); /* Value is stored to a non static final field. */ @@ -898,7 +851,7 @@ private static SearchResult extractValueStoreField(ValueNode valueNode, Kind sub throw VMError.shouldNotReachHereUnexpectedInput(valueNode); // ExcludeFromJacocoGeneratedReport } Supplier message = () -> "Could not determine the field where the value produced by the " + producer + - " for the " + kindAsString(substitutionKind) + " computation is stored. The " + operation + + " for the " + kindAsString(recomputeKind) + " computation is stored. The " + operation + " is not directly followed by a field store or by a sign extend node followed directly by a field store. "; unsuccessfulReasons.add(message); return SearchResult.foundIllegalUse(); @@ -912,7 +865,7 @@ private static SearchResult extractValueStoreField(ValueNode valueNode, Kind sub * Determine if the valueNodeUsage parameter is an allowed usage of an offset, indexScale or * indexShift unsafe value. */ - private static boolean isAllowedUnsafeValueSink(Node valueNodeUsage) { + private boolean isAllowedUnsafeValueSink(Node valueNodeUsage) { if (valueNodeUsage instanceof FrameState) { /* * The frame state keeps track of the local variables and operand stack at a particular @@ -930,7 +883,7 @@ private static boolean isAllowedUnsafeValueSink(Node valueNodeUsage) { */ MethodCallTargetNode methodCallTarget = (MethodCallTargetNode) valueNodeUsage; ResolvedJavaType declaringClass = methodCallTarget.targetMethod().getDeclaringClass(); - if (declaringClass.equals(resolvedUnsafeClass) || declaringClass.equals(resolvedSunMiscUnsafeClass)) { + if (declaringClass.equals(jdkInternalUnsafeType) || declaringClass.equals(sunMiscUnsafeType)) { return true; } } @@ -938,19 +891,17 @@ private static boolean isAllowedUnsafeValueSink(Node valueNodeUsage) { } /** - * Try to register the automatic substitution for a field. Bail if the field was deleted or - * another substitution is detected. - * - * @param field stores the value of the recomputation, i.e., an offset, array idx scale or shift + * Try to register the automatic transformation for a field. Bail if the field was deleted or a + * conflicting substitution is detected. */ - private boolean tryAutomaticRecomputation(ResolvedJavaField field, Kind kind, Supplier substitutionSupplier) { + private boolean tryAutomaticTransformation(BigBang bb, ResolvedJavaField field, Kind kind, Supplier transformationSupplier) { if (annotationSubstitutions.isDeleted(field)) { String conflictingSubstitution = "The field " + field.format("%H.%n") + " is marked as deleted. "; reportConflictingSubstitution(field, kind, conflictingSubstitution); return false; } else { - ComputedValueField computedValueField = substitutionSupplier.get(); - Field targetField = computedValueField.getTargetField(); + ComputedValueField transformation = transformationSupplier.get(); + Field targetField = transformation.getTargetField(); if (targetField != null && annotationSubstitutions.isDeleted(targetField)) { String conflictingSubstitution = "The target field of " + field.format("%H.%n") + " is marked as deleted. "; reportSkippedSubstitution(field, kind, conflictingSubstitution); @@ -958,39 +909,30 @@ private boolean tryAutomaticRecomputation(ResolvedJavaField field, Kind kind, Su } Optional annotationSubstitution = annotationSubstitutions.findSubstitution(field); if (annotationSubstitution.isPresent()) { - /* An annotation substitutions detected. */ ResolvedJavaField substitutionField = annotationSubstitution.get(); - if (substitutionField instanceof ComputedValueField) { - ComputedValueField computedSubstitutionField = (ComputedValueField) substitutionField; - if (computedSubstitutionField.getRecomputeValueKind().equals(kind)) { - if (computedSubstitutionField.getTargetField().equals(computedValueField.getTargetField())) { + if (substitutionField instanceof ComputedValueField computedValueField) { + if (computedValueField.getRecomputeValueKind().equals(kind)) { + if (computedValueField.getTargetField().equals(transformation.getTargetField())) { /* * Skip the warning when the target field of the found manual * substitution differs from the target field of the discovered original * offset computation. This will avoid printing false positives for * substitutions like Target_java_lang_Class_Atomic.*Offset. */ - reportUnnecessarySubstitution(computedValueField, computedSubstitutionField); + reportUnnecessarySubstitution(transformation, computedValueField); } return false; - } else if (computedSubstitutionField.getRecomputeValueKind().equals(Kind.None)) { + } else if (computedValueField.getRecomputeValueKind().equals(Kind.None)) { /* * This is essentially an @Alias field. An @Alias for a field with an - * automatic recomputed value is allowed but the alias needs to be - * overwritten otherwise the value from the original field would be read. To - * do this a new recomputed value field is registered in the automatic - * substitution processor, which follows the annotation substitution - * processor in the substitutions chain. Thus, every time the substitutions - * chain is queried for the original field, e.g., in - * AnalysisUniverse.lookupAllowUnresolved(JavaField), the alias field is - * forwarded to the automatic substitution. + * automatic recomputed value is allowed. */ - addSubstitutionField(computedSubstitutionField, computedValueField); - reportOvewrittenSubstitution(substitutionField, kind, computedSubstitutionField.getAnnotated(), computedSubstitutionField.getRecomputeValueKind()); + addTransformation(bb, field, transformation); + reportOvewrittenSubstitution(substitutionField, kind, computedValueField.getAnnotated(), computedValueField.getRecomputeValueKind()); return true; } else { - String conflictingSubstitution = "Detected RecomputeFieldValue." + computedSubstitutionField.getRecomputeValueKind() + - " " + computedSubstitutionField.getAnnotated().format("%H.%n") + " substitution field. "; + String conflictingSubstitution = "Detected RecomputeFieldValue." + computedValueField.getRecomputeValueKind() + + " " + computedValueField.getAnnotated().format("%H.%n") + " substitution field. "; reportConflictingSubstitution(substitutionField, kind, conflictingSubstitution); return false; } @@ -1001,42 +943,42 @@ private boolean tryAutomaticRecomputation(ResolvedJavaField field, Kind kind, Su } } else { /* No other substitutions detected. */ - addSubstitutionField(field, computedValueField); + addTransformation(bb, field, transformation); return true; } } } - private static void reportSkippedSubstitution(ResolvedJavaType type) { - if (Options.UnsafeAutomaticSubstitutionsLogLevel.getValue() >= DEBUG_LEVEL) { - LogUtils.warning("Skipped automatic unsafe substitutions analysis for type " + type.getName() + + private static void reportSkippedTransformation(ResolvedJavaType type) { + if (Options.AutomaticUnsafeTransformationLogLevel.getValue() >= DEBUG_LEVEL) { + LogUtils.warning("Skipped automatic unsafe transformation analysis for type " + type.getName() + ". The entire type is substituted, therefore its class initializer is eliminated."); } } private static void reportUnnecessarySubstitution(ResolvedJavaField offsetField, ComputedValueField computedSubstitutionField) { - if (Options.UnsafeAutomaticSubstitutionsLogLevel.getValue() >= BASIC_LEVEL) { + if (Options.AutomaticUnsafeTransformationLogLevel.getValue() >= BASIC_LEVEL) { Kind kind = computedSubstitutionField.getRecomputeValueKind(); String kindStr = RecomputeFieldValue.class.getSimpleName() + "." + kind; String annotatedFieldStr = computedSubstitutionField.getAnnotated().format("%H.%n"); String offsetFieldStr = offsetField.format("%H.%n"); - String optionStr = SubstrateOptionsParser.commandArgument(Options.UnsafeAutomaticSubstitutionsLogLevel, "+"); - LogUtils.warning( - "Detected unnecessary %s %s substitution field for %s. The annotated field can be removed. This %s computation can be detected automatically. Use option -H:+%s=%s to print all automatically detected substitutions.", + String optionStr = SubstrateOptionsParser.commandArgument(Options.AutomaticUnsafeTransformationLogLevel, "+"); + LogUtils.warning("Detected unnecessary %s %s substitution field for %s. The annotated field can be removed. " + + "This %s computation can be detected automatically. Use option -H:+%s=%s to print all automatically detected substitutions.", kindStr, annotatedFieldStr, offsetFieldStr, kind, optionStr, INFO_LEVEL); } } - private static void reportSuccessfulAutomaticRecomputation(Kind substitutionKind, ResolvedJavaField substitutedField, String target) { - if (Options.UnsafeAutomaticSubstitutionsLogLevel.getValue() >= INFO_LEVEL) { - String substitutionKindStr = RecomputeFieldValue.class.getSimpleName() + "." + substitutionKind; + private static void reportSuccessfulAutomaticRecomputation(Kind recomputeKind, ResolvedJavaField substitutedField, String target) { + if (Options.AutomaticUnsafeTransformationLogLevel.getValue() >= INFO_LEVEL) { + String recomputeKindStr = RecomputeFieldValue.class.getSimpleName() + "." + recomputeKind; String substitutedFieldStr = substitutedField.format("%H.%n"); - LogUtils.info("%s substitution automatically registered for %s, target element %s.", substitutionKindStr, substitutedFieldStr, target); + LogUtils.info("%s substitution automatically registered for %s, target element %s.", recomputeKindStr, substitutedFieldStr, target); } } private static void reportOvewrittenSubstitution(ResolvedJavaField offsetField, Kind newKind, ResolvedJavaField overwrittenField, Kind overwrittenKind) { - if (Options.UnsafeAutomaticSubstitutionsLogLevel.getValue() >= INFO_LEVEL) { + if (Options.AutomaticUnsafeTransformationLogLevel.getValue() >= INFO_LEVEL) { String newKindStr = RecomputeFieldValue.class.getSimpleName() + "." + newKind; String overwrittenKindStr = RecomputeFieldValue.class.getSimpleName() + "." + overwrittenKind; String offsetFieldStr = offsetField.format("%H.%n"); @@ -1045,49 +987,49 @@ private static void reportOvewrittenSubstitution(ResolvedJavaField offsetField, } } - private static void reportConflictingSubstitution(ResolvedJavaField field, Kind substitutionKind, String conflictingSubstitution) { - if (Options.UnsafeAutomaticSubstitutionsLogLevel.getValue() >= BASIC_LEVEL) { + private static void reportConflictingSubstitution(ResolvedJavaField field, Kind recomputeKind, String conflictingSubstitution) { + if (Options.AutomaticUnsafeTransformationLogLevel.getValue() >= BASIC_LEVEL) { String fieldStr = field.format("%H.%n"); - String substitutionKindStr = RecomputeFieldValue.class.getSimpleName() + "." + substitutionKind; - LogUtils.warning( - "The %s substitution for %s could not be recomputed automatically because a conflicting substitution was detected. Conflicting substitution: %s. Add a %s manual substitution for %s.", - substitutionKindStr, fieldStr, conflictingSubstitution, substitutionKindStr, fieldStr); + String recomputeKindStr = RecomputeFieldValue.class.getSimpleName() + "." + recomputeKind; + LogUtils.warning("The %s substitution for %s could not be recomputed automatically because a conflicting substitution was detected. " + + "Conflicting substitution: %s. Add a %s manual substitution for %s.", + recomputeKindStr, fieldStr, conflictingSubstitution, recomputeKindStr, fieldStr); } } - private static void reportSkippedSubstitution(ResolvedJavaField field, Kind substitutionKind, String conflictingSubstitution) { - if (Options.UnsafeAutomaticSubstitutionsLogLevel.getValue() >= BASIC_LEVEL) { + private static void reportSkippedSubstitution(ResolvedJavaField field, Kind recomputeKind, String conflictingSubstitution) { + if (Options.AutomaticUnsafeTransformationLogLevel.getValue() >= BASIC_LEVEL) { String fieldStr = field.format("%H.%n"); - String substitutionKindStr = RecomputeFieldValue.class.getSimpleName() + "." + substitutionKind; + String recomputeKindStr = RecomputeFieldValue.class.getSimpleName() + "." + recomputeKind; LogUtils.warning("The %s substitution for %s could not be recomputed automatically because a conflicting substitution was detected. Conflicting substitution: %s.", - substitutionKindStr, fieldStr, conflictingSubstitution); + recomputeKindStr, fieldStr, conflictingSubstitution); } } - private void reportUnsuccessfulAutomaticRecomputation(ResolvedJavaType type, ResolvedJavaField computedField, Invoke invoke, Kind substitutionKind, List> reasons) { + private void reportUnsuccessfulAutomaticRecomputation(ResolvedJavaType type, ResolvedJavaField computedField, Invoke invoke, Kind recomputeKind, List> reasons) { String msg = ""; - if (Options.UnsafeAutomaticSubstitutionsLogLevel.getValue() >= BASIC_LEVEL) { - if (!suppressWarningsFor(type) || Options.UnsafeAutomaticSubstitutionsLogLevel.getValue() >= DEBUG_LEVEL) { - String substitutionKindStr = RecomputeFieldValue.class.getSimpleName() + "." + substitutionKind; + if (Options.AutomaticUnsafeTransformationLogLevel.getValue() >= BASIC_LEVEL) { + if (!suppressWarningsFor(type) || Options.AutomaticUnsafeTransformationLogLevel.getValue() >= DEBUG_LEVEL) { + String recomputeKindStr = RecomputeFieldValue.class.getSimpleName() + "." + recomputeKind; String invokeStr = invoke.callTarget().targetMethod().format("%H.%n(%p)"); - msg += substitutionKindStr + " automatic substitution failed. "; - msg += "The automatic substitution registration was attempted because "; - if (substitutionKind == ArrayIndexShift) { + msg += recomputeKindStr + " automatic field value transformation failed. "; + msg += "The automatic registration was attempted because "; + if (recomputeKind == ArrayIndexShift) { msg += "an " + ArrayIndexScale + " computation followed by a call to " + invokeStr + " "; } else { msg += "a call to " + invokeStr + " "; } - msg += "was detected in the static initializer of " + type.toJavaName() + ". "; + msg += "was detected in the class initializer of " + type.toJavaName() + ". "; if (computedField != null) { /* If the computed field is null then reasons will contain the details. */ - msg += "Add a " + substitutionKindStr + " manual substitution for " + computedField.format("%H.%n") + ". "; + msg += "Add a " + recomputeKindStr + " manual substitution for " + computedField.format("%H.%n") + ". "; } msg += "Detailed failure reason(s): " + reasons.stream().map(s -> s.get()).collect(Collectors.joining(", ")); } } - if (Options.UnsafeAutomaticSubstitutionsLogLevel.getValue() >= DEBUG_LEVEL) { + if (Options.AutomaticUnsafeTransformationLogLevel.getValue() >= DEBUG_LEVEL) { if (suppressWarningsFor(type)) { msg += "(This warning is suppressed by default because this type "; if (warningsAreWhiteListed(type)) { @@ -1107,8 +1049,8 @@ private void reportUnsuccessfulAutomaticRecomputation(ResolvedJavaType type, Res } } - private static String kindAsString(Kind substitutionKind) { - switch (substitutionKind) { + private static String kindAsString(Kind recomputeKind) { + switch (recomputeKind) { case FieldOffset: return "field offset"; case StaticFieldBase: @@ -1120,7 +1062,7 @@ private static String kindAsString(Kind substitutionKind) { case ArrayIndexShift: return "array index shift"; default: - throw VMError.shouldNotReachHere("Unexpected substitution kind: " + substitutionKind); + throw VMError.shouldNotReachHere("Unexpected kind: " + recomputeKind); } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/ComputedValueField.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/ComputedValueField.java index 353413539823..9062a321892b 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/ComputedValueField.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/ComputedValueField.java @@ -24,7 +24,6 @@ */ package com.oracle.svm.hosted.substitute; -import static com.oracle.svm.core.SubstrateUtil.toUnboxedClass; import static com.oracle.svm.core.annotate.RecomputeFieldValue.Kind.AtomicFieldUpdaterOffset; import static com.oracle.svm.core.annotate.RecomputeFieldValue.Kind.FieldOffset; import static com.oracle.svm.core.annotate.RecomputeFieldValue.Kind.StaticFieldBase; @@ -38,11 +37,7 @@ import java.lang.reflect.Modifier; import java.util.EnumSet; import java.util.Objects; -import java.util.concurrent.locks.ReentrantReadWriteLock; -import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock; -import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; -import org.graalvm.collections.EconomicMap; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.hosted.FieldValueTransformer; @@ -81,7 +76,7 @@ * @see RecomputeFieldValue * @see NativeImageReinitialize */ -public class ComputedValueField implements ReadableJavaField, OriginalFieldProvider, AnnotationWrapper { +public final class ComputedValueField extends FieldValueTransformation implements ReadableJavaField, OriginalFieldProvider, AnnotationWrapper { private static final EnumSet offsetComputationKinds = EnumSet.of(FieldOffset, TranslateFieldOffset, AtomicFieldUpdaterOffset); private final ResolvedJavaField original; @@ -90,10 +85,7 @@ public class ComputedValueField implements ReadableJavaField, OriginalFieldProvi private final RecomputeFieldValue.Kind kind; private final Class targetClass; private final Field targetField; - private final Class transformedValueAllowedType; - private final FieldValueTransformer fieldValueTransformer; private final boolean isFinal; - private final boolean disableCaching; /** True if the value doesn't depend on any analysis results. */ private final boolean isValueAvailableBeforeAnalysis; /** True if the value depends on analysis results. */ @@ -103,47 +95,35 @@ public class ComputedValueField implements ReadableJavaField, OriginalFieldProvi private JavaConstant constantValue; - private final EconomicMap valueCache; - /** - * Economic map does not allow to store null keys. Therefore null key is stored in an extra - * field. - */ - private JavaConstant valueCacheNullKey; - private final ReentrantReadWriteLock valueCacheLock = new ReentrantReadWriteLock(); - - public ComputedValueField(ResolvedJavaField original, ResolvedJavaField annotated, RecomputeFieldValue.Kind kind, Class targetClass, String targetName, boolean isFinal) { - this(original, annotated, kind, null, null, targetClass, targetName, isFinal, false); + public static ComputedValueField create(ResolvedJavaField original, ResolvedJavaField annotated, RecomputeFieldValue.Kind kind, Class targetClass, String targetName, boolean isFinal) { + return create(original, annotated, kind, null, null, targetClass, targetName, isFinal, false); } - @SuppressWarnings("this-escape") - public ComputedValueField(ResolvedJavaField original, ResolvedJavaField annotated, RecomputeFieldValue.Kind kind, Class transformedValueAllowedType, FieldValueTransformer initialTransformer, - Class targetClass, String targetName, boolean isFinal, boolean disableCaching) { + public static ComputedValueField create(ResolvedJavaField original, ResolvedJavaField annotated, RecomputeFieldValue.Kind kind, Class transformedValueAllowedType, + FieldValueTransformer initialTransformer, Class targetClass, String targetName, boolean isFinal, boolean disableCaching) { assert original != null; assert initialTransformer != null || targetClass != null; - this.original = original; - this.annotated = annotated; - this.kind = kind; - this.transformedValueAllowedType = transformedValueAllowedType; - this.targetClass = targetClass; - this.isFinal = isFinal; - this.disableCaching = disableCaching; - boolean customValueAvailableBeforeAnalysis = true; boolean customValueAvailableOnlyAfterAnalysis = false; boolean customValueAvailableOnlyAfterCompilation = false; FieldValueTransformer transformer = null; Field f = null; + JavaConstant constantValue = null; switch (kind) { case Reset: - constantValue = JavaConstant.defaultForKind(getJavaKind()); + constantValue = JavaConstant.defaultForKind(original.getType().getJavaKind()); break; case FieldOffset: f = getField(annotated, targetClass, targetName); break; case StaticFieldBase: f = getField(annotated, targetClass, targetName); - UserError.guarantee(Modifier.isStatic(f.getModifiers()), "Target field must be static for %s computation of %s", StaticFieldBase, fieldFormat()); + Object[] args = new Object[]{}; + if (!Modifier.isStatic(f.getModifiers())) { + throw UserError.abort("Target field must be static for " + StaticFieldBase + " computation of field " + original.format("%H.%n") + + (annotated != null ? " specified by alias " + annotated.format("%H.%n") : "")); + } break; case Custom: if (initialTransformer != null) { @@ -162,12 +142,12 @@ public ComputedValueField(ResolvedJavaField original, ResolvedJavaField annotate boolean isOffsetField = isOffsetRecomputation(kind); boolean isStaticFieldBase = kind == StaticFieldBase; guarantee(!isFinal || !isOffsetField); - this.isValueAvailableBeforeAnalysis = customValueAvailableBeforeAnalysis && !isOffsetField && !isStaticFieldBase; - this.isValueAvailableOnlyAfterAnalysis = customValueAvailableOnlyAfterAnalysis || isOffsetField || isStaticFieldBase; - this.isValueAvailableOnlyAfterCompilation = customValueAvailableOnlyAfterCompilation; - this.targetField = f; - this.fieldValueTransformer = transformer; - this.valueCache = EconomicMap.create(); + boolean isValueAvailableBeforeAnalysis = customValueAvailableBeforeAnalysis && !isOffsetField && !isStaticFieldBase; + boolean isValueAvailableOnlyAfterAnalysis = customValueAvailableOnlyAfterAnalysis || isOffsetField || isStaticFieldBase; + boolean isValueAvailableOnlyAfterCompilation = customValueAvailableOnlyAfterCompilation; + + return new ComputedValueField(original, annotated, kind, transformedValueAllowedType, transformer, isFinal, disableCaching, targetClass, f, isValueAvailableBeforeAnalysis, + isValueAvailableOnlyAfterAnalysis, isValueAvailableOnlyAfterCompilation, constantValue); } private static Field getField(ResolvedJavaField annotated, Class targetClass, String targetName) { @@ -182,9 +162,20 @@ public static boolean isOffsetRecomputation(RecomputeFieldValue.Kind kind) { return offsetComputationKinds.contains(kind); } - @Override - public boolean isValueAvailableBeforeAnalysis() { - return isValueAvailableBeforeAnalysis; + private ComputedValueField(ResolvedJavaField original, ResolvedJavaField annotated, RecomputeFieldValue.Kind kind, + Class transformedValueAllowedType, FieldValueTransformer fieldValueTransformer, boolean isFinal, boolean disableCaching, Class targetClass, Field targetField, + boolean isValueAvailableBeforeAnalysis, boolean isValueAvailableOnlyAfterAnalysis, boolean isValueAvailableOnlyAfterCompilation, JavaConstant constantValue) { + super(transformedValueAllowedType, fieldValueTransformer, disableCaching); + this.original = original; + this.annotated = annotated; + this.kind = kind; + this.targetClass = targetClass; + this.targetField = targetField; + this.isFinal = isFinal; + this.isValueAvailableBeforeAnalysis = isValueAvailableBeforeAnalysis; + this.isValueAvailableOnlyAfterAnalysis = isValueAvailableOnlyAfterAnalysis; + this.isValueAvailableOnlyAfterCompilation = isValueAvailableOnlyAfterCompilation; + this.constantValue = constantValue; } @Override @@ -196,7 +187,7 @@ public boolean isValueAvailable() { * value available when strengthening graphs after analysis, i.e., when applying analysis * results back into the IR. */ - return constantValue != null || isValueAvailableBeforeAnalysis() || + return constantValue != null || isValueAvailableBeforeAnalysis || (isValueAvailableOnlyAfterAnalysis && BuildPhaseProvider.isHostedUniverseBuilt()) || (isValueAvailableOnlyAfterCompilation && BuildPhaseProvider.isCompilationFinished()); } @@ -205,6 +196,10 @@ public ResolvedJavaField getAnnotated() { return annotated; } + Class getTargetClass() { + return targetClass; + } + public Field getTargetField() { return targetField; } @@ -308,52 +303,19 @@ public JavaConstant readValue(ClassInitializationSupport classInitializationSupp return constantValue; } - ReadLock readLock = valueCacheLock.readLock(); - try { - readLock.lock(); - JavaConstant result = getCached(receiver); - if (result != null) { - return result; - } - } finally { - readLock.unlock(); - } - - WriteLock writeLock = valueCacheLock.writeLock(); - try { - writeLock.lock(); - /* - * Check the cache again, now that we are holding the write-lock, i.e., we know that no - * other thread is computing a value right now. - */ - JavaConstant result = getCached(receiver); - if (result != null) { - return result; - } - /* - * Note that the value computation must be inside the lock, because we want to guarantee - * that field-value computers are only executed once per unique receiver. - */ - result = computeValue(classInitializationSupport, receiver); - putCached(receiver, result); - return result; - } finally { - writeLock.unlock(); - } + return super.readValue(classInitializationSupport, original, receiver); } - private JavaConstant computeValue(ClassInitializationSupport classInitializationSupport, JavaConstant receiver) { + @Override + protected JavaConstant computeValue(ClassInitializationSupport classInitializationSupport, ResolvedJavaField field, JavaConstant receiver) { assert isValueAvailable() : "Field " + format("%H.%n") + " value not available for reading."; - SnippetReflectionProvider originalSnippetReflection = GraalAccess.getOriginalSnippetReflection(); JavaConstant result; - Object originalValue; switch (kind) { case NewInstanceWhenNotNull: - originalValue = fetchOriginalValue(classInitializationSupport, receiver, originalSnippetReflection); - result = originalValue == null ? originalSnippetReflection.forObject(null) : createNewInstance(originalSnippetReflection); + result = fetchOriginalValue(classInitializationSupport, field, receiver) == null ? JavaConstant.NULL_POINTER : createNewInstance(); break; case NewInstance: - result = createNewInstance(originalSnippetReflection); + result = createNewInstance(); break; case AtomicFieldUpdaterOffset: result = computeAtomicFieldUpdaterOffset(classInitializationSupport, receiver); @@ -362,91 +324,25 @@ private JavaConstant computeValue(ClassInitializationSupport classInitialization result = translateFieldOffset(classInitializationSupport, receiver, targetClass); break; case Custom: - Object receiverValue = receiver == null ? null : originalSnippetReflection.asObject(Object.class, receiver); - originalValue = fetchOriginalValue(classInitializationSupport, receiver, originalSnippetReflection); - Object newValue = fieldValueTransformer.transform(receiverValue, originalValue); - checkValue(newValue); - result = originalSnippetReflection.forBoxed(original.getJavaKind(), newValue); - - assert result.getJavaKind() == original.getJavaKind(); + result = super.computeValue(classInitializationSupport, field, receiver); break; default: - throw shouldNotReachHere("Field recomputation of kind " + kind + " for " + fieldFormat() + " not yet supported"); + throw shouldNotReachHere("Field recomputation of kind " + kind + " for field " + original.format("%H.%n") + + (annotated != null ? " specified by alias " + annotated.format("%H.%n") : "") + " not yet supported"); } return result; } - private void checkValue(Object newValue) { - boolean primitive = transformedValueAllowedType.isPrimitive(); - if (newValue == null) { - if (primitive) { - throw UserError.abort("Field value transformer returned null for primitive %s", fieldFormat()); - } else { - /* Null is always allowed for reference fields. */ - return; - } - } - /* - * The compute/transform methods autobox primitive values. We unbox them here, but only if - * the original field is primitive. - */ - Class actualType = primitive ? toUnboxedClass(newValue.getClass()) : newValue.getClass(); - if (!transformedValueAllowedType.isAssignableFrom(actualType)) { - throw UserError.abort("Field value transformer returned value of type `%s` that is not assignable to declared type `%s` of %s", - actualType.getTypeName(), transformedValueAllowedType.getTypeName(), fieldFormat()); - } - } - - private String fieldFormat() { - return "field " + original.format("%H.%n") + (annotated != null ? " specified by alias " + annotated.format("%H.%n") : ""); - } - - private JavaConstant createNewInstance(SnippetReflectionProvider originalSnippetReflection) { + private JavaConstant createNewInstance() { JavaConstant result; try { - result = originalSnippetReflection.forObject(ReflectionUtil.newInstance(targetClass)); + result = GraalAccess.getOriginalSnippetReflection().forObject(ReflectionUtil.newInstance(targetClass)); } catch (ReflectionUtilError ex) { throw VMError.shouldNotReachHere("Error performing field recomputation for alias " + annotated.format("%H.%n"), ex.getCause()); } return result; } - private Object fetchOriginalValue(ClassInitializationSupport classInitializationSupport, JavaConstant receiver, - SnippetReflectionProvider originalSnippetReflection) { - JavaConstant originalValueConstant = ReadableJavaField.readFieldValue(classInitializationSupport, original, receiver); - if (originalValueConstant == null) { - /* - * The class is still uninitialized, so static fields cannot be read. Or it is an - * instance field in a substitution class, i.e., a field that does not exist in the - * hosted object. - */ - return null; - } else if (originalValueConstant.getJavaKind().isPrimitive()) { - return originalValueConstant.asBoxedPrimitive(); - } else { - return originalSnippetReflection.asObject(Object.class, originalValueConstant); - } - } - - private void putCached(JavaConstant receiver, JavaConstant result) { - if (disableCaching) { - return; - } - if (receiver == null) { - valueCacheNullKey = result; - } else { - valueCache.put(receiver, result); - } - } - - private JavaConstant getCached(JavaConstant receiver) { - if (receiver == null) { - return valueCacheNullKey; - } else { - return valueCache.get(receiver); - } - } - @Override public boolean injectFinalForRuntimeCompilation() { if (original.isFinal()) { @@ -530,8 +426,8 @@ public String toString() { } @Override - public Field getJavaField() { - return OriginalFieldProvider.getJavaField(original); + public ResolvedJavaField unwrapTowardsOriginalField() { + return original; } @Override diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/FieldValueTransformation.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/FieldValueTransformation.java new file mode 100644 index 000000000000..20f8332ede05 --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/FieldValueTransformation.java @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.hosted.substitute; + +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock; +import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; + +import org.graalvm.collections.EconomicMap; +import org.graalvm.nativeimage.hosted.FieldValueTransformer; + +import com.oracle.graal.pointsto.util.GraalAccess; +import com.oracle.svm.core.SubstrateUtil; +import com.oracle.svm.core.util.UserError; +import com.oracle.svm.hosted.ameta.ReadableJavaField; +import com.oracle.svm.hosted.classinitialization.ClassInitializationSupport; + +import jdk.vm.ci.meta.JavaConstant; +import jdk.vm.ci.meta.ResolvedJavaField; + +public class FieldValueTransformation { + protected final FieldValueTransformer fieldValueTransformer; + protected final Class transformedValueAllowedType; + protected final boolean disableCaching; + + private final EconomicMap valueCache = EconomicMap.create(); + private final ReentrantReadWriteLock valueCacheLock = new ReentrantReadWriteLock(); + + public FieldValueTransformation(Class transformedValueAllowedType, FieldValueTransformer fieldValueTransformer, boolean disableCaching) { + this.fieldValueTransformer = fieldValueTransformer; + this.transformedValueAllowedType = transformedValueAllowedType; + this.disableCaching = disableCaching; + } + + public FieldValueTransformer getFieldValueTransformer() { + return fieldValueTransformer; + } + + public JavaConstant readValue(ClassInitializationSupport classInitializationSupport, ResolvedJavaField field, JavaConstant receiver) { + ReadLock readLock = valueCacheLock.readLock(); + try { + readLock.lock(); + JavaConstant result = getCached(receiver); + if (result != null) { + return result; + } + } finally { + readLock.unlock(); + } + + WriteLock writeLock = valueCacheLock.writeLock(); + try { + writeLock.lock(); + /* + * Check the cache again, now that we are holding the write-lock, i.e., we know that no + * other thread is computing a value right now. + */ + JavaConstant result = getCached(receiver); + if (result != null) { + return result; + } + /* + * Note that the value computation must be inside the lock, because we want to guarantee + * that field-value computers are only executed once per unique receiver. + */ + result = computeValue(classInitializationSupport, field, receiver); + putCached(receiver, result); + return result; + } finally { + writeLock.unlock(); + } + } + + protected JavaConstant computeValue(ClassInitializationSupport classInitializationSupport, ResolvedJavaField field, JavaConstant receiver) { + Object receiverValue = receiver == null ? null : GraalAccess.getOriginalSnippetReflection().asObject(Object.class, receiver); + Object originalValue = fetchOriginalValue(classInitializationSupport, field, receiver); + Object newValue = fieldValueTransformer.transform(receiverValue, originalValue); + checkValue(newValue, field); + JavaConstant result = GraalAccess.getOriginalSnippetReflection().forBoxed(field.getJavaKind(), newValue); + + assert result.getJavaKind() == field.getJavaKind(); + return result; + } + + private void checkValue(Object newValue, ResolvedJavaField field) { + boolean primitive = transformedValueAllowedType.isPrimitive(); + if (newValue == null) { + if (primitive) { + throw UserError.abort("Field value transformer returned null for primitive %s", field.format("%H.%n")); + } else { + /* Null is always allowed for reference fields. */ + return; + } + } + /* + * The compute/transform methods autobox primitive values. We unbox them here, but only if + * the original field is primitive. + */ + Class actualType = primitive ? SubstrateUtil.toUnboxedClass(newValue.getClass()) : newValue.getClass(); + if (!transformedValueAllowedType.isAssignableFrom(actualType)) { + throw UserError.abort("Field value transformer returned value of type `%s` that is not assignable to declared type `%s` of %s", + actualType.getTypeName(), transformedValueAllowedType.getTypeName(), field.format("%H.%n")); + } + } + + protected Object fetchOriginalValue(ClassInitializationSupport classInitializationSupport, ResolvedJavaField field, JavaConstant receiver) { + JavaConstant originalValueConstant = ReadableJavaField.readFieldValue(classInitializationSupport, field, receiver); + if (originalValueConstant == null) { + /* + * The class is still uninitialized, so static fields cannot be read. Or it is an + * instance field in a substitution class, i.e., a field that does not exist in the + * hosted object. + */ + return null; + } else if (originalValueConstant.getJavaKind().isPrimitive()) { + return originalValueConstant.asBoxedPrimitive(); + } else { + return GraalAccess.getOriginalSnippetReflection().asObject(Object.class, originalValueConstant); + } + } + + private void putCached(JavaConstant receiver, JavaConstant result) { + if (disableCaching) { + return; + } + JavaConstant key = receiver == null ? JavaConstant.NULL_POINTER : receiver; + valueCache.put(key, result); + } + + private JavaConstant getCached(JavaConstant receiver) { + JavaConstant key = receiver == null ? JavaConstant.NULL_POINTER : receiver; + return valueCache.get(key); + } +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/SubstitutionField.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/SubstitutionField.java index 0c12e1b87c37..e72df883588b 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/SubstitutionField.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/SubstitutionField.java @@ -25,7 +25,6 @@ package com.oracle.svm.hosted.substitute; import java.lang.reflect.AnnotatedElement; -import java.lang.reflect.Field; import com.oracle.graal.pointsto.infrastructure.OriginalFieldProvider; import com.oracle.svm.hosted.ameta.ReadableJavaField; @@ -55,7 +54,7 @@ public SubstitutionField(ResolvedJavaField original, ResolvedJavaField annotated } @Override - public boolean isValueAvailableBeforeAnalysis() { + public boolean isValueAvailable() { return true; } @@ -131,8 +130,8 @@ public AnnotatedElement getAnnotationRoot() { } @Override - public Field getJavaField() { - return OriginalFieldProvider.getJavaField(original); + public ResolvedJavaField unwrapTowardsOriginalField() { + return original; } @Override diff --git a/substratevm/src/com.oracle.svm.truffle/src/com/oracle/svm/truffle/TruffleBaseFeature.java b/substratevm/src/com.oracle.svm.truffle/src/com/oracle/svm/truffle/TruffleBaseFeature.java index a91e7eef28d0..c9ee3819b84b 100644 --- a/substratevm/src/com.oracle.svm.truffle/src/com/oracle/svm/truffle/TruffleBaseFeature.java +++ b/substratevm/src/com.oracle.svm.truffle/src/com/oracle/svm/truffle/TruffleBaseFeature.java @@ -1412,7 +1412,7 @@ public ValueAvailability valueAvailability() { public Object transform(Object receiver, Object originalValue) { Class generatedStorageClass = ReflectionUtil.readField(SHAPE_GENERATOR, "generatedStorageClass", receiver); Field field = ReflectionUtil.lookupField(generatedStorageClass, storageClassFieldName); - int offset = ImageSingletons.lookup(ReflectionSubstitutionSupport.class).getFieldOffset(field, false); + int offset = ReflectionSubstitutionSupport.singleton().getFieldOffset(field, false); if (offset <= 0) { throw VMError.shouldNotReachHere("Field is not marked as accessed: " + field); } @@ -1537,7 +1537,7 @@ public Object transform(Object receiver, Object originalValue) { Class declaringClass = ReflectionUtil.readField(receiver.getClass(), "declaringClass", receiver); String name = ReflectionUtil.readField(receiver.getClass(), "name", receiver); Field field = ReflectionUtil.lookupField(declaringClass, name); - int offset = ImageSingletons.lookup(ReflectionSubstitutionSupport.class).getFieldOffset(field, false); + int offset = ReflectionSubstitutionSupport.singleton().getFieldOffset(field, false); if (offset <= 0) { throw VMError.shouldNotReachHere("Field is not marked as accessed: " + field); } @@ -1569,7 +1569,7 @@ public Object transform(Object receiver, Object originalValue) { Class declaringClass = ReflectionUtil.readField(InlinableField.class.getSuperclass(), "declaringClass", receiver); String name = ReflectionUtil.readField(InlinableField.class.getSuperclass(), "name", receiver); Field field = ReflectionUtil.lookupField(declaringClass, name); - int offset = ImageSingletons.lookup(ReflectionSubstitutionSupport.class).getFieldOffset(field, false); + int offset = ReflectionSubstitutionSupport.singleton().getFieldOffset(field, false); if (offset == -1) { throw VMError.shouldNotReachHere("Field is not marked as accessed: " + field); }