From 1115ad4ad316a6e38ec8cd6eac9110dd32fbdab2 Mon Sep 17 00:00:00 2001 From: David Kozak Date: Wed, 10 May 2023 18:47:31 +0200 Subject: [PATCH] Track primitive values in the points-to analysis --- .../AnalysisObjectScanningObserver.java | 14 ++ .../com/oracle/graal/pointsto/BigBang.java | 2 + .../graal/pointsto/PointsToAnalysis.java | 113 +++++++++++---- .../graal/pointsto/api/PointstoOptions.java | 3 + .../flow/AnyPrimitiveSourceTypeFlow.java | 41 ++++++ .../flow/ConstantPrimitiveSourceTypeFlow.java | 47 ++++++ .../pointsto/flow/FieldFilterTypeFlow.java | 11 +- .../graal/pointsto/flow/FieldTypeFlow.java | 8 +- .../graal/pointsto/flow/MethodFlowsGraph.java | 4 +- .../pointsto/flow/MethodFlowsGraphClone.java | 4 +- .../graal/pointsto/flow/MethodTypeFlow.java | 4 + .../pointsto/flow/MethodTypeFlowBuilder.java | 137 ++++++++++-------- .../pointsto/flow/OffsetLoadTypeFlow.java | 22 ++- .../graal/pointsto/flow/PrimitiveFlow.java | 31 ++++ .../oracle/graal/pointsto/flow/TypeFlow.java | 60 +++++++- .../flow/builder/TypeFlowBuilder.java | 10 ++ .../BytecodeSensitiveAnalysisPolicy.java | 2 +- .../flow/context/object/AnalysisObject.java | 3 +- .../graal/pointsto/heap/ImageHeapScanner.java | 2 + .../graal/pointsto/meta/AnalysisField.java | 8 +- .../graal/pointsto/meta/AnalysisType.java | 17 ++- .../pointsto/meta/PointsToAnalysisField.java | 32 +++- .../pointsto/meta/PointsToAnalysisMethod.java | 8 +- .../pointsto/meta/PointsToAnalysisType.java | 5 +- .../pointsto/results/StrengthenGraphs.java | 33 ++++- .../typestate/AnyPrimitiveTypeState.java | 116 +++++++++++++++ .../typestate/PrimitiveConstantTypeState.java | 84 +++++++++++ .../graal/pointsto/typestate/TypeState.java | 20 +++ .../pointsto/typestate/TypeStateUtils.java | 3 + .../ReachabilityAnalysisEngine.java | 4 + .../oracle/svm/core/BuildPhaseProvider.java | 16 ++ .../com/oracle/svm/core/hub/DynamicHub.java | 13 ++ .../svm/hosted/NativeImageGenerator.java | 1 + .../analysis/CustomTypeFieldHandler.java | 9 +- .../PointsToCustomTypeFieldHandler.java | 4 +- .../hosted/dashboard/PointsToJsonObject.java | 4 + 36 files changed, 769 insertions(+), 126 deletions(-) create mode 100644 substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/AnyPrimitiveSourceTypeFlow.java create mode 100644 substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/ConstantPrimitiveSourceTypeFlow.java create mode 100644 substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/PrimitiveFlow.java create mode 100644 substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/AnyPrimitiveTypeState.java create mode 100644 substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/PrimitiveConstantTypeState.java diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/AnalysisObjectScanningObserver.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/AnalysisObjectScanningObserver.java index 309a05eabd4d..fe32c5adb4a3 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/AnalysisObjectScanningObserver.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/AnalysisObjectScanningObserver.java @@ -47,6 +47,10 @@ public boolean forRelocatedPointerFieldValue(JavaConstant receiver, AnalysisFiel if (!field.isWritten()) { return field.registerAsWritten(reason); } + if (fieldValue.isNonNull()) { + FieldTypeFlow fieldTypeFlow = getFieldTypeFlow(field, receiver); + return fieldTypeFlow.addState(getAnalysis(), TypeState.anyPrimitiveState()); + } return false; } @@ -71,6 +75,16 @@ public boolean forNonNullFieldValue(JavaConstant receiver, AnalysisField field, return fieldTypeFlow.addState(analysis, bb.analysisPolicy().constantTypeState(analysis, fieldValue, fieldType)); } + @Override + public boolean forPrimitiveFieldValue(JavaConstant receiver, AnalysisField field, JavaConstant fieldValue, ScanReason reason) { + PointsToAnalysis analysis = getAnalysis(); + + /* Add the constant value object to the field's type flow. */ + FieldTypeFlow fieldTypeFlow = getFieldTypeFlow(field, receiver); + /* Add the new constant to the field's flow state. */ + return fieldTypeFlow.addState(analysis, TypeState.forPrimitiveConstant(fieldValue.asLong())); + } + /** * Get the field type flow give a receiver. */ diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/BigBang.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/BigBang.java index 2dfb06b1aa65..af914b935320 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/BigBang.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/BigBang.java @@ -94,6 +94,8 @@ default HostedProviders getProviders(AnalysisMethod method) { void runAnalysis(DebugContext debug, Function duringAnalysisAction) throws InterruptedException; + boolean trackPrimitiveValues(); + /** You can blacklist certain callees here. */ @SuppressWarnings("unused") default boolean isCallAllowed(PointsToAnalysis bb, AnalysisMethod caller, AnalysisMethod target, BytecodePosition srcPosition) { diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/PointsToAnalysis.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/PointsToAnalysis.java index 3a7f70efd98a..86ee885065c0 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/PointsToAnalysis.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/PointsToAnalysis.java @@ -46,6 +46,7 @@ import com.oracle.graal.pointsto.api.PointstoOptions; import com.oracle.graal.pointsto.constraints.UnsupportedFeatures; import com.oracle.graal.pointsto.flow.AllSynchronizedTypeFlow; +import com.oracle.graal.pointsto.flow.AnyPrimitiveSourceTypeFlow; import com.oracle.graal.pointsto.flow.FieldTypeFlow; import com.oracle.graal.pointsto.flow.FormalParamTypeFlow; import com.oracle.graal.pointsto.flow.InvokeTypeFlow; @@ -61,8 +62,10 @@ import com.oracle.graal.pointsto.meta.AnalysisMethod; import com.oracle.graal.pointsto.meta.AnalysisType; import com.oracle.graal.pointsto.meta.AnalysisUniverse; +import com.oracle.graal.pointsto.meta.PointsToAnalysisField; import com.oracle.graal.pointsto.meta.PointsToAnalysisMethod; import com.oracle.graal.pointsto.reports.StatisticsPrinter; +import com.oracle.graal.pointsto.typestate.AnyPrimitiveTypeState; import com.oracle.graal.pointsto.typestate.PointsToStats; import com.oracle.graal.pointsto.typestate.TypeState; import com.oracle.graal.pointsto.util.AnalysisError; @@ -89,6 +92,20 @@ public abstract class PointsToAnalysis extends AbstractAnalysisEngine { /** The type of {@link java.lang.Object}. */ private final AnalysisType objectType; + /** + * Enables propagating primitive values interproceduraly using the typeflow graph. Only simple + * constants are propagated. Arithmetic operations and merges of different constants result in a + * special {@link AnyPrimitiveTypeState } state that leads to immediate saturation. + *

+ * This optimization also handles word types, which are essentially primitive values. + *

+ * Unsafe loads and stores are NOT modeled, because it would lead to merging of primitive and + * objects states (all unsafe fields are merged into a single flow). Instead, all unsafe + * accessed primitive fields are assigned the PrimitiveTypeState state and any unsafe read is + * immediately represented as {@link com.oracle.graal.pointsto.flow.AnyPrimitiveSourceTypeFlow}. + */ + private final boolean trackPrimitiveValues; + private AnyPrimitiveSourceTypeFlow anyPrimitiveSourceTypeFlow; private TypeFlow allSynchronizedTypeFlow; protected final boolean trackTypeFlowInputs; @@ -107,6 +124,8 @@ public PointsToAnalysis(OptionValues options, AnalysisUniverse universe, HostVM ConstantReflectionProvider constantReflectionProvider, WordTypes wordTypes, UnsupportedFeatures unsupportedFeatures, DebugContext debugContext, TimerCollection timerCollection) { super(options, universe, hostVM, metaAccess, snippetReflectionProvider, constantReflectionProvider, wordTypes, unsupportedFeatures, debugContext, timerCollection); this.typeFlowTimer = timerCollection.createTimer("(typeflow)"); + this.trackPrimitiveValues = PointstoOptions.TrackPrimitiveValues.getValue(options); + this.anyPrimitiveSourceTypeFlow = new AnyPrimitiveSourceTypeFlow(null, null); this.objectType = metaAccess.lookupJavaType(Object.class); /* @@ -199,15 +218,26 @@ public void forceUnsafeUpdate(AnalysisField field) { } @Override - public void registerAsJNIAccessed(AnalysisField field, boolean writable) { + public void registerAsJNIAccessed(AnalysisField f, boolean writable) { + PointsToAnalysisField field = (PointsToAnalysisField) f; // Same as addRootField() and addRootStaticField(): // create type flows for any subtype of the field's declared type TypeFlow declaredTypeFlow = field.getType().getTypeFlow(this, true); - if (field.isStatic()) { - declaredTypeFlow.addUse(this, field.getStaticFieldFlow()); - } else { - FieldTypeFlow instanceFieldFlow = field.getDeclaringClass().getContextInsensitiveAnalysisObject().getInstanceFieldFlow(this, field, writable); - declaredTypeFlow.addUse(this, instanceFieldFlow); + if (isSupportedJavaKind(field.getStorageKind())) { + if (field.isStatic()) { + if (field.getStorageKind().isObject()) { + declaredTypeFlow.addUse(this, field.getStaticFieldFlow()); + } else { + field.saturatePrimitiveField(); + } + } else { + FieldTypeFlow instanceFieldFlow = field.getDeclaringClass().getContextInsensitiveAnalysisObject().getInstanceFieldFlow(this, field, writable); + if (field.getStorageKind().isObject()) { + declaredTypeFlow.addUse(this, instanceFieldFlow); + } else { + field.saturatePrimitiveField(); + } + } } } @@ -224,6 +254,7 @@ public boolean trackConcreteAnalysisObjects(@SuppressWarnings("unused") Analysis public void cleanupAfterAnalysis() { super.cleanupAfterAnalysis(); allSynchronizedTypeFlow = null; + anyPrimitiveSourceTypeFlow = null; unsafeLoads = null; unsafeStores = null; @@ -263,6 +294,10 @@ public TypeFlow getAllSynchronizedTypeFlow() { return allSynchronizedTypeFlow; } + public AnyPrimitiveSourceTypeFlow getAnyPrimitiveSourceTypeFlow() { + return anyPrimitiveSourceTypeFlow; + } + @Override public Iterable getAllSynchronizedTypes() { /* @@ -306,10 +341,7 @@ public AnalysisMethod addRootMethod(AnalysisMethod aMethod, boolean invokeSpecia for (int idx = 0; idx < paramCount; idx++) { AnalysisType declaredParamType = (AnalysisType) signature.getParameterType(idx, declaringClass); FormalParamTypeFlow parameter = flowInfo.getParameter(idx); - if (declaredParamType.getJavaKind() == JavaKind.Object && parameter != null) { - TypeFlow initialParameterFlow = declaredParamType.getTypeFlow(this, true); - initialParameterFlow.addUse(this, parameter); - } + processParam(declaredParamType, parameter); } }); }; @@ -369,10 +401,7 @@ public AnalysisMethod addRootMethod(AnalysisMethod aMethod, boolean invokeSpecia */ AnalysisType declaredParamType = (AnalysisType) signature.getParameterType(idx - 1, declaringClass); TypeFlow actualParameterFlow = invoke.getActualParameter(idx); - if (declaredParamType.getJavaKind() == JavaKind.Object && actualParameterFlow != null) { - TypeFlow initialParameterFlow = declaredParamType.getTypeFlow(this, true); - initialParameterFlow.addUse(this, actualParameterFlow); - } + processParam(declaredParamType, actualParameterFlow); } }); } @@ -380,6 +409,17 @@ public AnalysisMethod addRootMethod(AnalysisMethod aMethod, boolean invokeSpecia } + private void processParam(AnalysisType declaredParamType, TypeFlow actualParameterFlow) { + if (actualParameterFlow != null && isSupportedJavaKind(declaredParamType.getStorageKind())) { + if (declaredParamType.getStorageKind() == JavaKind.Object) { + TypeFlow initialParameterFlow = declaredParamType.getTypeFlow(this, true); + initialParameterFlow.addUse(this, actualParameterFlow); + } else { + actualParameterFlow.addState(this, TypeState.anyPrimitiveState()); + } + } + } + public static PointsToAnalysisMethod assertPointsToAnalysisMethod(AnalysisMethod aMethod) { assert aMethod instanceof PointsToAnalysisMethod : "Only points-to analysis methods are supported"; return ((PointsToAnalysisMethod) aMethod); @@ -400,12 +440,7 @@ public AnalysisType addRootClass(AnalysisType type, boolean addFields, boolean a if (addFields) { field.registerAsAccessed("field of root class"); } - /* - * For system classes any instantiated (sub)type of the declared field type can be - * written to the field flow. - */ - TypeFlow fieldDeclaredTypeFlow = field.getType().getTypeFlow(this, true); - fieldDeclaredTypeFlow.addUse(this, type.getContextInsensitiveAnalysisObject().getInstanceFieldFlow(this, field, true)); + processRootField(type, field); } if (type.getSuperclass() != null) { addRootClass(type.getSuperclass(), addFields, addArrayClass); @@ -424,16 +459,28 @@ public AnalysisType addRootField(Class clazz, String fieldName) { AnalysisField field = (AnalysisField) javaField; if (field.getName().equals(fieldName)) { field.registerAsAccessed("root field"); + processRootField(type, field); + return field.getType(); + } + } + throw shouldNotReachHere("field not found: " + fieldName); + } + + private void processRootField(AnalysisType type, AnalysisField field) { + JavaKind storageKind = field.getStorageKind(); + if (isSupportedJavaKind(storageKind)) { + var fieldFlow = type.getContextInsensitiveAnalysisObject().getInstanceFieldFlow(this, field, true); + if (storageKind.isObject()) { /* * For system classes any instantiated (sub)type of the declared field type can be * written to the field flow. */ TypeFlow fieldDeclaredTypeFlow = field.getType().getTypeFlow(this, true); - fieldDeclaredTypeFlow.addUse(this, type.getContextInsensitiveAnalysisObject().getInstanceFieldFlow(this, field, true)); - return field.getType(); + fieldDeclaredTypeFlow.addUse(this, fieldFlow); + } else { + fieldFlow.addState(this, TypeState.anyPrimitiveState()); } } - throw shouldNotReachHere("field not found: " + fieldName); } @SuppressWarnings({"try", "unused"}) @@ -444,8 +491,15 @@ public AnalysisType addRootStaticField(Class clazz, String fieldName) { reflectField = clazz.getField(fieldName); AnalysisField field = metaAccess.lookupJavaField(reflectField); field.registerAsAccessed("static root field"); - TypeFlow fieldFlow = field.getType().getTypeFlow(this, true); - fieldFlow.addUse(this, field.getStaticFieldFlow()); + JavaKind storageKind = field.getStorageKind(); + if (isSupportedJavaKind(storageKind)) { + if (storageKind.isObject()) { + TypeFlow fieldFlow = field.getType().getTypeFlow(this, true); + fieldFlow.addUse(this, field.getStaticFieldFlow()); + } else { + field.getStaticFieldFlow().addState(this, TypeState.anyPrimitiveState()); + } + } return field.getType(); } catch (NoSuchFieldException e) { @@ -457,6 +511,15 @@ public AnalysisType addRootStaticField(Class clazz, String fieldName) { public void checkUserLimitations() { } + public boolean isSupportedJavaKind(JavaKind javaKind) { + return javaKind == JavaKind.Object || (trackPrimitiveValues && javaKind.isNumericInteger()); + } + + @Override + public boolean trackPrimitiveValues() { + return trackPrimitiveValues; + } + public interface TypeFlowRunnable extends DebugContextRunnable { TypeFlow getTypeFlow(); } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/api/PointstoOptions.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/api/PointstoOptions.java index 4c74fc6abb18..de975a2ca381 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/api/PointstoOptions.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/api/PointstoOptions.java @@ -33,6 +33,9 @@ public class PointstoOptions { + @Option(help = "Track primitive values using the infrastructure of points-to analysis.")// + public static final OptionKey TrackPrimitiveValues = new OptionKey<>(false); + @Option(help = "Use experimental Reachability Analysis instead of points-to.")// public static final OptionKey UseExperimentalReachabilityAnalysis = new OptionKey<>(false); diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/AnyPrimitiveSourceTypeFlow.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/AnyPrimitiveSourceTypeFlow.java new file mode 100644 index 000000000000..fb4a6ca856f4 --- /dev/null +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/AnyPrimitiveSourceTypeFlow.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.graal.pointsto.flow; + +import com.oracle.graal.pointsto.meta.AnalysisType; +import com.oracle.graal.pointsto.typestate.TypeState; + +import jdk.vm.ci.code.BytecodePosition; + +/** + * Produces AnyPrimitive state that leads to immediate saturation of all uses. Used to represent any + * operation that is not modeled by the analysis. + */ +public final class AnyPrimitiveSourceTypeFlow extends TypeFlow implements PrimitiveFlow { + + public AnyPrimitiveSourceTypeFlow(BytecodePosition source, AnalysisType type) { + super(source, type, TypeState.anyPrimitiveState()); + } +} diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/ConstantPrimitiveSourceTypeFlow.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/ConstantPrimitiveSourceTypeFlow.java new file mode 100644 index 000000000000..ddf4024a9f0d --- /dev/null +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/ConstantPrimitiveSourceTypeFlow.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.graal.pointsto.flow; + +import com.oracle.graal.pointsto.PointsToAnalysis; +import com.oracle.graal.pointsto.meta.AnalysisType; +import com.oracle.graal.pointsto.typestate.TypeState; + +import jdk.vm.ci.code.BytecodePosition; + +public class ConstantPrimitiveSourceTypeFlow extends TypeFlow implements PrimitiveFlow { + + public ConstantPrimitiveSourceTypeFlow(BytecodePosition source, AnalysisType type, long value) { + super(source, type, TypeState.forPrimitiveConstant(value)); + } + + public ConstantPrimitiveSourceTypeFlow(ConstantPrimitiveSourceTypeFlow original, MethodFlowsGraph methodFlows) { + super(original, methodFlows, original.getState()); + } + + @Override + public TypeFlow copy(PointsToAnalysis bb, MethodFlowsGraph methodFlows) { + return new ConstantPrimitiveSourceTypeFlow(this, methodFlows); + } +} diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/FieldFilterTypeFlow.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/FieldFilterTypeFlow.java index e5258c7bc56e..8ea933c85ea6 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/FieldFilterTypeFlow.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/FieldFilterTypeFlow.java @@ -46,7 +46,16 @@ public FieldFilterTypeFlow(AnalysisField field) { @Override public TypeState filter(PointsToAnalysis bb, TypeState update) { - if (declaredType.equals(bb.getObjectType())) { + if (isPrimitiveFlow) { + if (!update.isPrimitive()) { + /* + * Sources for these inconsistent updates are unsafe flows, but unsafe accessed + * primitive fields are saturated anyway, so we can return any primitive state. + */ + return TypeState.anyPrimitiveState(); + } + return update; + } else if (declaredType.equals(bb.getObjectType())) { /* No need to filter. */ return update; } else { diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/FieldTypeFlow.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/FieldTypeFlow.java index 1195f64c72b6..76bfb6223a48 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/FieldTypeFlow.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/FieldTypeFlow.java @@ -34,15 +34,15 @@ import com.oracle.graal.pointsto.meta.AnalysisType; import com.oracle.graal.pointsto.typestate.TypeState; -import jdk.vm.ci.meta.JavaKind; - public class FieldTypeFlow extends TypeFlow { private static final AtomicReferenceFieldUpdater FILTER_FLOW_UPDATER = AtomicReferenceFieldUpdater.newUpdater(FieldTypeFlow.class, FieldFilterTypeFlow.class, "filterFlow"); private static TypeState initialFieldState(AnalysisField field) { - if (field.getJavaKind() == JavaKind.Object && field.canBeNull()) { + if (field.getStorageKind().isPrimitive()) { + return TypeState.forPrimitiveConstant(0); + } else if (field.canBeNull()) { /* * All object type instance fields of a new object can be null. Instance fields are null * in the time between the new-instance and the first write to a field. This is even @@ -88,7 +88,7 @@ public boolean canSaturate() { protected void onInputSaturated(PointsToAnalysis bb, TypeFlow input) { /* * When a field store is saturated conservatively assume that the field state can contain - * any subtype of its declared type. + * any subtype of its declared type or any primitive value for primitive fields. */ getDeclaredType().getTypeFlow(bb, true).addUse(bb, this); } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/MethodFlowsGraph.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/MethodFlowsGraph.java index 3fbbf47bc076..81ee33673555 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/MethodFlowsGraph.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/MethodFlowsGraph.java @@ -119,9 +119,9 @@ public static boolean crossMethodUse(TypeFlow flow, TypeFlow use) { public static boolean nonMethodFlow(TypeFlow flow) { /* - * All-instantiated flow doesn't belong to any method, but it can be reachable from a use. + * These flows do not belong to any method, but can be reachable from a use. */ - return flow instanceof AllInstantiatedTypeFlow || flow instanceof AllSynchronizedTypeFlow; + return flow instanceof AllInstantiatedTypeFlow || flow instanceof AllSynchronizedTypeFlow || flow instanceof AnyPrimitiveSourceTypeFlow; } /** diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/MethodFlowsGraphClone.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/MethodFlowsGraphClone.java index a1cdc3db97e9..e788b1f8a166 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/MethodFlowsGraphClone.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/MethodFlowsGraphClone.java @@ -127,8 +127,8 @@ public > T lookupCloneOf(PointsToAnalysis bb, T original) assert !(original instanceof FieldTypeFlow) : "Trying to clone a field type flow"; assert !(original instanceof ArrayElementsTypeFlow) : "Trying to clone an mixed elements type flow"; - if (original instanceof AllInstantiatedTypeFlow || original instanceof AllSynchronizedTypeFlow) { - /* All instantiated is not cloneable. */ + if (original instanceof AllInstantiatedTypeFlow || original instanceof AllSynchronizedTypeFlow || original instanceof AnyPrimitiveSourceTypeFlow) { + /* These flows are not cloneable. */ return original; } if (original instanceof ProxyTypeFlow) { diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/MethodTypeFlow.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/MethodTypeFlow.java index 99e18d8c206b..be8af5a17052 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/MethodTypeFlow.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/MethodTypeFlow.java @@ -235,6 +235,10 @@ public TypeFlow getParameter(int idx) { return flowsGraph == null ? null : flowsGraph.getParameter(idx); } + public TypeFlow getReturn() { + return flowsGraph == null ? null : flowsGraph.getReturnFlow(); + } + /** Check if the type flow is saturated, i.e., any of its clones is saturated. */ public boolean isSaturated(@SuppressWarnings("unused") PointsToAnalysis bb, TypeFlow originalTypeFlow) { return originalTypeFlow.isSaturated(); diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/MethodTypeFlowBuilder.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/MethodTypeFlowBuilder.java index 50f129be8ca6..788ba14365b8 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/MethodTypeFlowBuilder.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/MethodTypeFlowBuilder.java @@ -68,7 +68,9 @@ import jdk.graal.compiler.core.common.spi.ForeignCallDescriptor; import jdk.graal.compiler.core.common.spi.ForeignCallsProvider; import jdk.graal.compiler.core.common.type.AbstractObjectStamp; +import jdk.graal.compiler.core.common.type.IntegerStamp; import jdk.graal.compiler.core.common.type.ObjectStamp; +import jdk.graal.compiler.core.common.type.Stamp; import jdk.graal.compiler.core.common.type.TypeReference; import jdk.graal.compiler.debug.DebugContext; import jdk.graal.compiler.graph.Node; @@ -165,10 +167,13 @@ public class MethodTypeFlowBuilder { protected final TypeFlowGraphBuilder typeFlowGraphBuilder; protected List> postInitFlows = List.of(); + private final TypeFlowBuilder anyPrimitiveSourceTypeFlowBuilder; + public MethodTypeFlowBuilder(PointsToAnalysis bb, PointsToAnalysisMethod method, MethodFlowsGraph flowsGraph, GraphKind graphKind) { this.bb = bb; this.method = method; this.graphKind = graphKind; + this.anyPrimitiveSourceTypeFlowBuilder = bb.trackPrimitiveValues() ? TypeFlowBuilder.create(bb, null, AnyPrimitiveSourceTypeFlow.class, bb::getAnyPrimitiveSourceTypeFlow) : null; if (flowsGraph == null) { this.flowsGraph = new MethodFlowsGraph(method, graphKind); newFlowsGraph = true; @@ -407,7 +412,7 @@ private boolean handleNodeIntrinsic() { if (AnnotationAccess.isAnnotationPresent(method, NodeIntrinsic.class)) { graph.getDebug().log("apply MethodTypeFlow on node intrinsic %s", method); AnalysisType returnType = (AnalysisType) method.getSignature().getReturnType(method.getDeclaringClass()); - if (returnType.getJavaKind() == JavaKind.Object) { + if (bb.isSupportedJavaKind(returnType.getJavaKind())) { /* * This is a method used in a snippet, so most likely the return value does not * matter at all. However, some methods return an object, and the snippet continues @@ -451,7 +456,7 @@ private void insertPlaceholderParamAndReturnFlows() { BytecodePosition position = AbstractAnalysisEngine.syntheticSourcePosition(null, method); for (int index = 0; index < paramTypes.length; index++) { if (flowsGraph.getParameter(index) == null) { - if (paramTypes[index].getJavaKind().isObject()) { + if (bb.isSupportedJavaKind(paramTypes[index].getJavaKind())) { AnalysisType paramType = (AnalysisType) paramTypes[index]; FormalParamTypeFlow parameter; if (!isStatic && index == 0) { @@ -467,7 +472,7 @@ private void insertPlaceholderParamAndReturnFlows() { if (flowsGraph.getReturnFlow() == null) { AnalysisType returnType = (AnalysisType) method.getSignature().getReturnType(method.getDeclaringClass()); - if (returnType.getJavaKind().isObject()) { + if (bb.isSupportedJavaKind(returnType.getJavaKind())) { flowsGraph.setReturnFlow(new FormalReturnTypeFlow(position, returnType)); } } @@ -481,7 +486,7 @@ private void createTypeFlow() { for (Node n : graph.getNodes()) { if (n instanceof ParameterNode) { ParameterNode node = (ParameterNode) n; - if (node.getStackKind() == JavaKind.Object) { + if (bb.isSupportedJavaKind(node.getStackKind())) { TypeFlowBuilder paramBuilder = TypeFlowBuilder.create(bb, node, FormalParamTypeFlow.class, () -> { boolean isStatic = Modifier.isStatic(method.getModifiers()); int index = node.index(); @@ -637,39 +642,54 @@ public boolean contains(ValueNode node) { } public TypeFlowBuilder lookup(ValueNode n) { - assert n.stamp(NodeView.DEFAULT) instanceof ObjectStamp : n; - ValueNode node = typeFlowUnproxify(n); TypeFlowBuilder result = flows.get(node); if (result == null) { /* * There is no type flow set, yet. Therefore we have no info for the node. */ - ObjectStamp stamp = (ObjectStamp) n.stamp(NodeView.DEFAULT); - if (stamp.isEmpty()) { - throw AnalysisError.shouldNotReachHere("Stamp for node " + n + " is empty."); - } - AnalysisType stampType = (AnalysisType) StampTool.typeOrNull(stamp, bb.getMetaAccess()); - if (stamp.isExactType()) { - /* - * We are lucky: the stamp tells us which type the node has. - */ - result = TypeFlowBuilder.create(bb, node, SourceTypeFlow.class, () -> { - SourceTypeFlow src = new SourceTypeFlow(AbstractAnalysisEngine.sourcePosition(node), stampType, !stamp.nonNull()); - flowsGraph.addMiscEntryFlow(src); - return src; - }); + Stamp s = n.stamp(NodeView.DEFAULT); + if (s instanceof IntegerStamp stamp) { + long lo = stamp.lowerBound(); + long hi = stamp.upperBound(); + var type = (AnalysisType) stamp.javaType(bb.getMetaAccess()); + if (lo == hi) { + result = TypeFlowBuilder.create(bb, node, ConstantPrimitiveSourceTypeFlow.class, () -> { + var flow = new ConstantPrimitiveSourceTypeFlow(AbstractAnalysisEngine.sourcePosition(node), type, lo); + flowsGraph.addMiscEntryFlow(flow); + return flow; + }); + } else { + result = anyPrimitiveSourceTypeFlowBuilder; + } + } else if (s instanceof ObjectStamp stamp) { + if (stamp.isEmpty()) { + throw AnalysisError.shouldNotReachHere("Stamp for node " + n + " is empty."); + } + AnalysisType stampType = (AnalysisType) StampTool.typeOrNull(stamp, bb.getMetaAccess()); + if (stamp.isExactType()) { + /* + * We are lucky: the stamp tells us which type the node has. + */ + result = TypeFlowBuilder.create(bb, node, SourceTypeFlow.class, () -> { + SourceTypeFlow src = new SourceTypeFlow(AbstractAnalysisEngine.sourcePosition(node), stampType, !stamp.nonNull()); + flowsGraph.addMiscEntryFlow(src); + return src; + }); + } else { + /* + * Use a type state which consists of all allocated types (which are + * compatible to the node's type). This is a conservative assumption. + */ + result = TypeFlowBuilder.create(bb, node, TypeFlow.class, () -> { + TypeFlow proxy = bb.analysisPolicy().proxy(AbstractAnalysisEngine.sourcePosition(node), stampType.getTypeFlow(bb, true)); + flowsGraph.addMiscEntryFlow(proxy); + return proxy; + }); + } } else { - /* - * Use a type state which consists of all allocated types (which are compatible - * to the node's type). This is a conservative assumption. - */ - result = TypeFlowBuilder.create(bb, node, TypeFlow.class, () -> { - TypeFlow proxy = bb.analysisPolicy().proxy(AbstractAnalysisEngine.sourcePosition(node), stampType.getTypeFlow(bb, true)); - flowsGraph.addMiscEntryFlow(proxy); - return proxy; - }); + AnalysisError.shouldNotReachHere("Unsupported stamp " + s); } flows.put(node, result); @@ -753,7 +773,7 @@ class NodeIterator extends PostOrderNodeIterator { private TypeFlowBuilder uniqueReturnFlowBuilder(ReturnNode node) { if (returnBuilder == null) { AnalysisType returnType = (AnalysisType) method.getSignature().getReturnType(method.getDeclaringClass()); - if (returnType.getJavaKind() == JavaKind.Object) { + if (bb.isSupportedJavaKind(returnType.getJavaKind())) { returnBuilder = TypeFlowBuilder.create(bb, node, FormalReturnTypeFlow.class, () -> { FormalReturnTypeFlow returnFlow = flowsGraph.getReturnFlow(); if (returnFlow != null) { @@ -819,7 +839,7 @@ protected void node(FixedNode n) { LoopBeginNode merge = end.loopBegin(); int predIdx = merge.phiPredecessorIndex(end); for (PhiNode phi : merge.phis()) { - if (phi.getStackKind() == JavaKind.Object) { + if (bb.isSupportedJavaKind(phi.getStackKind())) { loopPhiFlows.get(phi).addUseDependency(state.lookup(phi.valueAt(predIdx))); } } @@ -827,7 +847,7 @@ protected void node(FixedNode n) { } else if (n instanceof LoopBeginNode) { LoopBeginNode merge = (LoopBeginNode) n; for (PhiNode phi : merge.phis()) { - if (phi.getStackKind() == JavaKind.Object) { + if (bb.isSupportedJavaKind(phi.getStackKind())) { TypeFlowBuilder newFlowBuilder = TypeFlowBuilder.create(bb, merge, MergeTypeFlow.class, () -> { MergeTypeFlow newFlow = new MergeTypeFlow(AbstractAnalysisEngine.sourcePosition(merge)); flowsGraph.addMiscEntryFlow(newFlow); @@ -848,7 +868,7 @@ protected void node(FixedNode n) { AbstractMergeNode merge = end.merge(); int predIdx = merge.phiPredecessorIndex(end); for (PhiNode phi : merge.phis()) { - if (phi.getStackKind() == JavaKind.Object) { + if (bb.isSupportedJavaKind(phi.getStackKind())) { state.add(phi, state.lookup(phi.valueAt(predIdx))); } } @@ -883,7 +903,7 @@ protected void node(FixedNode n) { */ if (!method.getReturnsAllInstantiatedTypes()) { ReturnNode node = (ReturnNode) n; - if (node.result() != null && node.result().getStackKind() == JavaKind.Object) { + if (node.result() != null && bb.isSupportedJavaKind(node.result().getStackKind())) { TypeFlowBuilder returnFlowBuilder = uniqueReturnFlowBuilder(node); returnFlowBuilder.addUseDependency(state.lookup(node.result())); } @@ -915,9 +935,7 @@ protected void node(FixedNode n) { * generate a heap object for each instantiated type. */ instanceType = bb.getObjectType(); - instanceTypeBuilder = TypeFlowBuilder.create(bb, instanceType, AllInstantiatedTypeFlow.class, () -> { - return instanceType.getTypeFlow(bb, false); - }); + instanceTypeBuilder = TypeFlowBuilder.create(bb, instanceType, AllInstantiatedTypeFlow.class, () -> ((AllInstantiatedTypeFlow) instanceType.getTypeFlow(bb, false))); } TypeFlowBuilder dynamicNewInstanceBuilder = TypeFlowBuilder.create(bb, node, DynamicNewInstanceTypeFlow.class, () -> { DynamicNewInstanceTypeFlow newInstanceTypeFlow = new DynamicNewInstanceTypeFlow(AbstractAnalysisEngine.sourcePosition(node), instanceTypeBuilder.get(), instanceType); @@ -968,7 +986,7 @@ protected void node(FixedNode n) { } else if (n instanceof LoadFieldNode node) { // value = object.field AnalysisField field = (AnalysisField) node.field(); assert field.isAccessed() : field; - if (node.getStackKind() == JavaKind.Object) { + if (bb.isSupportedJavaKind(node.getStackKind())) { TypeFlowBuilder loadFieldBuilder; BytecodePosition loadLocation = AbstractAnalysisEngine.sourcePosition(node); if (node.isStatic()) { @@ -1441,7 +1459,7 @@ protected void processMethodInvocation(TypeFlowsOfNodes state, ValueNode invoke, TypeFlowBuilder[] actualParametersBuilders = new TypeFlowBuilder[arguments.size()]; for (int i = 0; i < actualParametersBuilders.length; i++) { ValueNode actualParam = arguments.get(i); - if (actualParam.getStackKind() == JavaKind.Object) { + if (bb.isSupportedJavaKind(actualParam.getStackKind())) { TypeFlowBuilder paramBuilder = state.lookup(actualParam); actualParametersBuilders[i] = paramBuilder; paramBuilder.markAsBuildingAnActualParameter(); @@ -1518,7 +1536,7 @@ protected void processMethodInvocation(TypeFlowsOfNodes state, ValueNode invoke, return invokeFlow; }); - if (!createDeoptInvokeTypeFlow && invoke.asNode().getStackKind() == JavaKind.Object) { + if (!createDeoptInvokeTypeFlow && bb.isSupportedJavaKind(invoke.asNode().getStackKind())) { /* Create the actual return builder. */ AnalysisType returnType = (AnalysisType) targetMethod.getSignature().getReturnType(null); TypeFlowBuilder actualReturnBuilder = TypeFlowBuilder.create(bb, invoke.asNode(), ActualReturnTypeFlow.class, () -> { @@ -1534,24 +1552,25 @@ protected void processMethodInvocation(TypeFlowsOfNodes state, ValueNode invoke, return actualReturn; }); - ObjectStamp stamp = (ObjectStamp) invoke.stamp(NodeView.DEFAULT); - AnalysisType stampType = (AnalysisType) StampTool.typeOrNull(stamp, bb.getMetaAccess()); - if (stamp.nonNull() && !returnType.equals(stampType) && returnType.isAssignableFrom(stampType)) { - /* - * If the invoke stamp has a more precise type than the return type use that to - * filter the returned values. This can happen for example for MacroInvokable nodes - * when more concrete stamp information can be inferred for example from parameter - * types. In that case the Graal graph optimizations may decide to remove a - * checkcast that would normally follow the invoke, so we need to introduce the - * filter to avoid loosing precision. - */ - TypeFlowBuilder filterBuilder = TypeFlowBuilder.create(bb, invoke, FilterTypeFlow.class, () -> { - FilterTypeFlow filterFlow = new FilterTypeFlow(invokeLocation, stampType, stamp.isExactType(), true, true); - flowsGraph.addMiscEntryFlow(filterFlow); - return filterFlow; - }); - filterBuilder.addUseDependency(actualReturnBuilder); - actualReturnBuilder = filterBuilder; + if (invoke.stamp(NodeView.DEFAULT) instanceof ObjectStamp stamp) { + AnalysisType stampType = (AnalysisType) StampTool.typeOrNull(stamp, bb.getMetaAccess()); + if (stamp.nonNull() && !returnType.equals(stampType) && returnType.isAssignableFrom(stampType)) { + /* + * If the invoke stamp has a more precise type than the return type use that to + * filter the returned values. This can happen for example for MacroInvokable + * nodes when more concrete stamp information can be inferred for example from + * parameter types. In that case the Graal graph optimizations may decide to + * remove a checkcast that would normally follow the invoke, so we need to + * introduce the filter to avoid loosing precision. + */ + TypeFlowBuilder filterBuilder = TypeFlowBuilder.create(bb, invoke, FilterTypeFlow.class, () -> { + FilterTypeFlow filterFlow = new FilterTypeFlow(invokeLocation, stampType, stamp.isExactType(), true, true); + flowsGraph.addMiscEntryFlow(filterFlow); + return filterFlow; + }); + filterBuilder.addUseDependency(actualReturnBuilder); + actualReturnBuilder = filterBuilder; + } } typeFlowGraphBuilder.registerSinkBuilder(actualReturnBuilder); @@ -1632,7 +1651,7 @@ protected void processStoreField(StoreFieldNode node, TypeFlowsOfNodes state) { protected void processStoreField(ValueNode node, AnalysisField field, ValueNode object, ValueNode value, TypeFlowsOfNodes state) { assert field.isWritten() : field; - if (value.getStackKind() == JavaKind.Object) { + if (bb.isSupportedJavaKind(value.getStackKind())) { TypeFlowBuilder valueBuilder = state.lookup(value); TypeFlowBuilder storeFieldBuilder; diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/OffsetLoadTypeFlow.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/OffsetLoadTypeFlow.java index d24b2ba63dc8..cb9a2b3917e5 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/OffsetLoadTypeFlow.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/OffsetLoadTypeFlow.java @@ -183,7 +183,21 @@ public void forceUpdate(PointsToAnalysis bb) { * read from any of the static fields marked for unsafe access. */ for (AnalysisField field : bb.getUniverse().getUnsafeAccessedStaticFields()) { - field.getStaticFieldFlow().addUse(bb, this); + /* + * Primitive type states are not propagated through unsafe loads/stores. Instead, + * both primitive fields that are unsafe written and all unsafe loads for primitives + * are pre-saturated. + */ + if (field.getStorageKind().isObject()) { + field.getStaticFieldFlow().addUse(bb, this); + } + } + } + + protected void processField(PointsToAnalysis bb, AnalysisObject object, AnalysisField field) { + if (field.getStorageKind().isObject()) { + TypeFlow fieldFlow = object.getInstanceFieldFlow(bb, objectFlow, source, field, false); + fieldFlow.addUse(bb, this); } } } @@ -225,8 +239,7 @@ public void onObservedUpdate(PointsToAnalysis bb) { } else { for (AnalysisField field : objectType.unsafeAccessedFields()) { assert field != null; - TypeFlow fieldFlow = object.getInstanceFieldFlow(bb, objectFlow, source, field, false); - fieldFlow.addUse(bb, this); + processField(bb, object, field); } } } @@ -281,8 +294,7 @@ public void onObservedUpdate(PointsToAnalysis bb) { assert !objectType.isArray() : objectType; for (AnalysisField field : objectType.unsafeAccessedFields(partitionKind)) { - TypeFlow fieldFlow = object.getInstanceFieldFlow(bb, objectFlow, source, field, false); - fieldFlow.addUse(bb, this); + processField(bb, object, field); } } } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/PrimitiveFlow.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/PrimitiveFlow.java new file mode 100644 index 000000000000..7b1777c5af73 --- /dev/null +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/PrimitiveFlow.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.graal.pointsto.flow; + +/** + * Marker interface to distinguish flows that can contain only primitive values. + */ +public interface PrimitiveFlow { +} diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/TypeFlow.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/TypeFlow.java index e7cf0ce16796..c31c0fdc6a02 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/TypeFlow.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/TypeFlow.java @@ -35,6 +35,7 @@ import com.oracle.graal.pointsto.meta.PointsToAnalysisMethod; import com.oracle.graal.pointsto.results.StrengthenGraphs; import com.oracle.graal.pointsto.typestate.PointsToStats; +import com.oracle.graal.pointsto.typestate.PrimitiveConstantTypeState; import com.oracle.graal.pointsto.typestate.TypeState; import com.oracle.graal.pointsto.util.AnalysisError; import com.oracle.graal.pointsto.util.ConcurrentLightHashSet; @@ -112,6 +113,8 @@ public abstract class TypeFlow { @SuppressWarnings("rawtypes")// private static final AtomicReferenceFieldUpdater STATE_UPDATER = AtomicReferenceFieldUpdater.newUpdater(TypeFlow.class, TypeState.class, "state"); + protected final boolean isPrimitiveFlow; + private TypeFlow(T source, AnalysisType declaredType, TypeState typeState, int slot, boolean isClone, MethodFlowsGraph graphRef) { this.id = nextId.incrementAndGet(); this.source = source; @@ -120,8 +123,14 @@ private TypeFlow(T source, AnalysisType declaredType, TypeState typeState, int s this.isClone = isClone; this.graphRef = graphRef; this.state = typeState; - + if (declaredType != null) { + isPrimitiveFlow = declaredType.isPrimitive() || declaredType.isWordType(); + } else { + /* If the declared type is not set, try to determine using the initial type state. */ + isPrimitiveFlow = typeState.isPrimitive(); + } validateSource(); + assert primitiveFlowCheck(state) : this; } private void validateSource() { @@ -317,6 +326,7 @@ public boolean addState(PointsToAnalysis bb, TypeState add, boolean postFlow) { PointsToStats.registerTypeFlowSuccessfulUpdate(bb, this, add); + assert !bb.trackPrimitiveValues() || primitiveFlowCheck(after) : this + "," + after; if (checkSaturated(bb, after)) { onSaturated(bb); } else if (postFlow) { @@ -325,13 +335,48 @@ public boolean addState(PointsToAnalysis bb, TypeState add, boolean postFlow) { return true; } + + /** + * Primitive flows should only have primitive or empty type states. + */ + private boolean primitiveFlowCheck(TypeState newState) { + return !isPrimitiveFlow || newState.isPrimitive() || newState.isEmpty(); + } + // manage uses public boolean addUse(PointsToAnalysis bb, TypeFlow use) { return addUse(bb, use, true); } + /** + * Verifies that primitive flows are only connected with other primitive flows. + */ + private boolean checkDefUseCompatibility(TypeFlow use) { + if (this.declaredType == null || use.declaredType == null) { + /* Some flows, e.g. MergeTypeFlow, do not have a declared type. */ + return true; + } + if (this.isPrimitiveFlow != use.isPrimitiveFlow) { + if (this instanceof OffsetStoreTypeFlow.AbstractUnsafeStoreTypeFlow) { + /* + * The links between unsafe store and its uses are the only place where the mix of + * primitive/object type states actually happens due to the fact that all unsafe + * accessed fields are conceptually merged into one. It does not matter though, + * because we only propagate object types states through them. Unsafe accessed + * primitive fields are set to AnyPrimitiveTypeState and so are the corresponding + * FieldFilterTypeFlows. + */ + return true; + } + return false; + } + return true; + + } + public boolean addUse(PointsToAnalysis bb, TypeFlow use, boolean propagateTypeState) { + assert !bb.trackPrimitiveValues() || checkDefUseCompatibility(use) : "Incompatible flows: " + this + " connected with " + use; if (isSaturated() && propagateTypeState) { /* Register input. */ registerInput(bb, use); @@ -534,6 +579,10 @@ public TypeState declaredTypeFilter(PointsToAnalysis bb, TypeState newState, boo /* If the declared type is Object type there is no need to filter. */ return newState; } + if (isPrimitiveFlow) { + assert newState.isPrimitive() || newState.isEmpty() : newState + "," + this; + return newState; + } /* By default, filter all type flows with the declared type. */ return TypeState.forIntersection(bb, newState, declaredType.getAssignableTypes(true)); } @@ -549,11 +598,15 @@ public TypeState declaredTypeFilter(PointsToAnalysis bb, TypeState newState, boo * * Places where interface types need not be filtered: array element loads (because all array * stores have an array store check). + * + * One exception are word types. We do not filter them here, because they are transformed to + * primitive values later on anyway and the knowledge that a given type is a word type is useful + * when distinguishing primitive and object flows. */ public static AnalysisType filterUncheckedInterface(AnalysisType type) { if (type != null) { AnalysisType elementalType = type.getElementalType(); - if (elementalType.isInterface()) { + if (elementalType.isInterface() && !elementalType.isWordType()) { return type.getUniverse().objectType().getArrayClass(type.getArrayDimension()); } } @@ -594,6 +647,9 @@ boolean checkSaturated(PointsToAnalysis bb, TypeState typeState) { /* This type flow needs to track all its individual types. */ return false; } + if (typeState.isPrimitive()) { + return !(typeState instanceof PrimitiveConstantTypeState); + } return typeState.typesCount() > bb.analysisPolicy().typeFlowSaturationCutoff(); } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/builder/TypeFlowBuilder.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/builder/TypeFlowBuilder.java index 72770a8a3346..32baacd12abb 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/builder/TypeFlowBuilder.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/builder/TypeFlowBuilder.java @@ -32,6 +32,7 @@ import jdk.graal.compiler.phases.common.LazyValue; import com.oracle.graal.pointsto.PointsToAnalysis; +import com.oracle.graal.pointsto.flow.PrimitiveFlow; import com.oracle.graal.pointsto.flow.TypeFlow; import com.oracle.graal.pointsto.typestate.PointsToStats; @@ -47,10 +48,19 @@ public final class TypeFlowBuilder> { public static > TypeFlowBuilder create(PointsToAnalysis bb, Object source, Class clazz, Supplier supplier) { TypeFlowBuilder builder = new TypeFlowBuilder<>(source, clazz, new LazyValue<>(supplier)); + assert checkForPrimitiveFlows(bb, clazz) : "Primitive flow encountered without -H:+TrackPrimitiveValues: " + clazz; PointsToStats.registerTypeFlowBuilder(bb, builder); return builder; } + /** + * A sanity check. If tracking primitive values is disabled, no primitive flows should be + * created. + */ + private static > boolean checkForPrimitiveFlows(PointsToAnalysis bb, Class clazz) { + return bb.trackPrimitiveValues() || !PrimitiveFlow.class.isAssignableFrom(clazz); + } + private final Object source; private final Class flowClass; diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/context/bytecode/BytecodeSensitiveAnalysisPolicy.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/context/bytecode/BytecodeSensitiveAnalysisPolicy.java index 54a2931c0fc6..44b442a689d2 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/context/bytecode/BytecodeSensitiveAnalysisPolicy.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/context/bytecode/BytecodeSensitiveAnalysisPolicy.java @@ -394,7 +394,7 @@ static BytecodeAnalysisContextPolicy contextPolicy(BigBang bb) { @Override public TypeState forContextInsensitiveTypeState(PointsToAnalysis bb, TypeState state) { - if (state.isEmpty() || state.isNull()) { + if (state.isEmpty() || state.isNull() || state.isPrimitive()) { /* The type state is already context insensitive. */ return state; } else { diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/context/object/AnalysisObject.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/context/object/AnalysisObject.java index ac9b5d07f580..4e74d58f185d 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/context/object/AnalysisObject.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/context/object/AnalysisObject.java @@ -235,7 +235,8 @@ final FieldTypeStore getInstanceFieldTypeStore(PointsToAnalysis bb, TypeFlow INSTANCE_FIELD_TYPE_STORE_UPDATER.compareAndSet(this, null, new AtomicReferenceArray<>(type.getInstanceFields(true).length)); } - AnalysisError.guarantee(field.getPosition() >= 0 && field.getPosition() < instanceFieldsTypeStore.length()); + AnalysisError.guarantee(field.getPosition() >= 0 && field.getPosition() < instanceFieldsTypeStore.length(), "Field %s.%s has invalid position %d.", field.getDeclaringClass().toJavaName(), + field.getName(), field.getPosition()); FieldTypeStore fieldStore = instanceFieldsTypeStore.get(field.getPosition()); if (fieldStore == null) { diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageHeapScanner.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageHeapScanner.java index 81c2eb555c18..67b47fa7b430 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageHeapScanner.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageHeapScanner.java @@ -422,6 +422,8 @@ private boolean doNotifyAnalysis(AnalysisField field, JavaConstant receiver, Jav analysisModified = scanningObserver.forNullFieldValue(receiver, field, reason); } else if (fieldValue.getJavaKind() == JavaKind.Object) { analysisModified = scanningObserver.forNonNullFieldValue(receiver, field, fieldValue, reason); + } else if (bb.trackPrimitiveValues() && fieldValue.getJavaKind().isNumericInteger()) { + analysisModified = scanningObserver.forPrimitiveFieldValue(receiver, field, fieldValue, reason); } return analysisModified; } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisField.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisField.java index 6dbf73c56ae1..b78da63d0585 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisField.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisField.java @@ -81,16 +81,16 @@ public abstract class AnalysisField extends AnalysisElement implements WrappedJa public final ResolvedJavaField wrapped; /** Field type flow for the static fields. */ - private FieldTypeFlow staticFieldFlow; + protected FieldTypeFlow staticFieldFlow; /** Initial field type flow, i.e., as specified by the analysis client. */ - private FieldTypeFlow initialInstanceFieldFlow; + protected FieldTypeFlow initialInstanceFieldFlow; /** * Field type flow that reflects all the types flowing in this field on its declaring type and * all the sub-types. It doesn't track any context-sensitive information. */ - private ContextInsensitiveFieldTypeFlow instanceFieldFlow; + protected ContextInsensitiveFieldTypeFlow instanceFieldFlow; /** The reason flags contain a {@link BytecodePosition} or a reason object. */ @SuppressWarnings("unused") private volatile Object isRead; @@ -148,7 +148,7 @@ public AnalysisField(AnalysisUniverse universe, ResolvedJavaField wrappedField) this.staticFieldFlow = new FieldTypeFlow(this, getType()); this.initialInstanceFieldFlow = null; } else { - this.canBeNull = true; + this.canBeNull = !getStorageKind().isPrimitive(); this.instanceFieldFlow = new ContextInsensitiveFieldTypeFlow(this, getType()); this.initialInstanceFieldFlow = new FieldTypeFlow(this, getType()); } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisType.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisType.java index 8af3da270f50..66c0cbc87dfd 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisType.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisType.java @@ -49,6 +49,7 @@ import com.oracle.graal.pointsto.api.DefaultUnsafePartition; import com.oracle.graal.pointsto.constraints.UnsupportedFeatureException; import com.oracle.graal.pointsto.flow.AllInstantiatedTypeFlow; +import com.oracle.graal.pointsto.flow.TypeFlow; import com.oracle.graal.pointsto.flow.context.object.AnalysisObject; import com.oracle.graal.pointsto.flow.context.object.ConstantContextSensitiveObject; import com.oracle.graal.pointsto.heap.TypeData; @@ -228,6 +229,12 @@ public AnalysisType(AnalysisUniverse universe, ResolvedJavaType javaType, JavaKi isArray = wrapped.isArray(); isJavaLangObject = wrapped.isJavaLangObject(); this.storageKind = storageKind; + + if (!(isPrimitive() || isWordType())) { + this.instantiatedTypes = new AllInstantiatedTypeFlow(this, true); + this.instantiatedTypesNonNull = new AllInstantiatedTypeFlow(this, false); + } + if (universe.analysisPolicy().needsConstantCache()) { this.constantObjectsCache = new ConcurrentHashMap<>(); } @@ -422,15 +429,17 @@ private void mergeConstantObjects(PointsToAnalysis bb) { * type is marked as instantiated, e.g., a saturated field access type flow needs to be notified * when a sub-type of its declared type is marked as instantiated. */ - public AllInstantiatedTypeFlow instantiatedTypes = new AllInstantiatedTypeFlow(this, true); - public AllInstantiatedTypeFlow instantiatedTypesNonNull = new AllInstantiatedTypeFlow(this, false); + public AllInstantiatedTypeFlow instantiatedTypes; + public AllInstantiatedTypeFlow instantiatedTypesNonNull; /* * Returns a type flow containing all types that are assignable from this type and are also * instantiated. */ - public AllInstantiatedTypeFlow getTypeFlow(@SuppressWarnings("unused") BigBang bb, boolean includeNull) { - if (includeNull) { + public TypeFlow getTypeFlow(@SuppressWarnings("unused") BigBang bb, boolean includeNull) { + if (isPrimitive() || isWordType()) { + return ((PointsToAnalysis) bb).getAnyPrimitiveSourceTypeFlow(); + } else if (includeNull) { return instantiatedTypes; } else { return instantiatedTypesNonNull; diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/PointsToAnalysisField.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/PointsToAnalysisField.java index c80021195b5b..cc3ae97f8775 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/PointsToAnalysisField.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/PointsToAnalysisField.java @@ -27,10 +27,11 @@ import java.util.concurrent.atomic.AtomicReference; import com.oracle.graal.pointsto.PointsToAnalysis; -import com.oracle.graal.pointsto.flow.AllInstantiatedTypeFlow; import com.oracle.graal.pointsto.flow.StoreFieldTypeFlow; import com.oracle.graal.pointsto.flow.StoreFieldTypeFlow.StoreInstanceFieldTypeFlow; +import com.oracle.graal.pointsto.typestate.TypeState; import com.oracle.graal.pointsto.util.AtomicUtils; +import com.oracle.svm.util.UnsafePartitionKind; import jdk.vm.ci.code.BytecodePosition; import jdk.vm.ci.meta.ResolvedJavaField; @@ -55,7 +56,7 @@ public StoreFieldTypeFlow initAndGetContextInsensitiveStore(PointsToAnalysis bb, /** Create an unique, per field, context insensitive store. */ private StoreInstanceFieldTypeFlow createContextInsensitiveStore(PointsToAnalysis bb, BytecodePosition originalLocation) { /* The receiver object flow is the field declaring type flow. */ - AllInstantiatedTypeFlow objectFlow = declaringClass.getTypeFlow(bb, false); + var objectFlow = declaringClass.getTypeFlow(bb, false); /* * The context insensitive store doesn't have a value flow, it will instead be linked with * the value flows at all the locations where it is swapped in. @@ -78,4 +79,31 @@ public void cleanupAfterAnalysis() { super.cleanupAfterAnalysis(); contextInsensitiveStore.set(null); } + + @Override + public boolean registerAsUnsafeAccessed(UnsafePartitionKind partitionKind, Object reason) { + if (super.registerAsUnsafeAccessed(partitionKind, reason)) { + if (fieldType.getStorageKind().isPrimitive()) { + /* + * Primitive type states are not propagated through unsafe loads/stores. Instead, + * both primitive fields that are unsafe written and all unsafe loads for primitives + * are pre-saturated. + */ + saturatePrimitiveField(); + } + return true; + } + return false; + } + + public void saturatePrimitiveField() { + assert fieldType.isPrimitive() || fieldType.isWordType() : this; + var bb = ((PointsToAnalysis) getUniverse().getBigbang()); + if (isStatic()) { + staticFieldFlow.addState(bb, TypeState.anyPrimitiveState()); + } else { + initialInstanceFieldFlow.addState(bb, TypeState.anyPrimitiveState()); + instanceFieldFlow.addState(bb, TypeState.anyPrimitiveState()); + } + } } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/PointsToAnalysisMethod.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/PointsToAnalysisMethod.java index ec94979ef130..d5eafbf9689e 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/PointsToAnalysisMethod.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/PointsToAnalysisMethod.java @@ -34,7 +34,6 @@ import com.oracle.graal.pointsto.flow.AbstractVirtualInvokeTypeFlow; import com.oracle.graal.pointsto.flow.ActualParameterTypeFlow; import com.oracle.graal.pointsto.flow.ActualReturnTypeFlow; -import com.oracle.graal.pointsto.flow.AllInstantiatedTypeFlow; import com.oracle.graal.pointsto.flow.InvokeTypeFlow; import com.oracle.graal.pointsto.flow.MethodTypeFlow; import com.oracle.graal.pointsto.flow.TypeFlow; @@ -43,7 +42,6 @@ import com.oracle.svm.common.meta.MultiMethod; import jdk.vm.ci.code.BytecodePosition; -import jdk.vm.ci.meta.JavaKind; import jdk.vm.ci.meta.ResolvedJavaMethod; public final class PointsToAnalysisMethod extends AnalysisMethod { @@ -193,7 +191,7 @@ private static InvokeTypeFlow createContextInsensitiveInvoke(PointsToAnalysis bb * The receiver flow of the context insensitive invoke is the type flow of its declaring * class. */ - AllInstantiatedTypeFlow receiverFlow = receiverType.getTypeFlow(bb, false); + var receiverFlow = receiverType.getTypeFlow(bb, false); actualParameters[0] = receiverFlow; for (int i = 1; i < actualParameters.length; i++) { @@ -201,7 +199,7 @@ private static InvokeTypeFlow createContextInsensitiveInvoke(PointsToAnalysis bb } ActualReturnTypeFlow actualReturn = null; AnalysisType returnType = (AnalysisType) method.getSignature().getReturnType(null); - if (returnType.getStorageKind() == JavaKind.Object) { + if (bb.isSupportedJavaKind(returnType.getStorageKind())) { actualReturn = new ActualReturnTypeFlow(returnType); } @@ -225,7 +223,7 @@ private static InvokeTypeFlow createContextInsensitiveInvoke(PointsToAnalysis bb */ private static void initContextInsensitiveInvoke(PointsToAnalysis bb, AnalysisMethod method, InvokeTypeFlow invoke) { AnalysisType receiverType = method.getDeclaringClass(); - AllInstantiatedTypeFlow receiverFlow = receiverType.getTypeFlow(bb, false); + var receiverFlow = receiverType.getTypeFlow(bb, false); receiverFlow.addObserver(bb, invoke); } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/PointsToAnalysisType.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/PointsToAnalysisType.java index 2dc5bcec5b58..78e53212c156 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/PointsToAnalysisType.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/PointsToAnalysisType.java @@ -28,7 +28,6 @@ import com.oracle.graal.pointsto.BigBang; import com.oracle.graal.pointsto.PointsToAnalysis; -import com.oracle.graal.pointsto.flow.AllInstantiatedTypeFlow; import com.oracle.graal.pointsto.flow.OffsetStoreTypeFlow.StoreIndexedTypeFlow; import com.oracle.graal.pointsto.flow.OffsetStoreTypeFlow.UnsafeStoreTypeFlow; import com.oracle.graal.pointsto.typestate.TypeState; @@ -82,7 +81,7 @@ public UnsafeStoreTypeFlow initAndGetContextInsensitiveUnsafeStore(PointsToAnaly */ private UnsafeStoreTypeFlow createContextInsensitiveUnsafeStore(PointsToAnalysis bb, BytecodePosition originalLocation) { /* The receiver object flow is the flow corresponding to this type. */ - AllInstantiatedTypeFlow objectFlow = this.getTypeFlow(bb, false); + var objectFlow = this.getTypeFlow(bb, false); /* Use the Object type as a conservative type for the values loaded. */ AnalysisType componentType = bb.getObjectType(); /* @@ -114,7 +113,7 @@ public StoreIndexedTypeFlow initAndGetContextInsensitiveIndexedStore(PointsToAna private StoreIndexedTypeFlow createContextInsensitiveIndexedStore(PointsToAnalysis bb, BytecodePosition originalLocation) { assert this.isArray() : this; /* The receiver object flow is the flow corresponding to this type. */ - AllInstantiatedTypeFlow objectFlow = this.getTypeFlow(bb, false); + var objectFlow = this.getTypeFlow(bb, false); /* * The context insensitive store doesn't have a value flow, it will instead be linked with * the value flows at all the locations where it is swapped in. diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/results/StrengthenGraphs.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/results/StrengthenGraphs.java index 605132dd0f2d..54960c8513a2 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/results/StrengthenGraphs.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/results/StrengthenGraphs.java @@ -49,10 +49,12 @@ import com.oracle.graal.pointsto.meta.AnalysisMethod; import com.oracle.graal.pointsto.meta.AnalysisType; import com.oracle.graal.pointsto.meta.PointsToAnalysisMethod; +import com.oracle.graal.pointsto.typestate.PrimitiveConstantTypeState; import com.oracle.graal.pointsto.typestate.TypeState; import com.oracle.svm.util.ImageBuildStatistics; import jdk.graal.compiler.core.common.type.AbstractObjectStamp; +import jdk.graal.compiler.core.common.type.IntegerStamp; import jdk.graal.compiler.core.common.type.ObjectStamp; import jdk.graal.compiler.core.common.type.Stamp; import jdk.graal.compiler.core.common.type.StampFactory; @@ -111,6 +113,7 @@ import jdk.vm.ci.meta.JavaKind; import jdk.vm.ci.meta.JavaMethodProfile; import jdk.vm.ci.meta.JavaTypeProfile; +import jdk.vm.ci.meta.PrimitiveConstant; import jdk.vm.ci.meta.ResolvedJavaMethod; import jdk.vm.ci.meta.ResolvedJavaType; import jdk.vm.ci.meta.TriState; @@ -729,7 +732,7 @@ private ValueNode insertPi(ValueNode input, Object newStampOrConstant, FixedWith } private Object strengthenStampFromTypeFlow(ValueNode node, TypeFlow nodeFlow, FixedWithNextNode anchorPoint, SimplifierTool tool) { - if (nodeFlow == null || node.getStackKind() != JavaKind.Object) { + if (nodeFlow == null || !((PointsToAnalysis) bb).isSupportedJavaKind(node.getStackKind())) { return null; } if (methodFlow.isSaturated((PointsToAnalysis) bb, nodeFlow)) { @@ -757,7 +760,12 @@ private Object strengthenStampFromTypeFlow(ValueNode node, TypeFlow nodeFlow, } node.inferStamp(); - ObjectStamp oldStamp = (ObjectStamp) node.stamp(NodeView.DEFAULT); + Stamp s = node.stamp(NodeView.DEFAULT); + if (s.isIntegerStamp() || nodeTypeState.isPrimitive()) { + return getIntegerStamp(node, s, nodeTypeState); + } + + ObjectStamp oldStamp = (ObjectStamp) s; AnalysisType oldType = (AnalysisType) oldStamp.type(); boolean nonNull = oldStamp.nonNull() || !nodeTypeState.canBeNull(); @@ -848,6 +856,27 @@ assert getSingleImplementorType(baseType) == null || baseType.equals(getSingleIm return null; } + private IntegerStamp getIntegerStamp(ValueNode node, Stamp stamp, TypeState nodeTypeState) { + assert bb.trackPrimitiveValues() : nodeTypeState + "," + node + " in " + node.graph(); + assert nodeTypeState != null && (nodeTypeState.isEmpty() || nodeTypeState.isPrimitive()) : nodeTypeState + "," + node + " in " + node.graph(); + if (nodeTypeState instanceof PrimitiveConstantTypeState constantTypeState) { + long constantValue = constantTypeState.getValue(); + if (node instanceof ConstantNode constant) { + /* + * Sanity check, verify that what was proven by the analysis is consistent with + * the constant node in the graph. + */ + Constant value = constant.getValue(); + assert value instanceof PrimitiveConstant : "Node " + value + " should be a primitive constant when extracting an integer stamp, method " + node.graph().method(); + assert ((PrimitiveConstant) value).asLong() == constantValue : "The actual value of node: " + value + " is different than the value " + constantValue + + " computed by points-to analysis, method in " + node.graph().method(); + } else { + return IntegerStamp.createConstant(((IntegerStamp) stamp).getBits(), constantValue); + } + } + return null; + } + private void makeUnreachable(FixedNode node, CoreProviders providers, Supplier message) { FixedNode unreachableNode = createUnreachable(graph, providers, message); ((FixedWithNextNode) node.predecessor()).setNext(unreachableNode); diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/AnyPrimitiveTypeState.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/AnyPrimitiveTypeState.java new file mode 100644 index 000000000000..c359378ff1c5 --- /dev/null +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/AnyPrimitiveTypeState.java @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.graal.pointsto.typestate; + +import java.util.Iterator; + +import com.oracle.graal.pointsto.BigBang; +import com.oracle.graal.pointsto.PointsToAnalysis; +import com.oracle.graal.pointsto.flow.context.object.AnalysisObject; +import com.oracle.graal.pointsto.meta.AnalysisType; +import com.oracle.graal.pointsto.util.AnalysisError; + +/** + * Represents 'any' primitive value - a value, about which we do not maintain any useful + * information. + * + * It is implemented as a singleton, which is accessible via a factory method + * {@link TypeState#anyPrimitiveState}. + * + * It is used in two cases:
+ * 1) When we do not represent given Graal IR node in the type flow graph, e.g. arithmetic + * operations, unsafe loads, loads from arrays. These operations are represented using + * {@link com.oracle.graal.pointsto.flow.AnyPrimitiveSourceTypeFlow }, which immediately produces + * AnyPrimitiveTypeState.
+ * 2) When two different constant primitive states are merged in {@link TypeState#forUnion}.
+ *
+ * When a type flow receives this state, it leads to immediate saturation. + */ +public class AnyPrimitiveTypeState extends TypeState { + + static final AnyPrimitiveTypeState SINGLETON = new AnyPrimitiveTypeState(); + + protected AnyPrimitiveTypeState() { + } + + private static RuntimeException shouldNotReachHere() { + throw AnalysisError.shouldNotReachHere("This method should never be called."); + } + + @Override + public int typesCount() { + throw shouldNotReachHere(); + } + + @Override + public AnalysisType exactType() { + throw shouldNotReachHere(); + } + + @Override + protected Iterator typesIterator(BigBang bb) { + throw shouldNotReachHere(); + } + + @Override + public boolean containsType(AnalysisType exactType) { + throw shouldNotReachHere(); + } + + @Override + public int objectsCount() { + throw shouldNotReachHere(); + } + + @Override + protected Iterator objectsIterator(BigBang bb) { + throw shouldNotReachHere(); + } + + @Override + protected Iterator objectsIterator(AnalysisType type) { + throw shouldNotReachHere(); + } + + @Override + public boolean canBeNull() { + return false; + } + + @Override + public TypeState forCanBeNull(PointsToAnalysis bb, boolean stateCanBeNull) { + throw shouldNotReachHere(); + } + + @Override + public String toString() { + return "PrimitiveTypeState"; + } + + @Override + public boolean equals(Object o) { + return this == o; + } +} diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/PrimitiveConstantTypeState.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/PrimitiveConstantTypeState.java new file mode 100644 index 000000000000..ab5c0e923b55 --- /dev/null +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/PrimitiveConstantTypeState.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.graal.pointsto.typestate; + +import java.util.Objects; + +/** + * Represents a primitive constant that is propagated through the type flow graph. Instances for + * corresponding primitive values are accessible via a factory method + * {@link TypeState#forPrimitiveConstant }. + */ +public final class PrimitiveConstantTypeState extends AnyPrimitiveTypeState { + + private static final int CACHE_SIZE = 16; + + private static final PrimitiveConstantTypeState[] CACHE = new PrimitiveConstantTypeState[CACHE_SIZE]; + + static { + for (int i = 0; i < CACHE_SIZE; i++) { + CACHE[i] = new PrimitiveConstantTypeState(i); + } + } + + private final long value; + + public static TypeState forValue(long value) { + if (value >= 0 && value < CACHE_SIZE) { + return CACHE[(int) value]; + } + return new PrimitiveConstantTypeState(value); + } + + private PrimitiveConstantTypeState(long value) { + this.value = value; + } + + public long getValue() { + return value; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + PrimitiveConstantTypeState that = (PrimitiveConstantTypeState) o; + return value == that.value; + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), value); + } + + @Override + public String toString() { + return "PrimitiveConstantTypeState(" + value + ")"; + } +} diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/TypeState.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/TypeState.java index f0a66342bde8..9d94c7d9b6eb 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/TypeState.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/TypeState.java @@ -112,6 +112,10 @@ public boolean isNull() { return this == NullTypeState.SINGLETON; } + public boolean isPrimitive() { + return this instanceof AnyPrimitiveTypeState; + } + public abstract boolean canBeNull(); /** Note that the objects of this type state have been merged. */ @@ -140,6 +144,14 @@ public static TypeState forNull() { return NullTypeState.SINGLETON; } + public static TypeState forPrimitiveConstant(long value) { + return PrimitiveConstantTypeState.forValue(value); + } + + public static TypeState anyPrimitiveState() { + return AnyPrimitiveTypeState.SINGLETON; + } + /** Wraps an analysis object into a non-null type state. */ public static TypeState forNonNullObject(PointsToAnalysis bb, AnalysisObject object) { return bb.analysisPolicy().singleTypeState(bb, false, object.type(), object); @@ -184,6 +196,14 @@ public static TypeState forUnion(PointsToAnalysis bb, TypeState s1, TypeState s2 return s1; } else if (s2.isNull()) { return s1.forCanBeNull(bb, true); + } else if (s1 instanceof PrimitiveConstantTypeState c1 && s2 instanceof PrimitiveConstantTypeState c2 && c1.getValue() == c2.getValue()) { + return s1; + } else if (s1.isPrimitive()) { + assert s2.isPrimitive() : s2; + return TypeState.anyPrimitiveState(); + } else if (s2.isPrimitive()) { + assert s1.isPrimitive() : s1; + return TypeState.anyPrimitiveState(); } else if (s1 instanceof SingleTypeState && s2 instanceof SingleTypeState) { return bb.analysisPolicy().doUnion(bb, (SingleTypeState) s1, (SingleTypeState) s2); } else if (s1 instanceof SingleTypeState && s2 instanceof MultiTypeState) { diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/TypeStateUtils.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/TypeStateUtils.java index 21bb4c7d3576..cf666c22621e 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/TypeStateUtils.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/TypeStateUtils.java @@ -403,6 +403,9 @@ private static BitSet getClone(BitSet original) { } public static boolean closeToAllInstantiated(BigBang bb, TypeState state) { + if (state instanceof AnyPrimitiveTypeState) { + return false; + } return closeToAllInstantiated(bb, state.typesCount()); } diff --git a/substratevm/src/com.oracle.graal.reachability/src/com/oracle/graal/reachability/ReachabilityAnalysisEngine.java b/substratevm/src/com.oracle.graal.reachability/src/com/oracle/graal/reachability/ReachabilityAnalysisEngine.java index 954b3ae74853..5ca6ed4c41b7 100644 --- a/substratevm/src/com.oracle.graal.reachability/src/com/oracle/graal/reachability/ReachabilityAnalysisEngine.java +++ b/substratevm/src/com.oracle.graal.reachability/src/com/oracle/graal/reachability/ReachabilityAnalysisEngine.java @@ -366,4 +366,8 @@ public void processGraph(StructuredGraph graph) { reachabilityMethodProcessingHandler.processGraph(this, graph); } + @Override + public boolean trackPrimitiveValues() { + return false; + } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/BuildPhaseProvider.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/BuildPhaseProvider.java index 7f0fc3f49196..1977582a3bab 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/BuildPhaseProvider.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/BuildPhaseProvider.java @@ -36,6 +36,7 @@ public final class BuildPhaseProvider { private boolean analysisFinished; private boolean hostedUniverseBuilt; private boolean readyForCompilation; + private boolean compileQueueFinished; private boolean compilationFinished; private boolean heapLayoutFinished; @@ -74,6 +75,14 @@ public static boolean isReadyForCompilation() { return singleton().readyForCompilation; } + public static void markCompileQueueFinished() { + singleton().compileQueueFinished = true; + } + + public static boolean isCompileQueueFinished() { + return singleton().compileQueueFinished; + } + public static void markCompilationFinished() { singleton().compilationFinished = true; } @@ -111,6 +120,13 @@ public boolean getAsBoolean() { } } + public static class CompileQueueFinished implements BooleanSupplier { + @Override + public boolean getAsBoolean() { + return BuildPhaseProvider.isCompileQueueFinished(); + } + } + public static class AfterCompilation implements BooleanSupplier { @Override public boolean getAsBoolean() { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java index a46f1edc1e6b..9509d0328ca2 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java @@ -85,6 +85,7 @@ import com.oracle.svm.core.BuildPhaseProvider.AfterCompilation; import com.oracle.svm.core.BuildPhaseProvider.AfterHostedUniverse; +import com.oracle.svm.core.BuildPhaseProvider.CompileQueueFinished; import com.oracle.svm.core.RuntimeAssertionsSupport; import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.Uninterruptible; @@ -1850,18 +1851,25 @@ private static void setCachedConstructor(DynamicHub that, Constructor value) } private static final class DynamicHubMetadata { + @UnknownPrimitiveField(availability = CompileQueueFinished.class) // final int enclosingMethodInfoIndex; + @UnknownPrimitiveField(availability = CompileQueueFinished.class)// final int annotationsIndex; + @UnknownPrimitiveField(availability = CompileQueueFinished.class)// final int typeAnnotationsIndex; + @UnknownPrimitiveField(availability = CompileQueueFinished.class)// final int classesEncodingIndex; + @UnknownPrimitiveField(availability = CompileQueueFinished.class)// final int permittedSubclassesEncodingIndex; + @UnknownPrimitiveField(availability = CompileQueueFinished.class)// final int nestMembersEncodingIndex; + @UnknownPrimitiveField(availability = CompileQueueFinished.class)// final int signersEncodingIndex; private DynamicHubMetadata(int enclosingMethodInfoIndex, int annotationsIndex, int typeAnnotationsIndex, int classesEncodingIndex, int permittedSubclassesEncodingIndex, @@ -1877,14 +1885,19 @@ private DynamicHubMetadata(int enclosingMethodInfoIndex, int annotationsIndex, i } private static final class ReflectionMetadata { + @UnknownPrimitiveField(availability = CompileQueueFinished.class)// final int fieldsEncodingIndex; + @UnknownPrimitiveField(availability = CompileQueueFinished.class)// final int methodsEncodingIndex; + @UnknownPrimitiveField(availability = CompileQueueFinished.class)// final int constructorsEncodingIndex; + @UnknownPrimitiveField(availability = CompileQueueFinished.class)// final int recordComponentsEncodingIndex; + @UnknownPrimitiveField(availability = CompileQueueFinished.class)// final int classFlags; private ReflectionMetadata(int fieldsEncodingIndex, int methodsEncodingIndex, int constructorsEncodingIndex, int recordComponentsEncodingIndex, int classFlags) { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java index c4cd78577e44..cc5602d4417f 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java @@ -655,6 +655,7 @@ protected void doRun(Map entryPoints, JavaMainSupport j ImageSingletons.lookup(RuntimeCompilationSupport.class).onCompileQueueCreation(bb, hUniverse, compileQueue); } compileQueue.finish(debug); + BuildPhaseProvider.markCompileQueueFinished(); /* release memory taken by graphs for the image writing */ hUniverse.getMethods().forEach(HostedMethod::clear); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/CustomTypeFieldHandler.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/CustomTypeFieldHandler.java index 36a5c9ff94ff..ec793d8018a0 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/CustomTypeFieldHandler.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/CustomTypeFieldHandler.java @@ -35,6 +35,7 @@ import com.oracle.graal.pointsto.meta.AnalysisField; import com.oracle.graal.pointsto.meta.AnalysisMetaAccess; import com.oracle.graal.pointsto.meta.AnalysisType; +import com.oracle.graal.pointsto.meta.PointsToAnalysisField; import com.oracle.svm.hosted.substitute.ComputedValueField; public abstract class CustomTypeFieldHandler { @@ -57,13 +58,17 @@ public void handleField(AnalysisField field) { */ assert field.isAccessed(); if (field.wrapped instanceof ComputedValueField computedField) { - if (!computedField.isValueAvailableBeforeAnalysis() && field.getJavaKind().isObject()) { + if (!computedField.isValueAvailableBeforeAnalysis() && field.getStorageKind().isObject()) { injectFieldTypes(field, field.getType()); + } else if (bb.trackPrimitiveValues() && field.getStorageKind().isPrimitive() && field instanceof PointsToAnalysisField ptaField) { + ptaField.saturatePrimitiveField(); } } else if (field.isComputedValue()) { - if (!field.getStorageKind().isPrimitive()) { + if (field.getStorageKind().isObject()) { field.setCanBeNull(field.computedValueCanBeNull()); injectFieldTypes(field, transformTypes(field, field.computedValueTypes())); + } else if (bb.trackPrimitiveValues() && field.getStorageKind().isPrimitive() && field instanceof PointsToAnalysisField ptaField) { + ptaField.saturatePrimitiveField(); } } processedFields.add(field); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/PointsToCustomTypeFieldHandler.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/PointsToCustomTypeFieldHandler.java index 1288ef4adc0e..bc0179a8138b 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/PointsToCustomTypeFieldHandler.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/PointsToCustomTypeFieldHandler.java @@ -48,7 +48,7 @@ protected void injectFieldTypes(AnalysisField aField, AnalysisType... customType /* Link the field with all declared types. */ for (AnalysisType type : customTypes) { - if (type.isPrimitive()) { + if (type.isPrimitive() || type.isWordType()) { continue; } TypeFlow typeFlow = type.getTypeFlow(analysis, true); @@ -59,7 +59,7 @@ protected void injectFieldTypes(AnalysisField aField, AnalysisType... customType if (type.isArray()) { AnalysisType fieldComponentType = type.getComponentType(); aField.getInitialInstanceFieldFlow().addUse(analysis, aField.getInstanceFieldFlow()); - if (!fieldComponentType.isPrimitive()) { + if (!(fieldComponentType.isPrimitive() || fieldComponentType.isWordType())) { /* * Write the component type abstract object into the field array elements * type flow, i.e., the array elements type flow of the abstract object of diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/dashboard/PointsToJsonObject.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/dashboard/PointsToJsonObject.java index 9e2c8358d5d6..39a178d4a865 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/dashboard/PointsToJsonObject.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/dashboard/PointsToJsonObject.java @@ -828,6 +828,10 @@ private static void addUnique(Map list, T element) { */ private static ArrayList serializeTypeState(BigBang bb, TypeState typeState) { ArrayList types = new ArrayList<>(); + if (typeState.isPrimitive()) { + /* No types available in primitive type states */ + return types; + } for (AnalysisType type : typeState.types(bb)) { types.add(type.toJavaName()); }