diff --git a/sdk/src/com.oracle.svm.core.annotate/src/com/oracle/svm/core/annotate/RecomputeFieldValue.java b/sdk/src/com.oracle.svm.core.annotate/src/com/oracle/svm/core/annotate/RecomputeFieldValue.java index 7d5b5c2841a4..d4040773ca90 100644 --- a/sdk/src/com.oracle.svm.core.annotate/src/com/oracle/svm/core/annotate/RecomputeFieldValue.java +++ b/sdk/src/com.oracle.svm.core.annotate/src/com/oracle/svm/core/annotate/RecomputeFieldValue.java @@ -44,6 +44,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import java.lang.reflect.Field; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; @@ -113,6 +114,13 @@ enum Kind { * @since 22.3 */ FieldOffset, + /** + * The static field base Object as it would be computed by + * {@link sun.misc.Unsafe#staticFieldBase(Field)}. + * + * @since 23.0 + */ + StaticFieldBase, /** * The int or long field is set to the offset of the first array element of the array class * {@link #declClass}, as it would be computed by diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/RecomputedFields.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/RecomputedFields.java index d870320c04f5..31a9663c9266 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/RecomputedFields.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/RecomputedFields.java @@ -27,7 +27,6 @@ //Checkstyle: stop 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.Reset; import java.lang.ref.ReferenceQueue; @@ -341,7 +340,6 @@ public static int getCommonPoolParallelism() { private static Unsafe U; @Alias @TargetElement(onlyWith = JDK19OrLater.class) // - @RecomputeFieldValue(kind = FieldOffset, name = "poolIds") // private static long POOLIDS; @Substitute diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/ComputedValueField.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/ComputedValueField.java index 3de264d87507..4d29f4f90cfa 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/ComputedValueField.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/ComputedValueField.java @@ -26,6 +26,7 @@ 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; @@ -48,6 +49,7 @@ 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; @@ -133,11 +135,11 @@ public ComputedValueField(ResolvedJavaField original, ResolvedJavaField annotate constantValue = JavaConstant.defaultForKind(getJavaKind()); break; case FieldOffset: - try { - f = targetClass.getDeclaredField(targetName); - } catch (NoSuchFieldException e) { - throw shouldNotReachHere("could not find target field " + targetClass.getName() + "." + targetName + " for alias " + annotated.format("%H.%n")); - } + f = getField(annotated, targetClass, targetName); + break; + case StaticFieldBase: + f = getField(annotated, targetClass, targetName); + UserError.guarantee(Modifier.isStatic(f.getModifiers()), "Target field must be static for %s computation of %s", StaticFieldBase, fieldFormat()); break; case Custom: if (initialTransformer != null) { @@ -154,15 +156,24 @@ public ComputedValueField(ResolvedJavaField original, ResolvedJavaField annotate } } boolean isOffsetField = isOffsetRecomputation(kind); + boolean isStaticFieldBase = kind == StaticFieldBase; guarantee(!isFinal || !isOffsetField); - this.isValueAvailableBeforeAnalysis = customValueAvailableBeforeAnalysis && !isOffsetField; - this.isValueAvailableOnlyAfterAnalysis = customValueAvailableOnlyAfterAnalysis || isOffsetField; + this.isValueAvailableBeforeAnalysis = customValueAvailableBeforeAnalysis && !isOffsetField && !isStaticFieldBase; + this.isValueAvailableOnlyAfterAnalysis = customValueAvailableOnlyAfterAnalysis || isOffsetField || isStaticFieldBase; this.isValueAvailableOnlyAfterCompilation = customValueAvailableOnlyAfterCompilation; this.targetField = f; this.fieldValueTransformer = transformer; this.valueCache = EconomicMap.create(); } + private static Field getField(ResolvedJavaField annotated, Class targetClass, String targetName) { + try { + return targetClass.getDeclaredField(targetName); + } catch (NoSuchFieldException e) { + throw UserError.abort("Could not find target field " + targetClass.getName() + "." + targetName + " for alias " + annotated.format("%H.%n")); + } + } + public static boolean isOffsetRecomputation(RecomputeFieldValue.Kind kind) { return offsetComputationKinds.contains(kind); } @@ -282,6 +293,10 @@ public JavaConstant readValue(MetaAccessProvider metaAccess, JavaConstant receiv 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; } ReadLock readLock = valueCacheLock.readLock(); @@ -342,9 +357,9 @@ private JavaConstant computeValue(MetaAccessProvider metaAccess, JavaConstant re originalValue = fetchOriginalValue(metaAccess, receiver, originalSnippetReflection); Object newValue = fieldValueTransformer.transform(receiverValue, originalValue); checkValue(newValue); - result = originalSnippetReflection.forBoxed(annotated.getJavaKind(), newValue); + result = originalSnippetReflection.forBoxed(original.getJavaKind(), newValue); - assert result.getJavaKind() == annotated.getJavaKind(); + assert result.getJavaKind() == original.getJavaKind(); break; default: throw shouldNotReachHere("Field recomputation of kind " + kind + " for " + fieldFormat() + " not yet supported"); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/UnsafeAutomaticSubstitutionProcessor.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/UnsafeAutomaticSubstitutionProcessor.java index 1e4317f0f664..17f89d9127dd 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/UnsafeAutomaticSubstitutionProcessor.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/UnsafeAutomaticSubstitutionProcessor.java @@ -29,6 +29,7 @@ import static com.oracle.svm.core.annotate.RecomputeFieldValue.Kind.ArrayIndexScale; 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 org.graalvm.compiler.nodes.graphbuilderconf.InlineInvokePlugin.InlineInfo.createStandardInlineInfo; import java.lang.reflect.Field; @@ -87,6 +88,7 @@ import com.oracle.svm.hosted.ImageClassLoader; import com.oracle.svm.hosted.SVMHost; import com.oracle.svm.hosted.classinitialization.ClassInitializerGraphBuilderPhase; +import com.oracle.svm.hosted.phases.ConstantFoldLoadFieldPlugin; import com.oracle.svm.hosted.snippets.ReflectionPlugins; import jdk.vm.ci.meta.JavaKind; @@ -131,6 +133,8 @@ static class Options { private static ResolvedJavaType resolvedUnsafeClass; private static ResolvedJavaType resolvedSunMiscUnsafeClass; + private ResolvedJavaMethod unsafeStaticFieldOffsetMethod; + private ResolvedJavaMethod unsafeStaticFieldBaseMethod; private ResolvedJavaMethod unsafeObjectFieldOffsetFieldMethod; private ResolvedJavaMethod sunMiscUnsafeObjectFieldOffsetMethod; private ResolvedJavaMethod unsafeObjectFieldOffsetClassStringMethod; @@ -192,6 +196,16 @@ public void init(ImageClassLoader loader, MetaAccessProvider originalMetaAccess) resolvedUnsafeClass = originalMetaAccess.lookupJavaType(unsafeClass); resolvedSunMiscUnsafeClass = originalMetaAccess.lookupJavaType(sunMiscUnsafeClass); + Method unsafeStaticFieldOffset = unsafeClass.getMethod("staticFieldOffset", Field.class); + unsafeStaticFieldOffsetMethod = originalMetaAccess.lookupJavaMethod(unsafeStaticFieldOffset); + noCheckedExceptionsSet.add(unsafeStaticFieldOffsetMethod); + neverInlineSet.add(unsafeStaticFieldOffsetMethod); + + Method unsafeStaticFieldBase = unsafeClass.getMethod("staticFieldBase", Field.class); + unsafeStaticFieldBaseMethod = originalMetaAccess.lookupJavaMethod(unsafeStaticFieldBase); + noCheckedExceptionsSet.add(unsafeStaticFieldBaseMethod); + neverInlineSet.add(unsafeStaticFieldBaseMethod); + Method unsafeObjectFieldOffset = unsafeClass.getMethod("objectFieldOffset", java.lang.reflect.Field.class); unsafeObjectFieldOffsetFieldMethod = originalMetaAccess.lookupJavaMethod(unsafeObjectFieldOffset); noCheckedExceptionsSet.add(unsafeObjectFieldOffsetFieldMethod); @@ -266,6 +280,7 @@ public void init(ImageClassLoader loader, MetaAccessProvider originalMetaAccess) ReflectionPlugins.registerInvocationPlugins(loader, snippetReflection, annotationSubstitutions, classInitializationPlugin, plugins.getInvocationPlugins(), null, ParsingReason.UnsafeSubstitutionAnalysis); + plugins.appendNodePlugin(new ConstantFoldLoadFieldPlugin(ParsingReason.UnsafeSubstitutionAnalysis)); /* * Analyzing certain classes leads to false errors. We disable reporting for those classes @@ -362,8 +377,11 @@ public void computeSubstitutions(SVMHost hostVM, ResolvedJavaType hostType, Opti for (Invoke invoke : clinitGraph.getInvokes()) { if (invoke.callTarget() instanceof MethodCallTargetNode) { - if (isInvokeTo(invoke, unsafeObjectFieldOffsetFieldMethod) || isInvokeTo(invoke, sunMiscUnsafeObjectFieldOffsetMethod)) { - processUnsafeObjectFieldOffsetFieldInvoke(hostType, invoke); + if (isInvokeTo(invoke, unsafeStaticFieldBaseMethod)) { + processUnsafeFieldComputation(hostType, invoke, StaticFieldBase); + } else if (isInvokeTo(invoke, unsafeObjectFieldOffsetFieldMethod) || isInvokeTo(invoke, sunMiscUnsafeObjectFieldOffsetMethod) || + isInvokeTo(invoke, unsafeStaticFieldOffsetMethod)) { + processUnsafeFieldComputation(hostType, invoke, FieldOffset); } else if (isInvokeTo(invoke, unsafeObjectFieldOffsetClassStringMethod)) { processUnsafeObjectFieldOffsetClassStringInvoke(hostType, invoke); } else if (isInvokeTo(invoke, unsafeArrayBaseOffsetMethod)) { @@ -382,33 +400,39 @@ public void computeSubstitutions(SVMHost hostVM, ResolvedJavaType hostType, Opti } /** - * Process call to Unsafe.objectFieldOffset(Field). The matching logic below - * applies to the following code pattern: - * + * Process calls to Unsafe.objectFieldOffset(Field), + * Unsafe.staticFieldOffset(Field) and Unsafe.staticFieldBase(Field). + * The matching logic below applies to the following code patterns: + *

* static final long fieldOffset = Unsafe.getUnsafe().objectFieldOffset(X.class.getDeclaredField("f")); + *

+ * static final long fieldOffset = Unsafe.getUnsafe().staticFieldOffset(X.class.getDeclaredField("f")); + *

+ * static final long fieldOffset = Unsafe.getUnsafe().staticFieldBase(X.class.getDeclaredField("f")); */ - private void processUnsafeObjectFieldOffsetFieldInvoke(ResolvedJavaType type, Invoke unsafeObjectFieldOffsetInvoke) { + private void processUnsafeFieldComputation(ResolvedJavaType type, Invoke invoke, Kind kind) { List unsuccessfulReasons = new ArrayList<>(); Class targetFieldHolder = null; String targetFieldName = null; - ValueNode fieldArgument = unsafeObjectFieldOffsetInvoke.callTarget().arguments().get(1); + String methodFormat = invoke.callTarget().targetMethod().format("%H.%n(%P)"); + ValueNode fieldArgument = invoke.callTarget().arguments().get(1); if (fieldArgument.isConstant()) { - Field field = snippetReflection.asObject(Field.class, fieldArgument.asJavaConstant()); - if (isValidField(unsafeObjectFieldOffsetInvoke, field, unsuccessfulReasons)) { - targetFieldHolder = field.getDeclaringClass(); - targetFieldName = field.getName(); + Field targetField = snippetReflection.asObject(Field.class, fieldArgument.asJavaConstant()); + if (isValidField(invoke, targetField, unsuccessfulReasons, methodFormat)) { + targetFieldHolder = targetField.getDeclaringClass(); + targetFieldName = targetField.getName(); } } else { - unsuccessfulReasons.add("The argument of Unsafe.objectFieldOffset(Field) is not a constant field."); + unsuccessfulReasons.add("The argument of " + methodFormat + " is not a constant field."); } - processUnsafeObjectFieldOffsetInvoke(type, unsafeObjectFieldOffsetInvoke, unsuccessfulReasons, targetFieldHolder, targetFieldName); + processUnsafeFieldComputation(type, invoke, kind, unsuccessfulReasons, targetFieldHolder, targetFieldName); } - private boolean isValidField(Invoke invoke, Field field, List unsuccessfulReasons) { + private boolean isValidField(Invoke invoke, Field field, List unsuccessfulReasons, String methodFormat) { if (field == null) { - unsuccessfulReasons.add("The argument of Unsafe.objectFieldOffset() is a null constant."); + unsuccessfulReasons.add("The argument of " + methodFormat + " is a null constant."); return false; } @@ -416,11 +440,11 @@ private boolean isValidField(Invoke invoke, Field field, List unsuccessf if (JavaVersionUtil.JAVA_SPEC >= 17 && isInvokeTo(invoke, sunMiscUnsafeObjectFieldOffsetMethod)) { Class declaringClass = field.getDeclaringClass(); if (RecordSupport.singleton().isRecord(declaringClass)) { - unsuccessfulReasons.add("The argument to sun.misc.Unsafe.objectFieldOffset(Field) is a field of a record."); + unsuccessfulReasons.add("The argument to " + methodFormat + " is a field of a record."); valid = false; } if (SubstrateUtil.isHiddenClass(declaringClass)) { - unsuccessfulReasons.add("The argument to sun.misc.Unsafe.objectFieldOffset(Field) is a field of a hidden class."); + unsuccessfulReasons.add("The argument to " + methodFormat + " is a field of a hidden class."); valid = false; } } @@ -462,41 +486,34 @@ private void processUnsafeObjectFieldOffsetClassStringInvoke(ResolvedJavaType ty } else { unsuccessfulReasons.add("The name argument of Unsafe.objectFieldOffset(Class, String) is not a constant String."); } - processUnsafeObjectFieldOffsetInvoke(type, unsafeObjectFieldOffsetInvoke, unsuccessfulReasons, targetFieldHolder, targetFieldName); + processUnsafeFieldComputation(type, unsafeObjectFieldOffsetInvoke, FieldOffset, unsuccessfulReasons, targetFieldHolder, targetFieldName); } - private void processUnsafeObjectFieldOffsetInvoke( - ResolvedJavaType type, - Invoke unsafeObjectFieldOffsetInvoke, - List unsuccessfulReasons, - Class targetFieldHolder, - String targetFieldName) { - + private void processUnsafeFieldComputation(ResolvedJavaType type, Invoke invoke, Kind kind, List unsuccessfulReasons, Class targetFieldHolder, String targetFieldName) { + assert kind == FieldOffset || kind == StaticFieldBase; /* * If the value returned by the call to Unsafe.objectFieldOffset() is stored into a field * then that must be the offset field. */ - SearchResult result = extractValueStoreField(unsafeObjectFieldOffsetInvoke.asNode(), FieldOffset, unsuccessfulReasons); + SearchResult result = extractValueStoreField(invoke.asNode(), kind, unsuccessfulReasons); /* No field, but the value doesn't have illegal usages, ignore. */ if (result.valueStoreField == null && !result.illegalUseFound) { return; } - ResolvedJavaField offsetField = result.valueStoreField; + ResolvedJavaField valueStoreField = result.valueStoreField; /* * If the target field holder and name, and the offset field were found try to register a * substitution. */ - if (targetFieldHolder != null && targetFieldName != null && offsetField != null) { - Class finalTargetFieldHolder = targetFieldHolder; - String finalTargetFieldName = targetFieldName; - Supplier supplier = () -> new ComputedValueField(offsetField, null, FieldOffset, finalTargetFieldHolder, finalTargetFieldName, false); - if (tryAutomaticRecomputation(offsetField, FieldOffset, supplier)) { - reportSuccessfulAutomaticRecomputation(FieldOffset, offsetField, targetFieldHolder.getName() + "." + targetFieldName); + if (targetFieldHolder != null && targetFieldName != null && valueStoreField != null) { + Supplier supplier = () -> new ComputedValueField(valueStoreField, null, kind, targetFieldHolder, targetFieldName, false); + if (tryAutomaticRecomputation(valueStoreField, kind, supplier)) { + reportSuccessfulAutomaticRecomputation(kind, valueStoreField, targetFieldHolder.getName() + "." + targetFieldName); } } else { - reportUnsuccessfulAutomaticRecomputation(type, offsetField, unsafeObjectFieldOffsetInvoke, FieldOffset, unsuccessfulReasons); + reportUnsuccessfulAutomaticRecomputation(type, valueStoreField, invoke, kind, unsuccessfulReasons); } } @@ -776,7 +793,7 @@ static SearchResult didNotFindIllegalUse() { * reason is recorded in the unsuccessfulReasons parameter. */ private static SearchResult extractValueStoreField(ValueNode valueNode, Kind substitutionKind, List unsuccessfulReasons) { - ResolvedJavaField offsetField = null; + ResolvedJavaField valueStoreField = null; boolean illegalUseFound = false; /* @@ -784,13 +801,13 @@ private static SearchResult extractValueStoreField(ValueNode valueNode, Kind sub * continues until all usages are exhausted or an illegal use is found. */ outer: for (Node valueNodeUsage : valueNode.usages()) { - if (valueNodeUsage instanceof StoreFieldNode && offsetField == null) { - offsetField = ((StoreFieldNode) valueNodeUsage).field(); - } else if (valueNodeUsage instanceof SignExtendNode && offsetField == null) { + if (valueNodeUsage instanceof StoreFieldNode && valueStoreField == null) { + valueStoreField = ((StoreFieldNode) valueNodeUsage).field(); + } else if (valueNodeUsage instanceof SignExtendNode && valueStoreField == null) { SignExtendNode signExtendNode = (SignExtendNode) valueNodeUsage; for (Node signExtendNodeUsage : signExtendNode.usages()) { - if (signExtendNodeUsage instanceof StoreFieldNode && offsetField == null) { - offsetField = ((StoreFieldNode) signExtendNodeUsage).field(); + if (signExtendNodeUsage instanceof StoreFieldNode && valueStoreField == null) { + valueStoreField = ((StoreFieldNode) signExtendNodeUsage).field(); } else if (isAllowedUnsafeValueSink(signExtendNodeUsage)) { continue; } else { @@ -806,13 +823,13 @@ private static SearchResult extractValueStoreField(ValueNode valueNode, Kind sub } } - if (offsetField != null && !illegalUseFound) { - if (offsetField.isStatic() && offsetField.isFinal()) { + if (valueStoreField != null && !illegalUseFound) { + if (valueStoreField.isStatic() && valueStoreField.isFinal()) { /* Success! We found the static final field where this value is stored. */ - return SearchResult.foundField(offsetField); + return SearchResult.foundField(valueStoreField); } else { - String message = "The field " + offsetField.format("%H.%n") + ", where the value produced by the " + kindAsString(substitutionKind) + - " computation is stored, is not" + (!offsetField.isStatic() ? " static" : "") + (!offsetField.isFinal() ? " final" : "") + "."; + String message = "The field " + valueStoreField.format("%H.%n") + ", where the value produced by the " + kindAsString(substitutionKind) + + " computation is stored, is not" + (!valueStoreField.isStatic() ? " static" : "") + (!valueStoreField.isFinal() ? " final" : "") + "."; unsuccessfulReasons.add(message); /* Value is stored to a non static final field. */ return SearchResult.foundIllegalUse(); @@ -876,6 +893,8 @@ private static boolean isAllowedUnsafeValueSink(Node valueNodeUsage) { /** * Try to register the automatic substitution for a field. Bail if the field was deleted or * another substitution is detected. + * + * @param field stores the value of the recomputation, i.e., an offset, array idx scale or shift */ private boolean tryAutomaticRecomputation(ResolvedJavaField field, Kind kind, Supplier substitutionSupplier) { if (annotationSubstitutions.isDeleted(field)) { @@ -909,15 +928,15 @@ private boolean tryAutomaticRecomputation(ResolvedJavaField field, Kind kind, Su return false; } else if (computedSubstitutionField.getRecomputeValueKind().equals(Kind.None)) { /* - * This is essentially and @Alias field. An @Alias for a field with an + * This is essentially an @Alias field. An @Alias for a field with an * automatic recomputed value is allowed but the alias needs to be - * overwritten otherwise would read the value from the original field. To do - * this a new recomputed value field is registered in the automatic + * overwritten otherwise the value from the original field would be read. To + * do this a new recomputed value field is registered in the automatic * substitution processor, which follows the annotation substitution * processor in the substitutions chain. Thus, every time the substitutions * chain is queried for the original field, e.g., in * AnalysisUniverse.lookupAllowUnresolved(JavaField), the alias field is - * forwarded to the the automatic substitution. + * forwarded to the automatic substitution. */ addSubstitutionField(computedSubstitutionField, computedValueField); reportOvewrittenSubstitution(substitutionField, kind, computedSubstitutionField.getAnnotated(), computedSubstitutionField.getRecomputeValueKind()); @@ -1060,6 +1079,8 @@ private static String kindAsString(Kind substitutionKind) { switch (substitutionKind) { case FieldOffset: return "field offset"; + case StaticFieldBase: + return "static field base"; case ArrayBaseOffset: return "array base offset"; case ArrayIndexScale: