diff --git a/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/Target_jdk_internal_foreign_Utils_BaseAndScale.java b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/Target_jdk_internal_foreign_Utils_BaseAndScale.java index 94ad57c438ff..7c1f44a36409 100644 --- a/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/Target_jdk_internal_foreign_Utils_BaseAndScale.java +++ b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/Target_jdk_internal_foreign_Utils_BaseAndScale.java @@ -24,11 +24,12 @@ */ package com.oracle.svm.core.foreign; +import org.graalvm.nativeimage.hosted.FieldValueTransformer; + import com.oracle.svm.core.annotate.Alias; import com.oracle.svm.core.annotate.RecomputeFieldValue; import com.oracle.svm.core.annotate.TargetClass; import com.oracle.svm.core.config.ConfigurationValues; -import com.oracle.svm.core.fieldvaluetransformer.FieldValueTransformerWithAvailability; import com.oracle.svm.core.util.VMError; import jdk.internal.foreign.Utils; @@ -41,12 +42,7 @@ final class Target_jdk_internal_foreign_Utils_BaseAndScale { int base; } -final class BaseFieldRecomputer implements FieldValueTransformerWithAvailability { - - @Override - public ValueAvailability valueAvailability() { - return ValueAvailability.BeforeAnalysis; - } +final class BaseFieldRecomputer implements FieldValueTransformer { @Override public Object transform(Object receiver, Object originalValue) { 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 index 03943be9e403..ff2371d7320b 100644 --- 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 @@ -26,20 +26,18 @@ import org.graalvm.nativeimage.hosted.FieldValueTransformer; +import com.oracle.svm.core.annotate.RecomputeFieldValue.Kind; 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, JavaKind returnKind) { - super(returnKind); - this.targetClass = targetClass; - } +/** + * Implements the field value transformation semantics of {@link Kind#ArrayBaseOffset}. + */ +public record ArrayBaseOffsetFieldValueTransformer(Class targetClass, JavaKind returnKind) implements FieldValueTransformer { @Override public Object transform(Object receiver, Object originalValue) { - return box(ConfigurationValues.getObjectLayout().getArrayBaseOffset(JavaKind.fromJavaClass(targetClass.getComponentType()))); + return FieldOffsetFieldValueTransformer.box(returnKind, 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 index a728ddd09a0a..bf9f418f7834 100644 --- 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 @@ -26,20 +26,18 @@ import org.graalvm.nativeimage.hosted.FieldValueTransformer; +import com.oracle.svm.core.annotate.RecomputeFieldValue.Kind; 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, JavaKind returnKind) { - super(returnKind); - this.targetClass = targetClass; - } +/** + * Implements the field value transformation semantics of {@link Kind#ArrayIndexScale}. + */ +public record ArrayIndexScaleFieldValueTransformer(Class targetClass, JavaKind returnKind) implements FieldValueTransformer { @Override public Object transform(Object receiver, Object originalValue) { - return box(ConfigurationValues.getObjectLayout().getArrayIndexScale(JavaKind.fromJavaClass(targetClass.getComponentType()))); + return FieldOffsetFieldValueTransformer.box(returnKind, 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 index d49314ce127b..8f0a9570bb21 100644 --- 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 @@ -26,20 +26,18 @@ import org.graalvm.nativeimage.hosted.FieldValueTransformer; +import com.oracle.svm.core.annotate.RecomputeFieldValue.Kind; 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, JavaKind returnKind) { - super(returnKind); - this.targetClass = targetClass; - } +/** + * Implements the field value transformation semantics of {@link Kind#ArrayIndexShift}. + */ +public record ArrayIndexShiftFieldValueTransformer(Class targetClass, JavaKind returnKind) implements FieldValueTransformer { @Override public Object transform(Object receiver, Object originalValue) { - return box(ConfigurationValues.getObjectLayout().getArrayIndexShift(JavaKind.fromJavaClass(targetClass.getComponentType()))); + return FieldOffsetFieldValueTransformer.box(returnKind, ConfigurationValues.getObjectLayout().getArrayIndexShift(JavaKind.fromJavaClass(targetClass.getComponentType()))); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/fieldvaluetransformer/ConstantValueFieldValueTransformer.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/fieldvaluetransformer/ConstantValueFieldValueTransformer.java new file mode 100644 index 000000000000..e9bae7c32cdd --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/fieldvaluetransformer/ConstantValueFieldValueTransformer.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2024, 2024, 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.annotate.RecomputeFieldValue.Kind; +import com.oracle.svm.core.util.VMError; + +import jdk.vm.ci.meta.ResolvedJavaField; + +/** + * Sets the field to the provided constant value. + * + * When that value is the {@link #defaultValueForField default value for the field}, this + * transformer implements the field value transformation semantics of {@link Kind#Reset}. + */ +public record ConstantValueFieldValueTransformer(Object value) implements FieldValueTransformer { + + public static FieldValueTransformer defaultValueForField(ResolvedJavaField field) { + return new ConstantValueFieldValueTransformer(switch (field.getType().getJavaKind()) { + case Byte -> Byte.valueOf((byte) 0); + case Boolean -> Boolean.valueOf(false); + case Short -> Short.valueOf((short) 0); + case Char -> Character.valueOf((char) 0); + case Int -> Integer.valueOf(0); + case Long -> Long.valueOf(0); + case Float -> Float.valueOf(0); + case Double -> Double.valueOf(0); + case Object -> null; + default -> throw VMError.shouldNotReachHere(String.valueOf(field)); + }); + } + + @Override + public Object transform(Object receiver, Object originalValue) { + return value; + } +} 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 index 7225be1c2863..dfc07446d0a9 100644 --- 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 @@ -26,6 +26,8 @@ import java.lang.reflect.Field; +import com.oracle.svm.core.BuildPhaseProvider; +import com.oracle.svm.core.annotate.RecomputeFieldValue.Kind; import com.oracle.svm.core.graal.nodes.FieldOffsetNode; import com.oracle.svm.core.reflect.target.ReflectionSubstitutionSupport; import com.oracle.svm.core.util.VMError; @@ -35,17 +37,14 @@ import jdk.vm.ci.meta.JavaConstant; import jdk.vm.ci.meta.JavaKind; -public final class FieldOffsetFieldValueTransformer extends BoxingTransformer implements FieldValueTransformerWithAvailability { - private final Field targetField; - - public FieldOffsetFieldValueTransformer(Field targetField, JavaKind returnKind) { - super(returnKind); - this.targetField = targetField; - } +/** + * Implements the field value transformation semantics of {@link Kind#FieldOffset}. + */ +public record FieldOffsetFieldValueTransformer(Field targetField, JavaKind returnKind) implements FieldValueTransformerWithAvailability { @Override - public ValueAvailability valueAvailability() { - return ValueAvailability.AfterAnalysis; + public boolean isAvailable() { + return BuildPhaseProvider.isHostedUniverseBuilt(); } @Override @@ -54,7 +53,18 @@ public Object transform(Object receiver, Object originalValue) { if (offset <= 0) { throw VMError.shouldNotReachHere("Field is not marked as unsafe accessed: " + targetField); } - return box(offset); + return box(returnKind, offset); + } + + static Object box(JavaKind returnKind, int value) { + switch (returnKind) { + case Int: + return Integer.valueOf(value); + case Long: + return Long.valueOf(value); + default: + throw VMError.shouldNotReachHere("Unexpected kind: " + returnKind); + } } @Override diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/fieldvaluetransformer/FieldValueTransformerWithAvailability.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/fieldvaluetransformer/FieldValueTransformerWithAvailability.java index e81708142197..2beb2e5b7fb3 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/fieldvaluetransformer/FieldValueTransformerWithAvailability.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/fieldvaluetransformer/FieldValueTransformerWithAvailability.java @@ -36,34 +36,9 @@ public interface FieldValueTransformerWithAvailability extends FieldValueTransformer { /** - * Controls when the transformed value is available at image build time. + * Returns true when the value for this custom computation is available. */ - enum ValueAvailability { - /** - * The value is available without time constraints, i.e., it is independent of static - * analysis or compilation. - */ - BeforeAnalysis, - - /** - * The value depends on data computed by the static analysis and is therefore not yet - * available to the static analysis. The value still might be constant folded during - * compilation. - */ - AfterAnalysis, - - /** - * Value depends on data computed during compilation and is therefore available only when - * writing out the image heap into the native image. Such a value is never available for - * constant folding. - */ - AfterCompilation - } - - /** - * Returns information about when the value for this custom computation is available. - */ - ValueAvailability valueAvailability(); + boolean isAvailable(); /** * Optionally provide a Graal IR node to intrinsify the field access before the static analysis. diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/fieldvaluetransformer/NewInstanceOfFixedClassFieldValueTransformer.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/fieldvaluetransformer/NewInstanceOfFixedClassFieldValueTransformer.java new file mode 100644 index 000000000000..402664050d30 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/fieldvaluetransformer/NewInstanceOfFixedClassFieldValueTransformer.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2024, 2024, 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.annotate.RecomputeFieldValue.Kind; +import com.oracle.svm.util.ReflectionUtil; + +/** + * Implements the field value transformation semantics of {@link Kind#NewInstance} and + * {@link Kind#NewInstanceWhenNotNull}. + */ +public record NewInstanceOfFixedClassFieldValueTransformer(Class clazz, boolean onlyIfOriginalNotNull) implements FieldValueTransformer { + + @Override + public Object transform(Object receiver, Object originalValue) { + if (onlyIfOriginalNotNull && originalValue == null) { + return null; + } + return ReflectionUtil.newInstance(clazz); + } +} 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 index b9d06d6a976a..968e1d1da7f8 100644 --- 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 @@ -26,22 +26,22 @@ import java.lang.reflect.Field; +import com.oracle.svm.core.BuildPhaseProvider; import com.oracle.svm.core.StaticFieldsSupport; +import com.oracle.svm.core.annotate.RecomputeFieldValue.Kind; import jdk.graal.compiler.nodes.ValueNode; import jdk.graal.compiler.nodes.spi.CoreProviders; import jdk.vm.ci.meta.JavaConstant; -public final class StaticFieldBaseFieldValueTransformer implements FieldValueTransformerWithAvailability { - private final Field targetField; - - public StaticFieldBaseFieldValueTransformer(Field targetField) { - this.targetField = targetField; - } +/** + * Implements the field value transformation semantics of {@link Kind#StaticFieldBase}. + */ +public record StaticFieldBaseFieldValueTransformer(Field targetField) implements FieldValueTransformerWithAvailability { @Override - public ValueAvailability valueAvailability() { - return ValueAvailability.AfterAnalysis; + public boolean isAvailable() { + return BuildPhaseProvider.isHostedUniverseBuilt(); } @Override diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JavaLangSubstitutions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JavaLangSubstitutions.java index d682ee4ca287..675356454f70 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JavaLangSubstitutions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JavaLangSubstitutions.java @@ -49,6 +49,7 @@ import org.graalvm.nativeimage.hosted.FieldValueTransformer; import org.graalvm.nativeimage.impl.InternalPlatform; +import com.oracle.svm.core.BuildPhaseProvider; import com.oracle.svm.core.NeverInline; import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.SubstrateUtil; @@ -579,13 +580,13 @@ public Object transform(Object receiver, Object originalValue) { return map; } + /* + * We want to wait to constant fold this value until all possible HotSpot initialization code + * has run. + */ @Override - public ValueAvailability valueAvailability() { - /* - * We want to wait to constant fold this value until all possible HotSpot initialization - * code has run. - */ - return ValueAvailability.AfterAnalysis; + public boolean isAvailable() { + return BuildPhaseProvider.isHostedUniverseBuilt(); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/VarHandleSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/VarHandleSupport.java index d39eb3b03d9d..821a3095729a 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/VarHandleSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/VarHandleSupport.java @@ -26,6 +26,7 @@ import org.graalvm.nativeimage.ImageSingletons; +import com.oracle.svm.core.BuildPhaseProvider; import com.oracle.svm.core.StaticFieldsSupport; import com.oracle.svm.core.annotate.Alias; import com.oracle.svm.core.annotate.RecomputeFieldValue; @@ -63,8 +64,8 @@ abstract class VarHandleFieldOffsetComputer implements FieldValueTransformerWith } @Override - public ValueAvailability valueAvailability() { - return ValueAvailability.AfterAnalysis; + public boolean isAvailable() { + return BuildPhaseProvider.isHostedUniverseBuilt(); } @Override @@ -110,8 +111,8 @@ class VarHandleFieldOffsetAsLongComputer extends VarHandleFieldOffsetComputer { class VarHandleStaticBaseComputer implements FieldValueTransformerWithAvailability { @Override - public ValueAvailability valueAvailability() { - return ValueAvailability.AfterAnalysis; + public boolean isAvailable() { + return BuildPhaseProvider.isHostedUniverseBuilt(); } @Override 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 cffd02e1b145..f2cad44f3303 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,13 +26,14 @@ import java.lang.reflect.Field; +import com.oracle.svm.core.BuildPhaseProvider; import com.oracle.svm.core.fieldvaluetransformer.FieldValueTransformerWithAvailability; public class FieldOffsetComputer implements FieldValueTransformerWithAvailability { @Override - public ValueAvailability valueAvailability() { - return ValueAvailability.AfterAnalysis; + public boolean isAvailable() { + return BuildPhaseProvider.isHostedUniverseBuilt(); } @Override diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/ReflectionMetadataComputer.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/ReflectionMetadataComputer.java index cab8d248ebec..d26b0f7ee154 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/ReflectionMetadataComputer.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/ReflectionMetadataComputer.java @@ -24,12 +24,13 @@ */ package com.oracle.svm.core.reflect.target; +import com.oracle.svm.core.BuildPhaseProvider; import com.oracle.svm.core.fieldvaluetransformer.FieldValueTransformerWithAvailability; public abstract class ReflectionMetadataComputer implements FieldValueTransformerWithAvailability { @Override - public final ValueAvailability valueAvailability() { - return ValueAvailability.AfterCompilation; + public boolean isAvailable() { + return BuildPhaseProvider.isCompilationFinished(); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/Target_java_lang_reflect_AccessibleObject.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/Target_java_lang_reflect_AccessibleObject.java index 95bbfe1de8e4..edc7e5be0354 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/Target_java_lang_reflect_AccessibleObject.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/Target_java_lang_reflect_AccessibleObject.java @@ -27,13 +27,13 @@ import java.lang.reflect.AccessibleObject; import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.hosted.FieldValueTransformer; import com.oracle.svm.core.annotate.Alias; import com.oracle.svm.core.annotate.Inject; import com.oracle.svm.core.annotate.RecomputeFieldValue; import com.oracle.svm.core.annotate.TargetClass; import com.oracle.svm.core.configure.RuntimeConditionSet; -import com.oracle.svm.core.fieldvaluetransformer.FieldValueTransformerWithAvailability; @TargetClass(value = AccessibleObject.class) public final class Target_java_lang_reflect_AccessibleObject { @@ -62,16 +62,10 @@ public Object transform(Object receiver, Object originalValue) { } } - static class SatisfiedConditionComputer implements FieldValueTransformerWithAvailability { + static class SatisfiedConditionComputer implements FieldValueTransformer { @Override public Object transform(Object receiver, Object originalValue) { return RuntimeConditionSet.unmodifiableEmptySet(); } - - @Override - public final ValueAvailability valueAvailability() { - return ValueAvailability.BeforeAnalysis; - } - } } 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 4c21b744ecb7..5df04c6e1f8e 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 @@ -33,6 +33,7 @@ import org.graalvm.nativeimage.ImageSingletons; +import com.oracle.svm.core.BuildPhaseProvider; import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.annotate.Alias; import com.oracle.svm.core.annotate.Inject; @@ -127,8 +128,8 @@ private byte[] getTypeAnnotationBytes0() { public static final class FieldDeletionReasonComputer implements FieldValueTransformerWithAvailability { @Override - public ValueAvailability valueAvailability() { - return ValueAvailability.AfterAnalysis; + public boolean isAvailable() { + return BuildPhaseProvider.isHostedUniverseBuilt(); } @Override diff --git a/substratevm/src/com.oracle.svm.graal.hotspot.libgraal/src/com/oracle/svm/graal/hotspot/libgraal/LibGraalFieldsOffsetsFeature.java b/substratevm/src/com.oracle.svm.graal.hotspot.libgraal/src/com/oracle/svm/graal/hotspot/libgraal/LibGraalFieldsOffsetsFeature.java index a82a97be7fca..cf55ea2b1f88 100644 --- a/substratevm/src/com.oracle.svm.graal.hotspot.libgraal/src/com/oracle/svm/graal/hotspot/libgraal/LibGraalFieldsOffsetsFeature.java +++ b/substratevm/src/com.oracle.svm.graal.hotspot.libgraal/src/com/oracle/svm/graal/hotspot/libgraal/LibGraalFieldsOffsetsFeature.java @@ -195,8 +195,8 @@ private static class IterationMaskRecomputation implements FieldValueTransformer } @Override - public ValueAvailability valueAvailability() { - return ValueAvailability.AfterAnalysis; + public boolean isAvailable() { + return BuildPhaseProvider.isHostedUniverseBuilt(); } @Override diff --git a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/FieldsOffsetsFeature.java b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/FieldsOffsetsFeature.java index 4178cd1c4b19..a9ebb0d91453 100644 --- a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/FieldsOffsetsFeature.java +++ b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/FieldsOffsetsFeature.java @@ -68,8 +68,8 @@ public class FieldsOffsetsFeature implements Feature { abstract static class IterationMaskRecomputation implements FieldValueTransformerWithAvailability { @Override - public ValueAvailability valueAvailability() { - return ValueAvailability.AfterAnalysis; + public boolean isAvailable() { + return BuildPhaseProvider.isHostedUniverseBuilt(); } @Override diff --git a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/meta/SubstrateField.java b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/meta/SubstrateField.java index 64ac2c312209..c4b2dd333a23 100644 --- a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/meta/SubstrateField.java +++ b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/meta/SubstrateField.java @@ -32,6 +32,7 @@ import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; +import com.oracle.graal.pointsto.infrastructure.OriginalFieldProvider; import com.oracle.graal.pointsto.meta.AnalysisField; import com.oracle.svm.core.BuildPhaseProvider.AfterCompilation; import com.oracle.svm.core.heap.UnknownObjectField; @@ -40,11 +41,11 @@ import com.oracle.svm.core.meta.SharedField; import com.oracle.svm.core.util.HostedStringDeduplication; import com.oracle.svm.core.util.VMError; -import com.oracle.svm.hosted.ameta.ReadableJavaField; import jdk.vm.ci.meta.JavaConstant; import jdk.vm.ci.meta.JavaKind; import jdk.vm.ci.meta.PrimitiveConstant; +import jdk.vm.ci.meta.ResolvedJavaField; public class SubstrateField implements SharedField { @@ -67,8 +68,16 @@ public class SubstrateField implements SharedField { public SubstrateField(AnalysisField aField, HostedStringDeduplication stringTable) { VMError.guarantee(!aField.isInternal(), "Internal fields are not supported for JIT compilation"); + /* + * AliasField removes the "final" modifier for AOT compilation because the recomputed value + * is not guaranteed to be known yet. But for runtime compilation, we know that we can treat + * the field as "final". + */ + ResolvedJavaField oField = OriginalFieldProvider.getOriginalField(aField); + boolean injectFinalForRuntimeCompilation = oField != null && oField.isFinal(); + this.modifiers = aField.getModifiers() | - (ReadableJavaField.injectFinalForRuntimeCompilation(aField.wrapped) ? Modifier.FINAL : 0); + (injectFinalForRuntimeCompilation ? Modifier.FINAL : 0); this.name = stringTable.deduplicate(aField.getName(), true); this.hashCode = aField.hashCode(); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ClassLoaderFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ClassLoaderFeature.java index 8021c85703e9..3cd33eaa6f39 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ClassLoaderFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ClassLoaderFeature.java @@ -27,6 +27,7 @@ import java.util.concurrent.ConcurrentHashMap; import com.oracle.graal.pointsto.heap.ImageHeapConstant; +import com.oracle.svm.core.BuildPhaseProvider; import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; import com.oracle.svm.core.feature.InternalFeature; import com.oracle.svm.core.fieldvaluetransformer.FieldValueTransformerWithAvailability; @@ -127,8 +128,8 @@ public void beforeAnalysis(BeforeAnalysisAccess access) { access.registerFieldValueTransformer(packagesField, new FieldValueTransformerWithAvailability() { @Override - public ValueAvailability valueAvailability() { - return ValueAvailability.AfterAnalysis; + public boolean isAvailable() { + return BuildPhaseProvider.isHostedUniverseBuilt(); } @Override 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 18e28eaa2e3e..3c40181cf318 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 @@ -201,6 +201,7 @@ import com.oracle.svm.hosted.ProgressReporter.ReporterClosable; import com.oracle.svm.hosted.ameta.AnalysisConstantFieldProvider; import com.oracle.svm.hosted.ameta.AnalysisConstantReflectionProvider; +import com.oracle.svm.hosted.ameta.FieldValueInterceptionSupport; import com.oracle.svm.hosted.ameta.SVMHostedValueProvider; import com.oracle.svm.hosted.analysis.Inflation; import com.oracle.svm.hosted.analysis.NativeImagePointsToAnalysis; @@ -1124,7 +1125,9 @@ public static AnalysisUniverse createAnalysisUniverse(OptionValues options, Targ public static AnnotationSubstitutionProcessor createAnnotationSubstitutionProcessor(MetaAccessProvider originalMetaAccess, ImageClassLoader loader, ClassInitializationSupport classInitializationSupport) { AnnotationSubstitutionProcessor annotationSubstitutions = new AnnotationSubstitutionProcessor(loader, originalMetaAccess, classInitializationSupport); - annotationSubstitutions.init(); + var fieldValueInterceptionSupport = new FieldValueInterceptionSupport(annotationSubstitutions); + ImageSingletons.add(FieldValueInterceptionSupport.class, fieldValueInterceptionSupport); + annotationSubstitutions.init(fieldValueInterceptionSupport); return annotationSubstitutions; } @@ -1149,7 +1152,7 @@ public static void initializeBigBang(Inflation bb, OptionValues options, Feature /* * Eagerly register all target fields of recomputed value fields as unsafe accessed. */ - bb.getAnnotationSubstitutionProcessor().processComputedValueFields(bb); + bb.getAnnotationSubstitutionProcessor().registerUnsafeAccessedFields(bb); /* * Install feature supported substitutions. 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 1d0758971b4c..0cdf72ba615f 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 @@ -105,7 +105,6 @@ import com.oracle.svm.core.util.HostedStringDeduplication; import com.oracle.svm.core.util.UserError; import com.oracle.svm.core.util.VMError; -import com.oracle.svm.hosted.ameta.FieldValueInterceptionSupport; import com.oracle.svm.hosted.analysis.NativeImagePointsToAnalysis; import com.oracle.svm.hosted.analysis.SVMParsingSupport; import com.oracle.svm.hosted.classinitialization.ClassInitializationOptions; @@ -193,7 +192,6 @@ public enum UsageKind { private final SVMParsingSupport parsingSupport; private final InlineBeforeAnalysisPolicy inlineBeforeAnalysisPolicy; - private final FieldValueInterceptionSupport fieldValueInterceptionSupport; private final MissingRegistrationSupport missingRegistrationSupport; private final int layerId; @@ -229,8 +227,6 @@ public SVMHost(OptionValues options, ImageClassLoader loader, ClassInitializatio } else { parsingSupport = null; } - fieldValueInterceptionSupport = new FieldValueInterceptionSupport(annotationSubstitutions, classInitializationSupport); - ImageSingletons.add(FieldValueInterceptionSupport.class, fieldValueInterceptionSupport); layerId = ImageLayerBuildingSupport.buildingImageLayer() ? DynamicImageLayerInfo.singleton().layerNumber : 0; useBaseLayer = ImageLayerBuildingSupport.buildingExtensionLayer(); if (SubstrateOptions.includeAll()) { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java index 7ff0e187581e..dd5755cac711 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java @@ -87,6 +87,7 @@ import com.oracle.graal.pointsto.meta.AnalysisMethod; import com.oracle.graal.pointsto.reports.ReportUtils; +import com.oracle.svm.core.BuildPhaseProvider; import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.TypeResult; @@ -96,8 +97,8 @@ import com.oracle.svm.core.jdk.JNIRegistrationUtil; import com.oracle.svm.core.jdk.NativeLibrarySupport; import com.oracle.svm.core.jdk.PlatformNativeLibrarySupport; -import com.oracle.svm.core.option.HostedOptionKey; import com.oracle.svm.core.option.AccumulatingLocatableMultiOptionValue; +import com.oracle.svm.core.option.HostedOptionKey; import com.oracle.svm.core.util.UserError; import com.oracle.svm.core.util.VMError; import com.oracle.svm.hosted.FeatureImpl.BeforeAnalysisAccessImpl; @@ -331,12 +332,12 @@ public void beforeAnalysis(BeforeAnalysisAccess a) { } access.registerFieldValueTransformer(providerListField, new FieldValueTransformerWithAvailability() { + /* + * We must wait until all providers have been registered before filtering the list. + */ @Override - public ValueAvailability valueAvailability() { - /* - * We must wait until all providers have been registered before filtering the list. - */ - return ValueAvailability.AfterAnalysis; + public boolean isAvailable() { + return BuildPhaseProvider.isHostedUniverseBuilt(); } @Override @@ -361,12 +362,12 @@ public Object transform(Object receiver, Object originalValue) { }); access.registerFieldValueTransformer(verificationResultsField, new FieldValueTransformerWithAvailability() { + /* + * We must wait until all providers have been registered before filtering the list. + */ @Override - public ValueAvailability valueAvailability() { - /* - * We must wait until all providers have been registered before filtering the list. - */ - return ValueAvailability.AfterAnalysis; + public boolean isAvailable() { + return BuildPhaseProvider.isHostedUniverseBuilt(); } @Override 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 9998db7c12c4..a04e52a94e40 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 @@ -220,9 +220,9 @@ public JavaConstant readValue(AnalysisField field, JavaConstant receiver, boolea * receiver of a wrong type. The code will later be removed as dead code, and in * most cases the field read would also be rejected as illegal by the HotSpot * constant reflection provider doing the actual field load. But there are several - * other ways how a field can be accessed, e.g., our ReadableJavaField mechanism or - * fields of classes that are initialized at image run time. To avoid any surprises, - * we abort the field reading here early. + * other ways how a field can be accessed, e.g., fields of classes that are + * initialized at image run time. To avoid any surprises, we abort the field reading + * here early. */ return null; } 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 index 4f899c7a811b..c7d20ec4d54c 100644 --- 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 @@ -42,22 +42,19 @@ 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.RuntimeAssertionsSupport; +import com.oracle.svm.core.annotate.Alias; import com.oracle.svm.core.annotate.InjectAccessors; import com.oracle.svm.core.annotate.RecomputeFieldValue; import com.oracle.svm.core.config.ConfigurationValues; 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; @@ -77,12 +74,8 @@ * 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. + * automatically for {@link Alias} fields with a {@link RecomputeFieldValue} annotation, as well as + * by the {@link AutomaticUnsafeTransformationSupport}. * * {@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 @@ -93,15 +86,28 @@ public final class FieldValueInterceptionSupport { private final AnnotationSubstitutionProcessor annotationSubstitutions; private final Map fieldValueInterceptors = new ConcurrentHashMap<>(); - private final ClassInitializationSupport classInitializationSupport; public static FieldValueInterceptionSupport singleton() { return ImageSingletons.lookup(FieldValueInterceptionSupport.class); } - public FieldValueInterceptionSupport(AnnotationSubstitutionProcessor annotationSubstitutions, ClassInitializationSupport classInitializationSupport) { + public FieldValueInterceptionSupport(AnnotationSubstitutionProcessor annotationSubstitutions) { this.annotationSubstitutions = annotationSubstitutions; - this.classInitializationSupport = classInitializationSupport; + } + + /** + * Returns a {@link FieldValueTransformer} if one was already registered for the field. In + * contrast to most other methods of this class, invoking this method does not prevent a future + * registration of a field value transformer for that field. + */ + public FieldValueTransformer lookupAlreadyRegisteredTransformer(ResolvedJavaField oField) { + assert !(oField instanceof OriginalFieldProvider) : oField; + + var existingInterceptor = fieldValueInterceptors.get(oField); + if (existingInterceptor instanceof FieldValueTransformation fieldValueTransformation) { + return fieldValueTransformation.getFieldValueTransformer(); + } + return null; } /** @@ -113,18 +119,17 @@ public void registerFieldValueTransformer(Field reflectionField, FieldValueTrans } public void registerFieldValueTransformer(ResolvedJavaField oField, FieldValueTransformer transformer) { - assert oField != null && !(oField instanceof OriginalFieldProvider) : 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"); } + registerFieldValueTransformer(oField, OriginalClassProvider.getJavaClass(oField.getType()), transformer); + } - 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."); - } + public void registerFieldValueTransformer(ResolvedJavaField oField, Class transformedValueAllowedType, FieldValueTransformer transformer) { + assert oField != null && !(oField instanceof OriginalFieldProvider) : oField; - var transformation = new FieldValueTransformation(OriginalClassProvider.getJavaClass(oField.getType()), Objects.requireNonNull(transformer)); + var transformation = new FieldValueTransformation(transformedValueAllowedType, Objects.requireNonNull(transformer)); var existingInterceptor = fieldValueInterceptors.putIfAbsent(oField, transformation); if (existingInterceptor == INTERCEPTOR_ACCESSED_MARKER) { @@ -132,7 +137,7 @@ public void registerFieldValueTransformer(ResolvedJavaField oField, FieldValueTr "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."); + "A field value transformer is already registered for this field, or the field value is transformed via an @Alias annotation."); } } @@ -205,7 +210,7 @@ public boolean isValueAvailable(AnalysisField field) { var interceptor = lookupFieldValueInterceptor(field); if (interceptor instanceof FieldValueTransformation transformation) { if (transformation.getFieldValueTransformer() instanceof FieldValueTransformerWithAvailability transformerWithAvailability) { - if (!isAvailable(transformerWithAvailability.valueAvailability())) { + if (!transformerWithAvailability.isAvailable()) { return false; } } @@ -213,41 +218,16 @@ public boolean isValueAvailable(AnalysisField field) { 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; + return lookupFieldValueInterceptor(field) instanceof FieldValueTransformation; } /** @@ -257,13 +237,11 @@ public boolean hasFieldValueTransformer(AnalysisField field) { public ValueNode tryIntrinsifyFieldLoad(CoreProviders providers, LoadFieldNode node) { var field = (AnalysisField) node.field(); - FieldValueTransformer transformer = null; var interceptor = lookupFieldValueInterceptor(field); - if (interceptor instanceof FieldValueTransformation transformation) { - transformer = transformation.getFieldValueTransformer(); - } else if (field.wrapped instanceof ComputedValueField computedValueField) { - transformer = computedValueField.getFieldValueTransformer(); + if (!(interceptor instanceof FieldValueTransformation transformation)) { + return null; } + var transformer = transformation.getFieldValueTransformer(); if (!(transformer instanceof FieldValueTransformerWithAvailability transformerWithAvailability)) { return null; } @@ -292,14 +270,44 @@ JavaConstant readFieldValue(AnalysisField field, JavaConstant receiver) { JavaConstant value; var interceptor = lookupFieldValueInterceptor(field); if (interceptor instanceof FieldValueTransformation transformation) { - value = transformation.readValue(classInitializationSupport, field.wrapped, receiver); - } else { + value = transformation.readValue(field, receiver); + + } else if (!field.getDeclaringClass().isInitialized()) { /* - * If the wrapped field is ComputedValueField, the ReadableJavaField handling does the - * necessary field value transformations. + * The class is initialized at image run time. We must not use any field value from the + * image builder VM, even if the class is already initialized there. We need to return + * the value expected before running the class initializer. */ - value = ReadableJavaField.readFieldValue(classInitializationSupport, field.wrapped, receiver); + if (field.isStatic()) { + /* + * Use the value from the constant pool attribute for the static field. That is the + * value before the class initializer is executed. + */ + JavaConstant constantValue = field.getConstantValue(); + if (constantValue != null) { + value = constantValue; + } else { + value = JavaConstant.defaultForKind(field.getJavaKind()); + } + + } else { + /* + * Classes that are initialized at run time must not have instances in the image + * heap. Invoking instance methods would miss the class initialization checks. Image + * generation should have been aborted earlier with a user-friendly message, this is + * just a safeguard. + */ + throw VMError.shouldNotReachHere("Cannot read instance field of a class that is initialized at run time: " + field.format("%H.%n")); + } + + } else { + ResolvedJavaField oField = OriginalFieldProvider.getOriginalField(field); + if (oField == null) { + throw VMError.shouldNotReachHere("Cannot read value of field that has no host value: " + field.format("%H.%n")); + } + value = GraalAccess.getOriginalProviders().getConstantReflection().readFieldValue(oField, receiver); } + return interceptValue(field, value); } 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 deleted file mode 100644 index 4e255fc49bfa..000000000000 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ameta/ReadableJavaField.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (c) 2014, 2017, 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 com.oracle.graal.pointsto.meta.AnalysisField; -import com.oracle.graal.pointsto.util.GraalAccess; -import com.oracle.svm.core.util.VMError; -import com.oracle.svm.hosted.classinitialization.ClassInitializationSupport; -import com.oracle.svm.hosted.meta.HostedField; - -import jdk.vm.ci.meta.JavaConstant; -import jdk.vm.ci.meta.ResolvedJavaField; - -public interface ReadableJavaField extends ResolvedJavaField { - - static JavaConstant readFieldValue(ClassInitializationSupport classInitializationSupport, ResolvedJavaField field, JavaConstant receiver) { - assert !(field instanceof AnalysisField) && !(field instanceof HostedField) : "must have been unwrapped"; - - if (field instanceof ReadableJavaField readableField) { - /* - * A ReadableJavaField is able to provide a field value even when the class is - * initialized at run time, so this check must be before the class initialization check - * below. - */ - assert readableField.isValueAvailable() : "Field " + readableField.format("%H.%n") + " value not available for reading."; - return readableField.readValue(classInitializationSupport, receiver); - - } else if (!classInitializationSupport.maybeInitializeAtBuildTime(field.getDeclaringClass())) { - /* - * The class is initialized at image run time. We must not use any field value from the - * image builder VM, even if the class is already initialized there. We need to return - * the value expected before running the class initializer. - * - * Note that we cannot rely on field.getDeclaringClass().isInitialized() for the class - * initialization check: we are already in the HotSpot universe here, and a class that - * is initialized in the hosting HotSpot VM can still be initialized at run time. - */ - if (field.isStatic()) { - /* - * Use the value from the constant pool attribute for the static field. That is the - * value before the class initializer is executed. - */ - JavaConstant constantValue = field.getConstantValue(); - if (constantValue != null) { - return constantValue; - } else { - return JavaConstant.defaultForKind(field.getJavaKind()); - } - - } else { - /* - * Classes that are initialized at run time must not have instances in the image - * heap. Invoking instance methods would miss the class initialization checks. Image - * generation should have been aborted earlier with a user-friendly message, this is - * just a safeguard. - */ - throw VMError.shouldNotReachHere("Cannot read instance field of a class that is initialized at run time: " + field.format("%H.%n")); - } - - } else { - return GraalAccess.getOriginalProviders().getConstantReflection().readFieldValue(field, receiver); - } - } - - JavaConstant readValue(ClassInitializationSupport classInitializationSupport, JavaConstant receiver); - - boolean isValueAvailable(); - - boolean injectFinalForRuntimeCompilation(); - - static boolean injectFinalForRuntimeCompilation(ResolvedJavaField original) { - if (original instanceof ReadableJavaField) { - return ((ReadableJavaField) original).injectFinalForRuntimeCompilation(); - } else { - return false; - } - } -} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ameta/SVMHostedValueProvider.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ameta/SVMHostedValueProvider.java index f6bdd6af7278..7f1d1eeb0cae 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ameta/SVMHostedValueProvider.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ameta/SVMHostedValueProvider.java @@ -68,13 +68,11 @@ public ValueSupplier readFieldValue(AnalysisField field, JavaConst return ValueSupplier.eagerValue(doReadValue(field, receiver)); } /* - * Return a lazy value. First, this applies to fields annotated with - * RecomputeFieldValue.Kind.FieldOffset and RecomputeFieldValue.Kind.Custom whose value - * becomes available during hosted universe building and is installed by calling - * ComputedValueField.processSubstrate() or ComputedValueField.readValue(). Secondly, this - * applies to fields annotated with @UnknownObjectField whose value is set directly either - * during analysis or in a later phase. Attempts to materialize the value before it becomes - * available will result in an error. + * Return a lazy value. First, this applies to fields that have a + * FieldValueTransformerWithAvailability. Secondly, this applies to fields annotated + * with @UnknownObjectField whose value is set directly either 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), () -> fieldValueInterceptionSupport.isValueAvailable(field)); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jfr/JfrEventFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jfr/JfrEventFeature.java index 1c6d08899faa..8a78b8efe37b 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jfr/JfrEventFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jfr/JfrEventFeature.java @@ -36,6 +36,7 @@ import org.graalvm.nativeimage.hosted.RuntimeClassInitialization; import org.graalvm.nativeimage.hosted.RuntimeReflection; +import com.oracle.svm.core.BuildPhaseProvider; import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; import com.oracle.svm.core.feature.InternalFeature; import com.oracle.svm.core.fieldvaluetransformer.FieldValueTransformerWithAvailability; @@ -102,8 +103,8 @@ public void duringSetup(DuringSetupAccess c) { var configField = ReflectionUtil.lookupField(DynamicHubCompanion.class, "jfrEventConfiguration"); FieldValueInterceptionSupport.singleton().registerFieldValueTransformer(configField, new FieldValueTransformerWithAvailability() { @Override - public ValueAvailability valueAvailability() { - return ValueAvailability.AfterAnalysis; + public boolean isAvailable() { + return BuildPhaseProvider.isHostedUniverseBuilt(); } @Override 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 5c1a802569aa..a4e93d3373b7 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 @@ -100,7 +100,6 @@ import com.oracle.svm.hosted.imagelayer.HostedDynamicLayerInfo; import com.oracle.svm.hosted.imagelayer.HostedImageLayerBuildingSupport; import com.oracle.svm.hosted.substitute.AnnotationSubstitutionProcessor; -import com.oracle.svm.hosted.substitute.ComputedValueField; import com.oracle.svm.hosted.substitute.DeletedMethod; import com.oracle.svm.util.ReflectionUtil; @@ -139,11 +138,6 @@ public UniverseBuilder(AnalysisUniverse aUniverse, AnalysisMetaAccess aMetaAcces */ @SuppressWarnings("try") public void build(DebugContext debug) { - for (AnalysisField aField : aUniverse.getFields()) { - if (aField.wrapped instanceof ComputedValueField) { - ((ComputedValueField) aField.wrapped).processAnalysis(aMetaAccess); - } - } aUniverse.seal(); try (Indent indent = debug.logAndIndent("build universe")) { @@ -1005,9 +999,6 @@ 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.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/methodhandles/MethodHandleFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/methodhandles/MethodHandleFeature.java index 696bba0419cc..aa5d67560ee9 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/methodhandles/MethodHandleFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/methodhandles/MethodHandleFeature.java @@ -42,6 +42,7 @@ import com.oracle.graal.pointsto.heap.ImageHeapScanner; import com.oracle.graal.pointsto.meta.AnalysisMetaAccess; import com.oracle.graal.pointsto.meta.AnalysisType; +import com.oracle.svm.core.BuildPhaseProvider; import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; import com.oracle.svm.core.feature.InternalFeature; import com.oracle.svm.core.fieldvaluetransformer.FieldValueTransformerWithAvailability; @@ -196,8 +197,8 @@ public void beforeAnalysis(BeforeAnalysisAccess a) { * structure. GR-46027 will implement a safe solution. */ @Override - public FieldValueTransformerWithAvailability.ValueAvailability valueAvailability() { - return FieldValueTransformerWithAvailability.ValueAvailability.AfterAnalysis; + public boolean isAvailable() { + return BuildPhaseProvider.isHostedUniverseBuilt(); } @Override @@ -238,8 +239,8 @@ private boolean isSpeciesTypeInstantiated(Object speciesData) { ReflectionUtil.lookupField(ReflectionUtil.lookupClass(false, "java.lang.invoke.ClassSpecializer$SpeciesData"), "transformHelpers"), new FieldValueTransformerWithAvailability() { @Override - public FieldValueTransformerWithAvailability.ValueAvailability valueAvailability() { - return FieldValueTransformerWithAvailability.ValueAvailability.AfterAnalysis; + public boolean isAvailable() { + return BuildPhaseProvider.isHostedUniverseBuilt(); } @Override diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionFeature.java index 54b54b7c3823..5d80ffe544e6 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionFeature.java @@ -51,6 +51,7 @@ import com.oracle.graal.pointsto.infrastructure.UniverseMetaAccess; import com.oracle.graal.pointsto.meta.AnalysisMethod; import com.oracle.graal.pointsto.meta.AnalysisUniverse; +import com.oracle.svm.core.BuildPhaseProvider; import com.oracle.svm.core.ParsingReason; import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.annotate.Delete; @@ -461,8 +462,8 @@ String uniqueShortName() { final class ComputeVTableOffset implements FieldValueTransformerWithAvailability { @Override - public ValueAvailability valueAvailability() { - return ValueAvailability.AfterAnalysis; + public boolean isAvailable() { + return BuildPhaseProvider.isHostedUniverseBuilt(); } @Override @@ -485,8 +486,8 @@ public Object transform(Object receiver, Object originalValue) { final class ComputeInterfaceTypeID implements FieldValueTransformerWithAvailability { @Override - public ValueAvailability valueAvailability() { - return ValueAvailability.AfterAnalysis; + public boolean isAvailable() { + return BuildPhaseProvider.isHostedUniverseBuilt(); } @Override diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/AliasField.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/AliasField.java new file mode 100644 index 000000000000..326b406904f9 --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/AliasField.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2012, 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.lang.reflect.AnnotatedElement; +import java.lang.reflect.Modifier; + +import com.oracle.graal.pointsto.infrastructure.OriginalFieldProvider; +import com.oracle.svm.hosted.annotation.AnnotationWrapper; + +import jdk.vm.ci.meta.JavaConstant; +import jdk.vm.ci.meta.JavaType; +import jdk.vm.ci.meta.ResolvedJavaField; +import jdk.vm.ci.meta.ResolvedJavaType; + +public final class AliasField implements ResolvedJavaField, OriginalFieldProvider, AnnotationWrapper { + + final ResolvedJavaField original; + final ResolvedJavaField annotated; + + final boolean isFinal; + + AliasField(ResolvedJavaField original, ResolvedJavaField annotated, boolean isFinal) { + this.original = original; + this.annotated = annotated; + this.isFinal = isFinal; + } + + @Override + public String getName() { + return original.getName(); + } + + @Override + public JavaType getType() { + return original.getType(); + } + + @Override + public int getModifiers() { + int result = original.getModifiers(); + if (isFinal) { + result = result | Modifier.FINAL; + } else { + result = result & ~Modifier.FINAL; + } + return result; + } + + @Override + public int getOffset() { + return original.getOffset(); + } + + @Override + public boolean isInternal() { + return original.isInternal(); + } + + @Override + public boolean isSynthetic() { + return original.isSynthetic(); + } + + @Override + public ResolvedJavaType getDeclaringClass() { + return original.getDeclaringClass(); + } + + @Override + public AnnotatedElement getAnnotationRoot() { + return original; + } + + @Override + public String toString() { + return "AliasField"; + } + + @Override + public ResolvedJavaField unwrapTowardsOriginalField() { + return original; + } + + @Override + public JavaConstant getConstantValue() { + return original.getConstantValue(); + } +} 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 cab58f3f8fb0..289a7d66a613 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 @@ -28,19 +28,16 @@ import java.lang.reflect.AnnotatedElement; 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; import com.oracle.svm.hosted.annotation.SubstrateAnnotationExtractor; -import com.oracle.svm.hosted.classinitialization.ClassInitializationSupport; import jdk.vm.ci.meta.JavaConstant; import jdk.vm.ci.meta.JavaType; import jdk.vm.ci.meta.ResolvedJavaField; import jdk.vm.ci.meta.ResolvedJavaType; -public class AnnotatedField implements ReadableJavaField, OriginalFieldProvider, AnnotationWrapper { +public class AnnotatedField implements ResolvedJavaField, OriginalFieldProvider, AnnotationWrapper { private final ResolvedJavaField original; private final AnnotationValue[] injectedAnnotations; @@ -60,25 +57,6 @@ public AnnotationValue[] getInjectedAnnotations() { return injectedAnnotations; } - @Override - public JavaConstant readValue(ClassInitializationSupport classInitializationSupport, JavaConstant receiver) { - return ReadableJavaField.readFieldValue(classInitializationSupport, original, receiver); - } - - @Override - public boolean isValueAvailable() { - /* - * We assume that fields for which this class is used always have altered behavior for which - * constant folding before or during analysis is not valid. - */ - return BuildPhaseProvider.isAnalysisFinished(); - } - - @Override - public boolean injectFinalForRuntimeCompilation() { - return ReadableJavaField.injectFinalForRuntimeCompilation(original); - } - /* The remaining methods just forward to the original field. */ @Override @@ -118,7 +96,7 @@ public ResolvedJavaType getDeclaringClass() { @Override public String toString() { - return "InjectedAnnotationField"; + return "AnnotatedField"; } @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 82ed6d51d2cc..7a75a3ec2e82 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 @@ -51,6 +51,7 @@ 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; @@ -65,16 +66,23 @@ import com.oracle.svm.core.annotate.InjectAccessors; import com.oracle.svm.core.annotate.KeepOriginal; import com.oracle.svm.core.annotate.RecomputeFieldValue; -import com.oracle.svm.core.annotate.RecomputeFieldValue.Kind; import com.oracle.svm.core.annotate.Substitute; import com.oracle.svm.core.annotate.TargetClass; import com.oracle.svm.core.annotate.TargetElement; +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.ConstantValueFieldValueTransformer; +import com.oracle.svm.core.fieldvaluetransformer.FieldOffsetFieldValueTransformer; +import com.oracle.svm.core.fieldvaluetransformer.NewInstanceOfFixedClassFieldValueTransformer; +import com.oracle.svm.core.fieldvaluetransformer.StaticFieldBaseFieldValueTransformer; import com.oracle.svm.core.option.SubstrateOptionsParser; import com.oracle.svm.core.util.UserError; import com.oracle.svm.core.util.VMError; import com.oracle.svm.hosted.ImageClassLoader; import com.oracle.svm.hosted.NativeImageGenerator; import com.oracle.svm.hosted.NativeImageOptions; +import com.oracle.svm.hosted.ameta.FieldValueInterceptionSupport; import com.oracle.svm.hosted.classinitialization.ClassInitializationSupport; import com.oracle.svm.util.ReflectionUtil; import com.oracle.svm.util.ReflectionUtil.ReflectionUtilError; @@ -99,12 +107,14 @@ public class AnnotationSubstitutionProcessor extends SubstitutionProcessor { protected final ImageClassLoader imageClassLoader; protected final MetaAccessProvider metaAccess; + private FieldValueInterceptionSupport fieldValueInterceptionSupport; private final Map deleteAnnotations; private final Map typeSubstitutions; private final Map methodSubstitutions; private final Map polymorphicMethodSubstitutions; private final Map fieldSubstitutions; + private Map unsafeAccessedFields = new HashMap<>(); private final ClassInitializationSupport classInitializationSupport; public AnnotationSubstitutionProcessor(ImageClassLoader imageClassLoader, MetaAccessProvider metaAccess, ClassInitializationSupport classInitializationSupport) { @@ -276,23 +286,23 @@ public ResolvedJavaMethod lookup(ResolvedJavaMethod method) { /** * Eagerly register all target fields of recomputed value fields as unsafe accessed. */ - public void processComputedValueFields(BigBang bb) { - for (ResolvedJavaField field : fieldSubstitutions.values()) { - if (field instanceof ComputedValueField) { - ComputedValueField cvField = (ComputedValueField) field; - - switch (cvField.getRecomputeValueKind()) { - case FieldOffset: - AnalysisField targetField = bb.getMetaAccess().lookupJavaField(cvField.getTargetField()); - assert !AnnotationAccess.isAnnotationPresent(targetField, Delete.class); - targetField.registerAsUnsafeAccessed(cvField); - break; - } - } - } + public void registerUnsafeAccessedFields(BigBang bb) { + for (var entry : unsafeAccessedFields.entrySet()) { + AnalysisField targetField = bb.getMetaAccess().lookupJavaField(entry.getKey()); + assert !AnnotationAccess.isAnnotationPresent(targetField, Delete.class); + targetField.registerAsUnsafeAccessed(entry.getValue()); + } + /* Prevent later additions that would go unnoticed. */ + unsafeAccessedFields = null; } - public void init() { + public void init(FieldValueInterceptionSupport newFieldValueInterceptionSupport) { + /** + * Cannot set this field in the constructor due to cyclic dependencies between the two + * classes. + */ + this.fieldValueInterceptionSupport = newFieldValueInterceptionSupport; + List> annotatedClasses = findTargetClasses(); /* Sort by name to make processing order predictable for debugging. */ @@ -554,11 +564,8 @@ private void handleFieldInAliasClass(Field annotatedField, Class originalClas private static boolean isCompatible(ResolvedJavaField computedAlias, ResolvedJavaField existingAlias) { /* The only use case at the moment are multiple @Alias definitions for a final field. */ - if (computedAlias instanceof ComputedValueField) { - ComputedValueField c = (ComputedValueField) computedAlias; - if (c.getRecomputeValueKind() == RecomputeFieldValue.Kind.None) { - return c.isCompatible(existingAlias); - } + if (computedAlias instanceof AliasField computed && existingAlias instanceof AliasField existing) { + return computed.original.equals(existing.original) && computed.isFinal == existing.isFinal; } return false; } @@ -631,7 +638,9 @@ private void registerAsDeleted(ResolvedJavaMethod annotated, ResolvedJavaMethod private void registerAsDeleted(ResolvedJavaField annotated, ResolvedJavaField original, Delete deleteAnnotation) { if (NativeImageOptions.ReportUnsupportedElementsAtRuntime.getValue()) { - register(fieldSubstitutions, annotated, original, new AnnotatedField(original, deleteAnnotation)); + AnnotatedField annotatedField = new AnnotatedField(original, deleteAnnotation); + register(fieldSubstitutions, annotated, original, annotatedField); + fieldValueInterceptionSupport.registerFieldValueTransformer(original, null, new ValueNeverAvailableFieldValueTransformer(annotatedField)); } else { deleteAnnotations.put(original, deleteAnnotation); deleteAnnotations.put(annotated, deleteAnnotation); @@ -935,7 +944,9 @@ private ResolvedJavaField fieldValueRecomputation(Class originalClass, Resolv guarantee(numAnnotations <= 1, "Only one of @RecomputeFieldValue or @InjectAccessors can be used: %s", annotatedField); if (injectAccessorsAnnotation != null) { - return new AnnotatedField(original, injectAccessorsAnnotation); + AnnotatedField result = new AnnotatedField(original, injectAccessorsAnnotation); + fieldValueInterceptionSupport.registerFieldValueTransformer(original, null, new ValueNeverAvailableFieldValueTransformer(result)); + return result; } if (recomputeAnnotation == null && !original.isFinal()) { return original; @@ -950,7 +961,8 @@ private ResolvedJavaField fieldValueRecomputation(Class originalClass, Resolv kind = recomputeAnnotation.kind(); targetName = recomputeAnnotation.name(); isFinal = recomputeAnnotation.isFinal(); - guarantee(!isFinal || !ComputedValueField.isOffsetRecomputation(kind), "@%s with %s can never be final during analysis: unset isFinal in the annotation on %s", + guarantee(!isFinal || (kind != RecomputeFieldValue.Kind.FieldOffset && kind != RecomputeFieldValue.Kind.TranslateFieldOffset && kind != RecomputeFieldValue.Kind.AtomicFieldUpdaterOffset), + "@%s with %s can never be final during analysis: unset isFinal in the annotation on %s", RecomputeFieldValue.class.getSimpleName(), kind, annotated); if (recomputeAnnotation.declClass() != RecomputeFieldValue.class) { guarantee(recomputeAnnotation.declClassName().isEmpty(), "Both class and class name specified"); @@ -961,13 +973,67 @@ private ResolvedJavaField fieldValueRecomputation(Class originalClass, Resolv } Class transformedValueAllowedType = getTargetClass(annotatedField.getType()); - return ComputedValueField.create(original, annotated, kind, transformedValueAllowedType, null, targetClass, targetName, isFinal); + var newTransformer = switch (kind) { + case None, Manual -> null; + case Reset -> ConstantValueFieldValueTransformer.defaultValueForField(original); + case NewInstance -> new NewInstanceOfFixedClassFieldValueTransformer(targetClass, false); + case NewInstanceWhenNotNull -> new NewInstanceOfFixedClassFieldValueTransformer(targetClass, true); + case FromAlias -> { + if (!Modifier.isStatic(annotated.getModifiers())) { + throw UserError.abort("Cannot use " + kind + " on non-static alias " + annotated.format("%H.%n")); + } + yield new FromAliasFieldValueTransformer(annotated); + } + case FieldOffset -> { + var targetField = getField(annotated, targetClass, targetName); + unsafeAccessedFields.put(targetField, original); + yield new FieldOffsetFieldValueTransformer(targetField, original.getType().getJavaKind()); + } + case StaticFieldBase -> { + var targetField = getField(annotated, targetClass, targetName); + if (!Modifier.isStatic(targetField.getModifiers())) { + throw UserError.abort("Target field must be static for " + kind + " computation of alias " + annotated.format("%H.%n")); + } + yield new StaticFieldBaseFieldValueTransformer(targetField); + } + case ArrayBaseOffset -> + new ArrayBaseOffsetFieldValueTransformer(targetClass, original.getType().getJavaKind()); + case ArrayIndexScale -> + new ArrayIndexScaleFieldValueTransformer(targetClass, original.getType().getJavaKind()); + case ArrayIndexShift -> + new ArrayIndexShiftFieldValueTransformer(targetClass, original.getType().getJavaKind()); + case AtomicFieldUpdaterOffset -> new AtomicFieldUpdaterOffsetFieldValueTransformer(original, targetClass); + case TranslateFieldOffset -> new TranslateFieldOffsetFieldValueTransformer(original, targetClass); + case Custom -> (FieldValueTransformer) ReflectionUtil.newInstance(targetClass); + }; + + if (newTransformer != null) { + FieldValueTransformer existingTransformer = fieldValueInterceptionSupport.lookupAlreadyRegisteredTransformer(original); + if (existingTransformer != null) { + if (existingTransformer.equals(newTransformer)) { + /* Equivalent transformations are allowed, nothing to do. */ + } else { + throw UserError.abort("Field value recomputation %s conflicts with an already registered field value transformer.", annotated.format("%H.%n")); + } + } else { + fieldValueInterceptionSupport.registerFieldValueTransformer(original, transformedValueAllowedType, newTransformer); + } + } + + return new AliasField(original, annotated, isFinal); + } + + private static Field getField(ResolvedJavaField annotated, Class targetClass, String targetName) { + try { + return ReflectionUtil.lookupField(targetClass, targetName); + } catch (ReflectionUtilError e) { + throw UserError.abort("Could not find target field %s.%s for alias %s.", targetClass.getName(), targetName, annotated == null ? null : annotated.format("%H.%n")); + } } protected void reinitializeField(Field annotatedField) { ResolvedJavaField annotated = metaAccess.lookupJavaField(annotatedField); - ComputedValueField alias = ComputedValueField.create(annotated, annotated, Kind.Reset, annotatedField.getDeclaringClass(), "", false); - register(fieldSubstitutions, annotated, annotated, alias); + fieldValueInterceptionSupport.registerFieldValueTransformer(annotated, ConstantValueFieldValueTransformer.defaultValueForField(annotated)); } public Class getTargetClass(Class annotatedClass) { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/AtomicFieldUpdaterOffsetFieldValueTransformer.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/AtomicFieldUpdaterOffsetFieldValueTransformer.java new file mode 100644 index 000000000000..3572b20777c4 --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/AtomicFieldUpdaterOffsetFieldValueTransformer.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2024, 2024, 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 static com.oracle.svm.core.util.VMError.shouldNotReachHere; + +import java.lang.reflect.Modifier; + +import com.oracle.graal.pointsto.util.GraalAccess; +import com.oracle.svm.core.BuildPhaseProvider; +import com.oracle.svm.core.annotate.RecomputeFieldValue.Kind; +import com.oracle.svm.core.fieldvaluetransformer.FieldValueTransformerWithAvailability; + +import jdk.graal.compiler.api.replacements.SnippetReflectionProvider; +import jdk.vm.ci.meta.JavaConstant; +import jdk.vm.ci.meta.ResolvedJavaField; +import jdk.vm.ci.meta.ResolvedJavaType; + +/** + * Implements the field value transformation semantics of {@link Kind#AtomicFieldUpdaterOffset}. + */ +public record AtomicFieldUpdaterOffsetFieldValueTransformer(ResolvedJavaField original, Class updaterClass) implements FieldValueTransformerWithAvailability { + + @Override + public boolean isAvailable() { + return BuildPhaseProvider.isHostedUniverseBuilt(); + } + + @Override + public Object transform(Object receiver, Object originalValue) { + assert !Modifier.isStatic(original.getModifiers()); + + /* + * AtomicXxxFieldUpdater implementation objects cache the offset of some specified field. We + * have to search the declaring class for a field that has the same "unsafe" offset as the + * cached offset in this atomic updater object. + */ + ResolvedJavaField tclassField = findField(original.getDeclaringClass(), "tclass"); + SnippetReflectionProvider originalSnippetReflection = GraalAccess.getOriginalSnippetReflection(); + JavaConstant receiverConstant = GraalAccess.getOriginalSnippetReflection().forObject(receiver); + Class tclass = originalSnippetReflection.asObject(Class.class, GraalAccess.getOriginalProviders().getConstantReflection().readFieldValue(tclassField, receiverConstant)); + + return TranslateFieldOffsetFieldValueTransformer.translateFieldOffset(original, receiverConstant, tclass); + } + + private static ResolvedJavaField findField(ResolvedJavaType declaringClass, String name) { + for (ResolvedJavaField field : declaringClass.getInstanceFields(false)) { + if (field.getName().equals(name)) { + return field; + } + } + throw shouldNotReachHere("Field not found: " + declaringClass.toJavaName(true) + "." + name); + } +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/AutomaticUnsafeTransformationSupport.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/AutomaticUnsafeTransformationSupport.java index 7c5f1beadb6d..5d6e5d705dd3 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/AutomaticUnsafeTransformationSupport.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/AutomaticUnsafeTransformationSupport.java @@ -268,32 +268,6 @@ public AutomaticUnsafeTransformationSupport(OptionValues options, AnnotationSubs suppressWarnings = List.of(originalMetaAccess.lookupJavaType(ReflectionUtil.lookupClass(false, "sun.security.provider.ByteArrayAccess"))); } - /** - * 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. - */ - private static void addTransformation(BigBang bb, ResolvedJavaField original, ComputedValueField transformation) { - JavaKind returnKind = original.getType().getJavaKind(); - - FieldValueTransformer transformer = switch (transformation.getRecomputeValueKind()) { - case ArrayBaseOffset -> new ArrayBaseOffsetFieldValueTransformer(transformation.getTargetClass(), returnKind); - case ArrayIndexScale -> new ArrayIndexScaleFieldValueTransformer(transformation.getTargetClass(), returnKind); - case ArrayIndexShift -> new ArrayIndexShiftFieldValueTransformer(transformation.getTargetClass(), returnKind); - 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 static FieldOffsetFieldValueTransformer createFieldOffsetFieldValueTransformer(BigBang bb, ResolvedJavaField original, Field targetField) { - bb.postTask(debugContext -> bb.getMetaAccess().lookupJavaField(targetField).registerAsUnsafeAccessed(original)); - return new FieldOffsetFieldValueTransformer(targetField, original.getType().getJavaKind()); - } - @SuppressWarnings("try") public void computeTransformations(BigBang bb, SVMHost hostVM, ResolvedJavaType hostType) { if (hostType.isArray()) { @@ -371,22 +345,19 @@ public void computeTransformations(BigBang bb, SVMHost hostVM, ResolvedJavaType private void processUnsafeFieldComputation(BigBang bb, ResolvedJavaType type, Invoke invoke, Kind kind) { List> unsuccessfulReasons = new ArrayList<>(); - Class targetFieldHolder = null; - String targetFieldName = null; - + Field targetField = null; String methodFormat = invoke.callTarget().targetMethod().format("%H.%n(%P)"); ValueNode fieldArgumentNode = invoke.callTarget().arguments().get(1); JavaConstant fieldArgument = nodeAsConstant(fieldArgumentNode); if (fieldArgument != null) { - Field targetField = GraalAccess.getOriginalSnippetReflection().asObject(Field.class, fieldArgument); - if (isValidField(invoke, targetField, unsuccessfulReasons, methodFormat)) { - targetFieldHolder = targetField.getDeclaringClass(); - targetFieldName = targetField.getName(); + Field field = GraalAccess.getOriginalSnippetReflection().asObject(Field.class, fieldArgument); + if (isValidField(invoke, field, unsuccessfulReasons, methodFormat)) { + targetField = field; } } else { unsuccessfulReasons.add(() -> "The argument of " + methodFormat + " is not a constant value or a field load that can be constant-folded."); } - processUnsafeFieldComputation(bb, type, invoke, kind, unsuccessfulReasons, targetFieldHolder, targetFieldName); + processUnsafeFieldComputation(bb, type, invoke, kind, unsuccessfulReasons, targetField); } /** @@ -475,11 +446,17 @@ private void processUnsafeObjectFieldOffsetClassStringInvoke(BigBang bb, Resolve } else { unsuccessfulReasons.add(() -> "The name argument of Unsafe.objectFieldOffset(Class, String) is not a constant String."); } - processUnsafeFieldComputation(bb, type, unsafeObjectFieldOffsetInvoke, FieldOffset, unsuccessfulReasons, targetFieldHolder, targetFieldName); + Field targetField = null; + if (unsuccessfulReasons.isEmpty()) { + targetField = ReflectionUtil.lookupField(true, targetFieldHolder, targetFieldName); + if (targetField == null) { + unsuccessfulReasons.add(() -> "The arguments of Unsafe.objectFieldOffset(Class, String) do not reference an existing field."); + } + } + processUnsafeFieldComputation(bb, type, unsafeObjectFieldOffsetInvoke, FieldOffset, unsuccessfulReasons, targetField); } - private void processUnsafeFieldComputation(BigBang bb, 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, Field targetField) { assert kind == FieldOffset || kind == StaticFieldBase; /* * If the value returned by the call to Unsafe.objectFieldOffset() is stored into a field @@ -497,10 +474,9 @@ private void processUnsafeFieldComputation(BigBang bb, ResolvedJavaType type, In * If the target field holder and name, and the offset field were found try to register a * transformation. */ - if (targetFieldHolder != null && targetFieldName != null && valueStoreField != null) { - Supplier supplier = () -> ComputedValueField.create(valueStoreField, null, kind, targetFieldHolder, targetFieldName, false); - if (tryAutomaticTransformation(bb, valueStoreField, kind, supplier)) { - reportSuccessfulAutomaticRecomputation(kind, valueStoreField, targetFieldHolder.getName() + "." + targetFieldName); + if (targetField != null && valueStoreField != null) { + if (tryAutomaticTransformation(bb, valueStoreField, kind, null, targetField)) { + reportSuccessfulAutomaticRecomputation(kind, valueStoreField, targetField.getDeclaringClass().getName() + "." + targetField.getName()); } } else { reportUnsuccessfulAutomaticRecomputation(type, valueStoreField, invoke, kind, unsuccessfulReasons); @@ -534,8 +510,7 @@ private void processUnsafeArrayBaseOffsetInvoke(BigBang bb, ResolvedJavaType typ ResolvedJavaField offsetField = result.valueStoreField; if (arrayClass != null && offsetField != null) { Class finalArrayClass = arrayClass; - Supplier supplier = () -> ComputedValueField.create(offsetField, null, ArrayBaseOffset, finalArrayClass, null, true); - if (tryAutomaticTransformation(bb, offsetField, ArrayBaseOffset, supplier)) { + if (tryAutomaticTransformation(bb, offsetField, ArrayBaseOffset, finalArrayClass, null)) { reportSuccessfulAutomaticRecomputation(ArrayBaseOffset, offsetField, arrayClass.getCanonicalName()); } } else { @@ -576,9 +551,7 @@ private void processUnsafeArrayIndexScaleInvoke(BigBang bb, ResolvedJavaType typ if (arrayClass != null) { if (indexScaleField != null) { - Class finalArrayClass = arrayClass; - Supplier supplier = () -> ComputedValueField.create(indexScaleField, null, ArrayIndexScale, finalArrayClass, null, true); - if (tryAutomaticTransformation(bb, indexScaleField, ArrayIndexScale, supplier)) { + if (tryAutomaticTransformation(bb, indexScaleField, ArrayIndexScale, arrayClass, null)) { reportSuccessfulAutomaticRecomputation(ArrayIndexScale, indexScaleField, arrayClass.getCanonicalName()); indexScaleComputed = true; /* Try transformation for the array index shift computation if present. */ @@ -697,9 +670,7 @@ private boolean processArrayIndexShift(BigBang bb, ResolvedJavaType type, Class< } if (indexShiftField != null) { - ResolvedJavaField finalIndexShiftField = indexShiftField; - Supplier supplier = () -> ComputedValueField.create(finalIndexShiftField, null, ArrayIndexShift, arrayClass, null, true); - if (tryAutomaticTransformation(bb, indexShiftField, ArrayIndexShift, supplier)) { + if (tryAutomaticTransformation(bb, indexShiftField, ArrayIndexShift, arrayClass, null)) { reportSuccessfulAutomaticRecomputation(ArrayIndexShift, indexShiftField, arrayClass.getCanonicalName()); return true; } @@ -888,58 +859,62 @@ private boolean isAllowedUnsafeValueSink(Node valueNodeUsage) { * Try to register the automatic transformation for a field. Bail if the field was deleted or a * conflicting substitution is detected. */ - private boolean tryAutomaticTransformation(BigBang bb, ResolvedJavaField field, Kind kind, Supplier transformationSupplier) { + private boolean tryAutomaticTransformation(BigBang bb, ResolvedJavaField field, RecomputeFieldValue.Kind kind, Class targetClass, Field targetField) { if (annotationSubstitutions.isDeleted(field)) { String conflictingSubstitution = "The field " + field.format("%H.%n") + " is marked as deleted. "; reportConflictingSubstitution(field, kind, conflictingSubstitution); return false; } else { - 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); return false; } + + FieldValueTransformer newTransformer = switch (kind) { + case ArrayBaseOffset -> new ArrayBaseOffsetFieldValueTransformer(targetClass, field.getType().getJavaKind()); + case ArrayIndexScale -> new ArrayIndexScaleFieldValueTransformer(targetClass, field.getType().getJavaKind()); + case ArrayIndexShift -> new ArrayIndexShiftFieldValueTransformer(targetClass, field.getType().getJavaKind()); + case FieldOffset -> new FieldOffsetFieldValueTransformer(targetField, field.getType().getJavaKind()); + case StaticFieldBase -> new StaticFieldBaseFieldValueTransformer(targetField); + default -> throw VMError.shouldNotReachHere("Unexpected kind: " + kind); + }; + + FieldValueTransformer existingTransformer = FieldValueInterceptionSupport.singleton().lookupAlreadyRegisteredTransformer(field); + if (existingTransformer != null) { + if (existingTransformer.equals(newTransformer)) { + reportUnnecessarySubstitution(field, kind); + } else if (existingTransformer.getClass() == newTransformer.getClass()) { + /* + * Skip the warning when, for example, 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. + */ + } else { + String conflictingSubstitution = "Detected and existing field value transformer: " + existingTransformer; + reportConflictingSubstitution(field, kind, conflictingSubstitution); + } + return false; + } + Optional annotationSubstitution = annotationSubstitutions.findSubstitution(field); if (annotationSubstitution.isPresent()) { ResolvedJavaField substitutionField = annotationSubstitution.get(); - 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(transformation, computedValueField); - } - return false; - } 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. - */ - addTransformation(bb, field, transformation); - reportOvewrittenSubstitution(substitutionField, kind, computedValueField.getAnnotated(), computedValueField.getRecomputeValueKind()); - return true; - } else { - String conflictingSubstitution = "Detected RecomputeFieldValue." + computedValueField.getRecomputeValueKind() + - " " + computedValueField.getAnnotated().format("%H.%n") + " substitution field. "; - reportConflictingSubstitution(substitutionField, kind, conflictingSubstitution); - return false; - } + if (substitutionField instanceof AliasField) { + /* An @Alias for a field with an automatic recomputed value is allowed. */ } else { String conflictingSubstitution = "Detected " + substitutionField.format("%H.%n") + " substitution field. "; - reportConflictingSubstitution(substitutionField, kind, conflictingSubstitution); + reportConflictingSubstitution(field, kind, conflictingSubstitution); return false; } - } else { - /* No other substitutions detected. */ - addTransformation(bb, field, transformation); - return true; } + + if (kind == FieldOffset) { + bb.postTask(debugContext -> bb.getMetaAccess().lookupJavaField(targetField).registerAsUnsafeAccessed(field)); + } + FieldValueInterceptionSupport.singleton().registerFieldValueTransformer(field, newTransformer); + return true; } } @@ -950,16 +925,12 @@ private static void reportSkippedTransformation(ResolvedJavaType type) { } } - private static void reportUnnecessarySubstitution(ResolvedJavaField offsetField, ComputedValueField computedSubstitutionField) { + private static void reportUnnecessarySubstitution(ResolvedJavaField field, Kind kind) { 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.AutomaticUnsafeTransformationLogLevel, "+"); - LogUtils.warning("Detected unnecessary %s %s substitution field for %s. The annotated field can be removed. " + + 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); + RecomputeFieldValue.class.getSimpleName(), kind, field.format("%H.%n"), kind, optionStr, INFO_LEVEL); } } @@ -971,16 +942,6 @@ private static void reportSuccessfulAutomaticRecomputation(Kind recomputeKind, R } } - private static void reportOvewrittenSubstitution(ResolvedJavaField offsetField, Kind newKind, ResolvedJavaField overwrittenField, Kind overwrittenKind) { - if (Options.AutomaticUnsafeTransformationLogLevel.getValue() >= INFO_LEVEL) { - String newKindStr = RecomputeFieldValue.class.getSimpleName() + "." + newKind; - String overwrittenKindStr = RecomputeFieldValue.class.getSimpleName() + "." + overwrittenKind; - String offsetFieldStr = offsetField.format("%H.%n"); - String overwrittenFieldStr = overwrittenField.format("%H.%n"); - LogUtils.info("The %s %s substitution was overwritten. A %s substitution for %s was automatically registered.", overwrittenKindStr, overwrittenFieldStr, newKindStr, offsetFieldStr); - } - } - private static void reportConflictingSubstitution(ResolvedJavaField field, Kind recomputeKind, String conflictingSubstitution) { if (Options.AutomaticUnsafeTransformationLogLevel.getValue() >= BASIC_LEVEL) { String fieldStr = field.format("%H.%n"); 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 deleted file mode 100644 index c5b650b0cfc1..000000000000 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/ComputedValueField.java +++ /dev/null @@ -1,436 +0,0 @@ -/* - * Copyright (c) 2012, 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 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; -import static com.oracle.svm.core.annotate.RecomputeFieldValue.Kind.TranslateFieldOffset; -import static com.oracle.svm.core.util.VMError.guarantee; -import static com.oracle.svm.core.util.VMError.shouldNotReachHere; -import static com.oracle.svm.core.util.VMError.shouldNotReachHereUnexpectedInput; - -import java.lang.reflect.AnnotatedElement; -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import java.util.EnumSet; -import java.util.Objects; - -import org.graalvm.nativeimage.ImageSingletons; -import org.graalvm.nativeimage.hosted.FieldValueTransformer; - -import com.oracle.graal.pointsto.infrastructure.OriginalFieldProvider; -import com.oracle.graal.pointsto.meta.AnalysisField; -import com.oracle.graal.pointsto.meta.AnalysisMetaAccess; -import com.oracle.graal.pointsto.util.GraalAccess; -import com.oracle.svm.core.BuildPhaseProvider; -import com.oracle.svm.core.StaticFieldsSupport; -import com.oracle.svm.core.annotate.RecomputeFieldValue; -import com.oracle.svm.core.config.ConfigurationValues; -import com.oracle.svm.core.fieldvaluetransformer.FieldValueTransformerWithAvailability; -import com.oracle.svm.core.fieldvaluetransformer.FieldValueTransformerWithAvailability.ValueAvailability; -import com.oracle.svm.core.reflect.target.ReflectionSubstitutionSupport; -import com.oracle.svm.core.util.UserError; -import com.oracle.svm.core.util.VMError; -import com.oracle.svm.hosted.ameta.ReadableJavaField; -import com.oracle.svm.hosted.annotation.AnnotationWrapper; -import com.oracle.svm.hosted.classinitialization.ClassInitializationSupport; -import com.oracle.svm.hosted.meta.HostedMetaAccess; -import com.oracle.svm.util.ReflectionUtil; -import com.oracle.svm.util.ReflectionUtil.ReflectionUtilError; - -import jdk.graal.compiler.api.replacements.SnippetReflectionProvider; -import jdk.internal.misc.Unsafe; -import jdk.vm.ci.common.NativeImageReinitialize; -import jdk.vm.ci.meta.JavaConstant; -import jdk.vm.ci.meta.JavaKind; -import jdk.vm.ci.meta.JavaType; -import jdk.vm.ci.meta.ResolvedJavaField; -import jdk.vm.ci.meta.ResolvedJavaType; - -/** - * Wraps a field whose value is recomputed when added to an image. - * - * @see RecomputeFieldValue - * @see NativeImageReinitialize - */ -public final class ComputedValueField extends FieldValueTransformation implements ReadableJavaField, OriginalFieldProvider, AnnotationWrapper { - - private static final EnumSet offsetComputationKinds = EnumSet.of(FieldOffset, TranslateFieldOffset, AtomicFieldUpdaterOffset); - private final ResolvedJavaField original; - private final ResolvedJavaField annotated; - - private final RecomputeFieldValue.Kind kind; - private final Class targetClass; - private final Field targetField; - private final boolean isFinal; - /** True if the value doesn't depend on any analysis results. */ - private final boolean isValueAvailableBeforeAnalysis; - /** True if the value depends on analysis results. */ - private final boolean isValueAvailableOnlyAfterAnalysis; - /** True if the value depends on compilation results. */ - private final boolean isValueAvailableOnlyAfterCompilation; - - private JavaConstant constantValue; - - 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); - } - - public static ComputedValueField create(ResolvedJavaField original, ResolvedJavaField annotated, RecomputeFieldValue.Kind kind, Class transformedValueAllowedType, - FieldValueTransformer initialTransformer, Class targetClass, String targetName, boolean isFinal) { - assert original != null; - assert initialTransformer != null || targetClass != null; - - 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(original.getType().getJavaKind()); - break; - case FieldOffset: - f = getField(annotated, targetClass, targetName); - break; - case StaticFieldBase: - f = getField(annotated, targetClass, targetName); - 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) { - transformer = initialTransformer; - } else { - transformer = (FieldValueTransformer) ReflectionUtil.newInstance(targetClass); - } - - if (transformer instanceof FieldValueTransformerWithAvailability) { - ValueAvailability valueAvailability = ((FieldValueTransformerWithAvailability) transformer).valueAvailability(); - customValueAvailableBeforeAnalysis = valueAvailability == ValueAvailability.BeforeAnalysis; - customValueAvailableOnlyAfterAnalysis = valueAvailability == ValueAvailability.AfterAnalysis; - customValueAvailableOnlyAfterCompilation = valueAvailability == ValueAvailability.AfterCompilation; - } - } - boolean isOffsetField = isOffsetRecomputation(kind); - boolean isStaticFieldBase = kind == StaticFieldBase; - guarantee(!isFinal || !isOffsetField); - boolean isValueAvailableBeforeAnalysis = customValueAvailableBeforeAnalysis && !isOffsetField && !isStaticFieldBase; - boolean isValueAvailableOnlyAfterAnalysis = customValueAvailableOnlyAfterAnalysis || isOffsetField || isStaticFieldBase; - boolean isValueAvailableOnlyAfterCompilation = customValueAvailableOnlyAfterCompilation; - - return new ComputedValueField(original, annotated, kind, transformedValueAllowedType, transformer, isFinal, targetClass, f, isValueAvailableBeforeAnalysis, - isValueAvailableOnlyAfterAnalysis, isValueAvailableOnlyAfterCompilation, constantValue); - } - - private static Field getField(ResolvedJavaField annotated, Class targetClass, String targetName) { - try { - return ReflectionUtil.lookupField(targetClass, targetName); - } catch (ReflectionUtilError e) { - throw UserError.abort("Could not find target field %s.%s for alias %s.", targetClass.getName(), targetName, annotated == null ? null : annotated.format("%H.%n")); - } - } - - public static boolean isOffsetRecomputation(RecomputeFieldValue.Kind kind) { - return offsetComputationKinds.contains(kind); - } - - private ComputedValueField(ResolvedJavaField original, ResolvedJavaField annotated, RecomputeFieldValue.Kind kind, - Class transformedValueAllowedType, FieldValueTransformer fieldValueTransformer, boolean isFinal, Class targetClass, Field targetField, - boolean isValueAvailableBeforeAnalysis, boolean isValueAvailableOnlyAfterAnalysis, boolean isValueAvailableOnlyAfterCompilation, JavaConstant constantValue) { - super(transformedValueAllowedType, fieldValueTransformer); - 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 - public boolean isValueAvailable() { - /* - * 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 constantValue != null || isValueAvailableBeforeAnalysis || - (isValueAvailableOnlyAfterAnalysis && BuildPhaseProvider.isHostedUniverseBuilt()) || - (isValueAvailableOnlyAfterCompilation && BuildPhaseProvider.isCompilationFinished()); - } - - public ResolvedJavaField getAnnotated() { - return annotated; - } - - Class getTargetClass() { - return targetClass; - } - - public Field getTargetField() { - return targetField; - } - - public RecomputeFieldValue.Kind getRecomputeValueKind() { - return kind; - } - - @Override - public String getName() { - return original.getName(); - } - - @Override - public JavaType getType() { - return original.getType(); - } - - @Override - public int getModifiers() { - int result = original.getModifiers(); - if (isFinal) { - result = result | Modifier.FINAL; - } else { - result = result & ~Modifier.FINAL; - } - return result; - } - - @Override - public int getOffset() { - return original.getOffset(); - } - - @Override - public boolean isInternal() { - return original.isInternal(); - } - - @Override - public boolean isSynthetic() { - return original.isSynthetic(); - } - - public void processAnalysis(AnalysisMetaAccess aMetaAccess) { - switch (kind) { - case FieldOffset: - AnalysisField target = aMetaAccess.lookupJavaField(targetField); - target.registerAsAccessed(this); - break; - } - } - - private JavaConstant asConstant(int value) { - switch (getJavaKind()) { - case Int: - return JavaConstant.forInt(value); - case Long: - return JavaConstant.forLong(value); - default: - throw shouldNotReachHereUnexpectedInput(getJavaKind()); // ExcludeFromJacocoGeneratedReport - } - } - - public void processSubstrate(HostedMetaAccess metaAccess) { - switch (kind) { - case FieldOffset: - constantValue = asConstant(metaAccess.lookupJavaField(targetField).getLocation()); - break; - } - } - - @Override - public JavaConstant readValue(ClassInitializationSupport classInitializationSupport, JavaConstant receiver) { - if (constantValue != null) { - return constantValue; - } - switch (kind) { - case None: - case Manual: - return ReadableJavaField.readFieldValue(classInitializationSupport, original, receiver); - - case FromAlias: - assert Modifier.isStatic(annotated.getModifiers()) : "Cannot use " + kind + " on non-static alias " + annotated.format("%H.%n"); - annotated.getDeclaringClass().initialize(); - constantValue = ReadableJavaField.readFieldValue(classInitializationSupport, annotated, null); - return constantValue; - - case ArrayBaseOffset: - constantValue = asConstant(ConfigurationValues.getObjectLayout().getArrayBaseOffset(JavaKind.fromJavaClass(targetClass.getComponentType()))); - return constantValue; - case ArrayIndexScale: - constantValue = asConstant(ConfigurationValues.getObjectLayout().getArrayIndexScale(JavaKind.fromJavaClass(targetClass.getComponentType()))); - return constantValue; - case ArrayIndexShift: - constantValue = asConstant(ConfigurationValues.getObjectLayout().getArrayIndexShift(JavaKind.fromJavaClass(targetClass.getComponentType()))); - return constantValue; - case StaticFieldBase: - Object staticFieldsArray = targetField.getType().isPrimitive() ? StaticFieldsSupport.getStaticPrimitiveFields() : StaticFieldsSupport.getStaticObjectFields(); - constantValue = GraalAccess.getOriginalSnippetReflection().forObject(staticFieldsArray); - return constantValue; - } - - return super.readValue(classInitializationSupport, original, receiver); - } - - @Override - protected JavaConstant computeValue(ClassInitializationSupport classInitializationSupport, ResolvedJavaField field, JavaConstant receiver) { - assert isValueAvailable() : "Field " + format("%H.%n") + " value not available for reading."; - JavaConstant result; - switch (kind) { - case NewInstanceWhenNotNull: - result = fetchOriginalValue(classInitializationSupport, field, receiver) == null ? JavaConstant.NULL_POINTER : createNewInstance(); - break; - case NewInstance: - result = createNewInstance(); - break; - case AtomicFieldUpdaterOffset: - result = computeAtomicFieldUpdaterOffset(classInitializationSupport, receiver); - break; - case TranslateFieldOffset: - result = translateFieldOffset(classInitializationSupport, receiver, targetClass); - break; - case Custom: - result = super.computeValue(classInitializationSupport, field, receiver); - break; - default: - 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 JavaConstant createNewInstance() { - JavaConstant result; - try { - 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; - } - - @Override - public boolean injectFinalForRuntimeCompilation() { - if (original.isFinal()) { - /* - * We remove the "final" modifier for AOT compilation because the recomputed value is - * not guaranteed to be known yet. But for runtime compilation, we know that we can - * treat the field as "final". - */ - return true; - } - return ReadableJavaField.injectFinalForRuntimeCompilation(original); - } - - private JavaConstant translateFieldOffset(ClassInitializationSupport classInitializationSupport, JavaConstant receiver, Class tclass) { - long searchOffset = ReadableJavaField.readFieldValue(classInitializationSupport, original, receiver).asLong(); - // search the declared fields for a field with a matching offset - for (Field f : tclass.getDeclaredFields()) { - if (!Modifier.isStatic(f.getModifiers())) { - long fieldOffset = Unsafe.getUnsafe().objectFieldOffset(f); - if (fieldOffset == searchOffset) { - int location = ImageSingletons.lookup(ReflectionSubstitutionSupport.class).getFieldOffset(f, true); - VMError.guarantee(location > 0, "Location is missing for field whose offset is stored: %s.", f); - return JavaConstant.forLong(location); - } - } - } - throw shouldNotReachHere("unknown field offset class: " + tclass + ", offset = " + searchOffset); - } - - private JavaConstant computeAtomicFieldUpdaterOffset(ClassInitializationSupport classInitializationSupport, JavaConstant receiver) { - assert !Modifier.isStatic(original.getModifiers()); - assert receiver.isNonNull(); - - /* - * Explanation: java.util.concurrent is full of objects and classes that cache the offset of - * particular fields in the JDK. Here, AtomicFieldUpdater implementation objects cache - * the offset of some specified field. Which field? Oh...only Doug Lea knows that. We have - * to search the declaring class for a field that has the same "unsafe" offset as the cached - * offset in this atomic updater object. - */ - ResolvedJavaField tclassField = findField(original.getDeclaringClass(), "tclass"); - SnippetReflectionProvider originalSnippetReflection = GraalAccess.getOriginalSnippetReflection(); - Class tclass = originalSnippetReflection.asObject(Class.class, ReadableJavaField.readFieldValue(classInitializationSupport, tclassField, receiver)); - return translateFieldOffset(classInitializationSupport, receiver, tclass); - } - - private static ResolvedJavaField findField(ResolvedJavaType declaringClass, String name) { - for (ResolvedJavaField field : declaringClass.getInstanceFields(false)) { - if (field.getName().equals(name)) { - return field; - } - } - throw shouldNotReachHere("Field not found: " + declaringClass.toJavaName(true) + "." + name); - } - - @Override - public ResolvedJavaType getDeclaringClass() { - return original.getDeclaringClass(); - } - - @Override - public AnnotatedElement getAnnotationRoot() { - return original; - } - - public boolean isCompatible(ResolvedJavaField o) { - if (this.equals(o)) { - return true; - } else if (o == null || getClass() != o.getClass()) { - return false; - } - - ComputedValueField that = (ComputedValueField) o; - return isFinal == that.isFinal && original.equals(that.original) && kind == that.kind && - Objects.equals(targetClass, that.targetClass) && Objects.equals(targetField, that.targetField) && Objects.equals(constantValue, that.constantValue); - } - - @Override - public String toString() { - return "RecomputeValueField"; - } - - @Override - public ResolvedJavaField unwrapTowardsOriginalField() { - return original; - } - - @Override - public JavaConstant getConstantValue() { - return original.getConstantValue(); - } -} 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 index 5a238b8a442f..974c4ff4b774 100644 --- 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 @@ -31,11 +31,11 @@ import org.graalvm.collections.EconomicMap; import org.graalvm.nativeimage.hosted.FieldValueTransformer; +import com.oracle.graal.pointsto.infrastructure.OriginalFieldProvider; +import com.oracle.graal.pointsto.meta.AnalysisField; 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; @@ -56,7 +56,7 @@ public FieldValueTransformer getFieldValueTransformer() { return fieldValueTransformer; } - public JavaConstant readValue(ClassInitializationSupport classInitializationSupport, ResolvedJavaField field, JavaConstant receiver) { + public JavaConstant readValue(AnalysisField field, JavaConstant receiver) { ReadLock readLock = valueCacheLock.readLock(); try { readLock.lock(); @@ -83,7 +83,7 @@ public JavaConstant readValue(ClassInitializationSupport classInitializationSupp * 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); + result = computeValue(field, receiver); putCached(receiver, result); return result; } finally { @@ -91,9 +91,9 @@ public JavaConstant readValue(ClassInitializationSupport classInitializationSupp } } - protected JavaConstant computeValue(ClassInitializationSupport classInitializationSupport, ResolvedJavaField field, JavaConstant receiver) { + private JavaConstant computeValue(AnalysisField field, JavaConstant receiver) { Object receiverValue = receiver == null ? null : GraalAccess.getOriginalSnippetReflection().asObject(Object.class, receiver); - Object originalValue = fetchOriginalValue(classInitializationSupport, field, receiver); + Object originalValue = fetchOriginalValue(field, receiver); Object newValue = fieldValueTransformer.transform(receiverValue, originalValue); checkValue(newValue, field); JavaConstant result = GraalAccess.getOriginalSnippetReflection().forBoxed(field.getJavaKind(), newValue); @@ -102,7 +102,7 @@ protected JavaConstant computeValue(ClassInitializationSupport classInitializati return result; } - private void checkValue(Object newValue, ResolvedJavaField field) { + private void checkValue(Object newValue, AnalysisField field) { boolean primitive = transformedValueAllowedType.isPrimitive(); if (newValue == null) { if (primitive) { @@ -123,8 +123,12 @@ private void checkValue(Object newValue, ResolvedJavaField field) { } } - protected Object fetchOriginalValue(ClassInitializationSupport classInitializationSupport, ResolvedJavaField field, JavaConstant receiver) { - JavaConstant originalValueConstant = ReadableJavaField.readFieldValue(classInitializationSupport, field, receiver); + protected Object fetchOriginalValue(AnalysisField aField, JavaConstant receiver) { + ResolvedJavaField oField = OriginalFieldProvider.getOriginalField(aField); + if (oField == null) { + return null; + } + JavaConstant originalValueConstant = GraalAccess.getOriginalProviders().getConstantReflection().readFieldValue(oField, receiver); if (originalValueConstant == null) { /* * The class is still uninitialized, so static fields cannot be read. Or it is an diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/FromAliasFieldValueTransformer.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/FromAliasFieldValueTransformer.java new file mode 100644 index 000000000000..29d096088669 --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/FromAliasFieldValueTransformer.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2024, 2024, 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 org.graalvm.nativeimage.hosted.FieldValueTransformer; + +import com.oracle.graal.pointsto.util.GraalAccess; +import com.oracle.svm.core.annotate.RecomputeFieldValue.Kind; + +import jdk.vm.ci.meta.ResolvedJavaField; + +/** + * Implements the field value transformation semantics of {@link Kind#FromAlias}. + */ +public record FromAliasFieldValueTransformer(ResolvedJavaField aliasField) implements FieldValueTransformer { + + @Override + public Object transform(Object receiver, Object originalValue) { + aliasField.getDeclaringClass().initialize(); + var constant = GraalAccess.getOriginalProviders().getConstantReflection().readFieldValue(aliasField, null); + if (constant.getJavaKind().isPrimitive()) { + return constant.asBoxedPrimitive(); + } else { + return GraalAccess.getOriginalSnippetReflection().asObject(Object.class, constant); + } + } +} 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 e72df883588b..8863be363860 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 @@ -27,16 +27,14 @@ import java.lang.reflect.AnnotatedElement; import com.oracle.graal.pointsto.infrastructure.OriginalFieldProvider; -import com.oracle.svm.hosted.ameta.ReadableJavaField; import com.oracle.svm.hosted.annotation.AnnotationWrapper; -import com.oracle.svm.hosted.classinitialization.ClassInitializationSupport; import jdk.vm.ci.meta.JavaConstant; import jdk.vm.ci.meta.JavaType; import jdk.vm.ci.meta.ResolvedJavaField; import jdk.vm.ci.meta.ResolvedJavaType; -public class SubstitutionField implements ReadableJavaField, OriginalFieldProvider, AnnotationWrapper { +public class SubstitutionField implements ResolvedJavaField, OriginalFieldProvider, AnnotationWrapper { private final ResolvedJavaField original; private final ResolvedJavaField annotated; @@ -53,30 +51,6 @@ public SubstitutionField(ResolvedJavaField original, ResolvedJavaField annotated this.isUserSubstitution = isUserSubstitution; } - @Override - public boolean isValueAvailable() { - return true; - } - - @Override - public boolean injectFinalForRuntimeCompilation() { - return false; - } - - @Override - public JavaConstant readValue(ClassInitializationSupport classInitializationSupport, JavaConstant receiver) { - /* First try reading the value using the original field. */ - JavaConstant value = ReadableJavaField.readFieldValue(classInitializationSupport, original, receiver); - if (value == null) { - /* - * If the original field didn't yield a value, try reading using the annotated field. - * The value can be null only if the receiver doesn't contain the field. - */ - value = ReadableJavaField.readFieldValue(classInitializationSupport, annotated, receiver); - } - return value; - } - public boolean isUserSubstitution() { return isUserSubstitution; } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/TranslateFieldOffsetFieldValueTransformer.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/TranslateFieldOffsetFieldValueTransformer.java new file mode 100644 index 000000000000..182fbf36ebdb --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/TranslateFieldOffsetFieldValueTransformer.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2024, 2024, 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 static com.oracle.svm.core.util.VMError.shouldNotReachHere; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; + +import org.graalvm.nativeimage.ImageSingletons; + +import com.oracle.graal.pointsto.util.GraalAccess; +import com.oracle.svm.core.BuildPhaseProvider; +import com.oracle.svm.core.annotate.RecomputeFieldValue.Kind; +import com.oracle.svm.core.fieldvaluetransformer.FieldValueTransformerWithAvailability; +import com.oracle.svm.core.reflect.target.ReflectionSubstitutionSupport; +import com.oracle.svm.core.util.VMError; + +import jdk.internal.misc.Unsafe; +import jdk.vm.ci.meta.JavaConstant; +import jdk.vm.ci.meta.ResolvedJavaField; + +/** + * Implements the field value transformation semantics of {@link Kind#TranslateFieldOffset}. + */ +public record TranslateFieldOffsetFieldValueTransformer(ResolvedJavaField original, Class targetClass) implements FieldValueTransformerWithAvailability { + + @Override + public boolean isAvailable() { + return BuildPhaseProvider.isHostedUniverseBuilt(); + } + + @Override + public Object transform(Object receiver, Object originalValue) { + return translateFieldOffset(original, GraalAccess.getOriginalSnippetReflection().forObject(receiver), targetClass); + } + + static Object translateFieldOffset(ResolvedJavaField original, JavaConstant receiver, Class tclass) { + long searchOffset = GraalAccess.getOriginalProviders().getConstantReflection().readFieldValue(original, receiver).asLong(); + /* Search the declared fields for a field with a matching offset. */ + for (Field f : tclass.getDeclaredFields()) { + if (!Modifier.isStatic(f.getModifiers())) { + long fieldOffset = Unsafe.getUnsafe().objectFieldOffset(f); + if (fieldOffset == searchOffset) { + int location = ImageSingletons.lookup(ReflectionSubstitutionSupport.class).getFieldOffset(f, true); + VMError.guarantee(location > 0, "Location is missing for field whose offset is stored: %s.", f); + return Long.valueOf(location); + } + } + } + throw shouldNotReachHere("unknown field offset class: " + tclass + ", offset = " + searchOffset); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/fieldvaluetransformer/BoxingTransformer.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/ValueNeverAvailableFieldValueTransformer.java similarity index 66% rename from substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/fieldvaluetransformer/BoxingTransformer.java rename to substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/ValueNeverAvailableFieldValueTransformer.java index acf5fc962529..5390bb550f13 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/fieldvaluetransformer/BoxingTransformer.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/ValueNeverAvailableFieldValueTransformer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2024, 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 @@ -22,27 +22,22 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ -package com.oracle.svm.core.fieldvaluetransformer; +package com.oracle.svm.hosted.substitute; +import com.oracle.svm.core.fieldvaluetransformer.FieldValueTransformerWithAvailability; import com.oracle.svm.core.util.VMError; -import jdk.vm.ci.meta.JavaKind; +import jdk.vm.ci.meta.ResolvedJavaField; -abstract class BoxingTransformer { - final JavaKind returnKind; +public record ValueNeverAvailableFieldValueTransformer(ResolvedJavaField field) implements FieldValueTransformerWithAvailability { - BoxingTransformer(JavaKind returnKind) { - this.returnKind = returnKind; + @Override + public Object transform(Object receiver, Object originalValue) { + throw VMError.shouldNotReachHere("Value for this field is never available: " + field); } - Object box(int value) { - switch (returnKind) { - case Int: - return Integer.valueOf(value); - case Long: - return Long.valueOf(value); - default: - throw VMError.shouldNotReachHere("Unexpected kind: " + returnKind); - } + @Override + public boolean isAvailable() { + return false; } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/thread/HostedJavaThreadsFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/thread/HostedJavaThreadsFeature.java index a2011cf5210d..0f1c65a7b368 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/thread/HostedJavaThreadsFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/thread/HostedJavaThreadsFeature.java @@ -31,6 +31,7 @@ import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.hosted.RuntimeClassInitialization; +import com.oracle.svm.core.BuildPhaseProvider; import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; import com.oracle.svm.core.feature.AutomaticallyRegisteredImageSingleton; import com.oracle.svm.core.fieldvaluetransformer.FieldValueTransformerWithAvailability; @@ -80,13 +81,13 @@ public void duringSetup(DuringSetupAccess access) { public void beforeAnalysis(BeforeAnalysisAccess a) { a.registerFieldValueTransformer(ReflectionUtil.lookupField(ThreadGroup.class, "ngroups"), new FieldValueTransformerWithAvailability() { + /* + * We must wait until reachableThreadGroups stabilizes after analysis to replace this + * value. + */ @Override - public ValueAvailability valueAvailability() { - /* - * We must wait until reachableThreadGroups stabilizes after analysis to replace - * this value. - */ - return ValueAvailability.AfterAnalysis; + public boolean isAvailable() { + return BuildPhaseProvider.isHostedUniverseBuilt(); } @Override @@ -98,13 +99,13 @@ public Object transform(Object receiver, Object originalValue) { a.registerFieldValueTransformer(ReflectionUtil.lookupField(ThreadGroup.class, "groups"), new FieldValueTransformerWithAvailability() { + /* + * We must wait until reachableThreadGroups stabilizes after analysis to replace this + * value. + */ @Override - public ValueAvailability valueAvailability() { - /* - * We must wait until reachableThreadGroups stabilizes after analysis to replace - * this value. - */ - return ValueAvailability.AfterAnalysis; + public boolean isAvailable() { + return BuildPhaseProvider.isHostedUniverseBuilt(); } @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 25274cbe50a9..21d163d2e6af 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 @@ -79,6 +79,7 @@ import com.oracle.graal.pointsto.meta.AnalysisMetaAccess; import com.oracle.graal.pointsto.meta.AnalysisType; import com.oracle.svm.core.BuildArtifacts; +import com.oracle.svm.core.BuildPhaseProvider; import com.oracle.svm.core.NeverInline; import com.oracle.svm.core.ParsingReason; import com.oracle.svm.core.RuntimeAssertionsSupport; @@ -1273,8 +1274,8 @@ final class Target_com_oracle_truffle_api_staticobject_ArrayBasedStaticShape { private static class MapCleaner implements FieldValueTransformerWithAvailability { @Override - public ValueAvailability valueAvailability() { - return ValueAvailability.AfterCompilation; + public boolean isAvailable() { + return BuildPhaseProvider.isCompilationFinished(); } @Override @@ -1318,8 +1319,8 @@ final class StaticPropertyOffsetTransformer implements FieldValueTransformerWith } @Override - public ValueAvailability valueAvailability() { - return ValueAvailability.AfterAnalysis; + public boolean isAvailable() { + return BuildPhaseProvider.isHostedUniverseBuilt(); } @Override @@ -1389,8 +1390,8 @@ final class ArrayBasedShapeGeneratorOffsetTransformer implements FieldValueTrans } @Override - public ValueAvailability valueAvailability() { - return ValueAvailability.AfterAnalysis; + public boolean isAvailable() { + return BuildPhaseProvider.isHostedUniverseBuilt(); } @Override @@ -1460,12 +1461,7 @@ final class Target_com_oracle_truffle_polyglot_InternalResourceCache { @Alias @RecomputeFieldValue(kind = Kind.Custom, declClass = UseInternalResourcesComputer.class, isFinal = true) // private static boolean useInternalResources; - private static final class UseInternalResourcesComputer implements FieldValueTransformerWithAvailability { - @Override - public ValueAvailability valueAvailability() { - return ValueAvailability.BeforeAnalysis; - } - + private static final class UseInternalResourcesComputer implements FieldValueTransformer { @Override public Object transform(Object receiver, Object originalValue) { return SubstrateOptions.TruffleStableOptions.CopyLanguageResources.getValue(); @@ -1513,8 +1509,8 @@ final class Target_com_oracle_truffle_api_nodes_NodeClassImpl_NodeFieldData { private static class OffsetComputer implements FieldValueTransformerWithAvailability { @Override - public ValueAvailability valueAvailability() { - return ValueAvailability.AfterAnalysis; + public boolean isAvailable() { + return BuildPhaseProvider.isHostedUniverseBuilt(); } @Override @@ -1545,8 +1541,8 @@ final class Target_com_oracle_truffle_api_dsl_InlineSupport_UnsafeField { private static class OffsetComputer implements FieldValueTransformerWithAvailability { @Override - public ValueAvailability valueAvailability() { - return ValueAvailability.AfterAnalysis; + public boolean isAvailable() { + return BuildPhaseProvider.isHostedUniverseBuilt(); } @Override