From 2dd52721c9fb7f1b9a29d65c51cfff3f48d0f22e Mon Sep 17 00:00:00 2001 From: Josef Eisl Date: Thu, 4 Dec 2025 14:00:04 +0100 Subject: [PATCH 1/7] svm: introduce JVMCIFieldValueTransformer --- .../com/oracle/svm/hosted/FeatureImpl.java | 12 ++++ .../ameta/FieldValueInterceptionSupport.java | 52 +++++++++++++++ .../AnnotationSubstitutionProcessor.java | 11 +++- .../svm/util/JVMCIFieldValueTransformer.java | 65 +++++++++++++++++++ 4 files changed, 139 insertions(+), 1 deletion(-) create mode 100644 substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/JVMCIFieldValueTransformer.java diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/FeatureImpl.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/FeatureImpl.java index f8324fff06d6..32428bef7aa0 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/FeatureImpl.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/FeatureImpl.java @@ -103,6 +103,7 @@ import com.oracle.svm.hosted.meta.HostedUniverse; import com.oracle.svm.hosted.option.HostedOptionProvider; import com.oracle.svm.util.AnnotationUtil; +import com.oracle.svm.util.JVMCIFieldValueTransformer; import com.oracle.svm.util.ReflectionUtil; import jdk.graal.compiler.debug.Assertions; @@ -110,6 +111,7 @@ import jdk.graal.compiler.phases.util.Providers; import jdk.internal.vm.annotation.Stable; import jdk.vm.ci.meta.MetaAccessProvider; +import jdk.vm.ci.meta.ResolvedJavaField; import jdk.vm.ci.meta.ResolvedJavaType; @SuppressWarnings("deprecation") @@ -639,6 +641,16 @@ public void registerFieldValueTransformer(Field field, FieldValueTransformer tra FieldValueInterceptionSupport.singleton().registerFieldValueTransformer(field, transformer); } + /** + * Registers a field value transformer for the provided field. See the JavaDoc of + * {@link FieldValueTransformer} for details. + * + * @since 22.3 + */ + public void registerFieldValueTransformer(ResolvedJavaField field, JVMCIFieldValueTransformer transformer) { + FieldValueInterceptionSupport.singleton().registerJVMCIFieldValueTransformer(field, transformer); + } + /** * Registers a method as having an analysis-opaque return value. This designation limits the * type-flow analysis performed on the method's return value. 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 39a65667fe2e..55c6796329f9 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 @@ -47,6 +47,7 @@ import com.oracle.svm.core.config.ConfigurationValues; import com.oracle.svm.core.fieldvaluetransformer.FieldValueTransformerWithAvailability; import com.oracle.svm.core.fieldvaluetransformer.FieldValueTransformerWithReceiverBasedAvailability; +import com.oracle.svm.core.fieldvaluetransformer.JavaConstantWrapper; import com.oracle.svm.core.heap.UnknownObjectField; import com.oracle.svm.core.heap.UnknownPrimitiveField; import com.oracle.svm.core.layered.LayeredFieldValue; @@ -62,10 +63,12 @@ import com.oracle.svm.util.AnnotationUtil; import com.oracle.svm.util.ClassUtil; import com.oracle.svm.util.GraalAccess; +import com.oracle.svm.util.JVMCIFieldValueTransformer; import com.oracle.svm.util.OriginalClassProvider; import com.oracle.svm.util.OriginalFieldProvider; import com.oracle.svm.util.ReflectionUtil; +import jdk.graal.compiler.api.replacements.SnippetReflectionProvider; import jdk.graal.compiler.debug.Assertions; import jdk.graal.compiler.nodes.ValueNode; import jdk.graal.compiler.nodes.java.LoadFieldNode; @@ -128,6 +131,55 @@ public void registerFieldValueTransformer(Field reflectionField, FieldValueTrans registerFieldValueTransformer(GraalAccess.getOriginalProviders().getMetaAccess().lookupJavaField(reflectionField), transformer); } + /** + * Wraps a {@link JVMCIFieldValueTransformer} in an {@link FieldValueTransformer}. Eventually, + * this should be reversed, i.e., {@link FieldValueTransformer} should be wrapped in + * {@link JVMCIFieldValueTransformer} (GR-71666). + */ + public static final class FallbackFieldValueTransformer implements FieldValueTransformer { + private final JVMCIFieldValueTransformer jvmciFieldValueTransformer; + + public FallbackFieldValueTransformer(JVMCIFieldValueTransformer jvmciFieldValueTransformer) { + this.jvmciFieldValueTransformer = jvmciFieldValueTransformer; + } + + @Override + public Object transform(Object reflectionReceiver, Object reflectionOriginalValue) { + SnippetReflectionProvider originalSnippetReflection = GraalAccess.getOriginalSnippetReflection(); + JavaConstant receiver = reflectionReceiver == null ? null : originalSnippetReflection.forObject(reflectionReceiver); + JavaConstant originalValue = reflectionOriginalValue == null ? null : originalSnippetReflection.forObject(reflectionOriginalValue); + return new JavaConstantWrapper(Objects.requireNonNull(jvmciFieldValueTransformer.transform(receiver, originalValue), + "JVMCIFieldValueTransformer must not return `null`. Use `JavaConstant.NULL_POINTER`instead")); + } + + @Override + public boolean isAvailable() { + return jvmciFieldValueTransformer.isAvailable(); + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) + return false; + + FallbackFieldValueTransformer that = (FallbackFieldValueTransformer) o; + return Objects.equals(jvmciFieldValueTransformer, that.jvmciFieldValueTransformer); + } + + @Override + public int hashCode() { + return Objects.hashCode(jvmciFieldValueTransformer); + } + } + + /** + * Register a field value transformer for the provided field. There can only be one transformer + * per field, if there is already a transformation in place, a {@link UserError} is reported. + */ + public void registerJVMCIFieldValueTransformer(ResolvedJavaField resolvedJavaField, JVMCIFieldValueTransformer transformer) { + registerFieldValueTransformer(resolvedJavaField, new FallbackFieldValueTransformer(transformer)); + } + public void registerFieldValueTransformer(ResolvedJavaField oField, FieldValueTransformer transformer) { if (annotationSubstitutions.isDeleted(oField)) { throw UserError.abort("Cannot register a field value transformer for field %s: %s", oField.format("%H.%n"), 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 609c7fff5696..376c971012c5 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 @@ -90,6 +90,7 @@ import com.oracle.svm.hosted.classinitialization.ClassInitializationSupport; import com.oracle.svm.hosted.meta.HostedUniverse; import com.oracle.svm.util.AnnotationUtil; +import com.oracle.svm.util.JVMCIFieldValueTransformer; import com.oracle.svm.util.JVMCIReflectionUtil; import com.oracle.svm.util.OriginalClassProvider; import com.oracle.svm.util.ReflectionUtil; @@ -1047,6 +1048,7 @@ private ResolvedJavaField fieldValueRecomputation(Class originalClass, Resolv } Class transformedValueAllowedType = getTargetClass(annotatedField.getType()); + // GR-71666: Change variable to JVMCIFieldValueTransformer var newTransformer = switch (kind) { case None, Manual -> null; case Reset -> ConstantValueFieldValueTransformer.defaultValueForField(original); @@ -1078,7 +1080,14 @@ private ResolvedJavaField fieldValueRecomputation(Class originalClass, Resolv new ArrayIndexShiftFieldValueTransformer(targetClass, original.getType().getJavaKind()); case AtomicFieldUpdaterOffset -> new AtomicFieldUpdaterOffsetFieldValueTransformer(original, targetClass); case TranslateFieldOffset -> new TranslateFieldOffsetFieldValueTransformer(original, targetClass); - case Custom -> (FieldValueTransformer) ReflectionUtil.newInstance(targetClass); + case Custom -> { + if (JVMCIFieldValueTransformer.class.isAssignableFrom(targetClass)) { + var jvmciFieldValueTransformer = (JVMCIFieldValueTransformer) ReflectionUtil.newInstance(targetClass); + yield new FieldValueInterceptionSupport.FallbackFieldValueTransformer(jvmciFieldValueTransformer); + } else { + yield (FieldValueTransformer) ReflectionUtil.newInstance(targetClass); + } + } }; if (newTransformer != null) { diff --git a/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/JVMCIFieldValueTransformer.java b/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/JVMCIFieldValueTransformer.java new file mode 100644 index 000000000000..f8965820d9f2 --- /dev/null +++ b/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/JVMCIFieldValueTransformer.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2025, 2025, 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.util; + +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; +import org.graalvm.nativeimage.hosted.FieldValueTransformer; + +import jdk.vm.ci.meta.JavaConstant; + +/** + * A {@linkplain FieldValueTransformer transformer for a field value} based on {@link JavaConstant} + * that can be registered using + * {@code BeforeAnalysisAccessImpl#registerFieldValueTransformer(ResolvedJavaField, JVMCIFieldValueTransformer)}. + * + * @see FieldValueTransformer + */ +@Platforms(Platform.HOSTED_ONLY.class) +public interface JVMCIFieldValueTransformer { + + /** + * Transforms the field value for the provided receiver. The receiver is null for static fields. + * The original value of the field, i.e., the hosted value of the field in the image generator, + * is also provided as an argument. + *

+ * The returned constant should never be {@code null}. Use {@link JavaConstant#NULL_POINTER} + * instead. The type of the returned object must be assignable to the declared type of the + * field. If the field has a primitive type, the returned object must not be a + * {@linkplain JavaConstant#isNull() null constant}. + * + * @see FieldValueTransformer#transform(Object, Object) + */ + JavaConstant transform(JavaConstant receiver, JavaConstant originalValue); + + /** + * Returns true when the value for this custom computation is available. + * + * @see FieldValueTransformer#isAvailable() + */ + default boolean isAvailable() { + return true; + } +} From 6af596d51d875b523468a6f247cb831a998b1402 Mon Sep 17 00:00:00 2001 From: Josef Eisl Date: Fri, 5 Dec 2025 12:31:51 +0100 Subject: [PATCH 2/7] svm: introduce JVMCIReflectionUtil#newInstance --- .../src/com/oracle/svm/util/JVMCIReflectionUtil.java | 7 +++++++ .../com/oracle/svm/util/JVMCIReflectionUtilFallback.java | 5 +++++ 2 files changed, 12 insertions(+) diff --git a/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/JVMCIReflectionUtil.java b/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/JVMCIReflectionUtil.java index 332d702109a1..c45f890130a7 100644 --- a/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/JVMCIReflectionUtil.java +++ b/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/JVMCIReflectionUtil.java @@ -422,4 +422,11 @@ public static JavaConstant readStaticField(ResolvedJavaType declaringClass, Stri } return GraalAccess.getOriginalProviders().getConstantReflection().readFieldValue(field, null); } + + /** + * @see ReflectionUtil#newInstance + */ + public static JavaConstant newInstance(ResolvedJavaType type) { + return JVMCIReflectionUtilFallback.newInstance(type); + } } diff --git a/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/JVMCIReflectionUtilFallback.java b/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/JVMCIReflectionUtilFallback.java index 186be5518497..17e7172b8a76 100644 --- a/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/JVMCIReflectionUtilFallback.java +++ b/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/JVMCIReflectionUtilFallback.java @@ -34,6 +34,7 @@ import com.oracle.graal.vmaccess.ResolvedJavaPackage; import jdk.internal.loader.BootLoader; +import jdk.vm.ci.meta.JavaConstant; import jdk.vm.ci.meta.ResolvedJavaType; /** @@ -71,4 +72,8 @@ public static Stream bootLoaderPackages() { public static ResolvedJavaModuleLayer bootModuleLayer() { return new ResolvedJavaModuleLayerImpl(ModuleLayer.boot()); } + + public static JavaConstant newInstance(ResolvedJavaType type) { + return GraalAccess.getOriginalSnippetReflection().forObject(ReflectionUtil.newInstance(OriginalClassProvider.getJavaClass(type))); + } } From 569b2d785eb7ecd41a6be5af0d3d6527d89cdda4 Mon Sep 17 00:00:00 2001 From: Josef Eisl Date: Thu, 11 Dec 2025 13:29:13 +0100 Subject: [PATCH 3/7] svm: add JVMCIReflectionUtil#newArrayInstance --- .../src/com/oracle/svm/util/JVMCIReflectionUtil.java | 9 +++++++++ .../com/oracle/svm/util/JVMCIReflectionUtilFallback.java | 5 +++++ 2 files changed, 14 insertions(+) diff --git a/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/JVMCIReflectionUtil.java b/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/JVMCIReflectionUtil.java index c45f890130a7..0d4596f8d53f 100644 --- a/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/JVMCIReflectionUtil.java +++ b/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/JVMCIReflectionUtil.java @@ -429,4 +429,13 @@ public static JavaConstant readStaticField(ResolvedJavaType declaringClass, Stri public static JavaConstant newInstance(ResolvedJavaType type) { return JVMCIReflectionUtilFallback.newInstance(type); } + + /** + * Creates a new array with the specified component type and length. + * + * @see java.lang.reflect.Array#newInstance(Class, int) + */ + public static JavaConstant newArrayInstance(ResolvedJavaType componentType, int length) { + return JVMCIReflectionUtilFallback.newArrayInstance(componentType, length); + } } diff --git a/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/JVMCIReflectionUtilFallback.java b/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/JVMCIReflectionUtilFallback.java index 17e7172b8a76..23687091aa49 100644 --- a/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/JVMCIReflectionUtilFallback.java +++ b/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/JVMCIReflectionUtilFallback.java @@ -24,6 +24,7 @@ */ package com.oracle.svm.util; +import java.lang.reflect.Array; import java.net.URL; import java.security.CodeSource; import java.security.ProtectionDomain; @@ -76,4 +77,8 @@ public static ResolvedJavaModuleLayer bootModuleLayer() { public static JavaConstant newInstance(ResolvedJavaType type) { return GraalAccess.getOriginalSnippetReflection().forObject(ReflectionUtil.newInstance(OriginalClassProvider.getJavaClass(type))); } + + public static JavaConstant newArrayInstance(ResolvedJavaType componentType, int length) { + return GraalAccess.getOriginalSnippetReflection().forObject(Array.newInstance(OriginalClassProvider.getJavaClass(componentType), length)); + } } From 1dd35c891a19c505d0cd1f9b246cf975ca46ba1d Mon Sep 17 00:00:00 2001 From: Josef Eisl Date: Fri, 12 Dec 2025 11:51:22 +0100 Subject: [PATCH 4/7] svm: document ReflectionSubstitutionSupport migration issue --- .../svm/core/reflect/target/ReflectionSubstitutionSupport.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/ReflectionSubstitutionSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/ReflectionSubstitutionSupport.java index d9c07a5da80b..51a7e67b360c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/ReflectionSubstitutionSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/ReflectionSubstitutionSupport.java @@ -33,6 +33,9 @@ import com.oracle.svm.core.annotate.Delete; import com.oracle.svm.core.reflect.SubstrateAccessor; +/** + * Should be migrated to JVMCI (GR-71897). + */ public interface ReflectionSubstitutionSupport { static ReflectionSubstitutionSupport singleton() { From 3fd182325a4262825d19186a35b1ab94d985bfa9 Mon Sep 17 00:00:00 2001 From: Josef Eisl Date: Tue, 9 Dec 2025 10:28:58 +0100 Subject: [PATCH 5/7] svm: introduce JVMCIFieldValueTransformerWithAvailability and make FieldValueTransformerWithAvailability (also) a JVMCIFieldValueTransformer --- ...FieldValueTransformerWithAvailability.java | 57 +++++++++++++++--- ...FieldValueTransformerWithAvailability.java | 58 +++++++++++++++++++ .../ameta/FieldValueInterceptionSupport.java | 3 +- 3 files changed, 108 insertions(+), 10 deletions(-) create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/fieldvaluetransformer/JVMCIFieldValueTransformerWithAvailability.java 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 3f6390d7920e..3e0cf36d0593 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 @@ -28,12 +28,19 @@ import org.graalvm.nativeimage.Platforms; import org.graalvm.nativeimage.hosted.FieldValueTransformer; -import jdk.graal.compiler.nodes.ValueNode; -import jdk.graal.compiler.nodes.spi.CoreProviders; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.util.GraalAccess; + +import jdk.graal.compiler.api.replacements.SnippetReflectionProvider; import jdk.vm.ci.meta.JavaConstant; +/** + * Temporary implementation of {@link JVMCIFieldValueTransformerWithAvailability}, that falls back + * to {@link FieldValueTransformer}. Usages should be migrated to + * {@link JVMCIFieldValueTransformerWithAvailability} (GR-72015). + */ @Platforms(Platform.HOSTED_ONLY.class) -public interface FieldValueTransformerWithAvailability extends FieldValueTransformer { +public interface FieldValueTransformerWithAvailability extends FieldValueTransformer, JVMCIFieldValueTransformerWithAvailability { /** * Returns true when the value for this custom computation is available. @@ -41,13 +48,45 @@ public interface FieldValueTransformerWithAvailability extends FieldValueTransfo @Override boolean isAvailable(); + @Override + Object transform(Object receiver, Object originalValue); + + @Override + default JavaConstant transform(JavaConstant receiver, JavaConstant originalValue) { + return transformAndConvert(this, receiver, originalValue); + } + /** - * Optionally provide a Graal IR node to intrinsify the field access before the static analysis. - * This allows the compiler to optimize field values that are not available yet, as long as - * there is a dedicated high-level node available. + * Transform a field value using a {@linkplain FieldValueTransformer core reflection based field + * value transformer}. The {@link JavaConstant} inputs are unwrapped, the returned + * {@link Object} is wrapped. This is only a temporary helper. Eventually, core reflection based + * field value transformers will be executed via {@link com.oracle.graal.vmaccess.VMAccess}. */ - @SuppressWarnings("unused") - default ValueNode intrinsify(CoreProviders providers, JavaConstant receiver) { - return null; + static JavaConstant transformAndConvert(FieldValueTransformer fieldValueTransformer, JavaConstant receiver, JavaConstant originalValue) { + SnippetReflectionProvider originalSnippetReflection = GraalAccess.getOriginalSnippetReflection(); + VMError.guarantee(originalValue != null, "Original value should not be `null`. Use `JavaConstant.NULL_POINTER`."); + VMError.guarantee(receiver == null || !receiver.isNull(), "Receiver should not be a boxed `null` (`JavaConstant.isNull()`) for static fields. Use `null`instead"); + Object reflectionReceiver = toObject(receiver); + Object reflectionOriginalValue = toObject(originalValue); + Object newObject = fieldValueTransformer.transform(reflectionReceiver, reflectionOriginalValue); + if (newObject == null) { + return JavaConstant.NULL_POINTER; + } + if (newObject instanceof JavaConstantWrapper constantWrapper) { + return constantWrapper.constant(); + } + return originalSnippetReflection.forObject(newObject); } + + private static Object toObject(JavaConstant javaConstant) { + if (javaConstant == null || javaConstant.isNull()) { + return null; + } + if (javaConstant.getJavaKind().isObject()) { + SnippetReflectionProvider originalSnippetReflection = GraalAccess.getOriginalSnippetReflection(); + return originalSnippetReflection.asObject(Object.class, javaConstant); + } + return javaConstant.asBoxedPrimitive(); + } + } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/fieldvaluetransformer/JVMCIFieldValueTransformerWithAvailability.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/fieldvaluetransformer/JVMCIFieldValueTransformerWithAvailability.java new file mode 100644 index 000000000000..450ef150d97a --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/fieldvaluetransformer/JVMCIFieldValueTransformerWithAvailability.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2025, 2025, 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.Platform; +import org.graalvm.nativeimage.Platforms; + +import com.oracle.svm.util.JVMCIFieldValueTransformer; + +import jdk.graal.compiler.nodes.ValueNode; +import jdk.graal.compiler.nodes.spi.CoreProviders; +import jdk.vm.ci.meta.JavaConstant; + +@Platforms(Platform.HOSTED_ONLY.class) +public interface JVMCIFieldValueTransformerWithAvailability extends JVMCIFieldValueTransformer { + + /** + * Returns true when the value for this custom computation is available. + */ + @Override + boolean isAvailable(); + + // remove this override once GR-72015 is fixed. + @Override + JavaConstant transform(JavaConstant receiver, JavaConstant originalValue); + + /** + * Optionally provide a Graal IR node to intrinsify the field access before the static analysis. + * This allows the compiler to optimize field values that are not available yet, as long as + * there is a dedicated high-level node available. + */ + @SuppressWarnings("unused") + default ValueNode intrinsify(CoreProviders providers, JavaConstant receiver) { + 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 55c6796329f9..4f7260729bc1 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 @@ -159,8 +159,9 @@ public boolean isAvailable() { @Override public boolean equals(Object o) { - if (o == null || getClass() != o.getClass()) + if (o == null || getClass() != o.getClass()) { return false; + } FallbackFieldValueTransformer that = (FallbackFieldValueTransformer) o; return Objects.equals(jvmciFieldValueTransformer, that.jvmciFieldValueTransformer); From cccea3b2cdb99c19bb059667a7f79b426b03a0a1 Mon Sep 17 00:00:00 2001 From: Josef Eisl Date: Fri, 12 Dec 2025 11:53:00 +0100 Subject: [PATCH 6/7] svm: implement field value transformation with JVMCI --- .../com/oracle/svm/hosted/FeatureImpl.java | 11 ++- .../ameta/FieldValueInterceptionSupport.java | 65 +++++++------ .../AnnotationSubstitutionProcessor.java | 47 +++++----- .../substitute/FieldValueTransformation.java | 93 +++++++++++++------ 4 files changed, 135 insertions(+), 81 deletions(-) diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/FeatureImpl.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/FeatureImpl.java index 32428bef7aa0..197a57f530c1 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/FeatureImpl.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/FeatureImpl.java @@ -104,6 +104,7 @@ import com.oracle.svm.hosted.option.HostedOptionProvider; import com.oracle.svm.util.AnnotationUtil; import com.oracle.svm.util.JVMCIFieldValueTransformer; +import com.oracle.svm.util.OriginalFieldProvider; import com.oracle.svm.util.ReflectionUtil; import jdk.graal.compiler.debug.Assertions; @@ -638,17 +639,21 @@ public void registerClassInitializerReachabilityHandler(Consumeroriginal (Host VM) field. See + * {@link OriginalFieldProvider#getOriginalField}. + * @param transformer the transformer that should be applied */ public void registerFieldValueTransformer(ResolvedJavaField field, JVMCIFieldValueTransformer transformer) { - FieldValueInterceptionSupport.singleton().registerJVMCIFieldValueTransformer(field, transformer); + VMError.guarantee(!(field instanceof OriginalFieldProvider), + "The ResolvedJavaField %s must be the original (Host VM) field. You can use OriginalFieldProvider.getOriginalField() to retrieve that", field); + FieldValueInterceptionSupport.singleton().registerFieldValueTransformer(field, transformer); } /** 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 4f7260729bc1..3a3e6358b1be 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 @@ -47,7 +47,6 @@ import com.oracle.svm.core.config.ConfigurationValues; import com.oracle.svm.core.fieldvaluetransformer.FieldValueTransformerWithAvailability; import com.oracle.svm.core.fieldvaluetransformer.FieldValueTransformerWithReceiverBasedAvailability; -import com.oracle.svm.core.fieldvaluetransformer.JavaConstantWrapper; import com.oracle.svm.core.heap.UnknownObjectField; import com.oracle.svm.core.heap.UnknownPrimitiveField; import com.oracle.svm.core.layered.LayeredFieldValue; @@ -68,7 +67,6 @@ import com.oracle.svm.util.OriginalFieldProvider; import com.oracle.svm.util.ReflectionUtil; -import jdk.graal.compiler.api.replacements.SnippetReflectionProvider; import jdk.graal.compiler.debug.Assertions; import jdk.graal.compiler.nodes.ValueNode; import jdk.graal.compiler.nodes.java.LoadFieldNode; @@ -77,6 +75,7 @@ import jdk.vm.ci.meta.JavaConstant; import jdk.vm.ci.meta.JavaKind; import jdk.vm.ci.meta.ResolvedJavaField; +import jdk.vm.ci.meta.ResolvedJavaType; /** * This class centralizes access to the several ways we have to transform and intercept field @@ -113,7 +112,7 @@ public FieldValueInterceptionSupport(AnnotationSubstitutionProcessor annotationS * 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) { + public JVMCIFieldValueTransformer lookupAlreadyRegisteredTransformer(ResolvedJavaField oField) { assert !(oField instanceof OriginalFieldProvider) : oField; var existingInterceptor = fieldValueInterceptors.get(oField); @@ -127,34 +126,35 @@ public FieldValueTransformer lookupAlreadyRegisteredTransformer(ResolvedJavaFiel * Register a field value transformer for the provided field. There can only be one transformer * per field, if there is already a transformation in place, a {@link UserError} is reported. */ - public void registerFieldValueTransformer(Field reflectionField, FieldValueTransformer transformer) { - registerFieldValueTransformer(GraalAccess.getOriginalProviders().getMetaAccess().lookupJavaField(reflectionField), transformer); + public void registerLegacyFieldValueTransformer(Field reflectionField, FieldValueTransformer transformer) { + registerLegacyFieldValueTransformer(GraalAccess.getOriginalProviders().getMetaAccess().lookupJavaField(reflectionField), transformer); } /** - * Wraps a {@link JVMCIFieldValueTransformer} in an {@link FieldValueTransformer}. Eventually, - * this should be reversed, i.e., {@link FieldValueTransformer} should be wrapped in - * {@link JVMCIFieldValueTransformer} (GR-71666). + * Wraps a {@link FieldValueTransformer} in an {@link JVMCIFieldValueTransformer}. */ - public static final class FallbackFieldValueTransformer implements FieldValueTransformer { - private final JVMCIFieldValueTransformer jvmciFieldValueTransformer; + public static final class WrappedFieldValueTransformer implements JVMCIFieldValueTransformer { + private final FieldValueTransformer fieldValueTransformer; - public FallbackFieldValueTransformer(JVMCIFieldValueTransformer jvmciFieldValueTransformer) { - this.jvmciFieldValueTransformer = jvmciFieldValueTransformer; + public static JVMCIFieldValueTransformer create(FieldValueTransformer fieldValueTransformer) { + if (fieldValueTransformer instanceof JVMCIFieldValueTransformer jvmciFieldValueTransformer) { + return jvmciFieldValueTransformer; + } + return new WrappedFieldValueTransformer(fieldValueTransformer); + } + + private WrappedFieldValueTransformer(FieldValueTransformer fieldValueTransformer) { + this.fieldValueTransformer = fieldValueTransformer; } @Override - public Object transform(Object reflectionReceiver, Object reflectionOriginalValue) { - SnippetReflectionProvider originalSnippetReflection = GraalAccess.getOriginalSnippetReflection(); - JavaConstant receiver = reflectionReceiver == null ? null : originalSnippetReflection.forObject(reflectionReceiver); - JavaConstant originalValue = reflectionOriginalValue == null ? null : originalSnippetReflection.forObject(reflectionOriginalValue); - return new JavaConstantWrapper(Objects.requireNonNull(jvmciFieldValueTransformer.transform(receiver, originalValue), - "JVMCIFieldValueTransformer must not return `null`. Use `JavaConstant.NULL_POINTER`instead")); + public JavaConstant transform(JavaConstant receiver, JavaConstant originalValue) { + return FieldValueTransformerWithAvailability.transformAndConvert(fieldValueTransformer, receiver, originalValue); } @Override public boolean isAvailable() { - return jvmciFieldValueTransformer.isAvailable(); + return fieldValueTransformer.isAvailable(); } @Override @@ -163,33 +163,38 @@ public boolean equals(Object o) { return false; } - FallbackFieldValueTransformer that = (FallbackFieldValueTransformer) o; - return Objects.equals(jvmciFieldValueTransformer, that.jvmciFieldValueTransformer); + WrappedFieldValueTransformer that = (WrappedFieldValueTransformer) o; + return Objects.equals(fieldValueTransformer, that.fieldValueTransformer); } @Override public int hashCode() { - return Objects.hashCode(jvmciFieldValueTransformer); + return Objects.hashCode(fieldValueTransformer); + } + + @Override + public String toString() { + return "Wrapped[" + fieldValueTransformer + ']'; } } + public void registerLegacyFieldValueTransformer(ResolvedJavaField oField, FieldValueTransformer transformer) { + registerFieldValueTransformer(oField, WrappedFieldValueTransformer.create(transformer)); + } + /** * Register a field value transformer for the provided field. There can only be one transformer * per field, if there is already a transformation in place, a {@link UserError} is reported. */ - public void registerJVMCIFieldValueTransformer(ResolvedJavaField resolvedJavaField, JVMCIFieldValueTransformer transformer) { - registerFieldValueTransformer(resolvedJavaField, new FallbackFieldValueTransformer(transformer)); - } - - public void registerFieldValueTransformer(ResolvedJavaField oField, FieldValueTransformer transformer) { + public void registerFieldValueTransformer(ResolvedJavaField oField, JVMCIFieldValueTransformer transformer) { 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); + registerFieldValueTransformer(oField, OriginalClassProvider.getOriginalType(oField.getType()), transformer); } - public void registerFieldValueTransformer(ResolvedJavaField oField, Class transformedValueAllowedType, FieldValueTransformer transformer) { + public void registerFieldValueTransformer(ResolvedJavaField oField, ResolvedJavaType transformedValueAllowedType, JVMCIFieldValueTransformer transformer) { assert oField != null && !(oField instanceof OriginalFieldProvider) : oField; var transformation = new FieldValueTransformation(transformedValueAllowedType, Objects.requireNonNull(transformer)); @@ -467,7 +472,7 @@ private FieldValueTransformation createLayeredFieldValueTransformation(ResolvedJ LayeredFieldValue layeredFieldValue = AnnotationUtil.getAnnotation(aField, LayeredFieldValue.class); if (layeredFieldValue != null) { var transformer = layeredSupport.createTransformer(aField, layeredFieldValue); - return new FieldValueTransformation(OriginalClassProvider.getJavaClass(oField.getType()), transformer); + return new FieldValueTransformation(OriginalClassProvider.getOriginalType(oField.getType()), transformer); } return null; } 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 376c971012c5..3133956024eb 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 @@ -87,12 +87,15 @@ import com.oracle.svm.hosted.NativeImageOptions; import com.oracle.svm.hosted.SVMHost; import com.oracle.svm.hosted.ameta.FieldValueInterceptionSupport; +import com.oracle.svm.hosted.ameta.FieldValueInterceptionSupport.WrappedFieldValueTransformer; import com.oracle.svm.hosted.classinitialization.ClassInitializationSupport; import com.oracle.svm.hosted.meta.HostedUniverse; import com.oracle.svm.util.AnnotationUtil; +import com.oracle.svm.util.GraalAccess; import com.oracle.svm.util.JVMCIFieldValueTransformer; import com.oracle.svm.util.JVMCIReflectionUtil; import com.oracle.svm.util.OriginalClassProvider; +import com.oracle.svm.util.OriginalFieldProvider; import com.oracle.svm.util.ReflectionUtil; import com.oracle.svm.util.ReflectionUtil.ReflectionUtilError; @@ -150,7 +153,7 @@ public class AnnotationSubstitutionProcessor extends SubstitutionProcessor { private final Map methodSubstitutions; private final Map polymorphicMethodSubstitutions; private final Map fieldSubstitutions; - private Map unsafeAccessedFields = new HashMap<>(); + private Map unsafeAccessedFields = new HashMap<>(); private final ClassInitializationSupport classInitializationSupport; private final Set disabledSubstitutions; private final boolean reportUnsupportedElementAtRuntime; @@ -355,7 +358,7 @@ public ResolvedJavaMethod lookup(ResolvedJavaMethod method) { */ public void registerUnsafeAccessedFields(BigBang bb) { for (var entry : unsafeAccessedFields.entrySet()) { - AnalysisField targetField = bb.getMetaAccess().lookupJavaField(entry.getKey()); + AnalysisField targetField = bb.getUniverse().lookup(entry.getKey()); assert !AnnotationUtil.isAnnotationPresent(targetField, Delete.class); targetField.registerAsUnsafeAccessed(entry.getValue()); } @@ -1046,14 +1049,14 @@ private ResolvedJavaField fieldValueRecomputation(Class originalClass, Resolv targetClass = imageClassLoader.findClassOrFail(recomputeAnnotation.declClassName()); } } - Class transformedValueAllowedType = getTargetClass(annotatedField.getType()); + ResolvedJavaType targetType = GraalAccess.lookupType(targetClass); + ResolvedJavaType transformedValueAllowedType = GraalAccess.lookupType(getTargetClass(annotatedField.getType())); - // GR-71666: Change variable to JVMCIFieldValueTransformer - var newTransformer = switch (kind) { + JVMCIFieldValueTransformer 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 NewInstance -> new NewInstanceOfFixedClassFieldValueTransformer(targetType, false); + case NewInstanceWhenNotNull -> new NewInstanceOfFixedClassFieldValueTransformer(targetType, true); case FromAlias -> { if (!Modifier.isStatic(annotated.getModifiers())) { throw UserError.abort("Cannot use " + kind + " on non-static alias " + annotated.format("%H.%n")); @@ -1061,37 +1064,37 @@ private ResolvedJavaField fieldValueRecomputation(Class originalClass, Resolv yield new FromAliasFieldValueTransformer(annotated); } case FieldOffset -> { - var targetField = getField(annotated, targetClass, targetName); + var targetField = getField(annotated, targetType, targetName); unsafeAccessedFields.put(targetField, original); - yield new FieldOffsetFieldValueTransformer(targetField, original.getType().getJavaKind()); + yield new FieldOffsetFieldValueTransformer(OriginalFieldProvider.getJavaField(targetField), original.getType().getJavaKind()); } case StaticFieldBase -> { - var targetField = getField(annotated, targetClass, targetName); + var targetField = getField(annotated, targetType, 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); + yield new StaticFieldBaseFieldValueTransformer(OriginalFieldProvider.getJavaField(targetField)); } case ArrayBaseOffset -> - new ArrayBaseOffsetFieldValueTransformer(targetClass, original.getType().getJavaKind()); + new ArrayBaseOffsetFieldValueTransformer(targetType, original.getType().getJavaKind()); case ArrayIndexScale -> - new ArrayIndexScaleFieldValueTransformer(targetClass, original.getType().getJavaKind()); + new ArrayIndexScaleFieldValueTransformer(targetType, original.getType().getJavaKind()); case ArrayIndexShift -> - new ArrayIndexShiftFieldValueTransformer(targetClass, original.getType().getJavaKind()); + new ArrayIndexShiftFieldValueTransformer(targetType, original.getType().getJavaKind()); case AtomicFieldUpdaterOffset -> new AtomicFieldUpdaterOffsetFieldValueTransformer(original, targetClass); case TranslateFieldOffset -> new TranslateFieldOffsetFieldValueTransformer(original, targetClass); case Custom -> { if (JVMCIFieldValueTransformer.class.isAssignableFrom(targetClass)) { - var jvmciFieldValueTransformer = (JVMCIFieldValueTransformer) ReflectionUtil.newInstance(targetClass); - yield new FieldValueInterceptionSupport.FallbackFieldValueTransformer(jvmciFieldValueTransformer); + yield (JVMCIFieldValueTransformer) ReflectionUtil.newInstance(targetClass); } else { - yield (FieldValueTransformer) ReflectionUtil.newInstance(targetClass); + var fieldValueTransformer = (FieldValueTransformer) ReflectionUtil.newInstance(targetClass); + yield WrappedFieldValueTransformer.create(fieldValueTransformer); } } }; if (newTransformer != null) { - FieldValueTransformer existingTransformer = fieldValueInterceptionSupport.lookupAlreadyRegisteredTransformer(original); + JVMCIFieldValueTransformer existingTransformer = fieldValueInterceptionSupport.lookupAlreadyRegisteredTransformer(original); if (existingTransformer != null) { if (existingTransformer.equals(newTransformer)) { /* Equivalent transformations are allowed, nothing to do. */ @@ -1106,11 +1109,11 @@ private ResolvedJavaField fieldValueRecomputation(Class originalClass, Resolv return new AliasField(original, annotated, isFinal); } - private static Field getField(ResolvedJavaField annotated, Class targetClass, String targetName) { + private static ResolvedJavaField getField(ResolvedJavaField annotated, ResolvedJavaType targetType, 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")); + return JVMCIReflectionUtil.getUniqueDeclaredField(targetType, targetName); + } catch (NoSuchFieldError e) { + throw UserError.abort("Could not find target field %s.%s for alias %s.", targetType.toClassName(), targetName, annotated == null ? null : annotated.format("%H.%n")); } } 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 765aa4d7e538..0d6f0b83e08f 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,29 +31,37 @@ import org.graalvm.collections.EconomicMap; import org.graalvm.nativeimage.hosted.FieldValueTransformer; +import com.oracle.graal.pointsto.heap.TypedConstant; import com.oracle.graal.pointsto.meta.AnalysisField; -import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.fieldvaluetransformer.JavaConstantWrapper; 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.util.ClassUtil; import com.oracle.svm.util.GraalAccess; +import com.oracle.svm.util.JVMCIFieldValueTransformer; +import com.oracle.svm.util.JVMCIReflectionUtil; +import com.oracle.svm.util.OriginalClassProvider; import com.oracle.svm.util.OriginalFieldProvider; 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 FieldValueTransformation { - protected final FieldValueTransformer fieldValueTransformer; - protected final Class transformedValueAllowedType; + protected final JVMCIFieldValueTransformer fieldValueTransformer; + protected final ResolvedJavaType transformedValueAllowedType; private final EconomicMap valueCache = EconomicMap.create(); private final ReentrantReadWriteLock valueCacheLock = new ReentrantReadWriteLock(); - public FieldValueTransformation(Class transformedValueAllowedType, FieldValueTransformer fieldValueTransformer) { + public FieldValueTransformation(ResolvedJavaType transformedValueAllowedType, JVMCIFieldValueTransformer fieldValueTransformer) { this.fieldValueTransformer = fieldValueTransformer; this.transformedValueAllowedType = transformedValueAllowedType; } - public FieldValueTransformer getFieldValueTransformer() { + public JVMCIFieldValueTransformer getFieldValueTransformer() { return fieldValueTransformer; } @@ -93,25 +101,23 @@ public JavaConstant readValue(AnalysisField field, JavaConstant receiver) { } private JavaConstant computeValue(AnalysisField field, JavaConstant receiver) { - Object receiverValue = receiver == null ? null : GraalAccess.getOriginalSnippetReflection().asObject(Object.class, receiver); - Object originalValue = fetchOriginalValue(field, receiver); + var originalValue = fetchOriginalValue(field, receiver); - Object newValue = fieldValueTransformer.transform(receiverValue, originalValue); - JavaConstant result; - if (newValue instanceof JavaConstantWrapper(JavaConstant constant)) { - result = constant; - } else { - checkValue(newValue, field); - result = GraalAccess.getOriginalSnippetReflection().forBoxed(field.getJavaKind(), newValue); - } + VMError.guarantee(originalValue != null, "Original value must not be `null`. Use `JavaConstant.NULL_POINTER`instead"); + VMError.guarantee(receiver == null || !receiver.isNull(), "Receiver should not be a boxed `null` (`JavaConstant.isNull()`) for static fields. Use `null`instead"); + JavaConstant result = getUnboxedConstant(fieldValueTransformer.transform(receiver, originalValue)); + checkValue(result, field); - assert result.getJavaKind() == field.getJavaKind(); + assert result.getJavaKind() == field.getJavaKind() : result.getJavaKind() + " vs " + field.getJavaKind(); return result; } - private void checkValue(Object newValue, AnalysisField field) { - boolean primitive = transformedValueAllowedType.isPrimitive(); + private void checkValue(JavaConstant newValue, AnalysisField field) { if (newValue == null) { + throw UserError.abort("JVMCIFieldValueTransformer must not return `null`. Use `JavaConstant.NULL_POINTER`instead"); + } + boolean primitive = transformedValueAllowedType.isPrimitive(); + if (newValue.isNull()) { if (primitive) { throw UserError.abort("Field value transformer returned null for primitive %s", field.format("%H.%n")); } else { @@ -123,17 +129,54 @@ private void checkValue(Object newValue, AnalysisField field) { * The compute/transform methods autobox primitive values. We unbox them here, but only if * the original field is primitive. */ - Class actualType = primitive ? SubstrateUtil.toUnboxedClass(newValue.getClass()) : newValue.getClass(); + ResolvedJavaType actualType = getActualType(newValue); + VMError.guarantee(actualType != null); + if (actualType.equals(GraalAccess.lookupType(JavaConstantWrapper.class))) { + throw UserError.abort("Field value transformer %s returned %s value instead of returning the JavaConstant directly.", + fieldValueTransformer.getClass().getName(), JavaConstantWrapper.class.getSimpleName(), JVMCIReflectionUtil.getTypeName(transformedValueAllowedType), field.format("%H.%n")); + } if (!transformedValueAllowedType.isAssignableFrom(actualType)) { throw UserError.abort("Field value transformer returned value of type `%s` that is not assignable to declared type `%s` of %s", - actualType.getTypeName(), transformedValueAllowedType.getTypeName(), field.format("%H.%n")); + JVMCIReflectionUtil.getTypeName(actualType), JVMCIReflectionUtil.getTypeName(transformedValueAllowedType), field.format("%H.%n")); + } + } + + /** + * Gets the {@linkplain OriginalClassProvider#getOriginalType(JavaType) original} type of a + * constant. + */ + private static ResolvedJavaType getActualType(JavaConstant newValue) { + if (newValue.getJavaKind().isPrimitive()) { + VMError.guarantee(newValue.getJavaKind().isPrimitive()); + return GraalAccess.lookupType(newValue.getJavaKind().toJavaClass()); + } + if (newValue instanceof TypedConstant typedConstant) { + return OriginalClassProvider.getOriginalType(typedConstant.getType()); + } + return GraalAccess.getOriginalProviders().getMetaAccess().lookupJavaType(newValue); + } + + private JavaConstant getUnboxedConstant(JavaConstant newValue) { + if (transformedValueAllowedType.isPrimitive() && !newValue.getJavaKind().isPrimitive()) { + if (fieldValueTransformer instanceof FieldValueTransformer || fieldValueTransformer instanceof FieldValueInterceptionSupport.WrappedFieldValueTransformer) { + /* Legacy transformer that needs to return boxed values. Try to unbox. */ + JavaConstant unboxed = GraalAccess.getOriginalProviders().getConstantReflection().unboxPrimitive(newValue); + if (unboxed != null) { + return unboxed; + } + } + throw VMError.shouldNotReachHere("Type %s is primitive but new value not %s (transformer: %s)", + JVMCIReflectionUtil.getTypeName(transformedValueAllowedType), + JVMCIReflectionUtil.getTypeName(GraalAccess.getOriginalProviders().getMetaAccess().lookupJavaType(newValue)), + ClassUtil.getUnqualifiedName(fieldValueTransformer.getClass())); } + return newValue; } - protected Object fetchOriginalValue(AnalysisField aField, JavaConstant receiver) { + protected JavaConstant fetchOriginalValue(AnalysisField aField, JavaConstant receiver) { ResolvedJavaField oField = OriginalFieldProvider.getOriginalField(aField); if (oField == null) { - return null; + return JavaConstant.NULL_POINTER; } JavaConstant originalValueConstant = GraalAccess.getOriginalProviders().getConstantReflection().readFieldValue(oField, receiver); if (originalValueConstant == null) { @@ -142,11 +185,9 @@ protected Object fetchOriginalValue(AnalysisField aField, JavaConstant receiver) * instance field in a substitution class, i.e., a field that does not exist in the * hosted object. */ - return null; - } else if (originalValueConstant.getJavaKind().isPrimitive()) { - return originalValueConstant.asBoxedPrimitive(); + return JavaConstant.NULL_POINTER; } else { - return GraalAccess.getOriginalSnippetReflection().asObject(Object.class, originalValueConstant); + return originalValueConstant; } } From 60a1fc6db8dcf6fd6914b9d8fe177d943d789f5e Mon Sep 17 00:00:00 2001 From: Josef Eisl Date: Fri, 12 Dec 2025 11:50:53 +0100 Subject: [PATCH 7/7] svm: migrate various FieldValueTransformer to JVMCIFieldValueTransformer --- .../ArrayBaseOffsetFieldValueTransformer.java | 11 ++-- .../ArrayIndexScaleFieldValueTransformer.java | 11 ++-- .../ArrayIndexShiftFieldValueTransformer.java | 11 ++-- .../ConstantValueFieldValueTransformer.java | 28 ++++----- .../FieldOffsetFieldValueTransformer.java | 8 +++ .../NewEmptyArrayFieldValueTransformer.java | 26 +++++--- ...anceOfFixedClassFieldValueTransformer.java | 18 +++--- .../oracle/svm/hosted/ClassLoaderFeature.java | 16 ++--- .../svm/hosted/jfr/JfrEventFeature.java | 11 ++-- .../methodhandles/MethodHandleFeature.java | 3 +- .../svm/hosted/reflect/ReflectionFeature.java | 8 ++- .../AutomaticUnsafeTransformationSupport.java | 62 +++++++++---------- .../FromAliasFieldValueTransformer.java | 17 ++--- 13 files changed, 126 insertions(+), 104 deletions(-) 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 ff2371d7320b..b01c480cb738 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 @@ -24,20 +24,21 @@ */ 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.config.ConfigurationValues; +import com.oracle.svm.util.JVMCIFieldValueTransformer; +import jdk.vm.ci.meta.JavaConstant; import jdk.vm.ci.meta.JavaKind; +import jdk.vm.ci.meta.ResolvedJavaType; /** * Implements the field value transformation semantics of {@link Kind#ArrayBaseOffset}. */ -public record ArrayBaseOffsetFieldValueTransformer(Class targetClass, JavaKind returnKind) implements FieldValueTransformer { +public record ArrayBaseOffsetFieldValueTransformer(ResolvedJavaType targetClass, JavaKind returnKind) implements JVMCIFieldValueTransformer { @Override - public Object transform(Object receiver, Object originalValue) { - return FieldOffsetFieldValueTransformer.box(returnKind, ConfigurationValues.getObjectLayout().getArrayBaseOffset(JavaKind.fromJavaClass(targetClass.getComponentType()))); + public JavaConstant transform(JavaConstant receiver, JavaConstant originalValue) { + return FieldOffsetFieldValueTransformer.constant(returnKind, ConfigurationValues.getObjectLayout().getArrayBaseOffset(targetClass.getComponentType().getJavaKind())); } } 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 bf9f418f7834..9de9ad4ae126 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 @@ -24,20 +24,21 @@ */ 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.config.ConfigurationValues; +import com.oracle.svm.util.JVMCIFieldValueTransformer; +import jdk.vm.ci.meta.JavaConstant; import jdk.vm.ci.meta.JavaKind; +import jdk.vm.ci.meta.ResolvedJavaType; /** * Implements the field value transformation semantics of {@link Kind#ArrayIndexScale}. */ -public record ArrayIndexScaleFieldValueTransformer(Class targetClass, JavaKind returnKind) implements FieldValueTransformer { +public record ArrayIndexScaleFieldValueTransformer(ResolvedJavaType targetClass, JavaKind returnKind) implements JVMCIFieldValueTransformer { @Override - public Object transform(Object receiver, Object originalValue) { - return FieldOffsetFieldValueTransformer.box(returnKind, ConfigurationValues.getObjectLayout().getArrayIndexScale(JavaKind.fromJavaClass(targetClass.getComponentType()))); + public JavaConstant transform(JavaConstant receiver, JavaConstant originalValue) { + return FieldOffsetFieldValueTransformer.constant(returnKind, ConfigurationValues.getObjectLayout().getArrayIndexScale(targetClass.getComponentType().getJavaKind())); } } 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 8f0a9570bb21..100fec808d18 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 @@ -24,20 +24,21 @@ */ 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.config.ConfigurationValues; +import com.oracle.svm.util.JVMCIFieldValueTransformer; +import jdk.vm.ci.meta.JavaConstant; import jdk.vm.ci.meta.JavaKind; +import jdk.vm.ci.meta.ResolvedJavaType; /** * Implements the field value transformation semantics of {@link Kind#ArrayIndexShift}. */ -public record ArrayIndexShiftFieldValueTransformer(Class targetClass, JavaKind returnKind) implements FieldValueTransformer { +public record ArrayIndexShiftFieldValueTransformer(ResolvedJavaType targetClass, JavaKind returnKind) implements JVMCIFieldValueTransformer { @Override - public Object transform(Object receiver, Object originalValue) { - return FieldOffsetFieldValueTransformer.box(returnKind, ConfigurationValues.getObjectLayout().getArrayIndexShift(JavaKind.fromJavaClass(targetClass.getComponentType()))); + public JavaConstant transform(JavaConstant receiver, JavaConstant originalValue) { + return FieldOffsetFieldValueTransformer.constant(returnKind, ConfigurationValues.getObjectLayout().getArrayIndexShift(targetClass.getComponentType().getJavaKind())); } } 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 index e9bae7c32cdd..799a879d0797 100644 --- 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 @@ -24,11 +24,11 @@ */ 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 com.oracle.svm.util.JVMCIFieldValueTransformer; +import jdk.vm.ci.meta.JavaConstant; import jdk.vm.ci.meta.ResolvedJavaField; /** @@ -37,25 +37,25 @@ * 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 record ConstantValueFieldValueTransformer(JavaConstant value) implements JVMCIFieldValueTransformer { - public static FieldValueTransformer defaultValueForField(ResolvedJavaField field) { + public static JVMCIFieldValueTransformer 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; + case Byte -> JavaConstant.forByte((byte) 0); + case Boolean -> JavaConstant.FALSE; + case Short -> JavaConstant.forShort((short) 0); + case Char -> JavaConstant.forChar((char) 0); + case Int -> JavaConstant.INT_0; + case Long -> JavaConstant.LONG_0; + case Float -> JavaConstant.FLOAT_0; + case Double -> JavaConstant.DOUBLE_0; + case Object -> JavaConstant.NULL_POINTER; default -> throw VMError.shouldNotReachHere(String.valueOf(field)); }); } @Override - public Object transform(Object receiver, Object originalValue) { + public JavaConstant transform(JavaConstant receiver, JavaConstant 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 dfc07446d0a9..7fd23751b24f 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 @@ -67,6 +67,14 @@ static Object box(JavaKind returnKind, int value) { } } + static JavaConstant constant(JavaKind returnKind, int value) { + return switch (returnKind) { + case Int -> JavaConstant.forInt(value); + case Long -> JavaConstant.forLong(value); + default -> throw VMError.shouldNotReachHere("Unexpected kind: " + returnKind); + }; + } + @Override public ValueNode intrinsify(CoreProviders providers, JavaConstant receiver) { return FieldOffsetNode.create(returnKind, providers.getMetaAccess().lookupJavaField(targetField)); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/fieldvaluetransformer/NewEmptyArrayFieldValueTransformer.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/fieldvaluetransformer/NewEmptyArrayFieldValueTransformer.java index c89a840848b2..7265b0974076 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/fieldvaluetransformer/NewEmptyArrayFieldValueTransformer.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/fieldvaluetransformer/NewEmptyArrayFieldValueTransformer.java @@ -24,22 +24,30 @@ */ package com.oracle.svm.core.fieldvaluetransformer; -import java.lang.reflect.Array; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.util.GraalAccess; +import com.oracle.svm.util.JVMCIFieldValueTransformer; +import com.oracle.svm.util.JVMCIReflectionUtil; -import org.graalvm.nativeimage.hosted.FieldValueTransformer; +import jdk.graal.compiler.phases.util.Providers; +import jdk.vm.ci.meta.JavaConstant; +import jdk.vm.ci.meta.MetaAccessProvider; /** * Reset an array field to a new empty array of the same type and length. */ -public final class NewEmptyArrayFieldValueTransformer implements FieldValueTransformer { - public static final FieldValueTransformer INSTANCE = new NewEmptyArrayFieldValueTransformer(); +public final class NewEmptyArrayFieldValueTransformer implements JVMCIFieldValueTransformer { + public static final JVMCIFieldValueTransformer INSTANCE = new NewEmptyArrayFieldValueTransformer(); @Override - public Object transform(Object receiver, Object originalValue) { - if (originalValue == null) { - return null; + public JavaConstant transform(JavaConstant receiver, JavaConstant originalValue) { + if (originalValue.isNull()) { + return JavaConstant.NULL_POINTER; } - int originalLength = Array.getLength(originalValue); - return Array.newInstance(originalValue.getClass().getComponentType(), originalLength); + Providers originalProviders = GraalAccess.getOriginalProviders(); + MetaAccessProvider metaAccess = originalProviders.getMetaAccess(); + Integer originalLength = originalProviders.getConstantReflection().readArrayLength(originalValue); + VMError.guarantee(originalLength != null, "Original value is not an array or the array length is not known"); + return JVMCIReflectionUtil.newArrayInstance(metaAccess.lookupJavaType(originalValue).getComponentType(), originalLength); } } 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 index 402664050d30..1f019b48d8b4 100644 --- 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 @@ -24,22 +24,24 @@ */ 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; +import com.oracle.svm.util.JVMCIFieldValueTransformer; +import com.oracle.svm.util.JVMCIReflectionUtil; + +import jdk.vm.ci.meta.JavaConstant; +import jdk.vm.ci.meta.ResolvedJavaType; /** * Implements the field value transformation semantics of {@link Kind#NewInstance} and * {@link Kind#NewInstanceWhenNotNull}. */ -public record NewInstanceOfFixedClassFieldValueTransformer(Class clazz, boolean onlyIfOriginalNotNull) implements FieldValueTransformer { +public record NewInstanceOfFixedClassFieldValueTransformer(ResolvedJavaType type, boolean onlyIfOriginalNotNull) implements JVMCIFieldValueTransformer { @Override - public Object transform(Object receiver, Object originalValue) { - if (onlyIfOriginalNotNull && originalValue == null) { - return null; + public JavaConstant transform(JavaConstant receiver, JavaConstant originalValue) { + if (onlyIfOriginalNotNull && originalValue.isNull()) { + return JavaConstant.NULL_POINTER; } - return ReflectionUtil.newInstance(clazz); + return JVMCIReflectionUtil.newInstance(type); } } 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 9a85ef4b51d0..9b48275bfe0e 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 @@ -26,8 +26,6 @@ import java.util.concurrent.ConcurrentHashMap; -import org.graalvm.nativeimage.hosted.FieldValueTransformer; - import com.oracle.graal.pointsto.ObjectScanner.OtherReason; import com.oracle.graal.pointsto.ObjectScanner.ScanReason; import com.oracle.graal.pointsto.heap.ImageHeapConstant; @@ -40,7 +38,9 @@ import com.oracle.svm.core.util.VMError; import com.oracle.svm.hosted.imagelayer.CrossLayerConstantRegistry; import com.oracle.svm.hosted.jdk.HostedClassLoaderPackageManagement; -import com.oracle.svm.util.ReflectionUtil; +import com.oracle.svm.util.GraalAccess; +import com.oracle.svm.util.JVMCIFieldValueTransformer; +import com.oracle.svm.util.JVMCIReflectionUtil; import jdk.internal.loader.ClassLoaders; import jdk.vm.ci.meta.JavaConstant; @@ -135,15 +135,15 @@ public void duringSetup(DuringSetupAccess access) { @Override public void beforeAnalysis(BeforeAnalysisAccess access) { - var packagesField = ReflectionUtil.lookupField(ClassLoader.class, "packages"); var config = (FeatureImpl.BeforeAnalysisAccessImpl) access; + var packagesField = JVMCIReflectionUtil.getUniqueDeclaredField(GraalAccess.lookupType(ClassLoader.class), "packages"); if (!ImageLayerBuildingSupport.buildingImageLayer()) { - access.registerFieldValueTransformer(packagesField, new TraditionalPackageMapTransformer()); + config.registerFieldValueTransformer(packagesField, new TraditionalPackageMapTransformer()); } else { if (ImageLayerBuildingSupport.buildingInitialLayer()) { config.registerFieldValueTransformer(packagesField, new InitialLayerPackageMapTransformer()); } else { - access.registerFieldValueTransformer(packagesField, new ExtensionLayerPackageMapTransformer()); + config.registerFieldValueTransformer(packagesField, new ExtensionLayerPackageMapTransformer()); } } @@ -223,10 +223,10 @@ public Object transform(Object receiver, Object originalValue) { } } - static class ExtensionLayerPackageMapTransformer implements FieldValueTransformer { + static class ExtensionLayerPackageMapTransformer implements JVMCIFieldValueTransformer { @Override - public Object transform(Object receiver, Object originalValue) { + public JavaConstant transform(JavaConstant receiver, JavaConstant originalValue) { throw VMError.shouldNotReachHere("No classloaders should be installed in extension layers: %s", receiver); } } 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 7d9fd1cede9f..3c61ff019cff 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 @@ -38,7 +38,7 @@ 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; +import com.oracle.svm.core.fieldvaluetransformer.JVMCIFieldValueTransformerWithAvailability; import com.oracle.svm.core.hub.DynamicHub; import com.oracle.svm.core.hub.DynamicHubCompanion; import com.oracle.svm.core.hub.DynamicHubSupport; @@ -50,10 +50,11 @@ import com.oracle.svm.hosted.FeatureImpl; import com.oracle.svm.hosted.ameta.FieldValueInterceptionSupport; import com.oracle.svm.hosted.reflect.ReflectionFeature; -import com.oracle.svm.util.ReflectionUtil; +import com.oracle.svm.util.JVMCIReflectionUtil; import jdk.internal.event.Event; import jdk.jfr.internal.JVM; +import jdk.vm.ci.meta.JavaConstant; import jdk.vm.ci.meta.MetaAccessProvider; import sun.nio.ch.FileChannelImpl; @@ -97,15 +98,15 @@ public void duringSetup(DuringSetupAccess c) { * finishes, but only in case jfr is enabled, so we do not add @UnknownObjectField * annotation to it, because it will be null if jfr is disabled. */ - var configField = ReflectionUtil.lookupField(DynamicHubCompanion.class, "jfrEventConfiguration"); - FieldValueInterceptionSupport.singleton().registerFieldValueTransformer(configField, new FieldValueTransformerWithAvailability() { + var configField = JVMCIReflectionUtil.getUniqueDeclaredField(metaAccess.lookupJavaType(DynamicHubCompanion.class), "jfrEventConfiguration"); + FieldValueInterceptionSupport.singleton().registerFieldValueTransformer(configField, new JVMCIFieldValueTransformerWithAvailability() { @Override public boolean isAvailable() { return BuildPhaseProvider.isHostedUniverseBuilt(); } @Override - public Object transform(Object receiver, Object originalValue) { + public JavaConstant transform(JavaConstant receiver, JavaConstant originalValue) { return originalValue; } }); 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 ec207486d17e..de2995c304d8 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 @@ -55,6 +55,7 @@ import com.oracle.svm.hosted.FeatureImpl.BeforeAnalysisAccessImpl; import com.oracle.svm.hosted.FeatureImpl.DuringAnalysisAccessImpl; import com.oracle.svm.hosted.FeatureImpl.DuringSetupAccessImpl; +import com.oracle.svm.util.JVMCIReflectionUtil; import com.oracle.svm.util.ReflectionUtil; import sun.invoke.util.ValueConversions; @@ -190,7 +191,7 @@ public Object transform(Object receiver, Object originalValue) { } }); access.registerFieldValueTransformer( - ReflectionUtil.lookupField(ReflectionUtil.lookupClass("java.lang.invoke.DirectMethodHandle"), "ACCESSOR_FORMS"), + JVMCIReflectionUtil.getUniqueDeclaredField(access.findTypeByName("java.lang.invoke.DirectMethodHandle").unwrapTowardsOriginalType(), "ACCESSOR_FORMS"), NewEmptyArrayFieldValueTransformer.INSTANCE); access.registerFieldValueTransformer( ReflectionUtil.lookupField(ReflectionUtil.lookupClass("java.lang.invoke.MethodType"), "internTable"), 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 dd8a4c979abf..aef110b935cb 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 @@ -36,6 +36,7 @@ import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; +import jdk.vm.ci.meta.ResolvedJavaType; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.c.function.CFunctionPointer; import org.graalvm.nativeimage.dynamicaccess.AccessCondition; @@ -95,6 +96,8 @@ import com.oracle.svm.hosted.snippets.ReflectionPlugins; import com.oracle.svm.hosted.substitute.AnnotationSubstitutionProcessor; import com.oracle.svm.util.AnnotationUtil; +import com.oracle.svm.util.GraalAccess; +import com.oracle.svm.util.JVMCIReflectionUtil; import com.oracle.svm.util.ModuleSupport; import com.oracle.svm.util.ReflectionUtil; @@ -385,8 +388,9 @@ public void beforeAnalysis(BeforeAnalysisAccess access) { * These transformers have to be registered before registering methods below which causes * the analysis to already see SubstrateMethodAccessor.vtableIndex. */ - access.registerFieldValueTransformer(ReflectionUtil.lookupField(SubstrateMethodAccessor.class, "vtableIndex"), new ComputeVTableIndex()); - access.registerFieldValueTransformer(ReflectionUtil.lookupField(SubstrateMethodAccessor.class, "interfaceTypeID"), new ComputeInterfaceTypeID()); + ResolvedJavaType substrateMethodAccessorType = GraalAccess.lookupType(SubstrateMethodAccessor.class); + analysisAccess.registerFieldValueTransformer(JVMCIReflectionUtil.getUniqueDeclaredField(substrateMethodAccessorType, "vtableIndex"), new ComputeVTableIndex()); + analysisAccess.registerFieldValueTransformer(JVMCIReflectionUtil.getUniqueDeclaredField(substrateMethodAccessorType, "interfaceTypeID"), new ComputeInterfaceTypeID()); /* Make sure array classes don't need to be registered for reflection. */ RuntimeReflection.register(Object.class.getDeclaredMethods()); 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 1d35416886d6..fbebaecb692c 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 @@ -30,6 +30,7 @@ import static com.oracle.svm.core.annotate.RecomputeFieldValue.Kind.ArrayIndexShift; 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.hosted.ameta.FieldValueInterceptionSupport.singleton; import static jdk.graal.compiler.nodes.graphbuilderconf.InlineInvokePlugin.InlineInfo.createStandardInlineInfo; import java.lang.reflect.Field; @@ -40,14 +41,11 @@ import java.util.function.Supplier; import java.util.stream.Collectors; -import jdk.graal.compiler.nodes.calc.NarrowNode; import org.graalvm.collections.EconomicSet; import org.graalvm.nativeimage.ImageSingletons; -import org.graalvm.nativeimage.hosted.FieldValueTransformer; import com.oracle.graal.pointsto.BigBang; import com.oracle.graal.pointsto.phases.NoClassInitializationPlugin; -import com.oracle.svm.util.GraalAccess; import com.oracle.svm.core.ParsingReason; import com.oracle.svm.core.annotate.RecomputeFieldValue; import com.oracle.svm.core.annotate.RecomputeFieldValue.Kind; @@ -62,10 +60,11 @@ import com.oracle.svm.hosted.FallbackFeature; import com.oracle.svm.hosted.ImageClassLoader; import com.oracle.svm.hosted.SVMHost; -import com.oracle.svm.hosted.ameta.FieldValueInterceptionSupport; import com.oracle.svm.hosted.classinitialization.ClassInitializerGraphBuilderPhase; import com.oracle.svm.hosted.phases.ConstantFoldLoadFieldPlugin; import com.oracle.svm.hosted.snippets.ReflectionPlugins; +import com.oracle.svm.util.GraalAccess; +import com.oracle.svm.util.JVMCIFieldValueTransformer; import com.oracle.svm.util.LogUtils; import com.oracle.svm.util.ReflectionUtil; @@ -80,6 +79,7 @@ import jdk.graal.compiler.nodes.InvokeWithExceptionNode; import jdk.graal.compiler.nodes.StructuredGraph; import jdk.graal.compiler.nodes.ValueNode; +import jdk.graal.compiler.nodes.calc.NarrowNode; import jdk.graal.compiler.nodes.calc.SignExtendNode; import jdk.graal.compiler.nodes.calc.SubNode; import jdk.graal.compiler.nodes.graphbuilderconf.GraphBuilderConfiguration; @@ -492,11 +492,11 @@ private void processUnsafeFieldComputation(BigBang bb, ResolvedJavaType type, In private void processUnsafeArrayBaseOffsetInvoke(BigBang bb, ResolvedJavaType type, Invoke unsafeArrayBaseOffsetInvoke) { List> unsuccessfulReasons = new ArrayList<>(); - Class arrayClass = null; + ResolvedJavaType arrayType = null; ValueNode arrayClassArgument = unsafeArrayBaseOffsetInvoke.callTarget().arguments().get(1); if (arrayClassArgument.isJavaConstant()) { - arrayClass = GraalAccess.getOriginalSnippetReflection().asObject(Class.class, arrayClassArgument.asJavaConstant()); + arrayType = GraalAccess.getOriginalProviders().getConstantReflection().asJavaType(arrayClassArgument.asJavaConstant()); } else { unsuccessfulReasons.add(() -> "The argument of the call to Unsafe.arrayBaseOffset() is not a constant."); } @@ -508,10 +508,10 @@ private void processUnsafeArrayBaseOffsetInvoke(BigBang bb, ResolvedJavaType typ SearchResult result = extractValueStoreField(unsafeArrayBaseOffsetInvoke.asNode(), ArrayBaseOffset, unsuccessfulReasons); ResolvedJavaField offsetField = result.valueStoreField; - if (arrayClass != null && offsetField != null) { - Class finalArrayClass = arrayClass; - if (tryAutomaticTransformation(bb, offsetField, ArrayBaseOffset, finalArrayClass, null)) { - reportSuccessfulAutomaticRecomputation(ArrayBaseOffset, offsetField, arrayClass.getCanonicalName()); + if (arrayType != null && offsetField != null) { + ResolvedJavaType finalArrayType = arrayType; + if (tryAutomaticTransformation(bb, offsetField, ArrayBaseOffset, finalArrayType, null)) { + reportSuccessfulAutomaticRecomputation(ArrayBaseOffset, offsetField, arrayType.toClassName()); } } else { /* Don't report a failure if the value doesn't have illegal usages. */ @@ -530,11 +530,11 @@ private void processUnsafeArrayBaseOffsetInvoke(BigBang bb, ResolvedJavaType typ private void processUnsafeArrayIndexScaleInvoke(BigBang bb, ResolvedJavaType type, Invoke unsafeArrayIndexScale, StructuredGraph clinitGraph) { List> unsuccessfulReasons = new ArrayList<>(); - Class arrayClass = null; + ResolvedJavaType arrayType = null; ValueNode arrayClassArgument = unsafeArrayIndexScale.callTarget().arguments().get(1); if (arrayClassArgument.isJavaConstant()) { - arrayClass = GraalAccess.getOriginalSnippetReflection().asObject(Class.class, arrayClassArgument.asJavaConstant()); + arrayType = GraalAccess.getOriginalProviders().getConstantReflection().asJavaType(arrayClassArgument.asJavaConstant()); } else { unsuccessfulReasons.add(() -> "The argument of the call to Unsafe.arrayIndexScale() is not a constant."); } @@ -549,20 +549,20 @@ private void processUnsafeArrayIndexScaleInvoke(BigBang bb, ResolvedJavaType typ boolean indexScaleComputed = false; boolean indexShiftComputed = false; - if (arrayClass != null) { + if (arrayType != null) { if (indexScaleField != null) { - if (tryAutomaticTransformation(bb, indexScaleField, ArrayIndexScale, arrayClass, null)) { - reportSuccessfulAutomaticRecomputation(ArrayIndexScale, indexScaleField, arrayClass.getCanonicalName()); + if (tryAutomaticTransformation(bb, indexScaleField, ArrayIndexScale, arrayType, null)) { + reportSuccessfulAutomaticRecomputation(ArrayIndexScale, indexScaleField, arrayType.toClassName()); indexScaleComputed = true; /* Try transformation for the array index shift computation if present. */ - indexShiftComputed = processArrayIndexShiftFromField(bb, type, indexScaleField, arrayClass, clinitGraph); + indexShiftComputed = processArrayIndexShiftFromField(bb, type, indexScaleField, arrayType, clinitGraph); } } else { /* * The index scale is not stored into a field, it might be used to compute the index * shift. */ - indexShiftComputed = processArrayIndexShiftFromLocal(bb, type, unsafeArrayIndexScale, arrayClass); + indexShiftComputed = processArrayIndexShiftFromLocal(bb, type, unsafeArrayIndexScale, arrayType); } } if (!indexScaleComputed && !indexShiftComputed) { @@ -592,7 +592,7 @@ private void processUnsafeArrayIndexScaleInvoke(BigBang bb, ResolvedJavaType typ * It is important that constant folding is not enabled for the byteArrayIndexScale load because * it would break the link between the scale and shift computations. */ - private boolean processArrayIndexShiftFromField(BigBang bb, ResolvedJavaType type, ResolvedJavaField indexScaleField, Class arrayClass, StructuredGraph clinitGraph) { + private boolean processArrayIndexShiftFromField(BigBang bb, ResolvedJavaType type, ResolvedJavaField indexScaleField, ResolvedJavaType arrayType, StructuredGraph clinitGraph) { for (LoadFieldNode load : clinitGraph.getNodes().filter(LoadFieldNode.class)) { if (load.field().equals(indexScaleField)) { /* @@ -600,7 +600,7 @@ private boolean processArrayIndexShiftFromField(BigBang bb, ResolvedJavaType typ * field was already found. The case where both scale and shift are computed is * uncommon. */ - if (processArrayIndexShift(bb, type, arrayClass, load, true)) { + if (processArrayIndexShift(bb, type, arrayType, load, true)) { /* * Return true as soon as an index shift computed from the index scale field is * found. It is very unlikely that there are multiple shift computations from @@ -629,15 +629,15 @@ private boolean processArrayIndexShiftFromField(BigBang bb, ResolvedJavaType typ * } * */ - private boolean processArrayIndexShiftFromLocal(BigBang bb, ResolvedJavaType type, Invoke unsafeArrayIndexScale, Class arrayClass) { + private boolean processArrayIndexShiftFromLocal(BigBang bb, ResolvedJavaType type, Invoke unsafeArrayIndexScale, ResolvedJavaType arrayType) { /* Try to compute index shift. Report errors since the index scale field was not found. */ - return processArrayIndexShift(bb, type, arrayClass, unsafeArrayIndexScale.asNode(), false); + return processArrayIndexShift(bb, type, arrayType, unsafeArrayIndexScale.asNode(), false); } /** * Try to compute the arrayIndexShift. Return true if successful, false otherwise. */ - private boolean processArrayIndexShift(BigBang bb, ResolvedJavaType type, Class arrayClass, ValueNode indexScaleValue, boolean silentFailure) { + private boolean processArrayIndexShift(BigBang bb, ResolvedJavaType type, ResolvedJavaType arrayType, ValueNode indexScaleValue, boolean silentFailure) { NodeIterable loadMethodCallTargetUsages = indexScaleValue.usages().filter(MethodCallTargetNode.class); for (MethodCallTargetNode methodCallTarget : loadMethodCallTargetUsages) { /* Iterate over all the calls that use the index scale value. */ @@ -670,8 +670,8 @@ private boolean processArrayIndexShift(BigBang bb, ResolvedJavaType type, Class< } if (indexShiftField != null) { - if (tryAutomaticTransformation(bb, indexShiftField, ArrayIndexShift, arrayClass, null)) { - reportSuccessfulAutomaticRecomputation(ArrayIndexShift, indexShiftField, arrayClass.getCanonicalName()); + if (tryAutomaticTransformation(bb, indexShiftField, ArrayIndexShift, arrayType, null)) { + reportSuccessfulAutomaticRecomputation(ArrayIndexShift, indexShiftField, arrayType.toClassName()); return true; } } else { @@ -881,7 +881,7 @@ 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, RecomputeFieldValue.Kind kind, Class targetClass, Field targetField) { + private boolean tryAutomaticTransformation(BigBang bb, ResolvedJavaField field, RecomputeFieldValue.Kind kind, ResolvedJavaType targetType, Field targetField) { if (annotationSubstitutions.isDeleted(field)) { String conflictingSubstitution = "The field " + field.format("%H.%n") + " is marked as deleted. "; reportConflictingSubstitution(field, kind, conflictingSubstitution); @@ -893,16 +893,16 @@ private boolean tryAutomaticTransformation(BigBang bb, ResolvedJavaField field, 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()); + JVMCIFieldValueTransformer newTransformer = switch (kind) { + case ArrayBaseOffset -> new ArrayBaseOffsetFieldValueTransformer(targetType, field.getType().getJavaKind()); + case ArrayIndexScale -> new ArrayIndexScaleFieldValueTransformer(targetType, field.getType().getJavaKind()); + case ArrayIndexShift -> new ArrayIndexShiftFieldValueTransformer(targetType, 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); + JVMCIFieldValueTransformer existingTransformer = singleton().lookupAlreadyRegisteredTransformer(field); if (existingTransformer != null) { if (existingTransformer.equals(newTransformer)) { reportUnnecessarySubstitution(field, kind); @@ -935,7 +935,7 @@ private boolean tryAutomaticTransformation(BigBang bb, ResolvedJavaField field, if (kind == FieldOffset) { bb.postTask(_ -> bb.getMetaAccess().lookupJavaField(targetField).registerAsUnsafeAccessed(field)); } - FieldValueInterceptionSupport.singleton().registerFieldValueTransformer(field, newTransformer); + singleton().registerFieldValueTransformer(field, newTransformer); return true; } } 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 index d4e4db04723c..b603cc9ae420 100644 --- 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 @@ -24,26 +24,21 @@ */ package com.oracle.svm.hosted.substitute; -import org.graalvm.nativeimage.hosted.FieldValueTransformer; - -import com.oracle.svm.util.GraalAccess; import com.oracle.svm.core.annotate.RecomputeFieldValue.Kind; +import com.oracle.svm.util.GraalAccess; +import com.oracle.svm.util.JVMCIFieldValueTransformer; +import jdk.vm.ci.meta.JavaConstant; import jdk.vm.ci.meta.ResolvedJavaField; /** * Implements the field value transformation semantics of {@link Kind#FromAlias}. */ -public record FromAliasFieldValueTransformer(ResolvedJavaField aliasField) implements FieldValueTransformer { +public record FromAliasFieldValueTransformer(ResolvedJavaField aliasField) implements JVMCIFieldValueTransformer { @Override - public Object transform(Object receiver, Object originalValue) { + public JavaConstant transform(JavaConstant receiver, JavaConstant 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); - } + return GraalAccess.getOriginalProviders().getConstantReflection().readFieldValue(aliasField, null); } }