diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/AbstractAnalysisEngine.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/AbstractAnalysisEngine.java index a1b85dfa6b26..ccd6766e6b97 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/AbstractAnalysisEngine.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/AbstractAnalysisEngine.java @@ -75,6 +75,8 @@ public abstract class AbstractAnalysisEngine implements BigBang { private final HeapScanningPolicy heapScanningPolicy; protected final Boolean extendedAsserts; + protected final int maxConstantObjectsPerType; + protected final boolean profileConstantObjects; protected final OptionValues options; protected final DebugContext debug; @@ -115,6 +117,8 @@ public AbstractAnalysisEngine(OptionValues options, AnalysisUniverse universe, H this.analysisTimer = timerCollection.get(TimerCollection.Registry.ANALYSIS); this.extendedAsserts = PointstoOptions.ExtendedAsserts.getValue(options); + maxConstantObjectsPerType = PointstoOptions.MaxConstantObjectsPerType.getValue(options); + profileConstantObjects = PointstoOptions.ProfileConstantObjects.getValue(options); this.heapScanningPolicy = PointstoOptions.ExhaustiveHeapScan.getValue(options) ? HeapScanningPolicy.scanAll() @@ -230,6 +234,17 @@ public boolean extendedAsserts() { return extendedAsserts; } + public int maxConstantObjectsPerType() { + return maxConstantObjectsPerType; + } + + public void profileConstantObject(AnalysisType type) { + if (profileConstantObjects) { + PointsToAnalysis.ConstantObjectsProfiler.registerConstant(type); + PointsToAnalysis.ConstantObjectsProfiler.maybeDumpConstantHistogram(); + } + } + @Override public OptionValues getOptions() { return options; 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 95735017afa0..8f99b7a737df 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 @@ -67,10 +67,8 @@ public boolean forNonNullFieldValue(JavaConstant receiver, AnalysisField field, /* Add the constant value object to the field's type flow. */ FieldTypeFlow fieldTypeFlow = getFieldTypeFlow(field, receiver); - AnalysisObject constantObject = bb.analysisPolicy().createConstantObject(analysis, fieldValue, fieldType); /* Add the new constant to the field's flow state. */ - TypeState constantTypeState = TypeState.forNonNullObject(analysis, constantObject); - return fieldTypeFlow.addState(analysis, constantTypeState); + return fieldTypeFlow.addState(analysis, bb.analysisPolicy().constantTypeState(analysis, fieldValue, fieldType)); } /** @@ -107,10 +105,8 @@ public boolean forNullArrayElement(JavaConstant array, AnalysisType arrayType, i public boolean forNonNullArrayElement(JavaConstant array, AnalysisType arrayType, JavaConstant elementConstant, AnalysisType elementType, int elementIndex, ScanReason reason) { ArrayElementsTypeFlow arrayObjElementsFlow = getArrayElementsFlow(array, arrayType); PointsToAnalysis analysis = getAnalysis(); - AnalysisObject constantObject = bb.analysisPolicy().createConstantObject(analysis, elementConstant, elementType); /* Add the constant element to the constant's array type flow. */ - TypeState elementTypeState = TypeState.forNonNullObject(analysis, constantObject); - return arrayObjElementsFlow.addState(analysis, elementTypeState); + return arrayObjElementsFlow.addState(analysis, bb.analysisPolicy().constantTypeState(analysis, elementConstant, elementType)); } /** diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/AnalysisPolicy.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/AnalysisPolicy.java index d6e085ba4211..db8600c195ca 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/AnalysisPolicy.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/AnalysisPolicy.java @@ -123,6 +123,9 @@ public int typeFlowSaturationCutoff() { /** Create a constant object abstraction. */ public abstract AnalysisObject createConstantObject(PointsToAnalysis bb, JavaConstant constant, AnalysisType exactType); + /** Wrap a constant into a type state abstraction. */ + public abstract TypeState constantTypeState(PointsToAnalysis bb, JavaConstant constant, AnalysisType exactType); + /** Create type state for dynamic new instance. */ public abstract TypeState dynamicNewInstanceState(PointsToAnalysis bb, TypeState currentState, TypeState newState, BytecodePosition allocationSite, AnalysisContext allocationContext); @@ -135,7 +138,7 @@ public int typeFlowSaturationCutoff() { */ public abstract void linkClonedObjects(PointsToAnalysis bb, TypeFlow inputFlow, CloneTypeFlow cloneFlow, BytecodePosition source); - public abstract FieldTypeStore createFieldTypeStore(AnalysisObject object, AnalysisField field, AnalysisUniverse universe); + public abstract FieldTypeStore createFieldTypeStore(PointsToAnalysis bb, AnalysisObject object, AnalysisField field, AnalysisUniverse universe); public abstract ArrayElementsTypeStore createArrayElementsTypeStore(AnalysisObject object, AnalysisUniverse universe); 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 4b2ac3919dfa..f22f4bdb71f0 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 @@ -68,8 +68,9 @@ public class PointstoOptions { @Option(help = "The maximum number of objects recorded for each type of a type state before disabling heap sensitivity for that type. The analysis must be heap sensitive. It has a minimum value of 1.")// public static final OptionKey MaxObjectSetSize = new OptionKey<>(100); - @Option(help = "The maximum number of constant objects recorded for each type before merging the constants into one unique constant object per type. The analysis must be heap sensitive. It has a minimum value of 1.")// - public static final OptionKey MaxConstantObjectsPerType = new OptionKey<>(100); + @Option(help = "The maximum number of constant objects recorded for each type before merging the constants into one unique constant object per type. " + + "If the value is 0 there is no limit.")// + public static final OptionKey MaxConstantObjectsPerType = new OptionKey<>(0); @Option(help = "Track the progress of the static analysis.")// public static final OptionKey ProfileAnalysisOperations = new OptionKey<>(false); 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 a07f2d485ac3..c9af7992e368 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 @@ -143,6 +143,10 @@ public EconomicMap getInvokes() { return flowsGraph == null ? EconomicMap.emptyMap() : flowsGraph.getInvokes(); } + public TypeFlow getParameter(int idx) { + return flowsGraph == null ? null : flowsGraph.getParameter(idx); + } + public Iterable> getParameters() { return flowsGraph == null ? Collections.emptyList() : Arrays.asList(flowsGraph.getParameters()); } 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 6d68b763502e..9dad79a85bed 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 @@ -54,6 +54,7 @@ import com.oracle.graal.pointsto.flow.context.AnalysisContext; import com.oracle.graal.pointsto.flow.context.object.AllocationContextSensitiveObject; import com.oracle.graal.pointsto.flow.context.object.AnalysisObject; +import com.oracle.graal.pointsto.flow.context.object.ConstantContextSensitiveObject; import com.oracle.graal.pointsto.meta.AnalysisField; import com.oracle.graal.pointsto.meta.AnalysisType; import com.oracle.graal.pointsto.meta.AnalysisUniverse; @@ -152,12 +153,18 @@ public AnalysisObject createHeapObject(PointsToAnalysis bb, AnalysisType type, B public AnalysisObject createConstantObject(PointsToAnalysis bb, JavaConstant constant, AnalysisType exactType) { /* Get the analysis object wrapping the JavaConstant. */ if (bb.trackConcreteAnalysisObjects(exactType)) { - return exactType.getCachedConstantObject(bb, constant); + return exactType.getCachedConstantObject(bb, constant, (c) -> new ConstantContextSensitiveObject(bb, exactType, c)); } else { return exactType.getContextInsensitiveAnalysisObject(); } } + @Override + public TypeState constantTypeState(PointsToAnalysis bb, JavaConstant constant, AnalysisType exactType) { + AnalysisObject constantObject = bb.analysisPolicy().createConstantObject(bb, constant, exactType); + return TypeState.forNonNullObject(bb, constantObject); + } + @Override public TypeState dynamicNewInstanceState(PointsToAnalysis bb, TypeState currentState, TypeState newState, BytecodePosition allocationSite, AnalysisContext allocationContext) { /* Generate a heap object for every new incoming type. */ @@ -258,7 +265,7 @@ public void linkClonedObjects(PointsToAnalysis bb, TypeFlow inputFlow, CloneT } @Override - public FieldTypeStore createFieldTypeStore(AnalysisObject object, AnalysisField field, AnalysisUniverse universe) { + public FieldTypeStore createFieldTypeStore(PointsToAnalysis bb, AnalysisObject object, AnalysisField field, AnalysisUniverse universe) { assert PointstoOptions.AllocationSiteSensitiveHeap.getValue(options); if (object.isContextInsensitiveObject()) { /* 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 25fe1b281b5f..0d97c8456785 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 @@ -60,6 +60,7 @@ public class AnalysisObject implements Comparable { protected enum AnalysisObjectKind { /** The types of runtime objects that the analysis models. */ ContextInsensitive("!S"), + ConstantObject("C"), AllocationContextSensitive("AS"), ConstantContextSensitive("CS"); @@ -82,8 +83,19 @@ protected enum AnalysisObjectKind { protected final AnalysisObjectKind kind; /** - * Is this a context sensitive object that was merged with a context insensitive object, or a - * context insensitive object that has merged some context sensitive objects? + * The merging of analysis objects is used to erase the identity of more concrete analysis + * objects (i.e., objects that wrap a constant, or retain information about their allocation + * location) and to replace them with the per-type context-insensitive analysis object. + * + * This distinction between merged and un-merged objects is essential to correctly track field + * and array flows reads and writes. For example when a concrete analysis object is the receiver + * of a write operation then only the field flow associated with that receiver will get the + * state of the stored value, such that any read from the same receiver and field will return + * the written values, but no others. However, if there is a union between the concrete receiver + * and a context-insensitive object of the same type then the receiver needs to be marked as + * `merged` to signal that all reads from a field of this object must include all the state + * written to the corresponding field flow of the context-insensitive object and all writes must + * also flow in the corresponding field flow of the context-insensitive object. */ protected volatile boolean merged; @@ -171,8 +183,8 @@ public final boolean isConstantContextSensitiveObject() { return this.kind == AnalysisObjectKind.ConstantContextSensitive; } - public final boolean isContextSensitiveObject() { - return this.isAllocationContextSensitiveObject() || this.isConstantContextSensitiveObject(); + public final boolean isConstantObject() { + return this.kind == AnalysisObjectKind.ConstantObject; } public ArrayElementsTypeStore getArrayElementsTypeStore() { @@ -203,6 +215,10 @@ public UnsafeWriteSinkTypeFlow getUnsafeWriteSinkFrozenFilterFlow(PointsToAnalys return fieldTypeStore.unsafeWriteSinkFlow(bb); } + public FieldTypeStore getInstanceFieldTypeStore(PointsToAnalysis bb, AnalysisField field) { + return getInstanceFieldTypeStore(bb, null, null, field); + } + /** Returns the instance field flow corresponding to a filed of the object's type. */ public FieldTypeFlow getInstanceFieldFlow(PointsToAnalysis bb, AnalysisField field, boolean isStore) { return getInstanceFieldFlow(bb, null, null, field, isStore); @@ -231,7 +247,7 @@ final FieldTypeStore getInstanceFieldTypeStore(PointsToAnalysis bb, TypeFlow FieldTypeStore fieldStore = instanceFieldsTypeStore.get(field.getPosition()); if (fieldStore == null) { - fieldStore = bb.analysisPolicy().createFieldTypeStore(this, field, bb.getUniverse()); + fieldStore = bb.analysisPolicy().createFieldTypeStore(bb, this, field, bb.getUniverse()); boolean result = instanceFieldsTypeStore.compareAndSet(field.getPosition(), null, fieldStore); if (result) { fieldStore.init(bb); diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/context/object/ConstantContextSensitiveObject.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/context/object/ConstantContextSensitiveObject.java index 3a74b7a5c9d3..d4a5e93abc2c 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/context/object/ConstantContextSensitiveObject.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/context/object/ConstantContextSensitiveObject.java @@ -33,8 +33,8 @@ import jdk.vm.ci.meta.JavaConstant; /** - * A context sensitive analysis object that represents a constant. The context for this analysis - * object is the constant it wraps. + * A context-sensitive analysis object that represents a constant. The implicit context for this + * analysis object is the constant that it wraps. */ public class ConstantContextSensitiveObject extends ContextSensitiveAnalysisObject { @@ -76,6 +76,7 @@ public ConstantContextSensitiveObject(PointsToAnalysis bb, AnalysisType type, Ja super(bb.getUniverse(), type, AnalysisObjectKind.ConstantContextSensitive); assert bb.trackConcreteAnalysisObjects(type); this.constant = constant; + bb.profileConstantObject(type); } public JavaConstant getConstant() { @@ -137,7 +138,7 @@ public String toString() { if (constant == null) { result.append("MERGED CONSTANT"); } else { - // result.append(constant); + result.append(constant); } return result.toString(); } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/context/object/ContextSensitiveAnalysisObject.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/context/object/ContextSensitiveAnalysisObject.java index 75f3509072f8..73fa01c6b798 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/context/object/ContextSensitiveAnalysisObject.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/context/object/ContextSensitiveAnalysisObject.java @@ -41,6 +41,13 @@ import jdk.vm.ci.code.BytecodePosition; +/** + * This class models analysis objects that retain some information about their allocation. So here + * context doesn't refer to calling context, but rather to the allocation context of this object + * (which could contain information about the calling context of its allocator). More importantly, + * this object abstraction is responsible four correct routing of field and array read and write + * flows. + */ public class ContextSensitiveAnalysisObject extends AnalysisObject { public ContextSensitiveAnalysisObject(AnalysisUniverse universe, AnalysisType type, AnalysisObjectKind kind) { 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 ded0243b010d..0374dbd17924 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 @@ -39,6 +39,7 @@ import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; import java.util.function.Consumer; +import java.util.function.Function; import org.graalvm.compiler.debug.GraalError; import org.graalvm.compiler.graph.Node; @@ -47,9 +48,7 @@ import com.oracle.graal.pointsto.BigBang; import com.oracle.graal.pointsto.PointsToAnalysis; -import com.oracle.graal.pointsto.PointsToAnalysis.ConstantObjectsProfiler; import com.oracle.graal.pointsto.api.DefaultUnsafePartition; -import com.oracle.graal.pointsto.api.PointstoOptions; import com.oracle.graal.pointsto.constraints.UnsupportedFeatureException; import com.oracle.graal.pointsto.flow.AllInstantiatedTypeFlow; import com.oracle.graal.pointsto.flow.context.object.AnalysisObject; @@ -66,7 +65,6 @@ import com.oracle.svm.util.UnsafePartitionKind; import jdk.vm.ci.meta.Assumptions.AssumptionResult; -import jdk.vm.ci.meta.Constant; import jdk.vm.ci.meta.JavaConstant; import jdk.vm.ci.meta.JavaKind; import jdk.vm.ci.meta.PrimitiveConstant; @@ -81,8 +79,8 @@ public abstract class AnalysisType extends AnalysisElement implements WrappedJav private static final AtomicReferenceFieldUpdater UNSAFE_ACCESS_FIELDS_UPDATER = // AtomicReferenceFieldUpdater.newUpdater(AnalysisType.class, ConcurrentHashMap.class, "unsafeAccessedFields"); - private static final AtomicReferenceFieldUpdater UNIQUE_CONSTANT_UPDATER = // - AtomicReferenceFieldUpdater.newUpdater(AnalysisType.class, ConstantContextSensitiveObject.class, "uniqueConstant"); + private static final AtomicReferenceFieldUpdater UNIQUE_CONSTANT_UPDATER = // + AtomicReferenceFieldUpdater.newUpdater(AnalysisType.class, AnalysisObject.class, "uniqueConstant"); @SuppressWarnings("rawtypes")// private static final AtomicReferenceFieldUpdater INTERCEPTORS_UPDATER = // @@ -140,13 +138,13 @@ public abstract class AnalysisType extends AnalysisElement implements WrappedJav /** The unique context insensitive analysis object for this type. */ private AnalysisObject contextInsensitiveAnalysisObject; /** Mapping from JavaConstant to the analysis ConstantObject. */ - private ConcurrentMap constantObjectsCache; + private ConcurrentMap constantObjectsCache; /** * A unique ConstantObject per analysis type. When the size of {@link #constantObjectsCache} is * above a threshold all the ConstantObject recorded until that moment are merged in the * {@link #uniqueConstant}. */ - private volatile ConstantContextSensitiveObject uniqueConstant; + private volatile AnalysisObject uniqueConstant; /** * Cache for the resolved methods. @@ -341,10 +339,10 @@ public AnalysisObject getUniqueConstantObject() { return uniqueConstant; } - public AnalysisObject getCachedConstantObject(PointsToAnalysis bb, JavaConstant constant) { + public AnalysisObject getCachedConstantObject(PointsToAnalysis bb, JavaConstant constant, Function constantTransformer) { /* - * Constant caching is only used we certain analysis policies. Ideally we would store the + * Constant caching is only used with certain analysis policies. Ideally we would store the * cache in the policy, but it is simpler to store the cache for each type. */ assert bb.analysisPolicy().needsConstantCache() : "The analysis policy doesn't specify the need for a constants cache."; @@ -356,41 +354,32 @@ public AnalysisObject getCachedConstantObject(PointsToAnalysis bb, JavaConstant return uniqueConstant; } - if (constantObjectsCache.size() >= PointstoOptions.MaxConstantObjectsPerType.getValue(bb.getOptions())) { + /* If maxConstantObjectsPerType is 0 there is no limit, i.e., we track all constants. */ + if (bb.maxConstantObjectsPerType() > 0 && constantObjectsCache.size() >= bb.maxConstantObjectsPerType()) { // The number of constant objects has increased above the limit, // merge the constants in the uniqueConstant and return it mergeConstantObjects(bb); return uniqueConstant; } - // Get the analysis ConstantObject modeling the JavaConstant - AnalysisObject result = constantObjectsCache.get(constant); - if (result == null) { - // Create a ConstantObject to model each JavaConstant - ConstantContextSensitiveObject newValue = new ConstantContextSensitiveObject(bb, this, constant); - ConstantContextSensitiveObject oldValue = constantObjectsCache.putIfAbsent(constant, newValue); - result = oldValue != null ? oldValue : newValue; - - if (PointstoOptions.ProfileConstantObjects.getValue(bb.getOptions())) { - ConstantObjectsProfiler.registerConstant(this); - ConstantObjectsProfiler.maybeDumpConstantHistogram(); - } - } - - return result; + /* Get the analysis ConstantObject modeling the JavaConstant. */ + return constantObjectsCache.computeIfAbsent(constant, constantTransformer); } private void mergeConstantObjects(PointsToAnalysis bb) { - ConstantContextSensitiveObject uConstant = new ConstantContextSensitiveObject(bb, this, null); + ConstantContextSensitiveObject uConstant = new ConstantContextSensitiveObject(bb, this); if (UNIQUE_CONSTANT_UPDATER.compareAndSet(this, null, uConstant)) { - constantObjectsCache.values().stream().forEach(constantObject -> { + constantObjectsCache.values().forEach(constantObject -> { /* * The order of the two lines below matters: setting the merged flag first, before * doing the actual merging, ensures that concurrent updates to the flow are still * merged correctly. */ - constantObject.setMergedWithUniqueConstantObject(); - constantObject.mergeInstanceFieldsFlows(bb, uniqueConstant); + if (constantObject instanceof ConstantContextSensitiveObject) { + ConstantContextSensitiveObject ct = (ConstantContextSensitiveObject) constantObject; + ct.setMergedWithUniqueConstantObject(); + ct.mergeInstanceFieldsFlows(bb, uniqueConstant); + } }); } } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/ConstantTypeState.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/ConstantTypeState.java new file mode 100644 index 000000000000..ec634493bc31 --- /dev/null +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/ConstantTypeState.java @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2022, 2022, 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.Collections; +import java.util.Iterator; +import java.util.Objects; + +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; + +import jdk.vm.ci.meta.JavaConstant; + +/** + * Models a {@link JavaConstant} flowing through the data flow graph. As soon as this constant is + * merged with any other object (even of the same type) it is erased and the state falls back to a + * {@link SingleTypeState}. + */ +public class ConstantTypeState extends SingleTypeState { + /** The constant object of this type state. */ + protected final JavaConstant constant; + + /** Creates a new type state from incoming objects. */ + public ConstantTypeState(PointsToAnalysis bb, int properties, AnalysisType type, JavaConstant constant) { + super(bb, false, properties, type); + assert !bb.analysisPolicy().isContextSensitiveAnalysis() : "The ConstantTypeState is indented to be used with a context insensitive analysis."; + this.constant = constant; + } + + /** Create a type state with the same content and a reversed canBeNull value. */ + protected ConstantTypeState(PointsToAnalysis bb, boolean canBeNull, ConstantTypeState other) { + super(bb, canBeNull, other); + this.constant = other.constant; + } + + public JavaConstant getConstant() { + return constant; + } + + @Override + public TypeState forCanBeNull(PointsToAnalysis bb, boolean stateCanBeNull) { + if (stateCanBeNull == this.canBeNull()) { + return this; + } else { + return new ConstantTypeState(bb, stateCanBeNull, this); + } + } + + /** + * Although this state wraps a {@link JavaConstant}, from the point of view of data flow + * modeling in the context-insensitive analysis it is indistinguishable from the + * context-insensitive analysis object corresponding to its type. More concretely this means + * that it shares the same field and array flows, i.e., it doesn't track field and array + * load/stores separately. This allows us to propagate constants through the type flows as + * values, without changing the shape of the data flow graphs, thus avoiding significant + * overhead. + */ + private AnalysisObject getAnalysisObject() { + return type.getContextInsensitiveAnalysisObject(); + } + + @Override + public Iterator objectsIterator(BigBang bb) { + return singletonIterator(getAnalysisObject()); + } + + @Override + protected Iterator objectsIterator(AnalysisType t) { + return type.equals(t) ? singletonIterator(getAnalysisObject()) : Collections.emptyIterator(); + } + + @Override + public void noteMerge(PointsToAnalysis bb) { + AnalysisError.shouldNotReachHere("ConstantTypeState doesn't support merging. It is indented to be used with a context insensitive analysis."); + } + + @Override + public boolean isConstant() { + return true; + } + + @Override + public int hashCode() { + return constant.hashCode() * 31; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof ConstantTypeState) { + ConstantTypeState that = (ConstantTypeState) obj; + return this.canBeNull == that.canBeNull && this.merged == that.merged && + this.exactType().equals(that.exactType()) && Objects.equals(this.constant, that.constant); + } + return false; + } + + @Override + public String toString() { + return "ConstantObject<" + constant.toString() + ">"; + } +} diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/DefaultAnalysisPolicy.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/DefaultAnalysisPolicy.java index 046322b9397d..1d11ff364c2b 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/DefaultAnalysisPolicy.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/DefaultAnalysisPolicy.java @@ -25,6 +25,7 @@ package com.oracle.graal.pointsto.typestate; import java.util.BitSet; +import java.util.Objects; import org.graalvm.compiler.options.OptionValues; @@ -35,7 +36,7 @@ import com.oracle.graal.pointsto.flow.AbstractVirtualInvokeTypeFlow; import com.oracle.graal.pointsto.flow.ActualReturnTypeFlow; import com.oracle.graal.pointsto.flow.CloneTypeFlow; -import com.oracle.graal.pointsto.flow.ContextInsensitiveFieldTypeFlow; +import com.oracle.graal.pointsto.flow.FieldTypeFlow; import com.oracle.graal.pointsto.flow.InvokeTypeFlow; import com.oracle.graal.pointsto.flow.MethodFlowsGraph; import com.oracle.graal.pointsto.flow.MethodTypeFlow; @@ -114,20 +115,31 @@ public AnalysisObject createHeapObject(PointsToAnalysis bb, AnalysisType type, B return type.getContextInsensitiveAnalysisObject(); } + /** + * In the context-insensitive analysis, although we track constant values using the + * {@link ConstantTypeState}, we don't track fields or array elements separately for constant + * objects. The field and array flows used for load/store operations of a "constant" are shared + * with the context-insensitive object of its declared type. See also {@link ConstantTypeState}. + */ @Override public AnalysisObject createConstantObject(PointsToAnalysis bb, JavaConstant constant, AnalysisType exactType) { return exactType.getContextInsensitiveAnalysisObject(); } + @Override + public ConstantTypeState constantTypeState(PointsToAnalysis bb, JavaConstant constant, AnalysisType exactType) { + return new ConstantTypeState(bb, 0, exactType, constant); + } + @Override public TypeState dynamicNewInstanceState(PointsToAnalysis bb, TypeState currentState, TypeState newState, BytecodePosition allocationSite, AnalysisContext allocationContext) { /* Just return the new type state as there is no allocation context. */ - return newState.forNonNull(bb); + return eraseConstant(bb, newState).forNonNull(bb); } @Override public TypeState cloneState(PointsToAnalysis bb, TypeState currentState, TypeState inputState, BytecodePosition cloneSite, AnalysisContext allocationContext) { - return inputState.forNonNull(bb); + return eraseConstant(bb, inputState).forNonNull(bb); } @Override @@ -139,12 +151,14 @@ public void linkClonedObjects(PointsToAnalysis bb, TypeFlow inputFlow, CloneT } @Override - public FieldTypeStore createFieldTypeStore(AnalysisObject object, AnalysisField field, AnalysisUniverse universe) { - return new UnifiedFieldTypeStore(field, object, new ContextInsensitiveFieldTypeFlow(field, field.getType(), object)); + public FieldTypeStore createFieldTypeStore(PointsToAnalysis bb, AnalysisObject object, AnalysisField field, AnalysisUniverse universe) { + assert object.isContextInsensitiveObject(); + return new UnifiedFieldTypeStore(field, object, new FieldTypeFlow(field, field.getType(), object)); } @Override public ArrayElementsTypeStore createArrayElementsTypeStore(AnalysisObject object, AnalysisUniverse universe) { + assert object.isContextInsensitiveObject(); if (object.type().isArray()) { if (aliasArrayTypeFlows) { /* Alias all array type flows using the elements type flow model of Object type. */ @@ -223,7 +237,9 @@ public void registerAsImplementationInvoked(InvokeTypeFlow invoke, MethodFlowsGr @Override public TypeState forContextInsensitiveTypeState(PointsToAnalysis bb, TypeState state) { - /* The type state is already context insensitive. */ + if (state instanceof ConstantTypeState) { + return TypeState.forExactType(bb, state.exactType(), state.canBeNull()); + } return state; } @@ -237,19 +253,44 @@ public MultiTypeState multiTypeState(PointsToAnalysis bb, boolean canBeNull, int return new MultiTypeState(bb, canBeNull, properties, typesBitSet); } + /* + * When a constant state is an operand of a union, or it is the input for a clone or + * dynamic-new-instance, then the constant gets erased. + */ + private static TypeState eraseConstant(PointsToAnalysis bb, TypeState state) { + if (state instanceof ConstantTypeState) { + /* Return an exact type state that essentially models all objects of the given type. */ + return TypeState.forExactType(bb, state.exactType(), state.canBeNull()); + } + return state; + } + @Override - public TypeState doUnion(PointsToAnalysis bb, SingleTypeState s1, SingleTypeState s2) { + public TypeState doUnion(PointsToAnalysis bb, SingleTypeState state1, SingleTypeState state2) { + if (state1 instanceof ConstantTypeState && state2 instanceof ConstantTypeState) { + ConstantTypeState cs1 = (ConstantTypeState) state1; + ConstantTypeState cs2 = (ConstantTypeState) state2; + /* If the states wrap the same constant return one of them preserving the "null". */ + if (cs1.equals(cs2)) { + return cs1; + } else if (Objects.equals(cs1.getConstant(), cs2.getConstant())) { + assert cs1.exactType().equals(cs2.exactType()); + boolean resultCanBeNull = state1.canBeNull() || state2.canBeNull(); + return cs1.canBeNull() == resultCanBeNull ? cs1 : cs2; + } + } + + /* Otherwise, when a constant state is an operand of a union the constant is erased. */ + TypeState s1 = eraseConstant(bb, state1); + TypeState s2 = eraseConstant(bb, state2); + boolean resultCanBeNull = s1.canBeNull() || s2.canBeNull(); if (s1.exactType().equals(s2.exactType())) { /* * The inputs have the same type, so the result is a SingleTypeState. Check if any of * the states has the right null state. */ - if (s1.canBeNull() == resultCanBeNull) { - return s1; - } else { - return s2; - } + return s1.canBeNull() == resultCanBeNull ? s1 : s2; } else { /* * The inputs have different types, so the result is a MultiTypeState. We know the @@ -264,7 +305,10 @@ public TypeState doUnion(PointsToAnalysis bb, SingleTypeState s1, SingleTypeStat } @Override - public TypeState doUnion(PointsToAnalysis bb, MultiTypeState s1, SingleTypeState s2) { + public TypeState doUnion(PointsToAnalysis bb, MultiTypeState s1, SingleTypeState state2) { + /* If ConstantTypeState is an operand of a union operation the constant is erased. */ + TypeState s2 = eraseConstant(bb, state2); + boolean resultCanBeNull = s1.canBeNull() || s2.canBeNull(); if (s1.containsType(s2.exactType())) { diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/SingleTypeState.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/SingleTypeState.java index e1897cc1f9b2..25feaf035d2e 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/SingleTypeState.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/SingleTypeState.java @@ -105,7 +105,7 @@ protected Iterator objectsIterator(AnalysisType t) { return type.equals(t) ? singletonIterator(type.getContextInsensitiveAnalysisObject()) : Collections.emptyIterator(); } - private static Iterator singletonIterator(T object) { + protected static Iterator singletonIterator(T object) { return new Iterator<>() { boolean hasNext = true; @@ -170,7 +170,7 @@ public boolean equals(Object o) { @Override public String toString() { - return "SingleType<" + (canBeNull ? "null," : "") + ">"; + return "SingleType<" + type.getName() + ", " + (canBeNull ? "null," : "") + ">"; } } 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 91ca0171ff8c..fb89b57f405a 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 @@ -185,9 +185,7 @@ public static TypeState forNonNullObject(PointsToAnalysis bb, AnalysisObject obj public static TypeState forConstant(PointsToAnalysis bb, JavaConstant constant, AnalysisType exactType) { assert !constant.isNull(); assert exactType.isArray() || (exactType.isInstanceClass() && !Modifier.isAbstract(exactType.getModifiers())) : exactType; - - AnalysisObject constantObject = bb.analysisPolicy().createConstantObject(bb, constant, exactType); - return forNonNullObject(bb, constantObject); + return bb.analysisPolicy().constantTypeState(bb, constant, exactType); } public static SingleTypeState forExactType(PointsToAnalysis bb, AnalysisType exactType, boolean canBeNull) { diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/util/ConcurrentLightHashMap.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/util/ConcurrentLightHashMap.java index 55aef1f5471a..c94d86cc6cca 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/util/ConcurrentLightHashMap.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/util/ConcurrentLightHashMap.java @@ -36,8 +36,8 @@ /** * Implements a hash map that is concurrent, backed by a concurrent hash map, and memory efficient. - * The memory efficiency comes from the fact that the map is initialized only when the set contains - * more than one entry. When it contains a single entry it is simply stored in a field as a + * The memory efficiency comes from the fact that the underlying map is initialized only when it + * contains more than one entry. When it contains a single entry it is simply stored in a field as a * {@link SimpleImmutableEntry}. When the map is empty the field is null. In situations where is * likely that the map will contain no or only one entry there is no memory overhead incurred by * allocating the map.