From bee323624def2a02d0e9fea7bc767f5d7f05ebd1 Mon Sep 17 00:00:00 2001 From: Codrut Stancu Date: Mon, 16 May 2022 22:47:31 -0700 Subject: [PATCH 1/2] Simplify TypeState. --- substratevm/mx.substratevm/suite.py | 2 + .../AnalysisObjectScanningObserver.java | 18 +- .../oracle/graal/pointsto/AnalysisPolicy.java | 76 + .../BytecodeSensitiveAnalysisPolicy.java | 444 ------ .../graal/pointsto/PointsToAnalysis.java | 6 +- .../pointsto/flow/ArrayCopyTypeFlow.java | 4 +- .../flow/ContextInsensitiveFieldTypeFlow.java | 2 +- .../pointsto/flow/LoadFieldTypeFlow.java | 2 +- .../graal/pointsto/flow/MethodTypeFlow.java | 2 +- .../pointsto/flow/OffsetLoadTypeFlow.java | 6 +- .../pointsto/flow/OffsetStoreTypeFlow.java | 6 +- .../pointsto/flow/StoreFieldTypeFlow.java | 2 +- .../BytecodeSensitiveAnalysisPolicy.java | 1337 +++++++++++++++++ .../ContextSensitiveMultiTypeState.java | 289 ++++ .../ContextSensitiveSingleTypeState.java | 178 +++ .../context/free}/DefaultAnalysisPolicy.java | 198 ++- .../ContextSensitiveAnalysisObject.java | 44 - .../pointsto/typestate/MultiTypeState.java | 238 +-- .../pointsto/typestate/PointsToStats.java | 8 +- .../pointsto/typestate/SingleTypeState.java | 128 +- .../graal/pointsto/typestate/TypeState.java | 1159 +------------- .../pointsto/typestate/TypeStateUtils.java | 97 +- .../oracle/graal/pointsto/util/ListUtils.java | 148 ++ .../svm/hosted/NativeImageGenerator.java | 4 +- 24 files changed, 2449 insertions(+), 1949 deletions(-) delete mode 100644 substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/BytecodeSensitiveAnalysisPolicy.java create mode 100644 substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/context/bytecode/BytecodeSensitiveAnalysisPolicy.java create mode 100644 substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/context/bytecode/ContextSensitiveMultiTypeState.java create mode 100644 substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/context/bytecode/ContextSensitiveSingleTypeState.java rename substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/{ => flow/context/free}/DefaultAnalysisPolicy.java (68%) create mode 100644 substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/util/ListUtils.java diff --git a/substratevm/mx.substratevm/suite.py b/substratevm/mx.substratevm/suite.py index d034e5fb406a..fa8d9c0917cb 100644 --- a/substratevm/mx.substratevm/suite.py +++ b/substratevm/mx.substratevm/suite.py @@ -1497,6 +1497,8 @@ "com.oracle.graal.pointsto.typestate", "com.oracle.graal.pointsto.infrastructure", "com.oracle.graal.pointsto.flow.context.object", + "com.oracle.graal.pointsto.flow.context.free", + "com.oracle.graal.pointsto.flow.context.bytecode", ], "requires": [ "java.management", 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 b0458264c8bf..28d73673ec54 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 @@ -68,12 +68,9 @@ 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); - if (!fieldTypeFlow.getState().containsObject(constantObject)) { - /* Add the new constant to the field's flow state. */ - TypeState constantTypeState = TypeState.forNonNullObject(analysis, constantObject); - return fieldTypeFlow.addState(analysis, constantTypeState); - } - return false; + /* Add the new constant to the field's flow state. */ + TypeState constantTypeState = TypeState.forNonNullObject(analysis, constantObject); + return fieldTypeFlow.addState(analysis, constantTypeState); } /** @@ -111,12 +108,9 @@ public boolean forNonNullArrayElement(JavaConstant array, AnalysisType arrayType ArrayElementsTypeFlow arrayObjElementsFlow = getArrayElementsFlow(array, arrayType); PointsToAnalysis analysis = getAnalysis(); AnalysisObject constantObject = bb.analysisPolicy().createConstantObject(analysis, elementConstant, elementType); - if (!arrayObjElementsFlow.getState().containsObject(constantObject)) { - /* Add the constant element to the constant's array type flow. */ - TypeState elementTypeState = TypeState.forNonNullObject(analysis, constantObject); - return arrayObjElementsFlow.addState(analysis, elementTypeState); - } - return false; + /* Add the constant element to the constant's array type flow. */ + TypeState elementTypeState = TypeState.forNonNullObject(analysis, constantObject); + return arrayObjElementsFlow.addState(analysis, elementTypeState); } /** 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 60c96b590f0b..19e2fea5b872 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 @@ -24,6 +24,8 @@ */ package com.oracle.graal.pointsto; +import java.util.BitSet; + import org.graalvm.compiler.options.OptionValues; import com.oracle.graal.pointsto.api.PointstoOptions; @@ -41,7 +43,10 @@ import com.oracle.graal.pointsto.meta.AnalysisType; import com.oracle.graal.pointsto.meta.AnalysisUniverse; import com.oracle.graal.pointsto.meta.PointsToAnalysisMethod; +import com.oracle.graal.pointsto.typestate.MultiTypeState; +import com.oracle.graal.pointsto.typestate.SingleTypeState; import com.oracle.graal.pointsto.typestate.TypeState; +import com.oracle.graal.pointsto.typestate.TypeStateUtils; import com.oracle.graal.pointsto.typestore.ArrayElementsTypeStore; import com.oracle.graal.pointsto.typestore.FieldTypeStore; @@ -169,4 +174,75 @@ public int makePropertiesForUnion(TypeState s1, TypeState s2) { /* The default analysis policy doesn't use properties. */ return 0; } + + /** + * Simplifies a type state by replacing all context sensitive objects with context insensitive + * objects. + */ + public abstract TypeState forContextInsensitiveTypeState(PointsToAnalysis bb, TypeState state); + + public abstract SingleTypeState singleTypeState(PointsToAnalysis bb, boolean canBeNull, int properties, AnalysisType type, AnalysisObject... objects); + + public abstract MultiTypeState multiTypeState(PointsToAnalysis bb, boolean canBeNull, int properties, BitSet typesBitSet, AnalysisObject... objects); + + public abstract TypeState doUnion(PointsToAnalysis bb, SingleTypeState s1, SingleTypeState s2); + + public abstract TypeState doUnion(PointsToAnalysis bb, MultiTypeState s1, SingleTypeState s2); + + public abstract TypeState doUnion(PointsToAnalysis bb, MultiTypeState s1, MultiTypeState s2); + + @SuppressWarnings("static-method") + public final TypeState doIntersection(PointsToAnalysis bb, SingleTypeState s1, SingleTypeState s2) { + assert !bb.extendedAsserts() || TypeStateUtils.isContextInsensitiveTypeState(bb, s2) : "Current implementation limitation."; + boolean resultCanBeNull = s1.canBeNull() && s2.canBeNull(); + if (s1.exactType().equals(s2.exactType())) { + /* The inputs have the same type, the result will be s1. */ + return s1.forCanBeNull(bb, resultCanBeNull); + } else { + /* The inputs have different types then the result is empty or null. */ + return TypeState.forEmpty().forCanBeNull(bb, resultCanBeNull); + } + } + + @SuppressWarnings("static-method") + public final TypeState doIntersection(PointsToAnalysis bb, SingleTypeState s1, MultiTypeState s2) { + assert !bb.extendedAsserts() || TypeStateUtils.isContextInsensitiveTypeState(bb, s2) : "Current implementation limitation."; + boolean resultCanBeNull = s1.canBeNull() && s2.canBeNull(); + if (s2.containsType(s1.exactType())) { + return s1.forCanBeNull(bb, resultCanBeNull); + } else { + return TypeState.forEmpty().forCanBeNull(bb, resultCanBeNull); + } + } + + public abstract TypeState doIntersection(PointsToAnalysis bb, MultiTypeState s1, SingleTypeState s2); + + public abstract TypeState doIntersection(PointsToAnalysis bb, MultiTypeState s1, MultiTypeState s2); + + @SuppressWarnings("static-method") + public final TypeState doSubtraction(PointsToAnalysis bb, SingleTypeState s1, SingleTypeState s2) { + assert !bb.extendedAsserts() || TypeStateUtils.isContextInsensitiveTypeState(bb, s2) : "Current implementation limitation."; + boolean resultCanBeNull = s1.canBeNull() && !s2.canBeNull(); + if (s1.exactType().equals(s2.exactType())) { + return TypeState.forEmpty().forCanBeNull(bb, resultCanBeNull); + } else { + return s1.forCanBeNull(bb, resultCanBeNull); + } + } + + @SuppressWarnings("static-method") + public final TypeState doSubtraction(PointsToAnalysis bb, SingleTypeState s1, MultiTypeState s2) { + assert !bb.extendedAsserts() || TypeStateUtils.isContextInsensitiveTypeState(bb, s2) : "Current implementation limitation."; + boolean resultCanBeNull = s1.canBeNull() && !s2.canBeNull(); + if (s2.containsType(s1.exactType())) { + return TypeState.forEmpty().forCanBeNull(bb, resultCanBeNull); + } else { + return s1.forCanBeNull(bb, resultCanBeNull); + } + } + + public abstract TypeState doSubtraction(PointsToAnalysis bb, MultiTypeState s1, SingleTypeState s2); + + public abstract TypeState doSubtraction(PointsToAnalysis bb, MultiTypeState s1, MultiTypeState s2); + } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/BytecodeSensitiveAnalysisPolicy.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/BytecodeSensitiveAnalysisPolicy.java deleted file mode 100644 index 153e3e78e2c0..000000000000 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/BytecodeSensitiveAnalysisPolicy.java +++ /dev/null @@ -1,444 +0,0 @@ -/* - * Copyright (c) 2016, 2017, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ -package com.oracle.graal.pointsto; - -import java.lang.reflect.Modifier; -import java.util.ArrayList; -import java.util.Collection; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; - -import org.graalvm.compiler.options.OptionValues; - -import com.oracle.graal.pointsto.api.PointstoOptions; -import com.oracle.graal.pointsto.flow.AbstractSpecialInvokeTypeFlow; -import com.oracle.graal.pointsto.flow.AbstractVirtualInvokeTypeFlow; -import com.oracle.graal.pointsto.flow.ActualReturnTypeFlow; -import com.oracle.graal.pointsto.flow.ArrayElementsTypeFlow; -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.MethodFlowsGraph; -import com.oracle.graal.pointsto.flow.MethodTypeFlow; -import com.oracle.graal.pointsto.flow.TypeFlow; -import com.oracle.graal.pointsto.flow.context.AnalysisContext; -import com.oracle.graal.pointsto.flow.context.BytecodeLocation; -import com.oracle.graal.pointsto.flow.context.bytecode.BytecodeAnalysisContextPolicy; -import com.oracle.graal.pointsto.flow.context.object.AllocationContextSensitiveObject; -import com.oracle.graal.pointsto.flow.context.object.AnalysisObject; -import com.oracle.graal.pointsto.meta.AnalysisField; -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.PointsToAnalysisMethod; -import com.oracle.graal.pointsto.typestate.TypeState; -import com.oracle.graal.pointsto.typestate.TypeState.TypesObjectsIterator; -import com.oracle.graal.pointsto.typestore.ArrayElementsTypeStore; -import com.oracle.graal.pointsto.typestore.FieldTypeStore; -import com.oracle.graal.pointsto.typestore.SplitArrayElementsTypeStore; -import com.oracle.graal.pointsto.typestore.SplitFieldTypeStore; -import com.oracle.graal.pointsto.typestore.UnifiedArrayElementsTypeStore; -import com.oracle.graal.pointsto.typestore.UnifiedFieldTypeStore; - -import jdk.vm.ci.code.BytecodePosition; -import jdk.vm.ci.meta.JavaConstant; - -public class BytecodeSensitiveAnalysisPolicy extends AnalysisPolicy { - - private BytecodeAnalysisContextPolicy contextPolicy; - - public BytecodeSensitiveAnalysisPolicy(OptionValues options) { - super(options); - this.contextPolicy = new BytecodeAnalysisContextPolicy(); - } - - @Override - public boolean isContextSensitiveAnalysis() { - return true; - } - - @Override - public BytecodeAnalysisContextPolicy contextPolicy() { - return contextPolicy; - } - - @Override - public boolean needsConstantCache() { - return true; - } - - @Override - public boolean isSummaryObject(AnalysisObject object) { - /* Context insensitive objects summarize context sensitive objects of the same type. */ - return object.isContextInsensitiveObject(); - } - - @Override - public boolean isMergingEnabled() { - // the context sensitive analysis relies on proper signal of merging - return true; - } - - @Override - public void noteMerge(PointsToAnalysis bb, TypeState t) { - t.noteMerge(bb); - } - - @Override - public void noteMerge(PointsToAnalysis bb, AnalysisObject... a) { - for (AnalysisObject o : a) { - o.noteMerge(bb); - } - } - - @Override - public void noteMerge(PointsToAnalysis bb, AnalysisObject o) { - o.noteMerge(bb); - } - - @Override - public boolean isContextSensitiveAllocation(PointsToAnalysis bb, AnalysisType type, AnalysisContext allocationContext) { - return bb.trackConcreteAnalysisObjects(type); - } - - @Override - public AnalysisObject createHeapObject(PointsToAnalysis bb, AnalysisType type, BytecodeLocation allocationSite, AnalysisContext allocationContext) { - assert PointstoOptions.AllocationSiteSensitiveHeap.getValue(options); - if (isContextSensitiveAllocation(bb, type, allocationContext)) { - return new AllocationContextSensitiveObject(bb, type, allocationSite, allocationContext); - } else { - return type.getContextInsensitiveAnalysisObject(); - } - } - - @Override - 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); - } else { - return exactType.getContextInsensitiveAnalysisObject(); - } - } - - @Override - public TypeState dynamicNewInstanceState(PointsToAnalysis bb, TypeState currentState, TypeState newState, BytecodeLocation allocationSite, AnalysisContext allocationContext) { - /* Generate a heap object for every new incoming type. */ - TypeState resultState = TypeState.forEmpty(); - for (AnalysisType type : newState.types(bb)) { - if (!currentState.containsType(type)) { - TypeState typeState = TypeState.forAllocation(bb, allocationSite, type, allocationContext); - resultState = TypeState.forUnion(bb, resultState, typeState); - } - } - assert !resultState.canBeNull(); - return resultState; - } - - @Override - public TypeState cloneState(PointsToAnalysis bb, TypeState currentState, TypeState inputState, BytecodeLocation cloneSite, AnalysisContext allocationContext) { - TypeState resultState; - if (inputState.isEmpty() || inputState.isNull()) { - /* Nothing to be cloned if the input state is not a concrete type state. */ - resultState = inputState.forNonNull(bb); - } else { - resultState = TypeState.forEmpty(); - for (AnalysisType type : inputState.types(bb)) { - if (!currentState.containsType(type)) { - TypeState typeState = TypeState.forClone(bb, cloneSite, type, allocationContext); - resultState = TypeState.forUnion(bb, resultState, typeState); - } - } - } - assert !resultState.canBeNull(); - return resultState; - } - - @Override - public void linkClonedObjects(PointsToAnalysis bb, TypeFlow inputFlow, CloneTypeFlow cloneFlow, BytecodePosition source) { - TypeState inputState = inputFlow.getState(); - TypeState cloneState = cloneFlow.getState(); - - for (AnalysisType type : inputState.types(bb)) { - if (type.isArray()) { - if (bb.analysisPolicy().aliasArrayTypeFlows()) { - /* All arrays are aliased, no need to model the array clone operation. */ - continue; - } - - /* The object array clones must also get the elements flows of the originals. */ - for (AnalysisObject originalObject : inputState.objects(type)) { - if (originalObject.isPrimitiveArray() || originalObject.isEmptyObjectArrayConstant(bb)) { - /* Nothing to read from a primitive array or an empty array constant. */ - continue; - } - ArrayElementsTypeFlow originalObjectElementsFlow = originalObject.getArrayElementsFlow(bb, false); - - for (AnalysisObject cloneObject : cloneState.objects(type)) { - if (cloneObject.isPrimitiveArray() || cloneObject.isEmptyObjectArrayConstant(bb)) { - /* Cannot write to a primitive array or an empty array constant. */ - continue; - } - ArrayElementsTypeFlow cloneObjectElementsFlow = cloneObject.getArrayElementsFlow(bb, true); - originalObjectElementsFlow.addUse(bb, cloneObjectElementsFlow); - } - } - } else { - - /* The object clones must get field flows of the originals. */ - for (AnalysisObject originalObject : inputState.objects(type)) { - /* Link all the field flows of the original to the clone. */ - for (AnalysisField field : type.getInstanceFields(true)) { - FieldTypeFlow originalObjectFieldFlow = originalObject.getInstanceFieldFlow(bb, inputFlow, source, field, false); - - for (AnalysisObject cloneObject : cloneState.objects(type)) { - FieldTypeFlow cloneObjectFieldFlow = cloneObject.getInstanceFieldFlow(bb, cloneFlow, source, field, true); - originalObjectFieldFlow.addUse(bb, cloneObjectFieldFlow); - } - } - } - } - } - } - - @Override - public BytecodeLocation createAllocationSite(PointsToAnalysis bb, int bci, AnalysisMethod method) { - return BytecodeLocation.create(bci, method); - } - - @Override - public FieldTypeStore createFieldTypeStore(AnalysisObject object, AnalysisField field, AnalysisUniverse universe) { - assert PointstoOptions.AllocationSiteSensitiveHeap.getValue(options); - if (object.isContextInsensitiveObject()) { - /* - * Write flow is context-sensitive and read flow is context-insensitive. This split is - * used to model context sensitivity and context merging for fields of this - * context-insensitive object, and the interaction with the fields of context-sensitive - * objects of the same type. - * - * All values written to fields of context-sensitive receivers are also reflected to the - * context-insensitive receiver *read* flow, but without any context information, such - * that all the reads from the fields of the context insensitive object reflect all the - * types written to the context-sensitive ones, but without triggering merging. - * - * Once the context-sensitive receiver object is marked as merged, i.e., it looses its - * context sensitivity, the field flows are routed to the context-insensitive receiver - * *write* flow, thus triggering their merging. See ContextSensitiveAnalysisObject. - * mergeInstanceFieldFlow(). - */ - FieldTypeFlow writeFlow = new FieldTypeFlow(field, field.getType(), object); - ContextInsensitiveFieldTypeFlow readFlow = new ContextInsensitiveFieldTypeFlow(field, field.getType(), object); - return new SplitFieldTypeStore(field, object, writeFlow, readFlow); - } else { - return new UnifiedFieldTypeStore(field, object); - } - } - - @Override - public ArrayElementsTypeStore createArrayElementsTypeStore(AnalysisObject object, AnalysisUniverse universe) { - assert PointstoOptions.AllocationSiteSensitiveHeap.getValue(options); - if (object.type().isArray()) { - if (aliasArrayTypeFlows) { - /* Alias all array type flows using the elements type flow model of Object type. */ - if (object.type().getElementalType().isJavaLangObject() && object.isContextInsensitiveObject()) { - // return getArrayElementsTypeStore(object); - return new UnifiedArrayElementsTypeStore(object); - } - return universe.objectType().getArrayClass().getContextInsensitiveAnalysisObject().getArrayElementsTypeStore(); - } - return getArrayElementsTypeStore(object); - } else { - return null; - } - } - - private static ArrayElementsTypeStore getArrayElementsTypeStore(AnalysisObject object) { - if (object.isContextInsensitiveObject()) { - return new SplitArrayElementsTypeStore(object); - } else { - return new UnifiedArrayElementsTypeStore(object); - } - } - - @Override - public AbstractVirtualInvokeTypeFlow createVirtualInvokeTypeFlow(BytecodePosition invokeLocation, AnalysisType receiverType, PointsToAnalysisMethod targetMethod, - TypeFlow[] actualParameters, ActualReturnTypeFlow actualReturn, BytecodeLocation location) { - return new BytecodeSensitiveVirtualInvokeTypeFlow(invokeLocation, receiverType, targetMethod, actualParameters, actualReturn, location); - } - - @Override - public AbstractSpecialInvokeTypeFlow createSpecialInvokeTypeFlow(BytecodePosition invokeLocation, AnalysisType receiverType, PointsToAnalysisMethod targetMethod, - TypeFlow[] actualParameters, ActualReturnTypeFlow actualReturn, BytecodeLocation location) { - return new BytecodeSensitiveSpecialInvokeTypeFlow(invokeLocation, receiverType, targetMethod, actualParameters, actualReturn, location); - } - - /** - * Bytecode context sensitive implementation of the invoke virtual type flow update. - * - * TODO Can we merge the slow path (i.e., this class) and fast path (i.e., the default, context - * insensitive virtual invoke implementation) to be able to fall back to fast path when context - * sensitivity is disabled or reaches budget threshold? - */ - private static class BytecodeSensitiveVirtualInvokeTypeFlow extends AbstractVirtualInvokeTypeFlow { - - /* - * Remember all the callee clones that were already linked in each context at this - * invocation site to avoid redundant relinking. MethodFlows is unique for each method type - * flow and context combination. - */ - private final ConcurrentMap calleesFlows = new ConcurrentHashMap<>(4, 0.75f, 1); - private final AnalysisContext callerContext; - - protected BytecodeSensitiveVirtualInvokeTypeFlow(BytecodePosition invokeLocation, AnalysisType receiverType, PointsToAnalysisMethod targetMethod, - TypeFlow[] actualParameters, ActualReturnTypeFlow actualReturn, BytecodeLocation location) { - super(invokeLocation, receiverType, targetMethod, actualParameters, actualReturn, location); - callerContext = null; - } - - protected BytecodeSensitiveVirtualInvokeTypeFlow(PointsToAnalysis bb, MethodFlowsGraph methodFlows, BytecodeSensitiveVirtualInvokeTypeFlow original) { - super(bb, methodFlows, original); - callerContext = methodFlows.context(); - } - - @Override - public TypeFlow copy(PointsToAnalysis bb, MethodFlowsGraph methodFlows) { - return new BytecodeSensitiveVirtualInvokeTypeFlow(bb, methodFlows, this); - } - - @Override - public void onObservedUpdate(PointsToAnalysis bb) { - assert this.isClone(); - - /* - * Capture the current receiver state before the update. The type state objects are - * immutable and a later call to getState() can yield a different value. - */ - TypeState receiverState = getReceiver().getState(); - receiverState = filterReceiverState(bb, receiverState); - - /* Use the tandem types - objects iterator. */ - TypesObjectsIterator toi = receiverState.getTypesObjectsIterator(); - while (toi.hasNextType()) { - AnalysisType type = toi.nextType(); - - AnalysisMethod method = type.resolveConcreteMethod(getTargetMethod()); - if (method == null || Modifier.isAbstract(method.getModifiers())) { - /* - * Type states can be conservative, i.e., we can have receiver types that do not - * implement the method. Just ignore such types. - */ - while (toi.hasNextObject(type)) { - // skip the rest of the objects of the same type - toi.nextObject(type); - } - continue; - } - - assert !Modifier.isAbstract(method.getModifiers()); - - MethodTypeFlow callee = PointsToAnalysis.assertPointsToAnalysisMethod(method).getTypeFlow(); - - while (toi.hasNextObject(type)) { - AnalysisObject actualReceiverObject = toi.nextObject(type); - - // get the context based on the actualReceiverObject - AnalysisContext calleeContext = bb.contextPolicy().calleeContext(bb, actualReceiverObject, callerContext, callee); - - MethodFlowsGraph calleeFlows = callee.addContext(bb, calleeContext, this); - - if (calleesFlows.put(calleeFlows, Boolean.TRUE) == null) { - /* register the analysis method as a callee for this invoke */ - addCallee(calleeFlows.getMethod()); - /* linkCallee() does not link the receiver object. */ - linkCallee(bb, false, calleeFlows); - } - - updateReceiver(bb, calleeFlows, actualReceiverObject); - } - - } - } - - @Override - public void onObservedSaturated(PointsToAnalysis bb, TypeFlow observed) { - assert this.isClone(); - /* When the receiver flow saturates start observing the flow of the receiver type. */ - replaceObservedWith(bb, receiverType); - } - - @Override - public Collection getCalleesFlows(PointsToAnalysis bb) { - return new ArrayList<>(calleesFlows.keySet()); - } - } - - private static final class BytecodeSensitiveSpecialInvokeTypeFlow extends AbstractSpecialInvokeTypeFlow { - - /** - * Contexts of the resolved method. - */ - private ConcurrentMap calleesFlows = new ConcurrentHashMap<>(4, 0.75f, 1); - - BytecodeSensitiveSpecialInvokeTypeFlow(BytecodePosition invokeLocation, AnalysisType receiverType, PointsToAnalysisMethod targetMethod, - TypeFlow[] actualParameters, ActualReturnTypeFlow actualReturn, BytecodeLocation location) { - super(invokeLocation, receiverType, targetMethod, actualParameters, actualReturn, location); - } - - private BytecodeSensitiveSpecialInvokeTypeFlow(PointsToAnalysis bb, MethodFlowsGraph methodFlows, BytecodeSensitiveSpecialInvokeTypeFlow original) { - super(bb, methodFlows, original); - } - - @Override - public TypeFlow copy(PointsToAnalysis bb, MethodFlowsGraph methodFlows) { - return new BytecodeSensitiveSpecialInvokeTypeFlow(bb, methodFlows, this); - } - - @Override - public void onObservedUpdate(PointsToAnalysis bb) { - assert this.isClone() || this.isContextInsensitive(); - /* The receiver state has changed. Process the invoke. */ - - initCallee(); - - TypeState invokeState = filterReceiverState(bb, getReceiver().getState()); - for (AnalysisObject receiverObject : invokeState.objects()) { - AnalysisContext calleeContext = bb.contextPolicy().calleeContext(bb, receiverObject, callerContext, callee); - MethodFlowsGraph calleeFlows = callee.addContext(bb, calleeContext, this); - - if (calleesFlows.putIfAbsent(calleeFlows, Boolean.TRUE) == null) { - linkCallee(bb, false, calleeFlows); - } - - updateReceiver(bb, calleeFlows, receiverObject); - } - } - - @Override - public Collection getCalleesFlows(PointsToAnalysis bb) { - return new ArrayList<>(calleesFlows.keySet()); - } - } - -} 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 f9d570b00d3e..e9a9e5cbb557 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 @@ -110,6 +110,7 @@ public abstract class PointsToAnalysis implements BigBang { private TypeFlow allSynchronizedTypeFlow; protected final AnalysisUniverse universe; + private final AnalysisPolicy analysisPolicy; protected final AnalysisMetaAccess metaAccess; protected final HostVM hostVM; private final UnsupportedFeatures unsupportedFeatures; @@ -150,6 +151,7 @@ public PointsToAnalysis(OptionValues options, AnalysisUniverse universe, HostedP this.analysisTimer = timerCollection.get(TimerCollection.Registry.ANALYSIS); this.universe = universe; + this.analysisPolicy = universe.analysisPolicy(); this.metaAccess = (AnalysisMetaAccess) providers.getMetaAccess(); this.replacements = providers.getReplacements(); this.unsupportedFeatures = unsupportedFeatures; @@ -329,11 +331,11 @@ public void cleanupAfterAnalysis() { @Override public AnalysisPolicy analysisPolicy() { - return universe.analysisPolicy(); + return analysisPolicy; } public AnalysisContextPolicy contextPolicy() { - return universe.analysisPolicy().getContextPolicy(); + return analysisPolicy.getContextPolicy(); } @Override diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/ArrayCopyTypeFlow.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/ArrayCopyTypeFlow.java index a029c7ab570a..c43873980f4d 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/ArrayCopyTypeFlow.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/ArrayCopyTypeFlow.java @@ -129,7 +129,7 @@ private static void processStates(PointsToAnalysis bb, TypeState srcArrayState, * The source and destination array can have reference types which, although must be * compatible, can be different. */ - for (AnalysisObject srcArrayObject : srcArrayState.objects()) { + for (AnalysisObject srcArrayObject : srcArrayState.objects(bb)) { if (!srcArrayObject.type().isArray()) { /* * Ignore non-array type. Sometimes the analysis cannot filter out non-array types @@ -146,7 +146,7 @@ private static void processStates(PointsToAnalysis bb, TypeState srcArrayState, ArrayElementsTypeFlow srcArrayElementsFlow = srcArrayObject.getArrayElementsFlow(bb, false); - for (AnalysisObject dstArrayObject : dstArrayState.objects()) { + for (AnalysisObject dstArrayObject : dstArrayState.objects(bb)) { if (!dstArrayObject.type().isArray()) { /* Ignore non-array type. */ continue; diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/ContextInsensitiveFieldTypeFlow.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/ContextInsensitiveFieldTypeFlow.java index f6bed5624119..f81daedacbfb 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/ContextInsensitiveFieldTypeFlow.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/ContextInsensitiveFieldTypeFlow.java @@ -51,7 +51,7 @@ public boolean addState(PointsToAnalysis bb, TypeState add) { * objects as merged. The field sink type flow collects field concrete types, but it doesn't * need to track individual context-sensitive objects. */ - return super.addState(bb, TypeState.forContextInsensitiveTypeState(bb, add)); + return super.addState(bb, bb.analysisPolicy().forContextInsensitiveTypeState(bb, add)); } @Override diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/LoadFieldTypeFlow.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/LoadFieldTypeFlow.java index e49a5234728f..5bc6c6a52aa9 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/LoadFieldTypeFlow.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/LoadFieldTypeFlow.java @@ -132,7 +132,7 @@ public void onObservedUpdate(PointsToAnalysis bb) { TypeState objectState = objectFlow.getState(); objectState = filterObjectState(bb, objectState); /* Iterate over the receiver objects. */ - for (AnalysisObject object : objectState.objects()) { + for (AnalysisObject object : objectState.objects(bb)) { /* Get the field flow corresponding to the receiver object. */ FieldTypeFlow fieldFlow = object.getInstanceFieldFlow(bb, objectFlow, source, field, 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 2222fe4188fb..cc287d3f9383 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 @@ -201,7 +201,7 @@ public TypeState foldTypeFlow(PointsToAnalysis bb, TypeFlow originalTypeFlow) * objects, so that the union operation doesn't merge the concrete objects with abstract * objects. */ - TypeState cloneStateCopy = TypeState.forContextInsensitiveTypeState(bb, cloneState); + TypeState cloneStateCopy = bb.analysisPolicy().forContextInsensitiveTypeState(bb, cloneState); result = TypeState.forUnion(bb, result, cloneStateCopy); } return result; 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 fd734de31634..e764b2e91605 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 @@ -130,7 +130,7 @@ public void onObservedUpdate(PointsToAnalysis bb) { assert this.isClone(); TypeState arrayState = getObjectState(); - for (AnalysisObject object : arrayState.objects()) { + for (AnalysisObject object : arrayState.objects(bb)) { if (bb.analysisPolicy().relaxTypeFlowConstraints() && !object.type().isArray()) { /* Ignore non-array types when type flow constraints are relaxed. */ continue; @@ -208,7 +208,7 @@ public void onObservedUpdate(PointsToAnalysis bb) { assert this.isClone(); TypeState objectState = getObjectState(); - for (AnalysisObject object : objectState.objects()) { + for (AnalysisObject object : objectState.objects(bb)) { AnalysisType objectType = object.type(); if (objectType.isArray()) { if (object.isPrimitiveArray() || object.isEmptyObjectArrayConstant(bb)) { @@ -281,7 +281,7 @@ public void onObservedUpdate(PointsToAnalysis bb) { TypeState objectState = getObjectState(); - for (AnalysisObject object : objectState.objects()) { + for (AnalysisObject object : objectState.objects(bb)) { AnalysisType objectType = object.type(); assert !objectType.isArray(); diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/OffsetStoreTypeFlow.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/OffsetStoreTypeFlow.java index 9775c2d5d00e..632057aef51f 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/OffsetStoreTypeFlow.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/OffsetStoreTypeFlow.java @@ -147,7 +147,7 @@ public void onObservedUpdate(PointsToAnalysis bb) { TypeState objectState = objectFlow.getState(); /* Iterate over the receiver objects. */ - for (AnalysisObject object : objectState.objects()) { + for (AnalysisObject object : objectState.objects(bb)) { if (bb.analysisPolicy().relaxTypeFlowConstraints() && !object.type().isArray()) { /* Ignore non-array types when type flow constraints are relaxed. */ continue; @@ -273,7 +273,7 @@ public void onObservedUpdate(PointsToAnalysis bb) { TypeState objectState = objectFlow.getState(); /* Iterate over the receiver objects. */ - for (AnalysisObject object : objectState.objects()) { + for (AnalysisObject object : objectState.objects(bb)) { AnalysisType type = object.type(); if (type.isArray()) { if (object.isPrimitiveArray() || object.isEmptyObjectArrayConstant(bb)) { @@ -374,7 +374,7 @@ public void onObservedUpdate(PointsToAnalysis bb) { TypeState objectState = objectFlow.getState(); /* Iterate over the receiver objects. */ - for (AnalysisObject object : objectState.objects()) { + for (AnalysisObject object : objectState.objects(bb)) { AnalysisType type = object.type(); assert !type.isArray(); diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/StoreFieldTypeFlow.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/StoreFieldTypeFlow.java index 17aba66cfab0..150eff29b2be 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/StoreFieldTypeFlow.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/StoreFieldTypeFlow.java @@ -163,7 +163,7 @@ public void onObservedUpdate(PointsToAnalysis bb) { TypeState objectState = objectFlow.getState(); objectState = filterObjectState(bb, objectState); /* Iterate over the receiver objects. */ - for (AnalysisObject receiver : objectState.objects()) { + for (AnalysisObject receiver : objectState.objects(bb)) { /* Get the field flow corresponding to the receiver object. */ FieldTypeFlow fieldFlow = receiver.getInstanceFieldFlow(bb, objectFlow, source, field, true); /* Register the field flow as a use, if not already registered. */ 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 new file mode 100644 index 000000000000..f707c502f48b --- /dev/null +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/context/bytecode/BytecodeSensitiveAnalysisPolicy.java @@ -0,0 +1,1337 @@ +/* + * Copyright (c) 2016, 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.graal.pointsto.flow.context.bytecode; + +import static com.oracle.graal.pointsto.util.ListUtils.getTLArrayList; + +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.BitSet; +import java.util.Collection; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import com.oracle.graal.pointsto.util.AnalysisError; +import org.graalvm.compiler.options.OptionValues; + +import com.oracle.graal.pointsto.AnalysisPolicy; +import com.oracle.graal.pointsto.PointsToAnalysis; +import com.oracle.graal.pointsto.api.PointstoOptions; +import com.oracle.graal.pointsto.flow.AbstractSpecialInvokeTypeFlow; +import com.oracle.graal.pointsto.flow.AbstractVirtualInvokeTypeFlow; +import com.oracle.graal.pointsto.flow.ActualReturnTypeFlow; +import com.oracle.graal.pointsto.flow.ArrayElementsTypeFlow; +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.MethodFlowsGraph; +import com.oracle.graal.pointsto.flow.MethodTypeFlow; +import com.oracle.graal.pointsto.flow.TypeFlow; +import com.oracle.graal.pointsto.flow.context.AnalysisContext; +import com.oracle.graal.pointsto.flow.context.BytecodeLocation; +import com.oracle.graal.pointsto.flow.context.object.AllocationContextSensitiveObject; +import com.oracle.graal.pointsto.flow.context.object.AnalysisObject; +import com.oracle.graal.pointsto.meta.AnalysisField; +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.PointsToAnalysisMethod; +import com.oracle.graal.pointsto.typestate.MultiTypeState; +import com.oracle.graal.pointsto.typestate.PointsToStats; +import com.oracle.graal.pointsto.typestate.SingleTypeState; +import com.oracle.graal.pointsto.typestate.TypeState; +import com.oracle.graal.pointsto.typestate.TypeStateUtils; +import com.oracle.graal.pointsto.typestore.ArrayElementsTypeStore; +import com.oracle.graal.pointsto.typestore.FieldTypeStore; +import com.oracle.graal.pointsto.typestore.SplitArrayElementsTypeStore; +import com.oracle.graal.pointsto.typestore.SplitFieldTypeStore; +import com.oracle.graal.pointsto.typestore.UnifiedArrayElementsTypeStore; +import com.oracle.graal.pointsto.typestore.UnifiedFieldTypeStore; +import com.oracle.graal.pointsto.util.ListUtils; +import com.oracle.graal.pointsto.util.ListUtils.UnsafeArrayList; +import com.oracle.graal.pointsto.util.ListUtils.UnsafeArrayListClosable; + +import jdk.vm.ci.code.BytecodePosition; +import jdk.vm.ci.meta.JavaConstant; + +public class BytecodeSensitiveAnalysisPolicy extends AnalysisPolicy { + + private BytecodeAnalysisContextPolicy contextPolicy; + + public BytecodeSensitiveAnalysisPolicy(OptionValues options) { + super(options); + this.contextPolicy = new BytecodeAnalysisContextPolicy(); + } + + @Override + public boolean isContextSensitiveAnalysis() { + return true; + } + + @Override + public BytecodeAnalysisContextPolicy contextPolicy() { + return contextPolicy; + } + + @Override + public boolean needsConstantCache() { + return true; + } + + @Override + public boolean isSummaryObject(AnalysisObject object) { + /* Context insensitive objects summarize context sensitive objects of the same type. */ + return object.isContextInsensitiveObject(); + } + + @Override + public boolean isMergingEnabled() { + // the context sensitive analysis relies on proper signal of merging + return true; + } + + @Override + public void noteMerge(PointsToAnalysis bb, TypeState t) { + t.noteMerge(bb); + } + + @Override + public void noteMerge(PointsToAnalysis bb, AnalysisObject... a) { + for (AnalysisObject o : a) { + o.noteMerge(bb); + } + } + + @Override + public void noteMerge(PointsToAnalysis bb, AnalysisObject o) { + o.noteMerge(bb); + } + + @Override + public boolean isContextSensitiveAllocation(PointsToAnalysis bb, AnalysisType type, AnalysisContext allocationContext) { + return bb.trackConcreteAnalysisObjects(type); + } + + @Override + public AnalysisObject createHeapObject(PointsToAnalysis bb, AnalysisType type, BytecodeLocation allocationSite, AnalysisContext allocationContext) { + assert PointstoOptions.AllocationSiteSensitiveHeap.getValue(options); + if (isContextSensitiveAllocation(bb, type, allocationContext)) { + return new AllocationContextSensitiveObject(bb, type, allocationSite, allocationContext); + } else { + return type.getContextInsensitiveAnalysisObject(); + } + } + + @Override + 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); + } else { + return exactType.getContextInsensitiveAnalysisObject(); + } + } + + @Override + public TypeState dynamicNewInstanceState(PointsToAnalysis bb, TypeState currentState, TypeState newState, BytecodeLocation allocationSite, AnalysisContext allocationContext) { + /* Generate a heap object for every new incoming type. */ + TypeState resultState = TypeState.forEmpty(); + for (AnalysisType type : newState.types(bb)) { + if (!currentState.containsType(type)) { + TypeState typeState = TypeState.forAllocation(bb, allocationSite, type, allocationContext); + resultState = TypeState.forUnion(bb, resultState, typeState); + } + } + assert !resultState.canBeNull(); + return resultState; + } + + @Override + public TypeState cloneState(PointsToAnalysis bb, TypeState currentState, TypeState inputState, BytecodeLocation cloneSite, AnalysisContext allocationContext) { + TypeState resultState; + if (inputState.isEmpty() || inputState.isNull()) { + /* Nothing to be cloned if the input state is not a concrete type state. */ + resultState = inputState.forNonNull(bb); + } else { + resultState = TypeState.forEmpty(); + for (AnalysisType type : inputState.types(bb)) { + if (!currentState.containsType(type)) { + TypeState typeState = TypeState.forClone(bb, cloneSite, type, allocationContext); + resultState = TypeState.forUnion(bb, resultState, typeState); + } + } + } + assert !resultState.canBeNull(); + return resultState; + } + + @Override + public void linkClonedObjects(PointsToAnalysis bb, TypeFlow inputFlow, CloneTypeFlow cloneFlow, BytecodePosition source) { + TypeState inputState = inputFlow.getState(); + TypeState cloneState = cloneFlow.getState(); + + for (AnalysisType type : inputState.types(bb)) { + if (type.isArray()) { + if (bb.analysisPolicy().aliasArrayTypeFlows()) { + /* All arrays are aliased, no need to model the array clone operation. */ + continue; + } + + /* The object array clones must also get the elements flows of the originals. */ + for (AnalysisObject originalObject : inputState.objects(type)) { + if (originalObject.isPrimitiveArray() || originalObject.isEmptyObjectArrayConstant(bb)) { + /* Nothing to read from a primitive array or an empty array constant. */ + continue; + } + ArrayElementsTypeFlow originalObjectElementsFlow = originalObject.getArrayElementsFlow(bb, false); + + for (AnalysisObject cloneObject : cloneState.objects(type)) { + if (cloneObject.isPrimitiveArray() || cloneObject.isEmptyObjectArrayConstant(bb)) { + /* Cannot write to a primitive array or an empty array constant. */ + continue; + } + ArrayElementsTypeFlow cloneObjectElementsFlow = cloneObject.getArrayElementsFlow(bb, true); + originalObjectElementsFlow.addUse(bb, cloneObjectElementsFlow); + } + } + } else { + + /* The object clones must get field flows of the originals. */ + for (AnalysisObject originalObject : inputState.objects(type)) { + /* Link all the field flows of the original to the clone. */ + for (AnalysisField field : type.getInstanceFields(true)) { + FieldTypeFlow originalObjectFieldFlow = originalObject.getInstanceFieldFlow(bb, inputFlow, source, field, false); + + for (AnalysisObject cloneObject : cloneState.objects(type)) { + FieldTypeFlow cloneObjectFieldFlow = cloneObject.getInstanceFieldFlow(bb, cloneFlow, source, field, true); + originalObjectFieldFlow.addUse(bb, cloneObjectFieldFlow); + } + } + } + } + } + } + + @Override + public BytecodeLocation createAllocationSite(PointsToAnalysis bb, int bci, AnalysisMethod method) { + return BytecodeLocation.create(bci, method); + } + + @Override + public FieldTypeStore createFieldTypeStore(AnalysisObject object, AnalysisField field, AnalysisUniverse universe) { + assert PointstoOptions.AllocationSiteSensitiveHeap.getValue(options); + if (object.isContextInsensitiveObject()) { + /* + * Write flow is context-sensitive and read flow is context-insensitive. This split is + * used to model context sensitivity and context merging for fields of this + * context-insensitive object, and the interaction with the fields of context-sensitive + * objects of the same type. + * + * All values written to fields of context-sensitive receivers are also reflected to the + * context-insensitive receiver *read* flow, but without any context information, such + * that all the reads from the fields of the context insensitive object reflect all the + * types written to the context-sensitive ones, but without triggering merging. + * + * Once the context-sensitive receiver object is marked as merged, i.e., it looses its + * context sensitivity, the field flows are routed to the context-insensitive receiver + * *write* flow, thus triggering their merging. See ContextSensitiveAnalysisObject. + * mergeInstanceFieldFlow(). + */ + FieldTypeFlow writeFlow = new FieldTypeFlow(field, field.getType(), object); + ContextInsensitiveFieldTypeFlow readFlow = new ContextInsensitiveFieldTypeFlow(field, field.getType(), object); + return new SplitFieldTypeStore(field, object, writeFlow, readFlow); + } else { + return new UnifiedFieldTypeStore(field, object); + } + } + + @Override + public ArrayElementsTypeStore createArrayElementsTypeStore(AnalysisObject object, AnalysisUniverse universe) { + assert PointstoOptions.AllocationSiteSensitiveHeap.getValue(options); + if (object.type().isArray()) { + if (aliasArrayTypeFlows) { + /* Alias all array type flows using the elements type flow model of Object type. */ + if (object.type().getElementalType().isJavaLangObject() && object.isContextInsensitiveObject()) { + // return getArrayElementsTypeStore(object); + return new UnifiedArrayElementsTypeStore(object); + } + return universe.objectType().getArrayClass().getContextInsensitiveAnalysisObject().getArrayElementsTypeStore(); + } + return getArrayElementsTypeStore(object); + } else { + return null; + } + } + + private static ArrayElementsTypeStore getArrayElementsTypeStore(AnalysisObject object) { + if (object.isContextInsensitiveObject()) { + return new SplitArrayElementsTypeStore(object); + } else { + return new UnifiedArrayElementsTypeStore(object); + } + } + + @Override + public AbstractVirtualInvokeTypeFlow createVirtualInvokeTypeFlow(BytecodePosition invokeLocation, AnalysisType receiverType, PointsToAnalysisMethod targetMethod, + TypeFlow[] actualParameters, ActualReturnTypeFlow actualReturn, BytecodeLocation location) { + return new BytecodeSensitiveVirtualInvokeTypeFlow(invokeLocation, receiverType, targetMethod, actualParameters, actualReturn, location); + } + + @Override + public AbstractSpecialInvokeTypeFlow createSpecialInvokeTypeFlow(BytecodePosition invokeLocation, AnalysisType receiverType, PointsToAnalysisMethod targetMethod, + TypeFlow[] actualParameters, ActualReturnTypeFlow actualReturn, BytecodeLocation location) { + return new BytecodeSensitiveSpecialInvokeTypeFlow(invokeLocation, receiverType, targetMethod, actualParameters, actualReturn, location); + } + + /** + * Bytecode context sensitive implementation of the invoke virtual type flow update. + * + * TODO Can we merge the slow path (i.e., this class) and fast path (i.e., the default, context + * insensitive virtual invoke implementation) to be able to fall back to fast path when context + * sensitivity is disabled or reaches budget threshold? + */ + private static class BytecodeSensitiveVirtualInvokeTypeFlow extends AbstractVirtualInvokeTypeFlow { + + /* + * Remember all the callee clones that were already linked in each context at this + * invocation site to avoid redundant relinking. MethodFlows is unique for each method type + * flow and context combination. + */ + private final ConcurrentMap calleesFlows = new ConcurrentHashMap<>(4, 0.75f, 1); + private final AnalysisContext callerContext; + + protected BytecodeSensitiveVirtualInvokeTypeFlow(BytecodePosition invokeLocation, AnalysisType receiverType, PointsToAnalysisMethod targetMethod, + TypeFlow[] actualParameters, ActualReturnTypeFlow actualReturn, BytecodeLocation location) { + super(invokeLocation, receiverType, targetMethod, actualParameters, actualReturn, location); + callerContext = null; + } + + protected BytecodeSensitiveVirtualInvokeTypeFlow(PointsToAnalysis bb, MethodFlowsGraph methodFlows, BytecodeSensitiveVirtualInvokeTypeFlow original) { + super(bb, methodFlows, original); + callerContext = methodFlows.context(); + } + + @Override + public TypeFlow copy(PointsToAnalysis bb, MethodFlowsGraph methodFlows) { + return new BytecodeSensitiveVirtualInvokeTypeFlow(bb, methodFlows, this); + } + + @Override + public void onObservedUpdate(PointsToAnalysis bb) { + assert this.isClone() || this.isContextInsensitive(); + + /* + * Capture the current receiver state before the update. The type state objects are + * immutable and a later call to getState() can yield a different value. + */ + TypeState receiverState = getReceiver().getState(); + receiverState = filterReceiverState(bb, receiverState); + + if (receiverState.isEmpty() || receiverState.isNull()) { + return; + } + + /* Use the tandem types - objects iterator. */ + TypesObjectsIterator toi = new TypesObjectsIterator(receiverState); + while (toi.hasNextType()) { + AnalysisType type = toi.nextType(); + + AnalysisMethod method = type.resolveConcreteMethod(getTargetMethod()); + if (method == null || Modifier.isAbstract(method.getModifiers())) { + /* + * Type states can be conservative, i.e., we can have receiver types that do not + * implement the method. Just ignore such types. + */ + while (toi.hasNextObject(type)) { + // skip the rest of the objects of the same type + toi.nextObject(type); + } + continue; + } + + assert !Modifier.isAbstract(method.getModifiers()); + + MethodTypeFlow callee = PointsToAnalysis.assertPointsToAnalysisMethod(method).getTypeFlow(); + + while (toi.hasNextObject(type)) { + AnalysisObject actualReceiverObject = toi.nextObject(type); + + // get the context based on the actualReceiverObject + AnalysisContext calleeContext = bb.contextPolicy().calleeContext(bb, actualReceiverObject, callerContext, callee); + + MethodFlowsGraph calleeFlows = callee.addContext(bb, calleeContext, this); + + if (calleesFlows.put(calleeFlows, Boolean.TRUE) == null) { + /* register the analysis method as a callee for this invoke */ + addCallee(calleeFlows.getMethod()); + /* linkCallee() does not link the receiver object. */ + linkCallee(bb, false, calleeFlows); + } + + updateReceiver(bb, calleeFlows, actualReceiverObject); + } + + } + } + + @Override + public void onObservedSaturated(PointsToAnalysis bb, TypeFlow observed) { + assert this.isClone(); + /* When the receiver flow saturates start observing the flow of the receiver type. */ + replaceObservedWith(bb, receiverType); + } + + @Override + public Collection getCalleesFlows(PointsToAnalysis bb) { + return new ArrayList<>(calleesFlows.keySet()); + } + } + + /** + * This is a special iterator for the type state. It iterates over analysis types and objects in + * tandem doing a single pass over the objects array. It relies on the fact that the types and + * objects are sorted by ID. It is meant for situations where the types need some pre-processing + * or checking before processing their respective objects, e.g., as in virtual method resolution + * for InvokeTypeFlow. It those situations it avoids iterating over the types first and then + * searching for the range of objects corresponding to that type. When only objects, or only + * types, or only objects of a certain type need to be iterated use the other provided + * iterators. A correct use of this iterator is as follows: + * + * + * TypesObjectsIterator toi = state.getTypesObjectsIterator(); + * + * while(toi.hasNextType()) { + * AnalysisType t = toi.nextType(); + * // use type here + * + * while(toi.hasNextObject(t)) { + * AnalysisObject o = toi.nextObject(t); + * // use object here + * } + * } + * + */ + public static class TypesObjectsIterator { + private final int typesCount; + private final AnalysisObject[] objects; + private int typeIdx = 0; + private int objectIdx = 0; + + public TypesObjectsIterator(TypeState state) { + typesCount = state.typesCount(); + objects = objectsArray(state); + } + + private static AnalysisObject[] objectsArray(TypeState state) { + if (state.isEmpty() || state.isNull()) { + return AnalysisObject.EMPTY_ARRAY; + } + if (state instanceof ContextSensitiveSingleTypeState) { + return ((ContextSensitiveSingleTypeState) state).objects; + } + if (state instanceof ContextSensitiveMultiTypeState) { + return ((ContextSensitiveMultiTypeState) state).objects; + } + throw AnalysisError.shouldNotReachHere(); + } + + /** + * Returns true if there is a next type in the objects array, i.e., there are objects of a + * type other than the current type. + */ + public boolean hasNextType() { + return typeIdx < typesCount; + } + + /** Returns true if there are more objects of the current type. */ + public boolean hasNextObject(AnalysisType type) { + return objectIdx < objects.length && objects[objectIdx].getTypeId() == type.getId(); + } + + /** Gets the next type. */ + public AnalysisType nextType() { + /* Check that there is a next type. */ + assert hasNextType(); + /* Increment the type index. */ + typeIdx++; + /* Return the type at the 'objectIdx. */ + return objects[objectIdx].type(); + } + + /** Gets the next object. */ + public AnalysisObject nextObject(AnalysisType type) { + /* Check that there is a next object of the desired type. */ + assert hasNextObject(type); + /* Return the next object and increment objectIdx. */ + return objects[objectIdx++]; + } + } + + private static final class BytecodeSensitiveSpecialInvokeTypeFlow extends AbstractSpecialInvokeTypeFlow { + + /** + * Contexts of the resolved method. + */ + private ConcurrentMap calleesFlows = new ConcurrentHashMap<>(4, 0.75f, 1); + + BytecodeSensitiveSpecialInvokeTypeFlow(BytecodePosition invokeLocation, AnalysisType receiverType, PointsToAnalysisMethod targetMethod, + TypeFlow[] actualParameters, ActualReturnTypeFlow actualReturn, BytecodeLocation location) { + super(invokeLocation, receiverType, targetMethod, actualParameters, actualReturn, location); + } + + private BytecodeSensitiveSpecialInvokeTypeFlow(PointsToAnalysis bb, MethodFlowsGraph methodFlows, BytecodeSensitiveSpecialInvokeTypeFlow original) { + super(bb, methodFlows, original); + } + + @Override + public TypeFlow copy(PointsToAnalysis bb, MethodFlowsGraph methodFlows) { + return new BytecodeSensitiveSpecialInvokeTypeFlow(bb, methodFlows, this); + } + + @Override + public void onObservedUpdate(PointsToAnalysis bb) { + assert this.isClone() || this.isContextInsensitive(); + /* The receiver state has changed. Process the invoke. */ + + initCallee(); + + TypeState invokeState = filterReceiverState(bb, getReceiver().getState()); + for (AnalysisObject receiverObject : invokeState.objects(bb)) { + AnalysisContext calleeContext = bb.contextPolicy().calleeContext(bb, receiverObject, callerContext, callee); + MethodFlowsGraph calleeFlows = callee.addContext(bb, calleeContext, this); + + if (calleesFlows.putIfAbsent(calleeFlows, Boolean.TRUE) == null) { + linkCallee(bb, false, calleeFlows); + } + + updateReceiver(bb, calleeFlows, receiverObject); + } + } + + @Override + public Collection getCalleesFlows(PointsToAnalysis bb) { + return new ArrayList<>(calleesFlows.keySet()); + } + } + + @Override + public TypeState forContextInsensitiveTypeState(PointsToAnalysis bb, TypeState state) { + if (state.isEmpty() || state.isNull()) { + /* The type state is already context insensitive. */ + return state; + } else { + if (state instanceof SingleTypeState) { + AnalysisType type = state.exactType(); + AnalysisObject analysisObject = type.getContextInsensitiveAnalysisObject(); + return singleTypeState(bb, state.canBeNull(), makeProperties(bb, analysisObject), analysisObject.type(), analysisObject); + } else { + MultiTypeState multiState = (MultiTypeState) state; + AnalysisObject[] objectsArray = new AnalysisObject[multiState.typesCount()]; + + int i = 0; + for (AnalysisType type : multiState.types(bb)) { + objectsArray[i++] = type.getContextInsensitiveAnalysisObject(); + } + /* + * For types use the already created bit set. Since the original type state is + * immutable its types bit set cannot change. + */ + + BitSet typesBitSet = multiState.typesBitSet(); + int properties = makeProperties(bb, objectsArray); + return multiTypeState(bb, multiState.canBeNull(), properties, typesBitSet, objectsArray); + } + } + } + + @Override + public SingleTypeState singleTypeState(PointsToAnalysis bb, boolean canBeNull, int properties, AnalysisType type, AnalysisObject... objects) { + return new ContextSensitiveSingleTypeState(bb, canBeNull, properties, type, objects); + } + + @Override + public MultiTypeState multiTypeState(PointsToAnalysis bb, boolean canBeNull, int properties, BitSet typesBitSet, AnalysisObject... objects) { + return new ContextSensitiveMultiTypeState(bb, canBeNull, properties, typesBitSet, objects); + } + + @Override + public TypeState doUnion(PointsToAnalysis bb, SingleTypeState state1, SingleTypeState state2) { + if (state1.equals(state2)) { + return state1; + } + + ContextSensitiveSingleTypeState s1 = (ContextSensitiveSingleTypeState) state1; + ContextSensitiveSingleTypeState s2 = (ContextSensitiveSingleTypeState) 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. */ + + /* Create the resulting objects array. */ + AnalysisObject[] resultObjects = TypeStateUtils.union(bb, s1.objects, s2.objects); + + /* Check if any of the arrays contains the other. */ + if (resultObjects == s1.objects) { + return s1.forCanBeNull(bb, resultCanBeNull); + } else if (resultObjects == s2.objects) { + return s2.forCanBeNull(bb, resultCanBeNull); + } + + /* Due to the test above the union set cannot be equal to any of the two arrays. */ + assert !bb.extendedAsserts() || !Arrays.equals(resultObjects, s1.objects) && !Arrays.equals(resultObjects, s2.objects); + + /* Create the resulting exact type state. */ + SingleTypeState result = new ContextSensitiveSingleTypeState(bb, resultCanBeNull, bb.analysisPolicy().makePropertiesForUnion(s1, s2), s1.exactType(), resultObjects); + assert !s1.equals(result) && !s2.equals(result); + PointsToStats.registerUnionOperation(bb, s1, s2, result); + return result; + } else { + /* The inputs have different types, so the result is a MultiTypeState. */ + AnalysisObject[] resultObjects; + if (s1.exactType().getId() < s2.exactType().getId()) { + resultObjects = TypeStateUtils.concat(s1.objects, s2.objects); + } else { + resultObjects = TypeStateUtils.concat(s2.objects, s1.objects); + } + + /* We know the types, construct the types bit set without walking the objects. */ + BitSet typesBitSet = TypeStateUtils.newBitSet(s1.exactType().getId(), s2.exactType().getId()); + int properties = bb.analysisPolicy().makePropertiesForUnion(s1, s2); + TypeState result = new ContextSensitiveMultiTypeState(bb, resultCanBeNull, properties, typesBitSet, resultObjects); + PointsToStats.registerUnionOperation(bb, s1, s2, result); + return result; + } + } + + @Override + public TypeState doUnion(PointsToAnalysis bb, MultiTypeState state1, SingleTypeState state2) { + + ContextSensitiveMultiTypeState s1 = (ContextSensitiveMultiTypeState) state1; + ContextSensitiveSingleTypeState s2 = (ContextSensitiveSingleTypeState) state2; + + boolean resultCanBeNull = s1.canBeNull() || s2.canBeNull(); + + AnalysisObject[] so1 = s1.objects; + AnalysisObject[] so2 = s2.objects; + if (so2.length == 1 && containsObject(s1, so2[0])) { + /* + * Speculate that s2 has a single object and s1 already contains that object. This + * happens often during object scanning where we repeatedly add the scanned constants to + * field or array elements flows. The binary search executed by containsObject should be + * faster than the linear search below. + */ + return s1.forCanBeNull(bb, resultCanBeNull); + } + + if (s1.containsType(s2.exactType())) { + /* Objects of the same type as s2 are contained in s1. */ + + /* Get the range of objects in s1 corresponding to the type of s2. */ + ContextSensitiveMultiTypeState.Range typeRange = s1.findTypeRange(s2.exactType()); + /* Get the slice of objects in s1 corresponding to the type of s2. */ + AnalysisObject[] s1ObjectsSlice = s1.objectsArray(typeRange); + + /* Create the resulting objects array. */ + AnalysisObject[] unionObjects = TypeStateUtils.union(bb, s1ObjectsSlice, so2); + + /* Check if s1 contains s2's objects for this type. */ + if (unionObjects == s1ObjectsSlice) { + return s1.forCanBeNull(bb, resultCanBeNull); + } + + /* + * Due to the test above and to the fact that TypeStateUtils.union checks if one array + * contains the other the union set cannot be equal to s1's objects slice. + */ + assert !bb.extendedAsserts() || !Arrays.equals(unionObjects, s1ObjectsSlice); + + /* + * Replace the s1 objects slice of the same type as s2 with the union objects and create + * a new state. + */ + int resultSize = so1.length + unionObjects.length - s1ObjectsSlice.length; + AnalysisObject[] resultObjects = new AnalysisObject[resultSize]; + + System.arraycopy(so1, 0, resultObjects, 0, typeRange.left()); + System.arraycopy(unionObjects, 0, resultObjects, typeRange.left(), unionObjects.length); + System.arraycopy(so1, typeRange.right(), resultObjects, typeRange.left() + unionObjects.length, so1.length - typeRange.right()); + + /* The types bit set of the result and s1 are the same. */ + + int properties = bb.analysisPolicy().makePropertiesForUnion(s1, s2); + + MultiTypeState result = new ContextSensitiveMultiTypeState(bb, resultCanBeNull, properties, s1.typesBitSet(), resultObjects); + assert !result.equals(s1); + /* + * No need to check the result size against the all-instantiated since the type count + * didn't change. + */ + PointsToStats.registerUnionOperation(bb, s1, s2, result); + return result; + } else { + AnalysisObject[] resultObjects; + if (s2.exactType().getId() < s1.firstType().getId()) { + resultObjects = TypeStateUtils.concat(so2, so1); + } else if (s2.exactType().getId() > s1.lastType().getId()) { + resultObjects = TypeStateUtils.concat(so1, so2); + } else { + + /* Find insertion point within the s1.objects. */ + int idx1 = 0; + while (idx1 < so1.length && so1[idx1].getTypeId() < s2.exactType().getId()) { + idx1++; + } + + /* Create the resulting objects array and insert the s2 objects. */ + resultObjects = new AnalysisObject[so1.length + so2.length]; + + System.arraycopy(so1, 0, resultObjects, 0, idx1); + System.arraycopy(so2, 0, resultObjects, idx1, so2.length); + System.arraycopy(so1, idx1, resultObjects, idx1 + so2.length, so1.length - idx1); + } + + /* Create the types bit set by adding the s2 type to avoid walking the objects. */ + BitSet typesBitSet = TypeStateUtils.set(s1.typesBitSet(), s2.exactType().getId()); + int properties = bb.analysisPolicy().makePropertiesForUnion(s1, s2); + + MultiTypeState result = new ContextSensitiveMultiTypeState(bb, resultCanBeNull, properties, typesBitSet, resultObjects); + PointsToStats.registerUnionOperation(bb, s1, s2, result); + return result; + } + } + + /** Returns true if this type state contains the object, otherwise it returns false. */ + public static boolean containsObject(ContextSensitiveMultiTypeState state, AnalysisObject object) { + /* AnalysisObject implements Comparable and the objects array is always sorted by ID. */ + return state.containsType(object.type()) && Arrays.binarySearch(state.objects, object) >= 0; + } + + @Override + public TypeState doUnion(PointsToAnalysis bb, MultiTypeState state1, MultiTypeState state2) { + ContextSensitiveMultiTypeState s1 = (ContextSensitiveMultiTypeState) state1; + ContextSensitiveMultiTypeState s2 = (ContextSensitiveMultiTypeState) state2; + + assert s1.objectsCount() >= s2.objectsCount() : "Union is commutative, must call it with s1 being the bigger state"; + boolean resultCanBeNull = s1.canBeNull() || s2.canBeNull(); + + /* + * No need for a deep equality check (which would need to iterate the arrays), since the + * speculation logic below is doing that anyway. + */ + if (s1.objects == s2.objects) { + return s1.forCanBeNull(bb, resultCanBeNull); + } + + return doUnion0(bb, s1, s2, resultCanBeNull); + } + + private static TypeState doUnion0(PointsToAnalysis bb, ContextSensitiveMultiTypeState s1, ContextSensitiveMultiTypeState s2, boolean resultCanBeNull) { + + /* Speculate that s1 and s2 are distinct sets. */ + + if (s1.lastType().getId() < s2.firstType().getId()) { + /* Speculate that objects in s2 follow after objects in s1. */ + + /* Concatenate the objects. */ + AnalysisObject[] resultObjects = TypeStateUtils.concat(s1.objects, s2.objects); + + /* Logical OR the type bit sets. */ + BitSet resultTypesBitSet = TypeStateUtils.or(s1.typesBitSet(), s2.typesBitSet()); + int properties = bb.analysisPolicy().makePropertiesForUnion(s1, s2); + + MultiTypeState result = new ContextSensitiveMultiTypeState(bb, resultCanBeNull, properties, resultTypesBitSet, resultObjects); + PointsToStats.registerUnionOperation(bb, s1, s2, result); + return result; + + } else if (s2.lastType().getId() < s1.firstType().getId()) { + /* Speculate that objects in s1 follow after objects in s2. */ + + /* Concatenate the objects. */ + AnalysisObject[] resultObjects = TypeStateUtils.concat(s2.objects, s1.objects); + + /* Logical OR the type bit sets. */ + BitSet resultTypesBitSet = TypeStateUtils.or(s1.typesBitSet(), s2.typesBitSet()); + int properties = bb.analysisPolicy().makePropertiesForUnion(s1, s2); + + MultiTypeState result = new ContextSensitiveMultiTypeState(bb, resultCanBeNull, properties, resultTypesBitSet, resultObjects); + PointsToStats.registerUnionOperation(bb, s1, s2, result); + return result; + } + + return doUnion1(bb, s1, s2, resultCanBeNull); + } + + private static TypeState doUnion1(PointsToAnalysis bb, ContextSensitiveMultiTypeState s1, ContextSensitiveMultiTypeState s2, boolean resultCanBeNull) { + if (PointstoOptions.AllocationSiteSensitiveHeap.getValue(bb.getOptions())) { + return allocationSensitiveSpeculativeUnion1(bb, s1, s2, resultCanBeNull); + } else { + return allocationInsensitiveSpeculativeUnion1(bb, s1, s2, resultCanBeNull); + } + } + + /** + * Optimization that gives 1.5-3x in performance for the (typeflow) phase. + */ + private static TypeState allocationInsensitiveSpeculativeUnion1(PointsToAnalysis bb, ContextSensitiveMultiTypeState s1, ContextSensitiveMultiTypeState s2, boolean resultCanBeNull) { + if (s1.typesBitSet().length() >= s2.typesBitSet().length()) { + long[] bits1 = TypeStateUtils.extractBitSetField(s1.typesBitSet()); + long[] bits2 = TypeStateUtils.extractBitSetField(s2.typesBitSet()); + assert s2.typesBitSet().cardinality() == s2.objects.length : "Cardinality and length of objects must match."; + + boolean speculate = true; + int numberOfWords = Math.min(bits1.length, bits2.length); + for (int i = 0; i < numberOfWords; i++) { + /* bits2 is a subset of bits1 */ + if ((bits1[i] & bits2[i]) != bits2[i]) { + speculate = false; + break; + } + } + if (speculate) { + return s1.forCanBeNull(bb, resultCanBeNull); + } + } + return doUnion2(bb, s1, s2, resultCanBeNull, 0, 0); + } + + private static TypeState allocationSensitiveSpeculativeUnion1(PointsToAnalysis bb, ContextSensitiveMultiTypeState s1, ContextSensitiveMultiTypeState s2, boolean resultCanBeNull) { + int idx1 = 0; + int idx2 = 0; + AnalysisPolicy analysisPolicy = bb.analysisPolicy(); + AnalysisObject[] so1 = s1.objects; + AnalysisObject[] so2 = s2.objects; + while (idx1 < so1.length && idx2 < so2.length) { + AnalysisObject o1 = so1[idx1]; + AnalysisObject o2 = so2[idx2]; + if (analysisPolicy.isSummaryObject(o1) && o1.getTypeId() == o2.getTypeId()) { + idx1++; + /* Skip over s2 objects of this type while marking them as merged. */ + while (idx2 < s2.objectsCount() && so2[idx2].getTypeId() == o1.getTypeId()) { + analysisPolicy.noteMerge(bb, so2[idx2]); + idx2++; + } + } else if (o1.getId() < o2.getId()) { + idx1++; + } else if (o1.getId() == o2.getId()) { + /* If the objects are equal continue. */ + idx1++; + idx2++; + } else { + /* Our speculation failed. */ + break; + } + + if (idx2 == so2.length) { + return s1.forCanBeNull(bb, resultCanBeNull); + } + } + return doUnion2(bb, s1, s2, resultCanBeNull, idx1, idx2); + } + + private static final ThreadLocal> doUnion2TL = new ThreadLocal<>(); + private static final ThreadLocal> doUnion2ObjectsTL = new ThreadLocal<>(); + + private static TypeState doUnion2(PointsToAnalysis bb, ContextSensitiveMultiTypeState s1, ContextSensitiveMultiTypeState s2, boolean resultCanBeNull, int startId1, int startId2) { + try (UnsafeArrayListClosable resultObjectsClosable = getTLArrayList(doUnion2TL, s1.objects.length + s2.objects.length)) { + UnsafeArrayList resultObjects = resultObjectsClosable.list(); + /* Add the beginning of the s1 list that we already walked above. */ + AnalysisObject[] objects = s1.objects; + resultObjects.addAll(objects, 0, startId1); + + int idx1 = startId1; + int idx2 = startId2; + + /* Create the union of the overlapping sections of the s1 and s2. */ + try (UnsafeArrayListClosable tlUnionObjectsClosable = getTLArrayList(doUnion2ObjectsTL, s1.objects.length + s2.objects.length)) { + UnsafeArrayList unionObjects = tlUnionObjectsClosable.list(); + + AnalysisObject[] so1 = s1.objects; + AnalysisObject[] so2 = s2.objects; + AnalysisPolicy analysisPolicy = bb.analysisPolicy(); + while (idx1 < so1.length && idx2 < so2.length) { + AnalysisObject o1 = so1[idx1]; + AnalysisObject o2 = so2[idx2]; + int t1 = o1.getTypeId(); + int t2 = o2.getTypeId(); + if (analysisPolicy.isSummaryObject(o1) && t1 == t2) { + unionObjects.add(o1); + /* Skip over s2 objects of this type while marking them as merged. */ + while (idx2 < so2.length && t1 == so2[idx2].getTypeId()) { + analysisPolicy.noteMerge(bb, so2[idx2]); + idx2++; + } + idx1++; + } else if (analysisPolicy.isSummaryObject(o2) && t1 == t2) { + unionObjects.add(o2); + /* Skip over s1 objects of this type while marking them as merged. */ + while (idx1 < so1.length && so1[idx1].getTypeId() == t2) { + analysisPolicy.noteMerge(bb, so1[idx1]); + idx1++; + } + idx2++; + } else if (o1.getId() < o2.getId()) { + unionObjects.add(o1); + idx1++; + } else if (o1.getId() > o2.getId()) { + unionObjects.add(o2); + idx2++; + } else { + assert o1.equals(o2); + unionObjects.add(o1); + idx1++; + idx2++; + } + } + + /* + * Check if the union of objects of a type in the overlapping section reached the + * limit. The limit, bb.options().maxObjectSetSize(), has a minimum value of 1. + */ + if (PointstoOptions.LimitObjectArrayLength.getValue(bb.getOptions()) && unionObjects.size() > PointstoOptions.MaxObjectSetSize.getValue(bb.getOptions())) { + int idxStart = 0; + int idxEnd = 0; + while (idxEnd < unionObjects.size()) { + AnalysisObject oStart = unionObjects.get(idxStart); + + /* While types are equal and the end is not reached, advance idxEnd. */ + while (idxEnd < unionObjects.size() && oStart.equals(unionObjects.get(idxEnd))) { + idxEnd = idxEnd + 1; + } + /* + * Process the type change or, if idxEnd reached the end, process the last + * stride + */ + int size = idxEnd - idxStart; + if (size > PointstoOptions.MaxObjectSetSize.getValue(bb.getOptions())) { + /* + * Object count exceeds the limit. Mark the objects in the stride as + * merged. + */ + for (int i = idxStart; i < idxEnd; i += 1) { + bb.analysisPolicy().noteMerge(bb, unionObjects.get(i)); + } + /* Add the context insensitive object in the result list. */ + resultObjects.add(oStart.type().getContextInsensitiveAnalysisObject()); + } else { + /* Object count is within the limit, add them to the result. */ + resultObjects.addAll(unionObjects.elementData(), idxStart, idxEnd); + } + idxStart = idxEnd; + } + + } else { + resultObjects.addAll(unionObjects.elementData(), 0, unionObjects.size()); + } + } + + /* + * Add the leftover objects in the result list. + * + * Arrays.asList(a).subList(from, to) first creates a list wrapper over the array then + * it creates a view of a portion of the list, thus it only allocates the list and + * sub-list wrappers. Then ArrayList.addAll() calls System.arraycopy() which should be + * more efficient than copying one element at a time. + */ + if (idx1 < s1.objects.length) { + resultObjects.addAll(s1.objects, idx1, s1.objects.length); + } else if (idx2 < s2.objects.length) { + resultObjects.addAll(s2.objects, idx2, s2.objects.length); + } + + assert resultObjects.size() > 1 : "The result state of a (Multi U Multi) operation must have at least 2 objects"; + + /* Logical OR the type bit sets. */ + BitSet resultTypesBitSet = TypeStateUtils.or(s1.typesBitSet(), s2.typesBitSet()); + int properties = bb.analysisPolicy().makePropertiesForUnion(s1, s2); + + MultiTypeState result = new ContextSensitiveMultiTypeState(bb, resultCanBeNull, properties, resultTypesBitSet, resultObjects.copyToArray(new AnalysisObject[resultObjects.size()])); + assert !result.equals(s1) : "speculation code should prevent this case"; + + /* The result can be equal to s2 only if s1 and s2 have the same number of types. */ + if (s1.typesCount() == s2.typesCount() && result.equals(s2)) { + return s2.forCanBeNull(bb, resultCanBeNull); + } + + PointsToStats.registerUnionOperation(bb, s1, s2, result); + + return result; + } + } + + /* + * Implementation of intersection. + * + * The implementation of intersection is specific to our current use case, i.e., it is not a + * general set intersection implementation. The limitation, checked by the assertions below, + * refers to the fact that when we use intersection we only care about selecting all the objects + * of a certain type or types, e.g., for filtering. We don't currently have a situation where we + * only want to select a subset of objects of a type. In our use the types whose objects need to + * be selected are always specified in s2 through their context insensitive objects, thus s2 + * must only contain context insensitive objects. + */ + + @Override + public TypeState doIntersection(PointsToAnalysis bb, MultiTypeState s1, SingleTypeState s2) { + /* See comment above for the limitation explanation. */ + assert !bb.extendedAsserts() || TypeStateUtils.isContextInsensitiveTypeState(bb, s2) : "Current implementation limitation."; + + boolean resultCanBeNull = s1.canBeNull() && s2.canBeNull(); + if (s1.containsType(s2.exactType())) { + /* The s2's type is contained in s1, so pick all objects of the same type from s1. */ + AnalysisObject[] resultObjects = ((ContextSensitiveMultiTypeState) s1).objectsArray(s2.exactType()); + /* All objects must have the same type. */ + assert TypeStateUtils.holdsSingleTypeState(resultObjects); + return new ContextSensitiveSingleTypeState(bb, resultCanBeNull, bb.analysisPolicy().makeProperties(bb, resultObjects), s2.exactType(), resultObjects); + } else { + return TypeState.forEmpty().forCanBeNull(bb, resultCanBeNull); + } + } + + @Override + public TypeState doIntersection(PointsToAnalysis bb, MultiTypeState state1, MultiTypeState state2) { + ContextSensitiveMultiTypeState s1 = (ContextSensitiveMultiTypeState) state1; + ContextSensitiveMultiTypeState s2 = (ContextSensitiveMultiTypeState) state2; + + assert !bb.extendedAsserts() || TypeStateUtils.isContextInsensitiveTypeState(bb, s2) : "Current implementation limitation."; + + boolean resultCanBeNull = s1.canBeNull() && s2.canBeNull(); + + /* + * No need for a deep equality check (which would need to iterate the arrays), since the + * speculation logic below is doing that anyway. + */ + if (s1.objects == s2.objects) { + return s1.forCanBeNull(bb, resultCanBeNull); + } + + return doIntersection0(bb, s1, s2, resultCanBeNull); + } + + private static TypeState doIntersection0(PointsToAnalysis bb, ContextSensitiveMultiTypeState s1, ContextSensitiveMultiTypeState s2, boolean resultCanBeNull) { + /* Speculate that s1 and s2 have either the same types, or no types in common. */ + + if (s1.typesBitSet().equals(s2.typesBitSet())) { + /* Speculate that s1 and s2 have the same types, i.e., the result is s1. */ + return s1.forCanBeNull(bb, resultCanBeNull); + } + + if (!s1.typesBitSet().intersects(s2.typesBitSet())) { + /* Speculate that s1 and s2 have no types in common, i.e., the result is empty. */ + return TypeState.forEmpty().forCanBeNull(bb, resultCanBeNull); + } + + return doIntersection1(bb, s1, s2, resultCanBeNull); + } + + private static TypeState doIntersection1(PointsToAnalysis bb, ContextSensitiveMultiTypeState s1, ContextSensitiveMultiTypeState s2, boolean resultCanBeNull) { + /* + * Speculate that s2 contains all types of s1, i.e., the filter is broader than s1, thus the + * result is s1. + */ + + int idx1 = 0; + int idx2 = 0; + AnalysisObject[] so1 = s1.objects; + AnalysisObject[] so2 = s2.objects; + while (idx2 < so2.length) { + AnalysisObject o1 = so1[idx1]; + AnalysisObject o2 = so2[idx2]; + + /* See comment above for the limitation explanation. */ + assert o2.isContextInsensitiveObject() : "Current implementation limitation."; + + if (o1.getTypeId() > o2.getTypeId()) { + /* s2 is behind, advance s2. */ + idx2++; + } else if (o1.getTypeId() == o2.getTypeId()) { + /* If the types are equal continue with speculation. */ + while (idx1 < so1.length && so1[idx1].getTypeId() == o2.getTypeId()) { + /* Walk over the s1 objects of the same type as o2. */ + idx1++; + } + idx2++; + } else { + /* Our speculation failed. */ + break; + } + + if (idx1 == so1.length) { + /* + * Our speculation succeeded: we walked down the whole s1 list, and all of its types + * are included in s2. + */ + + return s1.forCanBeNull(bb, resultCanBeNull); + } + + } + + return doIntersection2(bb, s1, s2, resultCanBeNull, idx1, idx2); + } + + private static ThreadLocal> intersectionArrayListTL = new ThreadLocal<>(); + + private static TypeState doIntersection2(PointsToAnalysis bb, ContextSensitiveMultiTypeState s1, ContextSensitiveMultiTypeState s2, boolean resultCanBeNull, int idx1Param, int idx2Param) { + + try (ListUtils.UnsafeArrayListClosable tlArrayClosable = ListUtils.getTLArrayList(intersectionArrayListTL, 256)) { + ListUtils.UnsafeArrayList resultObjects = tlArrayClosable.list(); + + AnalysisObject[] so1 = s1.objects; + AnalysisObject[] so2 = s2.objects; + int[] types1 = s1.getObjectTypeIds(); + int[] types2 = s2.getObjectTypeIds(); + int idx1 = idx1Param; + int idx2 = idx2Param; + int l1 = so1.length; + int l2 = so2.length; + int t1 = types1[idx1]; + int t2 = types2[idx2]; + while (idx1 < l1 && idx2 < l2) { + assert so2[idx2].isContextInsensitiveObject() : "Current implementation limitation."; + if (t1 == t2) { + assert so1[idx1].type().equals(so2[idx2].type()); + resultObjects.add(so1[idx1]); + t1 = types1[++idx1]; + } else if (t1 < t2) { + t1 = types1[++idx1]; + } else if (t1 > t2) { + t2 = types2[++idx2]; + } + } + + int totalLength = idx1Param + resultObjects.size(); + + if (totalLength == 0) { + return TypeState.forEmpty().forCanBeNull(bb, resultCanBeNull); + } else { + AnalysisObject[] objects = new AnalysisObject[totalLength]; + /* Copy the recently touched first */ + resultObjects.copyToArray(objects, idx1Param); + /* Add the beginning of the s1 list that we already walked above. */ + System.arraycopy(s1.objects, 0, objects, 0, idx1Param); + + if (TypeStateUtils.holdsSingleTypeState(objects, objects.length)) { + /* Multiple objects of the same type. */ + return new ContextSensitiveSingleTypeState(bb, resultCanBeNull, bb.analysisPolicy().makeProperties(bb, objects), objects[0].type(), objects); + } else { + /* Logical AND the type bit sets. */ + BitSet resultTypesBitSet = TypeStateUtils.and(s1.typesBitSet(), s2.typesBitSet()); + MultiTypeState result = new ContextSensitiveMultiTypeState(bb, resultCanBeNull, bb.analysisPolicy().makeProperties(bb, objects), resultTypesBitSet, objects); + + /* + * The result can be equal to s1 if and only if s1 and s2 have the same type + * count. + */ + if (s1.typesCount() == s2.typesCount() && result.equals(s1)) { + return s1.forCanBeNull(bb, resultCanBeNull); + } + + /* + * Don't need to check if the result is close-to-all-instantiated since result + * <= s1. + */ + return result; + } + } + } + } + + /* + * Implementation of subtraction. + * + * The implementation of subtraction is specific to our current use case, i.e., it is not a + * general set subtraction implementation. The limitation, checked by the assertions below, + * refers to the fact that when we use subtraction we only care about eliminating all the + * objects of a certain type or types, e.g., for filtering. We don't currently have a situation + * where we only want to remove a subset of objects of a type. In our use the types whose + * objects need to be eliminated are always specified in s2 through their context insensitive + * objects, thus s2 must only contain context insensitive objects. + */ + + @Override + public TypeState doSubtraction(PointsToAnalysis bb, MultiTypeState state1, SingleTypeState state2) { + + ContextSensitiveMultiTypeState s1 = (ContextSensitiveMultiTypeState) state1; + ContextSensitiveSingleTypeState s2 = (ContextSensitiveSingleTypeState) state2; + + boolean resultCanBeNull = s1.canBeNull() && !s2.canBeNull(); + if (s1.containsType(s2.exactType())) { + /* s2 is contained in s1, so remove all objects of the same type from s1. */ + + /* See comment above for the limitation explanation. */ + assert !bb.extendedAsserts() || TypeStateUtils.isContextInsensitiveTypeState(bb, s2) : "Current implementation limitation."; + + /* Find the range of objects of s2.exactType() in s1. */ + ContextSensitiveMultiTypeState.Range typeRange = s1.findTypeRange(s2.exactType()); + int newLength = s1.objects.length - (typeRange.right() - typeRange.left()); + AnalysisObject[] resultObjects = new AnalysisObject[newLength]; + + /* Copy all the objects in s1 but the ones inside the range to the result list. */ + System.arraycopy(s1.objects, 0, resultObjects, 0, typeRange.left()); + System.arraycopy(s1.objects, typeRange.right(), resultObjects, typeRange.left(), s1.objects.length - typeRange.right()); + + if (resultObjects.length == 1) { + return new ContextSensitiveSingleTypeState(bb, resultCanBeNull, bb.analysisPolicy().makeProperties(bb, resultObjects[0]), resultObjects[0].type(), resultObjects[0]); + } else if (TypeStateUtils.holdsSingleTypeState(resultObjects)) { + /* Multiple objects of the same type. */ + return new ContextSensitiveSingleTypeState(bb, resultCanBeNull, bb.analysisPolicy().makeProperties(bb, resultObjects), resultObjects[0].type(), resultObjects); + } else { + BitSet resultTypesBitSet = TypeStateUtils.clear(s1.typesBitSet(), s2.exactType().getId()); + return new ContextSensitiveMultiTypeState(bb, resultCanBeNull, bb.analysisPolicy().makeProperties(bb, resultObjects), resultTypesBitSet, resultObjects); + } + + } else { + return s1.forCanBeNull(bb, resultCanBeNull); + } + } + + @Override + public TypeState doSubtraction(PointsToAnalysis bb, MultiTypeState state1, MultiTypeState state2) { + ContextSensitiveMultiTypeState s1 = (ContextSensitiveMultiTypeState) state1; + ContextSensitiveMultiTypeState s2 = (ContextSensitiveMultiTypeState) state2; + + boolean resultCanBeNull = s1.canBeNull() && !s2.canBeNull(); + /* + * No need for a deep equality check (which would need to iterate the arrays), since the + * speculation logic below is doing that anyway. + */ + if (s1.objects == s2.objects) { + return TypeState.forEmpty().forCanBeNull(bb, resultCanBeNull); + } + + return doSubtraction0(bb, s1, s2, resultCanBeNull); + } + + private static TypeState doSubtraction0(PointsToAnalysis bb, ContextSensitiveMultiTypeState s1, ContextSensitiveMultiTypeState s2, boolean resultCanBeNull) { + /* Speculate that s1 and s2 have either the same types, or no types in common. */ + + if (s1.typesBitSet().equals(s2.typesBitSet())) { + /* Speculate that s1 and s2 have the same types, i.e., the result is empty set. */ + return TypeState.forEmpty().forCanBeNull(bb, resultCanBeNull); + } + + if (!s1.typesBitSet().intersects(s2.typesBitSet())) { + /* Speculate that s1 and s2 have no types in common, i.e., the result is s1. */ + return s1.forCanBeNull(bb, resultCanBeNull); + } + + return doSubtraction1(bb, s1, s2, resultCanBeNull); + } + + private static TypeState doSubtraction1(PointsToAnalysis bb, ContextSensitiveMultiTypeState s1, ContextSensitiveMultiTypeState s2, boolean resultCanBeNull) { + /* + * Speculate that s1 and s2 have no overlap, i.e., they don't have any objects in common. In + * that case, the result is just s1. + */ + int idx1 = 0; + int idx2 = 0; + + AnalysisObject[] so1 = s1.objects; + AnalysisObject[] so2 = s2.objects; + while (true) { + AnalysisObject o1 = so1[idx1]; + AnalysisObject o2 = so2[idx2]; + + /* See comment above for the limitation explanation. */ + assert o2.isContextInsensitiveObject() : "Current implementation limitation."; + + if (o1.getTypeId() < o2.getTypeId()) { + idx1++; + if (idx1 == so1.length) { + return s1.forCanBeNull(bb, resultCanBeNull); + } + } else if (o1.getTypeId() > o2.getTypeId()) { + idx2++; + if (idx2 == so2.length) { + return s1.forCanBeNull(bb, resultCanBeNull); + } + } else { + /* Our speculation failed. */ + break; + } + } + + return doSubtraction2(bb, s1, s2, resultCanBeNull, idx1, idx2); + } + + private static TypeState doSubtraction2(PointsToAnalysis bb, ContextSensitiveMultiTypeState s1, ContextSensitiveMultiTypeState s2, boolean resultCanBeNull, int idx1Param, int idx2Param) { + try (ListUtils.UnsafeArrayListClosable tlArrayClosable = ListUtils.getTLArrayList(intersectionArrayListTL, 256)) { + ListUtils.UnsafeArrayList resultObjects = tlArrayClosable.list(); + + AnalysisObject[] so1 = s1.objects; + AnalysisObject[] so2 = s2.objects; + int[] types1 = s1.getObjectTypeIds(); + int[] types2 = s2.getObjectTypeIds(); + int idx1 = idx1Param; + int idx2 = idx2Param; + int l1 = so1.length; + int l2 = so2.length; + int t1 = types1[idx1]; + int t2 = types2[idx2]; + while (idx1 < l1 && idx2 < l2) { + assert so2[idx2].isContextInsensitiveObject() : "Current implementation limitation."; + if (t1 < t2) { + resultObjects.add(so1[idx1]); + t1 = types1[++idx1]; + } else if (t1 > t2) { + t2 = types2[++idx2]; + } else if (t1 == t2) { + assert so1[idx1].type().equals(so2[idx2].type()); + t1 = types1[++idx1]; + } + } + + int remainder = s1.objects.length - idx1; + int totalLength = idx1Param + resultObjects.size() + remainder; + + if (totalLength == 0) { + return TypeState.forEmpty().forCanBeNull(bb, resultCanBeNull); + } else { + AnalysisObject[] objects = new AnalysisObject[totalLength]; + /* Copy recently touched first */ + resultObjects.copyToArray(objects, idx1Param); + /* leading elements */ + System.arraycopy(s1.objects, 0, objects, 0, idx1Param); + /* trailing elements (remainder) */ + System.arraycopy(s1.objects, idx1, objects, totalLength - remainder, remainder); + + if (TypeStateUtils.holdsSingleTypeState(objects, totalLength)) { + /* Multiple objects of the same type. */ + return new ContextSensitiveSingleTypeState(bb, resultCanBeNull, bb.analysisPolicy().makeProperties(bb, objects), objects[0].type(), objects); + } else { + BitSet resultTypesBitSet = TypeStateUtils.andNot(s1.typesBitSet(), s2.typesBitSet()); + /* + * Don't need to check if the result is close-to-all-instantiated since result + * <= s1. + */ + return new ContextSensitiveMultiTypeState(bb, resultCanBeNull, bb.analysisPolicy().makeProperties(bb, objects), resultTypesBitSet, objects); + } + } + } + } + +} diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/context/bytecode/ContextSensitiveMultiTypeState.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/context/bytecode/ContextSensitiveMultiTypeState.java new file mode 100644 index 000000000000..495c424349f2 --- /dev/null +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/context/bytecode/ContextSensitiveMultiTypeState.java @@ -0,0 +1,289 @@ +/* + * Copyright (c) 2013, 2021, 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.context.bytecode; + +import java.util.Arrays; +import java.util.BitSet; +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.typestate.MultiTypeState; +import com.oracle.graal.pointsto.typestate.PointsToStats; +import com.oracle.graal.pointsto.typestate.TypeState; +import com.oracle.graal.pointsto.typestate.TypeStateUtils; + +public class ContextSensitiveMultiTypeState extends MultiTypeState { + + /** The objects of this type state. */ + protected final AnalysisObject[] objects; + /** See {@link #getObjectTypeIds()}. */ + protected int[] objectTypeIds; + + /** Creates a new type state using the provided types bit set and objects. */ + public ContextSensitiveMultiTypeState(PointsToAnalysis bb, boolean canBeNull, int properties, BitSet typesBitSet, AnalysisObject... objects) { + super(bb, canBeNull, properties, typesBitSet); + this.objects = objects; + /* + * Trim the typesBitSet to size eagerly. The typesBitSet is effectively immutable, i.e., no + * calls to mutating methods are made on it after it is set in the MultiTypeState, thus we + * don't need to use any external synchronization. However, to keep it immutable we use + * BitSet.clone() when deriving a new BitSet since the set operations (and, or, etc.) mutate + * the original object. The problem is that BitSet.clone() breaks the informal contract that + * the clone method should not modify the original object; it calls trimToSize() before + * creating a copy. Thus, trimming the bit set here ensures that cloning does not modify the + * typesBitSet. Since BitSet is not thread safe mutating it during cloning is problematic in + * a multithreaded environment. If for example you iterate over the bits at the same time as + * another thread calls clone() the words[] array can be in an inconsistent state. + */ + TypeStateUtils.trimBitSetToSize(typesBitSet); + long cardinality = typesBitSet.cardinality(); + assert cardinality < Integer.MAX_VALUE : "We don't expect so much types."; + assert typesCount > 1 : "Multi type state with single type."; + assert objects.length > 1 : "Multi type state with single object."; + assert !bb.extendedAsserts() || checkObjects(bb); + PointsToStats.registerTypeState(bb, this); + } + + /** Create a type state with the same content and a reversed canBeNull value. */ + protected ContextSensitiveMultiTypeState(PointsToAnalysis bb, boolean canBeNull, ContextSensitiveMultiTypeState other) { + super(bb, canBeNull, other); + this.objects = other.objects; + this.merged = other.merged; + PointsToStats.registerTypeState(bb, this); + } + + /** + * Returns an array of all type ids from the {@link #objects} array. This mitigates the CPU + * cache misses when iterating over all AnalysisObject and dereferencing the type field over and + * over again. + */ + public int[] getObjectTypeIds() { + if (objectTypeIds == null) { + // One item longer, so we can support readahead of one in the loop without + // ArrayOutOfBoundsException + int[] result = new int[objects.length + 1]; + for (int i = 0; i < objects.length; i++) { + result[i] = objects[i].getTypeId(); + } + this.objectTypeIds = result; + } + return objectTypeIds; + } + + private boolean checkObjects(PointsToAnalysis bb) { + assert bb.extendedAsserts(); + + for (int idx = 0; idx < objects.length - 1; idx++) { + AnalysisObject o0 = objects[idx]; + AnalysisObject o1 = objects[idx + 1]; + + assert o0 != null && o1 != null : "Object state must contain non null elements."; + + /* Check that the objects array are sorted by type. */ + assert (o0.type().equals(o1.type()) && o0.getId() < o1.getId()) || o0.type().getId() < o1.type().getId() : "Analysis objects must be sorted by type ID and ID."; + + /* Check that the bit is set for the types. */ + assert typesBitSet.get(o0.type().getId()); + assert typesBitSet.get(o1.type().getId()); + } + + return true; + } + + /** Get the number of objects. */ + @Override + public int objectsCount() { + return objects.length; + } + + @Override + protected Iterator objectsIterator(BigBang bb) { + return Arrays.asList(objects).iterator(); + } + + /** Get the type of the first object group. */ + public AnalysisType firstType() { + return objects[0].type(); + } + + /** Get the type of the last object group. */ + public AnalysisType lastType() { + return objects[objects.length - 1].type(); + } + + @Override + public TypeState forCanBeNull(PointsToAnalysis bb, boolean resultCanBeNull) { + if (resultCanBeNull == this.canBeNull()) { + return this; + } else { + /* Just flip the canBeNull flag and copy the rest of the values from this. */ + return new ContextSensitiveMultiTypeState(bb, resultCanBeNull, this); + } + } + + /** + * A [left, right) range (interval), i.e., left is inclusive, right is exclusive. The values in + * the defined range can be naturally iterated using + * for(int i = left; i < right; i++) {} . A range with {@code left} equal to + * {@code right} is an empty range. + */ + public static class Range { + + static final Range EMPTY = new Range(0, 0); + + protected static Range range(int up, int low) { + return new Range(up, low); + } + + /** An inclusive left end point. */ + final int left; + /** An exclusive right end point. */ + protected final int right; + + Range(int left, int right) { + this.left = left; + this.right = right; + } + + public int left() { + return left; + } + + public int right() { + return right; + } + + @Override + public String toString() { + return "[" + left + ", " + right + ")"; + } + } + + public Range findTypeRange(AnalysisType type) { + + /* First do a quick check using the types bit set. */ + if (!containsType(type)) { + /* There is no object of the inquired type in this array. */ + return Range.EMPTY; + } + + /* Then binary search to find some object of the inquired type. */ + int someIdx = Arrays.binarySearch(objects, type.getContextInsensitiveAnalysisObject(), AnalysisObject.objectsTypeComparator); + assert someIdx >= 0 : "The inquired type must be in the array."; + + int firstIdx = someIdx; + while (firstIdx >= 0 && objects[firstIdx].getTypeId() == type.getId()) { + /* Find the first index by walking down from the found index until the type changes. */ + firstIdx--; + } + int lastIdx = someIdx; + while (lastIdx < objects.length && objects[lastIdx].getTypeId() == type.getId()) { + /* Find the last index by walking up from the found index until the type changes. . */ + lastIdx++; + } + + /* + * Range.left is inclusive, so we must increment firstIdx. Range.right is exclusive so we + * just use lastIdx. + */ + return Range.range(firstIdx + 1, lastIdx); + } + + public AnalysisObject[] objectsArray(AnalysisType type) { + Range typeRange = findTypeRange(type); + return Arrays.copyOfRange(objects, typeRange.left, typeRange.right); + } + + public AnalysisObject[] objectsArray(Range typeRange) { + return Arrays.copyOfRange(objects, typeRange.left, typeRange.right); + } + + @Override + public Iterator objectsIterator(AnalysisType exactType) { + return new Iterator<>() { + private final Range typeRange = findTypeRange(exactType); + private int idx = typeRange.left; + + @Override + public boolean hasNext() { + return idx < typeRange.right; + } + + @Override + public AnalysisObject next() { + return objects[idx++]; + } + }; + } + + /** Note that the objects of this type state have been merged. */ + @Override + public void noteMerge(PointsToAnalysis bb) { + assert bb.analysisPolicy().isMergingEnabled(); + + if (!merged) { + for (AnalysisObject obj : objects) { + obj.noteMerge(bb); + } + merged = true; + } + } + + @Override + public boolean isMerged() { + return merged; + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Arrays.hashCode(objects); + result = 31 * result + (canBeNull ? 1 : 0); + return result; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + ContextSensitiveMultiTypeState that = (ContextSensitiveMultiTypeState) o; + return this.canBeNull == that.canBeNull && + this.typesCount == that.typesCount && this.typesBitSet.equals(that.typesBitSet) && + Arrays.equals(this.objects, that.objects); + } + + @Override + public String toString() { + return "MTypeMObject<" + objects.length + ":" + (canBeNull ? "null," : "") + Arrays.toString(objects) + ">"; + } +} diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/context/bytecode/ContextSensitiveSingleTypeState.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/context/bytecode/ContextSensitiveSingleTypeState.java new file mode 100644 index 000000000000..d6addaafbe95 --- /dev/null +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/context/bytecode/ContextSensitiveSingleTypeState.java @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2013, 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.graal.pointsto.flow.context.bytecode; + +import java.util.ArrayList; +import java.util.Arrays; +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.typestate.PointsToStats; +import com.oracle.graal.pointsto.typestate.SingleTypeState; +import com.oracle.graal.pointsto.typestate.TypeState; + +public class ContextSensitiveSingleTypeState extends SingleTypeState { + /** The objects of this type state. */ + protected final AnalysisObject[] objects; + + public ContextSensitiveSingleTypeState(PointsToAnalysis bb, boolean canBeNull, int properties, AnalysisType type, ArrayList objects) { + this(bb, canBeNull, properties, type, objects.toArray(AnalysisObject.EMPTY_ARRAY)); + assert objects.size() > 0 : "Single type state with no objects."; + } + + /** Creates a new type state from incoming objects. */ + public ContextSensitiveSingleTypeState(PointsToAnalysis bb, boolean canBeNull, int properties, AnalysisType type, AnalysisObject... objects) { + super(bb, canBeNull, properties, type); + this.objects = objects; + assert !bb.extendedAsserts() || checkObjects(bb); + + PointsToStats.registerTypeState(bb, this); + } + + /** Create a type state with the same content and a reversed canBeNull value. */ + protected ContextSensitiveSingleTypeState(PointsToAnalysis bb, boolean canBeNull, ContextSensitiveSingleTypeState other) { + super(bb, canBeNull, other); + this.objects = other.objects; + + PointsToStats.registerTypeState(bb, this); + } + + protected boolean checkObjects(BigBang bb) { + assert bb.extendedAsserts(); + + /* Check that the objects array are sorted by type. */ + for (int idx = 0; idx < objects.length - 1; idx++) { + AnalysisObject o0 = objects[idx]; + AnalysisObject o1 = objects[idx + 1]; + + assert o0 != null && o1 != null : "Object state must contain non null elements."; + + assert o0.type().equals(o1.type()) : "Single type state objects must have the same type."; + /* + * Check that the objects are sorted by ID. Since the objects should be unique (context + * sensitive objects are merged when they have the same type during the union + * operation), we use < for the test. + */ + assert o0.getId() < o1.getId() : "Analysis objects must be sorted by ID."; + } + + return true; + } + + @Override + public final int objectsCount() { + return objects.length; + } + + @Override + public Iterator objectsIterator(BigBang bb) { + return Arrays.asList(objects).iterator(); + } + + @Override + protected Iterator objectsIterator(AnalysisType t) { + return new Iterator<>() { + private final boolean typesEqual = type.equals(t); + private int idx = 0; + + @Override + public boolean hasNext() { + return typesEqual && idx < objects.length; + } + + @Override + public AnalysisObject next() { + return objects[idx++]; + } + }; + } + + @Override + public TypeState forCanBeNull(PointsToAnalysis bb, boolean stateCanBeNull) { + if (stateCanBeNull == this.canBeNull()) { + return this; + } else { + return new ContextSensitiveSingleTypeState(bb, stateCanBeNull, this); + } + } + + /** Note that the objects of this type state have been merged. */ + @Override + public void noteMerge(PointsToAnalysis bb) { + assert bb.analysisPolicy().isMergingEnabled(); + + if (!merged) { + for (AnalysisObject obj : objects) { + obj.noteMerge(bb); + } + merged = true; + } + } + + @Override + public boolean isMerged() { + return merged; + } + + @Override + public boolean isAllocation() { + return objects[0].isAllocationContextSensitiveObject(); + } + + @Override + public boolean isConstant() { + return objects[0].isConstantContextSensitiveObject(); + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Arrays.hashCode(objects); + result = 31 * result + (canBeNull ? 1 : 0); + return result; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + ContextSensitiveSingleTypeState that = (ContextSensitiveSingleTypeState) o; + return this.canBeNull == that.canBeNull && this.exactType().equals(that.exactType()) && Arrays.equals(this.objects, that.objects); + } + + @Override + public String toString() { + return "1TypeMObject<" + (canBeNull ? "null," : "") + Arrays.toString(objects) + ">"; + } + +} diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/DefaultAnalysisPolicy.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/context/free/DefaultAnalysisPolicy.java similarity index 68% rename from substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/DefaultAnalysisPolicy.java rename to substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/context/free/DefaultAnalysisPolicy.java index 0aa47eddb9c3..2dac5ee1e342 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/DefaultAnalysisPolicy.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/context/free/DefaultAnalysisPolicy.java @@ -22,16 +22,19 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ -package com.oracle.graal.pointsto; +package com.oracle.graal.pointsto.flow.context.free; import java.lang.reflect.Modifier; import java.util.ArrayList; +import java.util.BitSet; import java.util.Collection; import java.util.Collections; import java.util.List; import org.graalvm.compiler.options.OptionValues; +import com.oracle.graal.pointsto.AnalysisPolicy; +import com.oracle.graal.pointsto.PointsToAnalysis; import com.oracle.graal.pointsto.constraints.UnsupportedFeatureException; import com.oracle.graal.pointsto.flow.AbstractSpecialInvokeTypeFlow; import com.oracle.graal.pointsto.flow.AbstractVirtualInvokeTypeFlow; @@ -43,14 +46,17 @@ import com.oracle.graal.pointsto.flow.TypeFlow; import com.oracle.graal.pointsto.flow.context.AnalysisContext; import com.oracle.graal.pointsto.flow.context.BytecodeLocation; -import com.oracle.graal.pointsto.flow.context.free.DefaultAnalysisContextPolicy; import com.oracle.graal.pointsto.flow.context.object.AnalysisObject; import com.oracle.graal.pointsto.meta.AnalysisField; 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.PointsToAnalysisMethod; +import com.oracle.graal.pointsto.typestate.MultiTypeState; +import com.oracle.graal.pointsto.typestate.PointsToStats; +import com.oracle.graal.pointsto.typestate.SingleTypeState; import com.oracle.graal.pointsto.typestate.TypeState; +import com.oracle.graal.pointsto.typestate.TypeStateUtils; import com.oracle.graal.pointsto.typestore.ArrayElementsTypeStore; import com.oracle.graal.pointsto.typestore.FieldTypeStore; import com.oracle.graal.pointsto.typestore.UnifiedArrayElementsTypeStore; @@ -420,4 +426,192 @@ public Collection getCalleesFlows(PointsToAnalysis bb) { } } + @Override + public TypeState forContextInsensitiveTypeState(PointsToAnalysis bb, TypeState state) { + /* The type state is already context insensitive. */ + return state; + } + + @Override + public SingleTypeState singleTypeState(PointsToAnalysis bb, boolean canBeNull, int properties, AnalysisType type, AnalysisObject... objects) { + return new SingleTypeState(bb, canBeNull, properties, type); + } + + @Override + public MultiTypeState multiTypeState(PointsToAnalysis bb, boolean canBeNull, int properties, BitSet typesBitSet, AnalysisObject... objects) { + return new MultiTypeState(bb, canBeNull, properties, typesBitSet); + } + + @Override + public TypeState doUnion(PointsToAnalysis bb, SingleTypeState s1, SingleTypeState s2) { + 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; + } + } else { + /* + * The inputs have different types, so the result is a MultiTypeState. We know the + * types, just construct the types bit set. + */ + BitSet typesBitSet = TypeStateUtils.newBitSet(s1.exactType().getId(), s2.exactType().getId()); + TypeState result = new MultiTypeState(bb, resultCanBeNull, 0, typesBitSet); + PointsToStats.registerUnionOperation(bb, s1, s2, result); + return result; + } + } + + @Override + public TypeState doUnion(PointsToAnalysis bb, MultiTypeState s1, SingleTypeState s2) { + boolean resultCanBeNull = s1.canBeNull() || s2.canBeNull(); + + if (s1.containsType(s2.exactType())) { + /* The type of s2 is already contained in s1. */ + return s1.forCanBeNull(bb, resultCanBeNull); + } else { + BitSet typesBitSet = TypeStateUtils.set(s1.typesBitSet(), s2.exactType().getId()); + MultiTypeState result = new MultiTypeState(bb, resultCanBeNull, 0, typesBitSet); + PointsToStats.registerUnionOperation(bb, s1, s2, result); + return result; + } + } + + @Override + public TypeState doUnion(PointsToAnalysis bb, MultiTypeState s1, MultiTypeState s2) { + assert s1.typesCount() >= s2.typesCount(); + + boolean resultCanBeNull = s1.canBeNull() || s2.canBeNull(); + + /* + * No need for a deep equality check (which would need to iterate the arrays), since the + * speculation logic below is doing that anyway. + */ + if (s1.typesBitSet() == s2.typesBitSet()) { + return s1.forCanBeNull(bb, resultCanBeNull); + } + + /* Speculate that s1 is a superset of s2. */ + if (TypeStateUtils.isSuperset(s1.typesBitSet(), s2.typesBitSet())) { + return s1.forCanBeNull(bb, resultCanBeNull); + } + + /* Logical OR the type bit sets. */ + BitSet resultTypesBitSet = TypeStateUtils.or(s1.typesBitSet(), s2.typesBitSet()); + + MultiTypeState result = new MultiTypeState(bb, resultCanBeNull, 0, resultTypesBitSet); + assert !result.equals(s1) && !result.equals(s2); + PointsToStats.registerUnionOperation(bb, s1, s2, result); + return result; + } + + @Override + public TypeState doIntersection(PointsToAnalysis bb, MultiTypeState s1, SingleTypeState s2) { + assert !bb.extendedAsserts() || TypeStateUtils.isContextInsensitiveTypeState(bb, s2) : "Current implementation limitation."; + + boolean resultCanBeNull = s1.canBeNull() && s2.canBeNull(); + if (s1.containsType(s2.exactType())) { + /* s1 contains s2's type, return s2. */ + return s2.forCanBeNull(bb, resultCanBeNull); + } else { + return TypeState.forEmpty().forCanBeNull(bb, resultCanBeNull); + } + } + + @Override + public TypeState doIntersection(PointsToAnalysis bb, MultiTypeState s1, MultiTypeState s2) { + boolean resultCanBeNull = s1.canBeNull() && s2.canBeNull(); + + /* Speculate that s1 and s2 have either the same types, or no types in common. */ + + if (s1.typesBitSet().equals(s2.typesBitSet())) { + /* Speculate that s1 and s2 have the same types, i.e., the result is s1. */ + return s1.forCanBeNull(bb, resultCanBeNull); + } + + if (!s1.typesBitSet().intersects(s2.typesBitSet())) { + /* Speculate that s1 and s2 have no types in common, i.e., the result is empty. */ + return TypeState.forEmpty().forCanBeNull(bb, resultCanBeNull); + } + + /* + * Speculate that s2 contains all types of s1, i.e., the filter is broader than s1, thus the + * result is s1. + */ + if (TypeStateUtils.isSuperset(s2.typesBitSet(), s1.typesBitSet())) { + return s1.forCanBeNull(bb, resultCanBeNull); + } + + BitSet resultTypesBitSet = TypeStateUtils.and(s1.typesBitSet(), s2.typesBitSet()); + if (resultTypesBitSet.cardinality() == 0) { + return TypeState.forEmpty().forCanBeNull(bb, resultCanBeNull); + } else if (resultTypesBitSet.cardinality() == 1) { + AnalysisType type = bb.getUniverse().getType(resultTypesBitSet.nextSetBit(0)); + return new SingleTypeState(bb, resultCanBeNull, 0, type); + } else { + MultiTypeState result = new MultiTypeState(bb, resultCanBeNull, 0, resultTypesBitSet); + assert !result.equals(s1); + return result; + } + } + + @Override + public TypeState doSubtraction(PointsToAnalysis bb, MultiTypeState s1, SingleTypeState s2) { + assert !bb.extendedAsserts() || TypeStateUtils.isContextInsensitiveTypeState(bb, s2) : "Current implementation limitation."; + boolean resultCanBeNull = s1.canBeNull() && !s2.canBeNull(); + if (s1.containsType(s2.exactType())) { + /* s2 is contained in s1, so remove s2's type from s1. */ + BitSet resultTypesBitSet = TypeStateUtils.clear(s1.typesBitSet(), s2.exactType().getId()); + assert resultTypesBitSet.cardinality() > 0; + if (resultTypesBitSet.cardinality() == 1) { + AnalysisType type = bb.getUniverse().getType(resultTypesBitSet.nextSetBit(0)); + return new SingleTypeState(bb, resultCanBeNull, 0, type); + } else { + return new MultiTypeState(bb, resultCanBeNull, 0, resultTypesBitSet); + } + } else { + return s1.forCanBeNull(bb, resultCanBeNull); + } + } + + @Override + public TypeState doSubtraction(PointsToAnalysis bb, MultiTypeState s1, MultiTypeState s2) { + boolean resultCanBeNull = s1.canBeNull() && !s2.canBeNull(); + + /* Speculate that s1 and s2 have either the same types, or no types in common. */ + + if (s1.typesBitSet().equals(s2.typesBitSet())) { + /* Speculate that s1 and s2 have the same types, i.e., the result is empty set. */ + return TypeState.forEmpty().forCanBeNull(bb, resultCanBeNull); + } + + if (!s1.typesBitSet().intersects(s2.typesBitSet())) { + /* Speculate that s1 and s2 have no types in common, i.e., the result is s1. */ + return s1.forCanBeNull(bb, resultCanBeNull); + } + + /* + * Speculate that s2 contains all types of s1, i.e., the filter is broader than s1, thus the + * result is empty. + */ + if (TypeStateUtils.isSuperset(s2.typesBitSet(), s1.typesBitSet())) { + return TypeState.forEmpty().forCanBeNull(bb, resultCanBeNull); + } + + BitSet resultTypesBitSet = TypeStateUtils.andNot(s1.typesBitSet(), s2.typesBitSet()); + if (resultTypesBitSet.cardinality() == 0) { + return TypeState.forEmpty().forCanBeNull(bb, resultCanBeNull); + } else if (resultTypesBitSet.cardinality() == 1) { + AnalysisType type = bb.getUniverse().getType(resultTypesBitSet.nextSetBit(0)); + return new SingleTypeState(bb, resultCanBeNull, 0, type); + } else { + return new MultiTypeState(bb, resultCanBeNull, 0, resultTypesBitSet); + } + } + } 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 a4064efba9b6..75f3509072f8 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 @@ -25,9 +25,7 @@ package com.oracle.graal.pointsto.flow.context.object; import java.lang.reflect.Modifier; -import java.util.ArrayList; import java.util.Collections; -import java.util.HashSet; import java.util.List; import com.oracle.graal.pointsto.PointsToAnalysis; @@ -40,14 +38,11 @@ import com.oracle.graal.pointsto.meta.AnalysisType; import com.oracle.graal.pointsto.meta.AnalysisUniverse; import com.oracle.graal.pointsto.typestore.FieldTypeStore; -import com.oracle.graal.pointsto.typestore.UnifiedFieldTypeStore; import jdk.vm.ci.code.BytecodePosition; public class ContextSensitiveAnalysisObject extends AnalysisObject { - private List referencedObjects; - public ContextSensitiveAnalysisObject(AnalysisUniverse universe, AnalysisType type, AnalysisObjectKind kind) { super(universe, type, kind); assert PointstoOptions.AllocationSiteSensitiveHeap.getValue(universe.hostVM().options()); @@ -180,43 +175,4 @@ protected void linkFieldFlows(PointsToAnalysis bb, AnalysisField field, FieldTyp protected List getAllObjectsMergedWith() { return merged ? Collections.singletonList(type().getContextInsensitiveAnalysisObject()) : Collections.emptyList(); } - - /** - * Returns the list of referenced objects, i.e., field objects or array elements discovered by - * the static analysis. - * - * Since this list is not updated during the analysis, for complete results this should only be - * called when the base analysis has finished. - */ - public List getReferencedObjects() { - - if (referencedObjects == null) { - - // TODO do we need to materialize the objects in a HashSet here, or could we just - // iterate over them? - - HashSet objectsSet = new HashSet<>(); - if (this.type().isArray()) { - for (AnalysisObject object : arrayElementsTypeStore.readFlow().getState().objects()) { - objectsSet.add(object); - } - } else { - if (instanceFieldsTypeStore != null) { - for (int i = 0; i < instanceFieldsTypeStore.length(); i++) { - FieldTypeStore fieldTypeStore = instanceFieldsTypeStore.get(i); - if (fieldTypeStore != null) { - FieldTypeFlow fieldFlow = ((UnifiedFieldTypeStore) fieldTypeStore).readWriteFlow(); - for (AnalysisObject object : fieldFlow.getState().objects()) { - objectsSet.add(object); - } - } - } - } - } - - referencedObjects = new ArrayList<>(objectsSet); - } - - return referencedObjects; - } } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/MultiTypeState.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/MultiTypeState.java index adaa237f8246..6f2ce7eca25b 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/MultiTypeState.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/MultiTypeState.java @@ -24,7 +24,6 @@ */ package com.oracle.graal.pointsto.typestate; -import java.util.Arrays; import java.util.BitSet; import java.util.Iterator; @@ -32,31 +31,25 @@ 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.meta.AnalysisUniverse; +import com.oracle.graal.pointsto.util.AnalysisError; public class MultiTypeState extends TypeState { - /** The objects of this type state. */ - protected final AnalysisObject[] objects; - /** See {@link #getObjectTypeIds()}. */ - protected int[] objectTypeIds; /** * Keep a bit set for types to easily answer queries like contains type or types count, and - * quickly iterate over the types. It costs us one linear pass over the objects when the state - * is first created but the cost is amortized for frequently used states. + * quickly iterate over the types. */ - final BitSet typesBitSet; + protected final BitSet typesBitSet; /** Cache the number of types since BitSet.cardinality() computes it every time is called. */ - private final int typesCount; + protected final int typesCount; /** Can this type state represent the null value? */ protected final boolean canBeNull; /** Has this type state been merged with the all-instantiated type state? */ protected boolean merged; /** Creates a new type state using the provided types bit set and objects. */ - MultiTypeState(PointsToAnalysis bb, boolean canBeNull, int properties, BitSet typesBitSet, AnalysisObject... objects) { + public MultiTypeState(PointsToAnalysis bb, boolean canBeNull, int properties, BitSet typesBitSet) { super(properties); - this.objects = objects; /* * Trim the typesBitSet to size eagerly. The typesBitSet is effectively immutable, i.e., no * calls to mutating methods are made on it after it is set in the MultiTypeState, thus we @@ -77,15 +70,12 @@ public class MultiTypeState extends TypeState { this.canBeNull = canBeNull; this.merged = false; assert typesCount > 1 : "Multi type state with single type."; - assert objects.length > 1 : "Multi type state with single object."; - assert !bb.extendedAsserts() || checkObjects(bb); PointsToStats.registerTypeState(bb, this); } /** Create a type state with the same content and a reversed canBeNull value. */ - private MultiTypeState(PointsToAnalysis bb, boolean canBeNull, MultiTypeState other) { + protected MultiTypeState(PointsToAnalysis bb, boolean canBeNull, MultiTypeState other) { super(other.properties); - this.objects = other.objects; this.typesBitSet = other.typesBitSet; this.typesCount = other.typesCount; this.canBeNull = canBeNull; @@ -93,64 +83,20 @@ private MultiTypeState(PointsToAnalysis bb, boolean canBeNull, MultiTypeState ot PointsToStats.registerTypeState(bb, this); } - /** - * Returns an array of all type ids from the {@link #objects} array. This mitigates the CPU - * cache misses when iterating over all AnalysisObject and dereferencing the type field over and - * over again. - */ - public int[] getObjectTypeIds() { - if (objectTypeIds == null) { - // One item longer, so we can support readahead of one in the loop without - // ArrayOutOfBoundsException - int[] result = new int[objects.length + 1]; - for (int i = 0; i < objects.length; i++) { - result[i] = objects[i].getTypeId(); - } - this.objectTypeIds = result; - } - return objectTypeIds; - } - - private boolean checkObjects(PointsToAnalysis bb) { - assert bb.extendedAsserts(); - - for (int idx = 0; idx < objects.length - 1; idx++) { - AnalysisObject o0 = objects[idx]; - AnalysisObject o1 = objects[idx + 1]; - - assert o0 != null && o1 != null : "Object state must contain non null elements."; - - /* Check that the objects array are sorted by type. */ - assert (o0.type().equals(o1.type()) && o0.getId() < o1.getId()) || o0.type().getId() < o1.type().getId() : "Analysis objects must be sorted by type ID and ID."; - - /* Check that the bit is set for the types. */ - assert typesBitSet.get(o0.type().getId()); - assert typesBitSet.get(o1.type().getId()); - } - - return true; - } - /** Get the number of objects. */ @Override public int objectsCount() { - return objects.length; - } - - /** Returns the objects as an array. */ - @Override - public final AnalysisObject[] objects() { - return objects; + return typesCount; } @Override - public boolean hasExactTypes(BitSet inputTypesBitSet) { + public final boolean hasExactTypes(BitSet inputTypesBitSet) { return typesBitSet.equals(inputTypesBitSet); } @Override public AnalysisType exactType() { - return typesCount == 1 ? objects[0].type() : null; + return null; } @Override @@ -158,153 +104,64 @@ public int typesCount() { return typesCount; } - /** Get the type of the first object group. */ - AnalysisType firstType() { - return objects[0].type(); + public BitSet typesBitSet() { + return typesBitSet; } - /** Get the type of the last object group. */ - AnalysisType lastType() { - return objects[objects.length - 1].type(); - } - - /** - * It iterates over the types bit set and gets the types using - * {@link AnalysisUniverse#getType(int)}. The types are iterated in ascending order of their IDs - * by way of bit set iteration. - */ @Override - public Iterator typesIterator(BigBang bb) { - return new Iterator<>() { - - /** Initialize to the index of the first set bit. */ - private int currentTypeId = typesBitSet.nextSetBit(0); - - @Override - public boolean hasNext() { - return currentTypeId >= 0; - } - + public final Iterator typesIterator(BigBang bb) { + return new BitSetIterator<>() { @Override public AnalysisType next() { - AnalysisType next = bb.getUniverse().getType(currentTypeId); - currentTypeId = typesBitSet.nextSetBit(currentTypeId + 1); - return next; + return bb.getUniverse().getType(nextSetBit()); } }; } @Override - public boolean containsType(AnalysisType exactType) { - return typesBitSet.get(exactType.getId()); - } - - @Override - public TypeState exactTypeState(PointsToAnalysis bb, AnalysisType exactType) { - if (containsType(exactType)) { - AnalysisObject[] resultObjects = objectsArray(exactType); - return new SingleTypeState(bb, canBeNull, bb.analysisPolicy().makeProperties(bb, resultObjects), resultObjects); - } else { - return EmptyTypeState.SINGLETON; - } - } - - @Override - public TypeState forCanBeNull(PointsToAnalysis bb, boolean resultCanBeNull) { - if (resultCanBeNull == this.canBeNull()) { - return this; - } else { - /* Just flip the canBeNull flag and copy the rest of the values from this. */ - return new MultiTypeState(bb, resultCanBeNull, this); - } + protected Iterator objectsIterator(BigBang bb) { + return new BitSetIterator<>() { + @Override + public AnalysisObject next() { + return bb.getUniverse().getType(nextSetBit()).getContextInsensitiveAnalysisObject(); + } + }; } - /** - * A [left, right) range (interval), i.e., left is inclusive, right is exclusive. The values in - * the defined range can be naturally iterated using - * for(int i = left; i < right; i++) {} . A range with {@code left} equal to - * {@code right} is an empty range. - */ - public static class Range { - - static final Range EMPTY = new Range(0, 0); - - protected static Range range(int up, int low) { - return new Range(up, low); - } - - /** An inclusive left end point. */ - final int left; - /** An exclusive right end point. */ - protected final int right; - - Range(int left, int right) { - this.left = left; - this.right = right; - } + /** Iterates over the types bit set and returns the type IDs in ascending order. */ + private abstract class BitSetIterator implements Iterator { + private int current = typesBitSet.nextSetBit(0); @Override - public String toString() { - return "[" + left + ", " + right + ")"; - } - } - - Range findTypeRange(AnalysisType type) { - - /* First do a quick check using the types bit set. */ - if (!containsType(type)) { - /* There is no object of the inquired type in this array. */ - return Range.EMPTY; + public boolean hasNext() { + return current >= 0; } - /* Then binary search to find some object of the inquired type. */ - int someIdx = Arrays.binarySearch(objects, type.getContextInsensitiveAnalysisObject(), AnalysisObject.objectsTypeComparator); - assert someIdx >= 0 : "The inquired type must be in the array."; - - int firstIdx = someIdx; - while (firstIdx >= 0 && objects[firstIdx].getTypeId() == type.getId()) { - /* Find the first index by walking down from the found index until the type changes. */ - firstIdx--; - } - int lastIdx = someIdx; - while (lastIdx < objects.length && objects[lastIdx].getTypeId() == type.getId()) { - /* Find the last index by walking up from the found index until the type changes. . */ - lastIdx++; + public Integer nextSetBit() { + int next = current; + current = typesBitSet.nextSetBit(current + 1); + return next; } - - /* - * Range.left is inclusive, so we must increment firstIdx. Range.right is exclusive so we - * just use lastIdx. - */ - return Range.range(firstIdx + 1, lastIdx); } @Override - public AnalysisObject[] objectsArray(AnalysisType type) { - Range typeRange = findTypeRange(type); - return Arrays.copyOfRange(objects, typeRange.left, typeRange.right); + public Iterator objectsIterator(AnalysisType exactType) { + throw AnalysisError.shouldNotReachHere("unimplemented"); } - AnalysisObject[] objectsArray(Range typeRange) { - return Arrays.copyOfRange(objects, typeRange.left, typeRange.right); + @Override + public final boolean containsType(AnalysisType exactType) { + return typesBitSet.get(exactType.getId()); } @Override - public Iterator objectsIterator(AnalysisType exactType) { - return new Iterator<>() { - private Range typeRange = findTypeRange(exactType); - private int idx = typeRange.left; - - @Override - public boolean hasNext() { - return idx < typeRange.right; - } - - @Override - public AnalysisObject next() { - return objects[idx++]; - } - }; + public TypeState forCanBeNull(PointsToAnalysis bb, boolean resultCanBeNull) { + if (resultCanBeNull == this.canBeNull()) { + return this; + } else { + /* Just flip the canBeNull flag and copy the rest of the values from this. */ + return new MultiTypeState(bb, resultCanBeNull, this); + } } @Override @@ -318,8 +175,8 @@ public void noteMerge(PointsToAnalysis bb) { assert bb.analysisPolicy().isMergingEnabled(); if (!merged) { - for (AnalysisObject obj : objects) { - obj.noteMerge(bb); + for (AnalysisType type : types(bb)) { + type.getContextInsensitiveAnalysisObject().noteMerge(bb); } merged = true; } @@ -333,7 +190,7 @@ public boolean isMerged() { @Override public int hashCode() { int result = 1; - result = 31 * result + Arrays.hashCode(objects); + result = 31 * result + typesBitSet.hashCode(); result = 31 * result + (canBeNull ? 1 : 0); return result; } @@ -349,12 +206,11 @@ public boolean equals(Object o) { MultiTypeState that = (MultiTypeState) o; return this.canBeNull == that.canBeNull && - this.typesCount == that.typesCount && this.typesBitSet.equals(that.typesBitSet) && - Arrays.equals(this.objects, that.objects); + this.typesCount == that.typesCount && this.typesBitSet.equals(that.typesBitSet); } @Override public String toString() { - return "MTypeMObject<" + objects.length + ":" + (canBeNull ? "null," : "") + Arrays.toString(objects) + ">"; + return "MType<" + typesCount + ":" + (canBeNull ? "null," : "") + "TODO" + ">"; } } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/PointsToStats.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/PointsToStats.java index 2cf99bcab598..67087ae31854 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/PointsToStats.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/PointsToStats.java @@ -334,7 +334,7 @@ private static void reportTypeFlowStats(BufferedWriter out) { private static final AtomicInteger nextStateId = new AtomicInteger(); private static ConcurrentHashMap typeStateStats = new ConcurrentHashMap<>(); - static void registerTypeState(PointsToAnalysis bb, TypeState state) { + public static void registerTypeState(PointsToAnalysis bb, TypeState state) { if (!bb.reportAnalysisStatistics()) { return; @@ -373,7 +373,7 @@ private static void reportTypeStateStats(BufferedWriter out) { private static ConcurrentHashMap unionStats = new ConcurrentHashMap<>(); - static void registerUnionOperation(PointsToAnalysis bb, TypeState s1, TypeState s2, TypeState result) { + public static void registerUnionOperation(PointsToAnalysis bb, TypeState s1, TypeState s2, TypeState result) { if (!bb.reportAnalysisStatistics()) { return; @@ -632,8 +632,8 @@ public static String asString(TypeState s) { return ""; } - String sKind = s.isAllocation() ? "Alloc" : s.isConstant() ? "Const" : s.isSingleTypeState() ? "Single" : s.isMultiTypeState() ? "Multi" : ""; - String sSizeOrType = s.isMultiTypeState() ? s.typesCount() + "" : s.exactType().toJavaName(false); + String sKind = s.isAllocation() ? "Alloc" : s.isConstant() ? "Const" : s instanceof SingleTypeState ? "Single" : s instanceof MultiTypeState ? "Multi" : ""; + String sSizeOrType = s instanceof MultiTypeState ? s.typesCount() + "" : s.exactType().toJavaName(false); int objectsNumber = s.objectsCount(); String canBeNull = s.canBeNull() ? "null" : "!null"; 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 d513c9d77290..e1897cc1f9b2 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 @@ -24,9 +24,8 @@ */ package com.oracle.graal.pointsto.typestate; -import java.util.ArrayList; -import java.util.Arrays; import java.util.BitSet; +import java.util.Collections; import java.util.Iterator; import com.oracle.graal.pointsto.BigBang; @@ -35,25 +34,18 @@ import com.oracle.graal.pointsto.meta.AnalysisType; public class SingleTypeState extends TypeState { - /** The objects of this type state. */ - protected final AnalysisObject[] objects; + protected final AnalysisType type; /** Can this type state represent the null value? */ protected final boolean canBeNull; /** Has this type state been merged with the all-instantiated type state? */ - private boolean merged; - - public SingleTypeState(PointsToAnalysis bb, boolean canBeNull, int properties, ArrayList objects) { - this(bb, canBeNull, properties, objects.toArray(new AnalysisObject[objects.size()])); - } + protected boolean merged; /** Creates a new type state from incoming objects. */ - public SingleTypeState(PointsToAnalysis bb, boolean canBeNull, int properties, AnalysisObject... objects) { + public SingleTypeState(PointsToAnalysis bb, boolean canBeNull, int properties, AnalysisType type) { super(properties); - this.objects = objects; + this.type = type; this.canBeNull = canBeNull; this.merged = false; - assert objects.length > 0 : "Single type state with no objects."; - assert !bb.extendedAsserts() || checkObjects(bb); PointsToStats.registerTypeState(bb, this); } @@ -61,123 +53,77 @@ public SingleTypeState(PointsToAnalysis bb, boolean canBeNull, int properties, A /** Create a type state with the same content and a reversed canBeNull value. */ protected SingleTypeState(PointsToAnalysis bb, boolean canBeNull, SingleTypeState other) { super(other.properties); - this.objects = other.objects; + this.type = other.type; this.canBeNull = canBeNull; this.merged = other.merged; PointsToStats.registerTypeState(bb, this); } - protected boolean checkObjects(BigBang bb) { - assert bb.extendedAsserts(); - - /* Check that the objects array are sorted by type. */ - for (int idx = 0; idx < objects.length - 1; idx++) { - AnalysisObject o0 = objects[idx]; - AnalysisObject o1 = objects[idx + 1]; - - assert o0 != null && o1 != null : "Object state must contain non null elements."; - - assert o0.type().equals(o1.type()) : "Single type state objects must have the same type."; - /* - * Check that the objects are sorted by ID. Since the objects should be unique (context - * sensitive objects are merged when they have the same type during the union - * operation), we use < for the test. - */ - assert o0.getId() < o1.getId() : "Analysis objects must be sorted by ID."; - } - - return true; + @Override + public final int typesCount() { + return 1; } @Override - public int typesCount() { + public int objectsCount() { return 1; } @Override - public boolean hasExactTypes(BitSet inputTypesBitSet) { - return inputTypesBitSet.cardinality() == 1 && inputTypesBitSet.get(exactType().getId()); + public final boolean canBeNull() { + return canBeNull; } @Override - public AnalysisType exactType() { - return objects[0].type(); + public final AnalysisType exactType() { + return type; } @Override - protected Iterator typesIterator(BigBang bb) { - return new Iterator<>() { - - boolean hasNext = true; - - @Override - public boolean hasNext() { - return hasNext; - } - - @Override - public AnalysisType next() { - hasNext = false; - return exactType(); - } - }; + public final boolean containsType(AnalysisType exactType) { + return type.equals(exactType); } @Override - public boolean containsType(AnalysisType exactType) { - return exactType().equals(exactType); + public final boolean hasExactTypes(BitSet inputTypesBitSet) { + return inputTypesBitSet.cardinality() == 1 && inputTypesBitSet.get(type.getId()); } @Override - public final int objectsCount() { - return objects.length; + protected final Iterator typesIterator(BigBang bb) { + return singletonIterator(type); } @Override - public final AnalysisObject[] objects() { - return objects; + public Iterator objectsIterator(BigBang bb) { + return singletonIterator(type.getContextInsensitiveAnalysisObject()); } @Override - public AnalysisObject[] objectsArray(AnalysisType type) { - return exactType().equals(type) ? objects : null; + protected Iterator objectsIterator(AnalysisType t) { + return type.equals(t) ? singletonIterator(type.getContextInsensitiveAnalysisObject()) : Collections.emptyIterator(); } - @Override - protected Iterator objectsIterator(AnalysisType type) { + private static Iterator singletonIterator(T object) { return new Iterator<>() { - private boolean typesEqual = exactType().equals(type); - private int idx = 0; + boolean hasNext = true; @Override public boolean hasNext() { - return typesEqual && idx < objects.length; + return hasNext; } @Override - public AnalysisObject next() { - return objects[idx++]; + public T next() { + hasNext = false; + return object; } }; } @Override - public final boolean canBeNull() { - return canBeNull; - } - - @Override - public TypeState exactTypeState(PointsToAnalysis bb, AnalysisType exactType) { - if (this.containsType(exactType)) { - return this; - } else { - return EmptyTypeState.SINGLETON; - } - } - - @Override - protected TypeState forCanBeNull(PointsToAnalysis bb, boolean stateCanBeNull) { + public TypeState forCanBeNull(PointsToAnalysis bb, boolean stateCanBeNull) { if (stateCanBeNull == this.canBeNull()) { return this; } else { @@ -191,9 +137,7 @@ public void noteMerge(PointsToAnalysis bb) { assert bb.analysisPolicy().isMergingEnabled(); if (!merged) { - for (AnalysisObject obj : objects) { - obj.noteMerge(bb); - } + type.getContextInsensitiveAnalysisObject().noteMerge(bb); merged = true; } } @@ -206,7 +150,7 @@ public boolean isMerged() { @Override public int hashCode() { int result = 1; - result = 31 * result + Arrays.hashCode(objects); + result = 31 * result + type.hashCode(); result = 31 * result + (canBeNull ? 1 : 0); return result; } @@ -221,14 +165,12 @@ public boolean equals(Object o) { } SingleTypeState that = (SingleTypeState) o; - return this.canBeNull == that.canBeNull && this.exactType().equals(that.exactType()) && Arrays.equals(this.objects, that.objects); + return this.canBeNull == that.canBeNull && this.exactType().equals(that.exactType()); } @Override public String toString() { - StringBuilder strb = new StringBuilder(); - strb.append("1TypeMObject<").append(canBeNull ? "null," : "").append(Arrays.toString(objects)).append(">"); - return strb.toString(); + return "SingleType<" + (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 bb5b6c044109..77b8205938a2 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 @@ -25,22 +25,18 @@ package com.oracle.graal.pointsto.typestate; import java.lang.reflect.Modifier; -import java.util.Arrays; import java.util.BitSet; import java.util.Collections; import java.util.Iterator; import java.util.stream.Stream; import java.util.stream.StreamSupport; -import com.oracle.graal.pointsto.AnalysisPolicy; import com.oracle.graal.pointsto.BigBang; import com.oracle.graal.pointsto.PointsToAnalysis; -import com.oracle.graal.pointsto.api.PointstoOptions; import com.oracle.graal.pointsto.flow.context.AnalysisContext; import com.oracle.graal.pointsto.flow.context.BytecodeLocation; import com.oracle.graal.pointsto.flow.context.object.AnalysisObject; import com.oracle.graal.pointsto.meta.AnalysisType; -import com.oracle.graal.pointsto.typestate.MultiTypeState.Range; import com.oracle.graal.pointsto.util.BitArrayUtils; import jdk.vm.ci.meta.JavaConstant; @@ -97,11 +93,11 @@ public Stream typesStream(BigBang bb) { /** Get the number of objects. */ public abstract int objectsCount(); - /** Returns the objects as an array. */ - public abstract AnalysisObject[] objects(); + protected abstract Iterator objectsIterator(BigBang bb); - /** Returns the objects corresponding to the type. It copies those objects to a new array. */ - public abstract AnalysisObject[] objectsArray(AnalysisType type); + public final Iterable objects(BigBang bb) { + return () -> objectsIterator(bb); + } /** * Provides an iterator for the objects corresponding to the type. The objects are returned from @@ -117,112 +113,18 @@ public Iterable objects(AnalysisType type) { return () -> objectsIterator(type); } - /** Provides a stream for the objects. */ - public Stream objectsStream() { - return Arrays.stream(objects()); - } - - /** Returns true if this type state contains the object, otherwise it returns false. */ - public boolean containsObject(AnalysisObject object) { - /* AnalysisObject implements Comparable and the objects array is always sorted by ID. */ - return containsType(object.type()) && Arrays.binarySearch(objects(), object) >= 0; - } - - /** - * Provides a special iterator for the type state. It iterates over analysis types and objects - * in tandem doing a single pass over the objects array. See {@link TypesObjectsIterator} for a - * complete explanation. - */ - public TypesObjectsIterator getTypesObjectsIterator() { - return new TypesObjectsIterator(this); - } - - /** - * This is a special iterator for the type state. It iterates over analysis types and objects in - * tandem doing a single pass over the objects array. It relies on the fact that the types and - * objects are sorted by ID. It is meant for situations where the types need some pre-processing - * or checking before processing their respective objects, e.g., as in virtual method resolution - * for InvokeTypeFlow. It those situations it avoids iterating over the types first and then - * searching for the range of objects corresponding to that type. When only objects, or only - * types, or only objects of a certain type need to be iterated use the other provided - * iterators. A correct use of this iterator is as follows: - * - * - * TypesObjectsIterator toi = state.getTypesObjectsIterator(); - * - * while(toi.hasNextType()) { - * AnalysisType t = toi.nextType(); - * // use type here - * - * while(toi.hasNextObject(t)) { - * AnalysisObject o = toi.nextObject(t); - * // use object here - * } - * } - * - */ - public static class TypesObjectsIterator { - - private final TypeState state; - private int typeIdx = 0; - private int objectIdx = 0; - - public TypesObjectsIterator(TypeState state) { - this.state = state; - } - - /** - * Returns true if there is a next type in the objects array, i.e., there are objects of a - * type other than the current type. - */ - public boolean hasNextType() { - return typeIdx < state.typesCount(); - } - - /** Returns true if there are more objects of the current type. */ - public boolean hasNextObject(AnalysisType type) { - return objectIdx < state.objects().length && state.objects()[objectIdx].getTypeId() == type.getId(); - } - - /** Gets the next type. */ - public AnalysisType nextType() { - /* Check that there is a next type. */ - assert hasNextType(); - /* Increment the type index. */ - typeIdx++; - /* Return the type at the 'objectIdx. */ - return state.objects()[objectIdx].type(); - } - - /** Gets the next object. */ - public AnalysisObject nextObject(AnalysisType type) { - /* Check that there is a next object of the desired type. */ - assert hasNextObject(type); - /* Return the next object and increment objectIdx. */ - return state.objects()[objectIdx++]; - } - } - public boolean isAllocation() { - return objects().length == 1 && objects()[0].isAllocationContextSensitiveObject(); + return false; } public boolean isConstant() { - return objects().length == 1 && objects()[0].isConstantContextSensitiveObject(); + return false; } public boolean isEmpty() { return this == EmptyTypeState.SINGLETON; } - public boolean isSingleTypeState() { - return this.typesCount() == 1; - } - - public boolean isMultiTypeState() { - return this instanceof MultiTypeState; - } - public boolean isNull() { return this == NullTypeState.SINGLETON; } @@ -237,13 +139,6 @@ public boolean isMerged() { return false; } - /** - * This method is needed for accessing the SingleTypeState associated with an specific type of a - * MutiTypeState, e.g. for transferring the state from a virtual invoke type flow to the formal - * receiver flow of a specific callee resolved on the specified type. - */ - public abstract TypeState exactTypeState(PointsToAnalysis bb, AnalysisType exactType); - public boolean verifyDeclaredType(BigBang bb, AnalysisType declaredType) { if (declaredType != null) { for (AnalysisType e : types(bb)) { @@ -285,7 +180,7 @@ public static TypeState forNull() { /** Wraps an analysis object into a non-null type state. */ public static TypeState forNonNullObject(PointsToAnalysis bb, AnalysisObject object) { - return new SingleTypeState(bb, false, bb.analysisPolicy().makeProperties(bb, object), object); + return bb.analysisPolicy().singleTypeState(bb, false, bb.analysisPolicy().makeProperties(bb, object), object.type(), object); } /** Wraps the analysis object corresponding to a JavaConstant into a non-null type state. */ @@ -321,13 +216,13 @@ public static TypeState forClone(PointsToAnalysis bb, BytecodeLocation cloneSite return forAllocation(bb, cloneSite, type, allocationContext); } - public static TypeState forExactType(PointsToAnalysis bb, AnalysisType exactType, boolean canBeNull) { + public static SingleTypeState forExactType(PointsToAnalysis bb, AnalysisType exactType, boolean canBeNull) { return forExactType(bb, exactType.getContextInsensitiveAnalysisObject(), canBeNull); } - public static TypeState forExactType(PointsToAnalysis bb, AnalysisObject object, boolean canBeNull) { + public static SingleTypeState forExactType(PointsToAnalysis bb, AnalysisObject object, boolean canBeNull) { assert object.type().isArray() || (object.type().isInstanceClass() && !Modifier.isAbstract(object.type().getModifiers())) : object.type(); - return new SingleTypeState(bb, canBeNull, bb.analysisPolicy().makeProperties(bb, object), object); + return bb.analysisPolicy().singleTypeState(bb, canBeNull, bb.analysisPolicy().makeProperties(bb, object), object.type(), object); } public static TypeState forType(PointsToAnalysis bb, AnalysisType type, boolean canBeNull) { @@ -335,73 +230,14 @@ public static TypeState forType(PointsToAnalysis bb, AnalysisType type, boolean } public static TypeState forType(PointsToAnalysis bb, AnalysisObject object, boolean canBeNull) { - return new SingleTypeState(bb, canBeNull, bb.analysisPolicy().makeProperties(bb, object), object); - } - - public static TypeState forExactTypes(PointsToAnalysis bb, BitSet exactTypes, boolean canBeNull) { - int numTypes = exactTypes.cardinality(); - if (numTypes == 0) { - return forEmpty().forCanBeNull(bb, canBeNull); - } else if (numTypes == 1) { - AnalysisType type = bb.getUniverse().getType(exactTypes.nextSetBit(0)); - AnalysisObject analysisObject = type.getContextInsensitiveAnalysisObject(); - return new SingleTypeState(bb, canBeNull, bb.analysisPolicy().makeProperties(bb, analysisObject), analysisObject); - } else { - AnalysisObject[] objectsArray = new AnalysisObject[numTypes]; - int idx = 0; - for (int id = exactTypes.nextSetBit(0); id >= 0; id = exactTypes.nextSetBit(id + 1)) { - objectsArray[idx] = bb.getUniverse().getType(id).getContextInsensitiveAnalysisObject(); - idx++; - } - assert idx == objectsArray.length; - /* - * For types use the already created bit set, but clone it since it can change outside. - */ - BitSet typesBitSet = (BitSet) exactTypes.clone(); - int properties = bb.analysisPolicy().makeProperties(bb, objectsArray); - return new MultiTypeState(bb, canBeNull, properties, typesBitSet, objectsArray); - } - } - - /** - * Simplifies a type state by replacing all context sensitive objects with context insensitive - * objects. - */ - public static TypeState forContextInsensitiveTypeState(PointsToAnalysis bb, TypeState state) { - if (!PointstoOptions.AllocationSiteSensitiveHeap.getValue(bb.getOptions()) || - state.isEmpty() || state.isNull()) { - /* The type state is already context insensitive. */ - return state; - } else { - if (state.isSingleTypeState()) { - AnalysisType type = state.exactType(); - AnalysisObject analysisObject = type.getContextInsensitiveAnalysisObject(); - return new SingleTypeState(bb, state.canBeNull(), bb.analysisPolicy().makeProperties(bb, analysisObject), analysisObject); - } else { - MultiTypeState multiState = (MultiTypeState) state; - AnalysisObject[] objectsArray = new AnalysisObject[multiState.typesCount()]; - - int i = 0; - for (AnalysisType type : multiState.types(bb)) { - objectsArray[i++] = type.getContextInsensitiveAnalysisObject(); - } - /* - * For types use the already created bit set. Since the original type state is - * immutable its types bit set cannot change. - */ - - BitSet typesBitSet = multiState.typesBitSet; - int properties = bb.analysisPolicy().makeProperties(bb, objectsArray); - return new MultiTypeState(bb, multiState.canBeNull(), properties, typesBitSet, objectsArray); - } - } + return bb.analysisPolicy().singleTypeState(bb, canBeNull, bb.analysisPolicy().makeProperties(bb, object), object.type(), object); } public final TypeState forNonNull(PointsToAnalysis bb) { return forCanBeNull(bb, false); } - protected abstract TypeState forCanBeNull(PointsToAnalysis bb, boolean stateCanBeNull); + public abstract TypeState forCanBeNull(PointsToAnalysis bb, boolean stateCanBeNull); public static TypeState forUnion(PointsToAnalysis bb, TypeState s1, TypeState s2) { if (s1.isEmpty()) { @@ -413,17 +249,17 @@ public static TypeState forUnion(PointsToAnalysis bb, TypeState s1, TypeState s2 } else if (s2.isNull()) { return s1.forCanBeNull(bb, true); } else if (s1 instanceof SingleTypeState && s2 instanceof SingleTypeState) { - return doUnion(bb, (SingleTypeState) s1, (SingleTypeState) s2); + return bb.analysisPolicy().doUnion(bb, (SingleTypeState) s1, (SingleTypeState) s2); } else if (s1 instanceof SingleTypeState && s2 instanceof MultiTypeState) { - return doUnion(bb, (MultiTypeState) s2, (SingleTypeState) s1); + return bb.analysisPolicy().doUnion(bb, (MultiTypeState) s2, (SingleTypeState) s1); } else if (s1 instanceof MultiTypeState && s2 instanceof SingleTypeState) { - return doUnion(bb, (MultiTypeState) s1, (SingleTypeState) s2); + return bb.analysisPolicy().doUnion(bb, (MultiTypeState) s1, (SingleTypeState) s2); } else { assert s1 instanceof MultiTypeState && s2 instanceof MultiTypeState; if (s1.objectsCount() >= s2.objectsCount()) { - return doUnion(bb, (MultiTypeState) s1, (MultiTypeState) s2); + return bb.analysisPolicy().doUnion(bb, (MultiTypeState) s1, (MultiTypeState) s2); } else { - return doUnion(bb, (MultiTypeState) s2, (MultiTypeState) s1); + return bb.analysisPolicy().doUnion(bb, (MultiTypeState) s2, (MultiTypeState) s1); } } } @@ -443,14 +279,14 @@ public static TypeState forIntersection(PointsToAnalysis bb, TypeState s1, TypeS } else if (s2.isNull()) { return s2.forCanBeNull(bb, s1.canBeNull()); } else if (s1 instanceof SingleTypeState && s2 instanceof SingleTypeState) { - return doIntersection(bb, (SingleTypeState) s1, (SingleTypeState) s2); + return bb.analysisPolicy().doIntersection(bb, (SingleTypeState) s1, (SingleTypeState) s2); } else if (s1 instanceof SingleTypeState && s2 instanceof MultiTypeState) { - return doIntersection(bb, (SingleTypeState) s1, (MultiTypeState) s2); + return bb.analysisPolicy().doIntersection(bb, (SingleTypeState) s1, (MultiTypeState) s2); } else if (s1 instanceof MultiTypeState && s2 instanceof SingleTypeState) { - return doIntersection(bb, (MultiTypeState) s1, (SingleTypeState) s2); + return bb.analysisPolicy().doIntersection(bb, (MultiTypeState) s1, (SingleTypeState) s2); } else { assert s1 instanceof MultiTypeState && s2 instanceof MultiTypeState; - return doIntersection(bb, (MultiTypeState) s1, (MultiTypeState) s2); + return bb.analysisPolicy().doIntersection(bb, (MultiTypeState) s1, (MultiTypeState) s2); } } @@ -469,14 +305,14 @@ public static TypeState forSubtraction(PointsToAnalysis bb, TypeState s1, TypeSt } else if (s2.isNull()) { return s1.forCanBeNull(bb, false); } else if (s1 instanceof SingleTypeState && s2 instanceof SingleTypeState) { - return doSubtraction(bb, (SingleTypeState) s1, (SingleTypeState) s2); + return bb.analysisPolicy().doSubtraction(bb, (SingleTypeState) s1, (SingleTypeState) s2); } else if (s1 instanceof SingleTypeState && s2 instanceof MultiTypeState) { - return doSubtraction(bb, (SingleTypeState) s1, (MultiTypeState) s2); + return bb.analysisPolicy().doSubtraction(bb, (SingleTypeState) s1, (MultiTypeState) s2); } else if (s1 instanceof MultiTypeState && s2 instanceof SingleTypeState) { - return doSubtraction(bb, (MultiTypeState) s1, (SingleTypeState) s2); + return bb.analysisPolicy().doSubtraction(bb, (MultiTypeState) s1, (SingleTypeState) s2); } else { assert s1 instanceof MultiTypeState && s2 instanceof MultiTypeState; - return doSubtraction(bb, (MultiTypeState) s1, (MultiTypeState) s2); + return bb.analysisPolicy().doSubtraction(bb, (MultiTypeState) s1, (MultiTypeState) s2); } } @@ -490,900 +326,11 @@ private static boolean checkTypes(BigBang bb, TypeState state) { return true; } - /* Implementation of union. */ - - private static TypeState doUnion(PointsToAnalysis bb, SingleTypeState s1, SingleTypeState s2) { - if (s1.equals(s2)) { - return s1; - } - - boolean resultCanBeNull = s1.canBeNull() || s2.canBeNull(); - if (s1.exactType().equals(s2.exactType())) { - - /* The inputs have the same type, so the result is a SingleTypeState. */ - - /* Create the resulting objects array. */ - AnalysisObject[] resultObjects = TypeStateUtils.union(bb, s1.objects, s2.objects); - - /* Check if any of the arrays contains the other. */ - if (resultObjects == s1.objects) { - return s1.forCanBeNull(bb, resultCanBeNull); - } else if (resultObjects == s2.objects) { - return s2.forCanBeNull(bb, resultCanBeNull); - } - - /* Due to the test above the union set cannot be equal to any of the two arrays. */ - assert !bb.extendedAsserts() || !Arrays.equals(resultObjects, s1.objects) && !Arrays.equals(resultObjects, s2.objects); - - /* Create the resulting exact type state. */ - SingleTypeState result = new SingleTypeState(bb, resultCanBeNull, bb.analysisPolicy().makePropertiesForUnion(s1, s2), resultObjects); - assert !s1.equals(result) && !s2.equals(result); - PointsToStats.registerUnionOperation(bb, s1, s2, result); - return result; - } else { - /* The inputs have different types, so the result is a MultiTypeState. */ - AnalysisObject[] resultObjects; - if (s1.exactType().getId() < s2.exactType().getId()) { - resultObjects = TypeStateUtils.concat(s1.objects, s2.objects); - } else { - resultObjects = TypeStateUtils.concat(s2.objects, s1.objects); - } - - /* We know the types, construct the types bit set without walking the objects. */ - BitSet typesBitSet = new BitSet(); - typesBitSet.set(s1.exactType().getId()); - typesBitSet.set(s2.exactType().getId()); - - int properties = bb.analysisPolicy().makePropertiesForUnion(s1, s2); - - TypeState result = new MultiTypeState(bb, resultCanBeNull, properties, typesBitSet, resultObjects); - PointsToStats.registerUnionOperation(bb, s1, s2, result); - return result; - } - } - - private static TypeState doUnion(PointsToAnalysis bb, MultiTypeState s1, SingleTypeState s2) { - boolean resultCanBeNull = s1.canBeNull() || s2.canBeNull(); - - AnalysisObject[] so1 = s1.objects; - AnalysisObject[] so2 = s2.objects; - if (so2.length == 1 && s1.containsObject(so2[0])) { - /* - * Speculate that s2 has a single object and s1 already contains that object. This - * happens often during object scanning where we repeatedly add the scanned constants to - * field or array elements flows. The binary search executed by containsObject should be - * faster than the linear search below. - */ - return s1.forCanBeNull(bb, resultCanBeNull); - } - - if (s1.containsType(s2.exactType())) { - /* Objects of the same type as s2 are contained in s1. */ - - /* Get the range of objects in s1 corresponding to the type of s2. */ - Range typeRange = s1.findTypeRange(s2.exactType()); - /* Get the slice of objects in s1 corresponding to the type of s2. */ - AnalysisObject[] s1ObjectsSlice = s1.objectsArray(typeRange); - - /* Create the resulting objects array. */ - AnalysisObject[] unionObjects = TypeStateUtils.union(bb, s1ObjectsSlice, so2); - - /* Check if s1 contains s2's objects for this type. */ - if (unionObjects == s1ObjectsSlice) { - return s1.forCanBeNull(bb, resultCanBeNull); - } - - /* - * Due to the test above and to the fact that TypeStateUtils.union checks if one array - * contains the other the union set cannot be equal to s1's objects slice. - */ - assert !bb.extendedAsserts() || !Arrays.equals(unionObjects, s1ObjectsSlice); - - /* - * Replace the s1 objects slice of the same type as s2 with the union objects and create - * a new state. - */ - int resultSize = so1.length + unionObjects.length - s1ObjectsSlice.length; - AnalysisObject[] resultObjects = new AnalysisObject[resultSize]; - - System.arraycopy(so1, 0, resultObjects, 0, typeRange.left); - System.arraycopy(unionObjects, 0, resultObjects, typeRange.left, unionObjects.length); - System.arraycopy(so1, typeRange.right, resultObjects, typeRange.left + unionObjects.length, so1.length - typeRange.right); - - /* The types bit set of the result and s1 are the same. */ - - int properties = bb.analysisPolicy().makePropertiesForUnion(s1, s2); - - MultiTypeState result = new MultiTypeState(bb, resultCanBeNull, properties, s1.typesBitSet, resultObjects); - assert !result.equals(s1); - /* - * No need to check the result size against the all-instantiated since the type count - * didn't change. - */ - PointsToStats.registerUnionOperation(bb, s1, s2, result); - return result; - } else { - AnalysisObject[] resultObjects; - if (s2.exactType().getId() < s1.firstType().getId()) { - resultObjects = TypeStateUtils.concat(so2, so1); - } else if (s2.exactType().getId() > s1.lastType().getId()) { - resultObjects = TypeStateUtils.concat(so1, so2); - } else { - - /* Find insertion point within the s1.objects. */ - int idx1 = 0; - while (idx1 < so1.length && so1[idx1].getTypeId() < s2.exactType().getId()) { - idx1++; - } - - /* Create the resulting objects array and insert the s2 objects. */ - resultObjects = new AnalysisObject[so1.length + so2.length]; - - System.arraycopy(so1, 0, resultObjects, 0, idx1); - System.arraycopy(so2, 0, resultObjects, idx1, so2.length); - System.arraycopy(so1, idx1, resultObjects, idx1 + so2.length, so1.length - idx1); - } - - /* Create the types bit set by adding the s2 type to avoid walking the objects. */ - BitSet typesBitSet = TypeStateUtils.set(s1.typesBitSet, s2.exactType().getId()); - int properties = bb.analysisPolicy().makePropertiesForUnion(s1, s2); - - MultiTypeState result = new MultiTypeState(bb, resultCanBeNull, properties, typesBitSet, resultObjects); - PointsToStats.registerUnionOperation(bb, s1, s2, result); - return result; - } - } - - private static TypeState doUnion(PointsToAnalysis bb, MultiTypeState s1, MultiTypeState s2) { - assert s1.objectsCount() >= s2.objectsCount() : "Union is commutative, must call it with s1 being the bigger state"; - boolean resultCanBeNull = s1.canBeNull() || s2.canBeNull(); - - /* - * No need for a deep equality check (which would need to iterate the arrays), since the - * speculation logic below is doing that anyway. - */ - if (s1.objects == s2.objects) { - return s1.forCanBeNull(bb, resultCanBeNull); - } - - return doUnion0(bb, s1, s2, resultCanBeNull); - } - - private static TypeState doUnion0(PointsToAnalysis bb, MultiTypeState s1, MultiTypeState s2, boolean resultCanBeNull) { - - /* Speculate that s1 and s2 are distinct sets. */ - - if (s1.lastType().getId() < s2.firstType().getId()) { - /* Speculate that objects in s2 follow after objects in s1. */ - - /* Concatenate the objects. */ - AnalysisObject[] resultObjects = TypeStateUtils.concat(s1.objects, s2.objects); - - /* Logical OR the type bit sets. */ - BitSet resultTypesBitSet = TypeStateUtils.or(s1.typesBitSet, s2.typesBitSet); - int properties = bb.analysisPolicy().makePropertiesForUnion(s1, s2); - - MultiTypeState result = new MultiTypeState(bb, resultCanBeNull, properties, resultTypesBitSet, resultObjects); - PointsToStats.registerUnionOperation(bb, s1, s2, result); - return result; - - } else if (s2.lastType().getId() < s1.firstType().getId()) { - /* Speculate that objects in s1 follow after objects in s2. */ - - /* Concatenate the objects. */ - AnalysisObject[] resultObjects = TypeStateUtils.concat(s2.objects, s1.objects); - - /* Logical OR the type bit sets. */ - BitSet resultTypesBitSet = TypeStateUtils.or(s1.typesBitSet, s2.typesBitSet); - int properties = bb.analysisPolicy().makePropertiesForUnion(s1, s2); - - MultiTypeState result = new MultiTypeState(bb, resultCanBeNull, properties, resultTypesBitSet, resultObjects); - PointsToStats.registerUnionOperation(bb, s1, s2, result); - return result; - } - - return doUnion1(bb, s1, s2, resultCanBeNull); - } - - private static TypeState doUnion1(PointsToAnalysis bb, MultiTypeState s1, MultiTypeState s2, boolean resultCanBeNull) { - if (PointstoOptions.AllocationSiteSensitiveHeap.getValue(bb.getOptions())) { - return allocationSensitiveSpeculativeUnion1(bb, s1, s2, resultCanBeNull); - } else { - return allocationInsensitiveSpeculativeUnion1(bb, s1, s2, resultCanBeNull); - } - } - - /** - * Optimization that gives 1.5-3x in performance for the (typeflow) phase. - */ - private static TypeState allocationInsensitiveSpeculativeUnion1(PointsToAnalysis bb, MultiTypeState s1, MultiTypeState s2, boolean resultCanBeNull) { - if (s1.typesBitSet.length() >= s2.typesBitSet.length()) { - long[] bits1 = TypeStateUtils.extractBitSetField(s1.typesBitSet); - long[] bits2 = TypeStateUtils.extractBitSetField(s2.typesBitSet); - assert s2.typesBitSet.cardinality() == s2.objects.length : "Cardinality and length of objects must match."; - - boolean speculate = true; - int numberOfWords = Math.min(bits1.length, bits2.length); - for (int i = 0; i < numberOfWords; i++) { - /* bits2 is a subset of bits1 */ - if ((bits1[i] & bits2[i]) != bits2[i]) { - speculate = false; - break; - } - } - if (speculate) { - return s1.forCanBeNull(bb, resultCanBeNull); - } - } - return doUnion2(bb, s1, s2, resultCanBeNull, 0, 0); - } - - private static TypeState allocationSensitiveSpeculativeUnion1(PointsToAnalysis bb, MultiTypeState s1, MultiTypeState s2, boolean resultCanBeNull) { - int idx1 = 0; - int idx2 = 0; - AnalysisPolicy analysisPolicy = bb.analysisPolicy(); - AnalysisObject[] so1 = s1.objects; - AnalysisObject[] so2 = s2.objects; - while (idx1 < so1.length && idx2 < so2.length) { - AnalysisObject o1 = so1[idx1]; - AnalysisObject o2 = so2[idx2]; - if (analysisPolicy.isSummaryObject(o1) && o1.getTypeId() == o2.getTypeId()) { - idx1++; - /* Skip over s2 objects of this type while marking them as merged. */ - while (idx2 < s2.objectsCount() && so2[idx2].getTypeId() == o1.getTypeId()) { - analysisPolicy.noteMerge(bb, so2[idx2]); - idx2++; - } - } else if (o1.getId() < o2.getId()) { - idx1++; - } else if (o1.getId() == o2.getId()) { - /* If the objects are equal continue. */ - idx1++; - idx2++; - } else { - /* Our speculation failed. */ - break; - } - - if (idx2 == so2.length) { - return s1.forCanBeNull(bb, resultCanBeNull); - } - } - return doUnion2(bb, s1, s2, resultCanBeNull, idx1, idx2); - } - - private static ThreadLocal> doUnion2TL = new ThreadLocal<>(); - private static ThreadLocal> doUnion2ObjectsTL = new ThreadLocal<>(); - - private static TypeState doUnion2(PointsToAnalysis bb, MultiTypeState s1, MultiTypeState s2, boolean resultCanBeNull, int startId1, int startId2) { - try (UnsafeArrayListClosable resultObjectsClosable = getTLArrayList(doUnion2TL, s1.objects.length + s2.objects.length)) { - UnsafeArrayList resultObjects = resultObjectsClosable.list; - /* Add the beginning of the s1 list that we already walked above. */ - AnalysisObject[] objects = s1.objects; - resultObjects.addAll(objects, 0, startId1); - - int idx1 = startId1; - int idx2 = startId2; - - /* Create the union of the overlapping sections of the s1 and s2. */ - try (UnsafeArrayListClosable tlUnionObjectsClosable = getTLArrayList(doUnion2ObjectsTL, s1.objects.length + s2.objects.length)) { - UnsafeArrayList unionObjects = tlUnionObjectsClosable.list; - - AnalysisObject[] so1 = s1.objects; - AnalysisObject[] so2 = s2.objects; - AnalysisPolicy analysisPolicy = bb.analysisPolicy(); - while (idx1 < so1.length && idx2 < so2.length) { - AnalysisObject o1 = so1[idx1]; - AnalysisObject o2 = so2[idx2]; - int t1 = o1.getTypeId(); - int t2 = o2.getTypeId(); - if (analysisPolicy.isSummaryObject(o1) && t1 == t2) { - unionObjects.add(o1); - /* Skip over s2 objects of this type while marking them as merged. */ - while (idx2 < so2.length && t1 == so2[idx2].getTypeId()) { - analysisPolicy.noteMerge(bb, so2[idx2]); - idx2++; - } - idx1++; - } else if (analysisPolicy.isSummaryObject(o2) && t1 == t2) { - unionObjects.add(o2); - /* Skip over s1 objects of this type while marking them as merged. */ - while (idx1 < so1.length && so1[idx1].getTypeId() == t2) { - analysisPolicy.noteMerge(bb, so1[idx1]); - idx1++; - } - idx2++; - } else if (o1.getId() < o2.getId()) { - unionObjects.add(o1); - idx1++; - } else if (o1.getId() > o2.getId()) { - unionObjects.add(o2); - idx2++; - } else { - assert o1.equals(o2); - unionObjects.add(o1); - idx1++; - idx2++; - } - } - - /* - * Check if the union of objects of a type in the overlapping section reached the - * limit. The limit, bb.options().maxObjectSetSize(), has a minimum value of 1. - */ - if (PointstoOptions.LimitObjectArrayLength.getValue(bb.getOptions()) && unionObjects.size() > PointstoOptions.MaxObjectSetSize.getValue(bb.getOptions())) { - int idxStart = 0; - int idxEnd = 0; - while (idxEnd < unionObjects.size()) { - AnalysisObject oStart = unionObjects.get(idxStart); - - /* While types are equal and the end is not reached, advance idxEnd. */ - while (idxEnd < unionObjects.size() && oStart.equals(unionObjects.get(idxEnd))) { - idxEnd = idxEnd + 1; - } - /* - * Process the type change or, if idxEnd reached the end, process the last - * stride - */ - int size = idxEnd - idxStart; - if (size > PointstoOptions.MaxObjectSetSize.getValue(bb.getOptions())) { - /* - * Object count exceeds the limit. Mark the objects in the stride as - * merged. - */ - for (int i = idxStart; i < idxEnd; i += 1) { - bb.analysisPolicy().noteMerge(bb, unionObjects.get(i)); - } - /* Add the context insensitive object in the result list. */ - resultObjects.add(oStart.type().getContextInsensitiveAnalysisObject()); - } else { - /* Object count is within the limit, add them to the result. */ - resultObjects.addAll(unionObjects.elementData, idxStart, idxEnd); - } - idxStart = idxEnd; - } - - } else { - resultObjects.addAll(unionObjects.elementData, 0, unionObjects.size); - } - } - - /* - * Add the leftover objects in the result list. - * - * Arrays.asList(a).subList(from, to) first creates a list wrapper over the array then - * it creates a view of a portion of the list, thus it only allocates the list and - * sub-list wrappers. Then ArrayList.addAll() calls System.arraycopy() which should be - * more efficient than copying one element at a time. - */ - if (idx1 < s1.objects.length) { - resultObjects.addAll(s1.objects, idx1, s1.objects.length); - } else if (idx2 < s2.objects.length) { - resultObjects.addAll(s2.objects, idx2, s2.objects.length); - } - - assert resultObjects.size() > 1 : "The result state of a (Multi U Multi) operation must have at least 2 objects"; - - /* Logical OR the type bit sets. */ - BitSet resultTypesBitSet = TypeStateUtils.or(s1.typesBitSet, s2.typesBitSet); - int properties = bb.analysisPolicy().makePropertiesForUnion(s1, s2); - - MultiTypeState result = new MultiTypeState(bb, resultCanBeNull, properties, resultTypesBitSet, resultObjects.copyToArray(new AnalysisObject[resultObjects.size()])); - assert !result.equals(s1) : "speculation code should prevent this case"; - - /* The result can be equal to s2 only if s1 and s2 have the same number of types. */ - if (s1.typesCount() == s2.typesCount() && result.equals(s2)) { - return s2.forCanBeNull(bb, resultCanBeNull); - } - - PointsToStats.registerUnionOperation(bb, s1, s2, result); - - return result; - } - } - - /* - * Implementation of intersection. - * - * The implementation of intersection is specific to our current use case, i.e., it is not a - * general set intersection implementation. The limitation, checked by the assertions below, - * refers to the fact that when we use intersection we only care about selecting all the objects - * of a certain type or types, e.g., for filtering. We don't currently have a situation where we - * only want to select a subset of objects of a type. In our use the types whose objects need to - * be selected are always specified in s2 through their context insensitive objects, thus s2 - * must only contain context insensitive objects. - */ - private static TypeState doIntersection(PointsToAnalysis bb, SingleTypeState s1, SingleTypeState s2) { - assert s2.objects.length == 1 && s2.objects[0].isContextInsensitiveObject() : "Current implementation limitation."; - - boolean resultCanBeNull = s1.canBeNull() && s2.canBeNull(); - - TypeState result; - if (s1.exactType().equals(s2.exactType())) { - /* The inputs have the same type, the result will be s1. */ - return s1.forCanBeNull(bb, resultCanBeNull); - } else { - /* The inputs have different types then the result is empty or null. */ - result = TypeState.forEmpty().forCanBeNull(bb, resultCanBeNull); - } - - return result; - } - - private static TypeState doIntersection(PointsToAnalysis bb, SingleTypeState s1, MultiTypeState s2) { - assert !bb.extendedAsserts() || TypeStateUtils.isContextInsensitiveTypeState(s2) : "Current implementation limitation."; - - boolean resultCanBeNull = s1.canBeNull() && s2.canBeNull(); - - if (s2.containsType(s1.exactType())) { - AnalysisObject[] s2Objects = s2.objectsArray(s1.exactType()); - /* See comment above for the limitation explanation. */ - assert s2Objects.length == 1 && s2Objects[0].isContextInsensitiveObject() : "Current implementation limitation."; - return s1.forCanBeNull(bb, resultCanBeNull); - } else { - return TypeState.forEmpty().forCanBeNull(bb, resultCanBeNull); - } - } - - private static TypeState doIntersection(PointsToAnalysis bb, MultiTypeState s1, SingleTypeState s2) { - /* See comment above for the limitation explanation. */ - assert s2.objects.length == 1 && s2.objects[0].isContextInsensitiveObject() : "Current implementation limitation."; - - boolean resultCanBeNull = s1.canBeNull() && s2.canBeNull(); - if (s1.containsType(s2.exactType())) { - /* The s2's type is contained in s1, so pick all objects of the same type from s1. */ - AnalysisObject[] resultObjects = s1.objectsArray(s2.exactType()); - /* All objects must have the same type. */ - assert TypeStateUtils.holdsSingleTypeState(resultObjects); - return new SingleTypeState(bb, resultCanBeNull, bb.analysisPolicy().makeProperties(bb, resultObjects), resultObjects); - } else { - return TypeState.forEmpty().forCanBeNull(bb, resultCanBeNull); - } - } - - private static TypeState doIntersection(PointsToAnalysis bb, MultiTypeState s1, MultiTypeState s2) { - assert !bb.extendedAsserts() || TypeStateUtils.isContextInsensitiveTypeState(s2) : "Current implementation limitation."; - - boolean resultCanBeNull = s1.canBeNull() && s2.canBeNull(); - - /* - * No need for a deep equality check (which would need to iterate the arrays), since the - * speculation logic below is doing that anyway. - */ - if (s1.objects == s2.objects) { - return s1.forCanBeNull(bb, resultCanBeNull); - } - - return doIntersection0(bb, s1, s2, resultCanBeNull); - } - - private static TypeState doIntersection0(PointsToAnalysis bb, MultiTypeState s1, MultiTypeState s2, boolean resultCanBeNull) { - /* Speculate that s1 and s2 have either the same types, or no types in common. */ - - if (s1.typesBitSet.equals(s2.typesBitSet)) { - /* Speculate that s1 and s2 have the same types, i.e., the result is s1. */ - return s1.forCanBeNull(bb, resultCanBeNull); - } - - if (!s1.typesBitSet.intersects(s2.typesBitSet)) { - /* Speculate that s1 and s2 have no types in common, i.e., the result is empty. */ - return TypeState.forEmpty().forCanBeNull(bb, resultCanBeNull); - } - - return doIntersection1(bb, s1, s2, resultCanBeNull); - } - - private static TypeState doIntersection1(PointsToAnalysis bb, MultiTypeState s1, MultiTypeState s2, boolean resultCanBeNull) { - /* - * Speculate that s2 contains all types of s1, i.e., the filter is broader than s1, thus the - * result is s1. - */ - - int idx1 = 0; - int idx2 = 0; - AnalysisObject[] so1 = s1.objects; - AnalysisObject[] so2 = s2.objects; - while (idx2 < so2.length) { - AnalysisObject o1 = so1[idx1]; - AnalysisObject o2 = so2[idx2]; - - /* See comment above for the limitation explanation. */ - assert o2.isContextInsensitiveObject() : "Current implementation limitation."; - - if (o1.getTypeId() > o2.getTypeId()) { - /* s2 is behind, advance s2. */ - idx2++; - } else if (o1.getTypeId() == o2.getTypeId()) { - /* If the types are equal continue with speculation. */ - while (idx1 < so1.length && so1[idx1].getTypeId() == o2.getTypeId()) { - /* Walk over the s1 objects of the same type as o2. */ - idx1++; - } - idx2++; - } else { - /* Our speculation failed. */ - break; - } - - if (idx1 == so1.length) { - /* - * Our speculation succeeded: we walked down the whole s1 list, and all of its types - * are included in s2. - */ - - return s1.forCanBeNull(bb, resultCanBeNull); - } - - } - - return doIntersection2(bb, s1, s2, resultCanBeNull, idx1, idx2); - } - - private static ThreadLocal> intersectionArrayListTL = new ThreadLocal<>(); - - private static class UnsafeArrayList { - - static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; - E[] elementData; - int size; - - UnsafeArrayList(E[] initial) { - elementData = initial; - } - - T[] copyToArray(T[] a) { - System.arraycopy(elementData, 0, a, 0, size); - return a; - } - - T[] copyToArray(T[] a, int dstPos) { - System.arraycopy(elementData, 0, a, dstPos, size); - return a; - } - - public void addAll(E1[] c, int startIndex, int endIndex) { - assert startIndex <= endIndex : "start index can't be smaller than the end index."; - int newElements = endIndex - startIndex; - ensureCapacity(size() + newElements); - System.arraycopy(c, startIndex, elementData, size, newElements); - size += newElements; - } - - private int size() { - return size; - } - - public void add(E e) { - ensureCapacity(size + 1); - elementData[size] = e; - size = size + 1; - } - - public void clear() { - for (int i = 0; i < size; i++) { - elementData[i] = null; - } - - size = 0; - } - - public E get(int i) { - assert i < size && i >= 0; - return elementData[i]; - } - - private void ensureCapacity(int minCapacity) { - if (minCapacity - elementData.length > 0) { - grow(minCapacity); - } - } - - private void grow(int minCapacity) { - int oldCapacity = elementData.length; - int newCapacity = oldCapacity + (oldCapacity >> 1); - if (newCapacity - minCapacity < 0) { - newCapacity = minCapacity; - } - if (newCapacity - MAX_ARRAY_SIZE > 0) { - if (minCapacity < 0) { - throw new OutOfMemoryError(); - } - newCapacity = (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; - } - elementData = Arrays.copyOf(elementData, newCapacity); - } - - } - - private static TypeState doIntersection2(PointsToAnalysis bb, MultiTypeState s1, MultiTypeState s2, boolean resultCanBeNull, int idx1Param, int idx2Param) { - - try (UnsafeArrayListClosable tlArrayClosable = getTLArrayList(intersectionArrayListTL, 256)) { - UnsafeArrayList resultObjects = tlArrayClosable.list; - - AnalysisObject[] so1 = s1.objects; - AnalysisObject[] so2 = s2.objects; - int[] types1 = s1.getObjectTypeIds(); - int[] types2 = s2.getObjectTypeIds(); - int idx1 = idx1Param; - int idx2 = idx2Param; - int l1 = so1.length; - int l2 = so2.length; - int t1 = types1[idx1]; - int t2 = types2[idx2]; - while (idx1 < l1 && idx2 < l2) { - assert so2[idx2].isContextInsensitiveObject() : "Current implementation limitation."; - if (t1 == t2) { - assert so1[idx1].type().equals(so2[idx2].type()); - resultObjects.add(so1[idx1]); - t1 = types1[++idx1]; - } else if (t1 < t2) { - t1 = types1[++idx1]; - } else if (t1 > t2) { - t2 = types2[++idx2]; - } - } - - int totalLength = idx1Param + resultObjects.size(); - - if (totalLength == 0) { - return TypeState.forEmpty().forCanBeNull(bb, resultCanBeNull); - } else { - AnalysisObject[] objects = new AnalysisObject[totalLength]; - /* Copy the recently touched first */ - resultObjects.copyToArray(objects, idx1Param); - /* Add the beginning of the s1 list that we already walked above. */ - System.arraycopy(s1.objects, 0, objects, 0, idx1Param); - - if (TypeStateUtils.holdsSingleTypeState(objects, objects.length)) { - /* Multiple objects of the same type. */ - return new SingleTypeState(bb, resultCanBeNull, bb.analysisPolicy().makeProperties(bb, objects), objects); - } else { - /* Logical AND the type bit sets. */ - BitSet resultTypesBitSet = TypeStateUtils.and(s1.typesBitSet, s2.typesBitSet); - MultiTypeState result = new MultiTypeState(bb, resultCanBeNull, bb.analysisPolicy().makeProperties(bb, objects), resultTypesBitSet, objects); - - /* - * The result can be equal to s1 if and only if s1 and s2 have the same type - * count. - */ - if (s1.typesCount() == s2.typesCount() && result.equals(s1)) { - return s1.forCanBeNull(bb, resultCanBeNull); - } - - /* - * Don't need to check if the result is close-to-all-instantiated since result - * <= s1. - */ - return result; - } - } - } - } - - private static final class UnsafeArrayListClosable implements AutoCloseable { - private UnsafeArrayList list; - private boolean closed = true; - - private UnsafeArrayListClosable(UnsafeArrayList list) { - this.list = list; - } - - @Override - public void close() { - list.clear(); - closed = true; - } - } - - private static UnsafeArrayListClosable getTLArrayList(ThreadLocal> tl, int initialCapacity) { - UnsafeArrayListClosable result = tl.get(); - if (result == null) { - result = new UnsafeArrayListClosable<>(new UnsafeArrayList<>(new AnalysisObject[initialCapacity])); - tl.set(result); - } - if (result.closed) { - result.closed = false; - return result; - } else { - /* - * Happens very rarely that the same operation is done recursively. If this happens more - * often we should introduce a stack of arrays. - */ - return new UnsafeArrayListClosable<>(new UnsafeArrayList<>(new AnalysisObject[initialCapacity])); - } - } - - /* - * Implementation of subtraction. - * - * The implementation of subtraction is specific to our current use case, i.e., it is not a - * general set subtraction implementation. The limitation, checked by the assertions below, - * refers to the fact that when we use subtraction we only care about eliminating all the - * objects of a certain type or types, e.g., for filtering. We don't currently have a situation - * where we only want to remove a subset of objects of a type. In our use the types whose - * objects need to be eliminated are always specified in s2 through their context insensitive - * objects, thus s2 must only contain context insensitive objects. - */ - private static TypeState doSubtraction(PointsToAnalysis bb, SingleTypeState s1, SingleTypeState s2) { - boolean resultCanBeNull = s1.canBeNull() && !s2.canBeNull(); - if (s1.exactType().equals(s2.exactType())) { - /* See comment above for the limitation explanation. */ - assert s2.objects.length == 1 && s2.objects[0].isContextInsensitiveObject() : "Current implementation limitation."; - return TypeState.forEmpty().forCanBeNull(bb, resultCanBeNull); - } else { - return s1.forCanBeNull(bb, resultCanBeNull); - } - } - - private static TypeState doSubtraction(PointsToAnalysis bb, SingleTypeState s1, MultiTypeState s2) { - boolean resultCanBeNull = s1.canBeNull() && !s2.canBeNull(); - if (s2.containsType(s1.exactType())) { - AnalysisObject[] array = s2.objectsArray(s1.exactType()); - /* See comment above for the limitation explanation. */ - assert array.length == 1 && array[0].isContextInsensitiveObject() : "Current implementation limitation."; - return TypeState.forEmpty().forCanBeNull(bb, resultCanBeNull); - } else { - return s1.forCanBeNull(bb, resultCanBeNull); - } - } - - private static TypeState doSubtraction(PointsToAnalysis bb, MultiTypeState s1, SingleTypeState s2) { - boolean resultCanBeNull = s1.canBeNull() && !s2.canBeNull(); - if (s1.containsType(s2.exactType())) { - /* s2 is contained in s1, so remove all objects of the same type from s1. */ - - /* See comment above for the limitation explanation. */ - assert s2.objects.length == 1 && s2.objects[0].isContextInsensitiveObject() : "Current implementation limitation."; - - /* Find the range of objects of s2.exactType() in s1. */ - Range typeRange = s1.findTypeRange(s2.exactType()); - int newLength = s1.objects.length - (typeRange.right - typeRange.left); - AnalysisObject[] resultObjects = new AnalysisObject[newLength]; - - /* Copy all the objects in s1 but the ones inside the range to the result list. */ - System.arraycopy(s1.objects, 0, resultObjects, 0, typeRange.left); - System.arraycopy(s1.objects, typeRange.right, resultObjects, typeRange.left, s1.objects.length - typeRange.right); - - if (resultObjects.length == 1) { - return new SingleTypeState(bb, resultCanBeNull, bb.analysisPolicy().makeProperties(bb, resultObjects[0]), resultObjects[0]); - } else if (TypeStateUtils.holdsSingleTypeState(resultObjects)) { - /* Multiple objects of the same type. */ - return new SingleTypeState(bb, resultCanBeNull, bb.analysisPolicy().makeProperties(bb, resultObjects), resultObjects); - } else { - BitSet resultTypesBitSet = TypeStateUtils.clear(s1.typesBitSet, s2.exactType().getId()); - return new MultiTypeState(bb, resultCanBeNull, bb.analysisPolicy().makeProperties(bb, resultObjects), resultTypesBitSet, resultObjects); - } - - } else { - return s1.forCanBeNull(bb, resultCanBeNull); - } - } - - private static TypeState doSubtraction(PointsToAnalysis bb, MultiTypeState s1, MultiTypeState s2) { - boolean resultCanBeNull = s1.canBeNull() && !s2.canBeNull(); - /* - * No need for a deep equality check (which would need to iterate the arrays), since the - * speculation logic below is doing that anyway. - */ - if (s1.objects == s2.objects) { - return TypeState.forEmpty().forCanBeNull(bb, resultCanBeNull); - } - - return doSubtraction0(bb, s1, s2, resultCanBeNull); - } - - private static TypeState doSubtraction0(PointsToAnalysis bb, MultiTypeState s1, MultiTypeState s2, boolean resultCanBeNull) { - /* Speculate that s1 and s2 have either the same types, or no types in common. */ - - if (s1.typesBitSet.equals(s2.typesBitSet)) { - /* Speculate that s1 and s2 have the same types, i.e., the result is empty set. */ - return TypeState.forEmpty().forCanBeNull(bb, resultCanBeNull); - } - - if (!s1.typesBitSet.intersects(s2.typesBitSet)) { - /* Speculate that s1 and s2 have no types in common, i.e., the result is s1. */ - return s1.forCanBeNull(bb, resultCanBeNull); - } - - return doSubtraction1(bb, s1, s2, resultCanBeNull); - } - - private static TypeState doSubtraction1(PointsToAnalysis bb, MultiTypeState s1, MultiTypeState s2, boolean resultCanBeNull) { - /* - * Speculate that s1 and s2 have no overlap, i.e., they don't have any objects in common. In - * that case, the result is just s1. - */ - int idx1 = 0; - int idx2 = 0; - - AnalysisObject[] so1 = s1.objects; - AnalysisObject[] so2 = s2.objects; - while (true) { - AnalysisObject o1 = so1[idx1]; - AnalysisObject o2 = so2[idx2]; - - /* See comment above for the limitation explanation. */ - assert o2.isContextInsensitiveObject() : "Current implementation limitation."; - - if (o1.getTypeId() < o2.getTypeId()) { - idx1++; - if (idx1 == so1.length) { - return s1.forCanBeNull(bb, resultCanBeNull); - } - } else if (o1.getTypeId() > o2.getTypeId()) { - idx2++; - if (idx2 == so2.length) { - return s1.forCanBeNull(bb, resultCanBeNull); - } - } else { - /* Our speculation failed. */ - break; - } - } - - return doSubtraction2(bb, s1, s2, resultCanBeNull, idx1, idx2); - } - - private static TypeState doSubtraction2(PointsToAnalysis bb, MultiTypeState s1, MultiTypeState s2, boolean resultCanBeNull, int idx1Param, int idx2Param) { - try (UnsafeArrayListClosable tlArrayClosable = getTLArrayList(intersectionArrayListTL, 256)) { - UnsafeArrayList resultObjects = tlArrayClosable.list; - - AnalysisObject[] so1 = s1.objects; - AnalysisObject[] so2 = s2.objects; - int[] types1 = s1.getObjectTypeIds(); - int[] types2 = s2.getObjectTypeIds(); - int idx1 = idx1Param; - int idx2 = idx2Param; - int l1 = so1.length; - int l2 = so2.length; - int t1 = types1[idx1]; - int t2 = types2[idx2]; - while (idx1 < l1 && idx2 < l2) { - assert so2[idx2].isContextInsensitiveObject() : "Current implementation limitation."; - if (t1 < t2) { - resultObjects.add(so1[idx1]); - t1 = types1[++idx1]; - } else if (t1 > t2) { - t2 = types2[++idx2]; - } else if (t1 == t2) { - assert so1[idx1].type().equals(so2[idx2].type()); - t1 = types1[++idx1]; - } - } - - int remainder = s1.objects.length - idx1; - int totalLength = idx1Param + resultObjects.size + remainder; - - if (totalLength == 0) { - return TypeState.forEmpty().forCanBeNull(bb, resultCanBeNull); - } else { - AnalysisObject[] objects = new AnalysisObject[totalLength]; - /* Copy recently touched first */ - resultObjects.copyToArray(objects, idx1Param); - /* leading elements */ - System.arraycopy(s1.objects, 0, objects, 0, idx1Param); - /* trailing elements (remainder) */ - System.arraycopy(s1.objects, idx1, objects, totalLength - remainder, remainder); - - if (TypeStateUtils.holdsSingleTypeState(objects, totalLength)) { - /* Multiple objects of the same type. */ - return new SingleTypeState(bb, resultCanBeNull, bb.analysisPolicy().makeProperties(bb, objects), objects); - } else { - BitSet resultTypesBitSet = TypeStateUtils.andNot(s1.typesBitSet, s2.typesBitSet); - /* - * Don't need to check if the result is close-to-all-instantiated since result - * <= s1. - */ - return new MultiTypeState(bb, resultCanBeNull, bb.analysisPolicy().makeProperties(bb, objects), resultTypesBitSet, objects); - } - } - } - } } final class EmptyTypeState extends TypeState { - protected static final TypeState SINGLETON = new EmptyTypeState(); + static final TypeState SINGLETON = new EmptyTypeState(); private EmptyTypeState() { super(BitArrayUtils.EMPTY_BIT_ARRAY); @@ -1391,10 +338,7 @@ private EmptyTypeState() { @Override public boolean hasExactTypes(BitSet typesBitSet) { - if (typesBitSet.isEmpty()) { - return true; - } - return false; + return typesBitSet.isEmpty(); } @Override @@ -1413,8 +357,8 @@ public Iterator typesIterator(BigBang bb) { } @Override - public AnalysisObject[] objectsArray(AnalysisType type) { - return AnalysisObject.EMPTY_ARRAY; + protected Iterator objectsIterator(BigBang bb) { + return Collections.emptyIterator(); } @Override @@ -1428,17 +372,7 @@ public boolean containsType(AnalysisType exactType) { } @Override - public boolean containsObject(AnalysisObject object) { - return false; - } - - @Override - public TypeState exactTypeState(PointsToAnalysis bb, AnalysisType exactType) { - return this; - } - - @Override - protected TypeState forCanBeNull(PointsToAnalysis bb, boolean stateCanBeNull) { + public TypeState forCanBeNull(PointsToAnalysis bb, boolean stateCanBeNull) { return stateCanBeNull ? NullTypeState.SINGLETON : EmptyTypeState.SINGLETON; } @@ -1447,11 +381,6 @@ public int objectsCount() { return 0; } - @Override - public AnalysisObject[] objects() { - return AnalysisObject.EMPTY_ARRAY; - } - @Override public boolean canBeNull() { return false; @@ -1470,7 +399,7 @@ public String toString() { final class NullTypeState extends TypeState { - protected static final TypeState SINGLETON = new NullTypeState(); + static final TypeState SINGLETON = new NullTypeState(); private NullTypeState() { super(BitArrayUtils.EMPTY_BIT_ARRAY); @@ -1478,10 +407,7 @@ private NullTypeState() { @Override public boolean hasExactTypes(BitSet typesBitSet) { - if (typesBitSet.isEmpty()) { - return true; - } - return false; + return typesBitSet.isEmpty(); } @Override @@ -1500,13 +426,13 @@ public Iterator typesIterator(BigBang bb) { } @Override - public Iterator objectsIterator(AnalysisType type) { + protected Iterator objectsIterator(BigBang bb) { return Collections.emptyIterator(); } @Override - public AnalysisObject[] objectsArray(AnalysisType type) { - return AnalysisObject.EMPTY_ARRAY; + public Iterator objectsIterator(AnalysisType type) { + return Collections.emptyIterator(); } @Override @@ -1514,16 +440,6 @@ public boolean containsType(AnalysisType exactType) { return false; } - @Override - public boolean containsObject(AnalysisObject object) { - return false; - } - - @Override - public TypeState exactTypeState(PointsToAnalysis bb, AnalysisType exactType) { - return this; - } - @Override public TypeState forCanBeNull(PointsToAnalysis bb, boolean stateCanBeNull) { return stateCanBeNull ? NullTypeState.SINGLETON : EmptyTypeState.SINGLETON; @@ -1539,11 +455,6 @@ public int objectsCount() { return 0; } - @Override - public AnalysisObject[] objects() { - return AnalysisObject.EMPTY_ARRAY; - } - @Override public boolean canBeNull() { return true; 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 32555ed4b2cc..e2718cd97b21 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 @@ -31,6 +31,7 @@ import java.util.BitSet; import java.util.List; +import com.oracle.graal.pointsto.BigBang; import com.oracle.graal.pointsto.PointsToAnalysis; import com.oracle.graal.pointsto.api.PointstoOptions; import com.oracle.graal.pointsto.flow.context.object.AnalysisObject; @@ -61,7 +62,7 @@ public class TypeStateUtils { * @return the array belonging to the bitSet. Please use this value responsibly: not modify or * loose track of this value. */ - static long[] extractBitSetField(BitSet bitSet) { + public static long[] extractBitSetField(BitSet bitSet) { try { return (long[]) bitSetArrayAccess.invokeExact(bitSet); } catch (Throwable t) { @@ -69,7 +70,27 @@ static long[] extractBitSetField(BitSet bitSet) { } } - static void trimBitSetToSize(BitSet bs) { + /** Return true if {@code first} is a superset of {@code second}. */ + public static boolean isSuperset(BitSet first, BitSet second) { + if (first.length() >= second.length()) { + long[] bits1 = TypeStateUtils.extractBitSetField(first); + long[] bits2 = TypeStateUtils.extractBitSetField(second); + + boolean isSuperset = true; + int numberOfWords = Math.min(bits1.length, bits2.length); + for (int i = 0; i < numberOfWords; i++) { + /* bits2 is a subset of bits1 */ + if ((bits1[i] & bits2[i]) != bits2[i]) { + isSuperset = false; + break; + } + } + return isSuperset; + } + return false; + } + + public static void trimBitSetToSize(BitSet bs) { try { trimToSizeAccess.invokeExact(bs); } catch (Throwable t) { @@ -78,7 +99,7 @@ static void trimBitSetToSize(BitSet bs) { } - protected static AnalysisObject[] concat(AnalysisObject[] oa1, AnalysisObject[] oa2) { + public static AnalysisObject[] concat(AnalysisObject[] oa1, AnalysisObject[] oa2) { int resultSize = oa1.length + oa2.length; AnalysisObject[] result = new AnalysisObject[resultSize]; @@ -90,7 +111,7 @@ protected static AnalysisObject[] concat(AnalysisObject[] oa1, AnalysisObject[] } /** Returns the union of the two analysis object arrays of the same type. */ - protected static AnalysisObject[] union(PointsToAnalysis bb, AnalysisObject[] a1, AnalysisObject[] a2) { + public static AnalysisObject[] union(PointsToAnalysis bb, AnalysisObject[] a1, AnalysisObject[] a2) { // assert this.type() == other.type(); if (a1.length == 1 && bb.analysisPolicy().isSummaryObject(a1[0])) { @@ -317,8 +338,8 @@ private static AnalysisObject[] arraysIntersection(PointsToAnalysis bb, Analysis * Check if a type state contains only context insensitive objects, i.e., the only information * it stores is the set of types. */ - static boolean isContextInsensitiveTypeState(TypeState state) { - for (AnalysisObject object : state.objects()) { + public static boolean isContextInsensitiveTypeState(BigBang bb, TypeState state) { + for (AnalysisObject object : state.objects(bb)) { if (!object.isContextInsensitiveObject()) { return false; } @@ -326,12 +347,12 @@ static boolean isContextInsensitiveTypeState(TypeState state) { return true; } - static boolean holdsSingleTypeState(AnalysisObject[] objects) { + public static boolean holdsSingleTypeState(AnalysisObject[] objects) { return holdsSingleTypeState(objects, objects.length); } @SuppressWarnings("RedundantIfStatement") - static boolean holdsSingleTypeState(AnalysisObject[] objects, int size) { + public static boolean holdsSingleTypeState(AnalysisObject[] objects, int size) { assert size > 0; int firstType = objects[0].getTypeId(); int lastType = objects[size - 1].getTypeId(); @@ -343,16 +364,30 @@ static boolean holdsSingleTypeState(AnalysisObject[] objects, int size) { } /** Logical OR two bit sets without modifying the source. */ - protected static BitSet or(BitSet bs1, BitSet bs2) { - BitSet bsr = (BitSet) bs1.clone(); - bsr.or(bs2); + public static BitSet or(BitSet bs1, BitSet bs2) { + /* The result is a clone of the larger set to avoid expanding it when executing the OR. */ + BitSet bsr; + if (bs1.size() > bs2.size()) { + bsr = (BitSet) bs1.clone(); + bsr.or(bs2); + } else { + bsr = (BitSet) bs2.clone(); + bsr.or(bs1); + } return bsr; } /** Logical AND two bit sets without modifying the source. */ - protected static BitSet and(BitSet bs1, BitSet bs2) { - BitSet bsr = (BitSet) bs1.clone(); - bsr.and(bs2); + public static BitSet and(BitSet bs1, BitSet bs2) { + BitSet bsr; + /* For AND is more efficient to clone the smaller bit set as the tail bits are 0. */ + if (bs1.size() < bs2.size()) { + bsr = (BitSet) bs1.clone(); + bsr.and(bs2); + } else { + bsr = (BitSet) bs2.clone(); + bsr.and(bs1); + } return bsr; } @@ -360,7 +395,8 @@ protected static BitSet and(BitSet bs1, BitSet bs2) { * Logical AND-NOT of the two bit sets, i.e., clearing all bits in first operand whose * corresponding bits are set in the second one, without modifying the source. */ - static BitSet andNot(BitSet bs1, BitSet bs2) { + public static BitSet andNot(BitSet bs1, BitSet bs2) { + /* AND-NOT is not commutative, so we cannot optimize based on set size. */ BitSet bsr = (BitSet) bs1.clone(); bsr.andNot(bs2); return bsr; @@ -369,7 +405,7 @@ static BitSet andNot(BitSet bs1, BitSet bs2) { /** * Sets the bit specified by the index to {@code false} without modifying the source. */ - protected static BitSet clear(BitSet bs1, int bitIndex) { + public static BitSet clear(BitSet bs1, int bitIndex) { BitSet bsr = (BitSet) bs1.clone(); bsr.clear(bitIndex); return bsr; @@ -378,12 +414,35 @@ protected static BitSet clear(BitSet bs1, int bitIndex) { /** * Sets the bit specified by the index to {@code true} without modifying the source. */ - protected static BitSet set(BitSet bs1, int bitIndex) { - BitSet bsr = (BitSet) bs1.clone(); - bsr.set(bitIndex); + public static BitSet set(BitSet bs1, int bitIndex) { + BitSet bsr; + int highestSetIndex = bs1.length() - 1; + /* Check if the new bit index exceeds the capacity of bs1. */ + if (bitIndex > highestSetIndex) { + /* Preallocate the bit set to represent bitIndex without expansion. */ + bsr = new BitSet(bitIndex); + /* First add in the original bits, which will System.arraycopy() bs1 bits into bsr. */ + bsr.or(bs1); + /* ... then set the new index. */ + bsr.set(bitIndex); + /* Executing the OR first avoids element by element processing from 0 to bitIndex. */ + } else { + /* The input set can represent bitIndex without expansion. */ + bsr = (BitSet) bs1.clone(); + bsr.set(bitIndex); + } return bsr; } + /** Create a new bit set with the bits of the inputs IDs set. */ + public static BitSet newBitSet(int index1, int index2) { + /* Preallocate the result bit set to represent index1 and index2 without any expansion. */ + BitSet typesBitSet = new BitSet(Math.max(index1, index2)); + typesBitSet.set(index1); + typesBitSet.set(index2); + return typesBitSet; + } + public static boolean closeToAllInstantiated(PointsToAnalysis bb, TypeState state) { if (state.typesCount() > 200) { MultiTypeState allInstState = (MultiTypeState) bb.getAllInstantiatedTypes(); diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/util/ListUtils.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/util/ListUtils.java new file mode 100644 index 000000000000..7340c2dd5b17 --- /dev/null +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/util/ListUtils.java @@ -0,0 +1,148 @@ +/* + * 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.util; + +import com.oracle.graal.pointsto.flow.context.object.AnalysisObject; + +import java.util.Arrays; + +public class ListUtils { + + public static final class UnsafeArrayListClosable implements AutoCloseable { + private UnsafeArrayList list; + private boolean closed = true; + + private UnsafeArrayListClosable(UnsafeArrayList list) { + this.list = list; + } + + public UnsafeArrayList list() { + return list; + } + + @Override + public void close() { + list.clear(); + closed = true; + } + } + + public static UnsafeArrayListClosable getTLArrayList(ThreadLocal> tl, int initialCapacity) { + UnsafeArrayListClosable result = tl.get(); + if (result == null) { + result = new UnsafeArrayListClosable<>(new UnsafeArrayList<>(new AnalysisObject[initialCapacity])); + tl.set(result); + } + if (result.closed) { + result.closed = false; + return result; + } else { + /* + * Happens very rarely that the same operation is done recursively. If this happens more + * often we should introduce a stack of arrays. + */ + return new UnsafeArrayListClosable<>(new UnsafeArrayList<>(new AnalysisObject[initialCapacity])); + } + } + + public static class UnsafeArrayList { + + static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; + E[] elementData; + int size; + + UnsafeArrayList(E[] initial) { + elementData = initial; + } + + public T[] copyToArray(T[] a) { + System.arraycopy(elementData, 0, a, 0, size); + return a; + } + + public T[] copyToArray(T[] a, int dstPos) { + System.arraycopy(elementData, 0, a, dstPos, size); + return a; + } + + public void addAll(E1[] c, int startIndex, int endIndex) { + assert startIndex <= endIndex : "start index can't be smaller than the end index."; + int newElements = endIndex - startIndex; + ensureCapacity(size() + newElements); + System.arraycopy(c, startIndex, elementData, size, newElements); + size += newElements; + } + + public int size() { + return size; + } + + public E[] elementData() { + return elementData; + } + + public void add(E e) { + ensureCapacity(size + 1); + elementData[size] = e; + size = size + 1; + } + + public void clear() { + for (int i = 0; i < size; i++) { + elementData[i] = null; + } + + size = 0; + } + + public E get(int i) { + assert i < size && i >= 0; + return elementData[i]; + } + + private void ensureCapacity(int minCapacity) { + if (minCapacity - elementData.length > 0) { + grow(minCapacity); + } + } + + private void grow(int minCapacity) { + int oldCapacity = elementData.length; + int newCapacity = oldCapacity + (oldCapacity >> 1); + if (newCapacity - minCapacity < 0) { + newCapacity = minCapacity; + } + if (newCapacity - MAX_ARRAY_SIZE > 0) { + if (minCapacity < 0) { + throw new OutOfMemoryError(); + } + newCapacity = (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; + } + elementData = Arrays.copyOf(elementData, newCapacity); + } + + } + +} 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 b6e9351573a4..61840bee8f9f 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 @@ -130,8 +130,8 @@ import com.oracle.graal.pointsto.AnalysisObjectScanningObserver; import com.oracle.graal.pointsto.AnalysisPolicy; import com.oracle.graal.pointsto.BigBang; -import com.oracle.graal.pointsto.BytecodeSensitiveAnalysisPolicy; -import com.oracle.graal.pointsto.DefaultAnalysisPolicy; +import com.oracle.graal.pointsto.flow.context.bytecode.BytecodeSensitiveAnalysisPolicy; +import com.oracle.graal.pointsto.flow.context.free.DefaultAnalysisPolicy; import com.oracle.graal.pointsto.PointsToAnalysis; import com.oracle.graal.pointsto.api.PointstoOptions; import com.oracle.graal.pointsto.constraints.UnsupportedFeatureException; From 27299f80490423ae136e6a1a005fda993e8bcd7a Mon Sep 17 00:00:00 2001 From: Codrut Stancu Date: Mon, 16 May 2022 15:20:08 -0700 Subject: [PATCH 2/2] Refactor points-to packages. --- substratevm/mx.substratevm/suite.py | 1 - .../BytecodeSensitiveAnalysisPolicy.java | 36 +++++++++---------- .../ContextSensitiveMultiTypeState.java | 4 +++ .../DefaultAnalysisContext.java | 2 +- .../DefaultAnalysisContextPolicy.java | 2 +- .../DefaultAnalysisPolicy.java | 7 +--- .../pointsto/typestate/MultiTypeState.java | 2 +- .../svm/hosted/NativeImageGenerator.java | 2 +- 8 files changed, 27 insertions(+), 29 deletions(-) rename substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/{flow/context/free => typestate}/DefaultAnalysisContext.java (96%) rename substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/{flow/context/free => typestate}/DefaultAnalysisContextPolicy.java (98%) rename substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/{flow/context/free => typestate}/DefaultAnalysisPolicy.java (98%) diff --git a/substratevm/mx.substratevm/suite.py b/substratevm/mx.substratevm/suite.py index fa8d9c0917cb..3b8b0339b0d0 100644 --- a/substratevm/mx.substratevm/suite.py +++ b/substratevm/mx.substratevm/suite.py @@ -1497,7 +1497,6 @@ "com.oracle.graal.pointsto.typestate", "com.oracle.graal.pointsto.infrastructure", "com.oracle.graal.pointsto.flow.context.object", - "com.oracle.graal.pointsto.flow.context.free", "com.oracle.graal.pointsto.flow.context.bytecode", ], "requires": [ 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 f707c502f48b..6b082fa18d54 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 @@ -548,7 +548,7 @@ public TypeState forContextInsensitiveTypeState(PointsToAnalysis bb, TypeState s AnalysisObject analysisObject = type.getContextInsensitiveAnalysisObject(); return singleTypeState(bb, state.canBeNull(), makeProperties(bb, analysisObject), analysisObject.type(), analysisObject); } else { - MultiTypeState multiState = (MultiTypeState) state; + ContextSensitiveMultiTypeState multiState = (ContextSensitiveMultiTypeState) state; AnalysisObject[] objectsArray = new AnalysisObject[multiState.typesCount()]; int i = 0; @@ -560,7 +560,7 @@ public TypeState forContextInsensitiveTypeState(PointsToAnalysis bb, TypeState s * immutable its types bit set cannot change. */ - BitSet typesBitSet = multiState.typesBitSet(); + BitSet typesBitSet = multiState.bitSet(); int properties = makeProperties(bb, objectsArray); return multiTypeState(bb, multiState.canBeNull(), properties, typesBitSet, objectsArray); } @@ -684,7 +684,7 @@ public TypeState doUnion(PointsToAnalysis bb, MultiTypeState state1, SingleTypeS int properties = bb.analysisPolicy().makePropertiesForUnion(s1, s2); - MultiTypeState result = new ContextSensitiveMultiTypeState(bb, resultCanBeNull, properties, s1.typesBitSet(), resultObjects); + MultiTypeState result = new ContextSensitiveMultiTypeState(bb, resultCanBeNull, properties, s1.bitSet(), resultObjects); assert !result.equals(s1); /* * No need to check the result size against the all-instantiated since the type count @@ -715,7 +715,7 @@ public TypeState doUnion(PointsToAnalysis bb, MultiTypeState state1, SingleTypeS } /* Create the types bit set by adding the s2 type to avoid walking the objects. */ - BitSet typesBitSet = TypeStateUtils.set(s1.typesBitSet(), s2.exactType().getId()); + BitSet typesBitSet = TypeStateUtils.set(s1.bitSet(), s2.exactType().getId()); int properties = bb.analysisPolicy().makePropertiesForUnion(s1, s2); MultiTypeState result = new ContextSensitiveMultiTypeState(bb, resultCanBeNull, properties, typesBitSet, resultObjects); @@ -760,7 +760,7 @@ private static TypeState doUnion0(PointsToAnalysis bb, ContextSensitiveMultiType AnalysisObject[] resultObjects = TypeStateUtils.concat(s1.objects, s2.objects); /* Logical OR the type bit sets. */ - BitSet resultTypesBitSet = TypeStateUtils.or(s1.typesBitSet(), s2.typesBitSet()); + BitSet resultTypesBitSet = TypeStateUtils.or(s1.bitSet(), s2.bitSet()); int properties = bb.analysisPolicy().makePropertiesForUnion(s1, s2); MultiTypeState result = new ContextSensitiveMultiTypeState(bb, resultCanBeNull, properties, resultTypesBitSet, resultObjects); @@ -774,7 +774,7 @@ private static TypeState doUnion0(PointsToAnalysis bb, ContextSensitiveMultiType AnalysisObject[] resultObjects = TypeStateUtils.concat(s2.objects, s1.objects); /* Logical OR the type bit sets. */ - BitSet resultTypesBitSet = TypeStateUtils.or(s1.typesBitSet(), s2.typesBitSet()); + BitSet resultTypesBitSet = TypeStateUtils.or(s1.bitSet(), s2.bitSet()); int properties = bb.analysisPolicy().makePropertiesForUnion(s1, s2); MultiTypeState result = new ContextSensitiveMultiTypeState(bb, resultCanBeNull, properties, resultTypesBitSet, resultObjects); @@ -797,10 +797,10 @@ private static TypeState doUnion1(PointsToAnalysis bb, ContextSensitiveMultiType * Optimization that gives 1.5-3x in performance for the (typeflow) phase. */ private static TypeState allocationInsensitiveSpeculativeUnion1(PointsToAnalysis bb, ContextSensitiveMultiTypeState s1, ContextSensitiveMultiTypeState s2, boolean resultCanBeNull) { - if (s1.typesBitSet().length() >= s2.typesBitSet().length()) { - long[] bits1 = TypeStateUtils.extractBitSetField(s1.typesBitSet()); - long[] bits2 = TypeStateUtils.extractBitSetField(s2.typesBitSet()); - assert s2.typesBitSet().cardinality() == s2.objects.length : "Cardinality and length of objects must match."; + if (s1.bitSet().length() >= s2.bitSet().length()) { + long[] bits1 = TypeStateUtils.extractBitSetField(s1.bitSet()); + long[] bits2 = TypeStateUtils.extractBitSetField(s2.bitSet()); + assert s2.bitSet().cardinality() == s2.objects.length : "Cardinality and length of objects must match."; boolean speculate = true; int numberOfWords = Math.min(bits1.length, bits2.length); @@ -965,7 +965,7 @@ private static TypeState doUnion2(PointsToAnalysis bb, ContextSensitiveMultiType assert resultObjects.size() > 1 : "The result state of a (Multi U Multi) operation must have at least 2 objects"; /* Logical OR the type bit sets. */ - BitSet resultTypesBitSet = TypeStateUtils.or(s1.typesBitSet(), s2.typesBitSet()); + BitSet resultTypesBitSet = TypeStateUtils.or(s1.bitSet(), s2.bitSet()); int properties = bb.analysisPolicy().makePropertiesForUnion(s1, s2); MultiTypeState result = new ContextSensitiveMultiTypeState(bb, resultCanBeNull, properties, resultTypesBitSet, resultObjects.copyToArray(new AnalysisObject[resultObjects.size()])); @@ -1034,12 +1034,12 @@ public TypeState doIntersection(PointsToAnalysis bb, MultiTypeState state1, Mult private static TypeState doIntersection0(PointsToAnalysis bb, ContextSensitiveMultiTypeState s1, ContextSensitiveMultiTypeState s2, boolean resultCanBeNull) { /* Speculate that s1 and s2 have either the same types, or no types in common. */ - if (s1.typesBitSet().equals(s2.typesBitSet())) { + if (s1.bitSet().equals(s2.bitSet())) { /* Speculate that s1 and s2 have the same types, i.e., the result is s1. */ return s1.forCanBeNull(bb, resultCanBeNull); } - if (!s1.typesBitSet().intersects(s2.typesBitSet())) { + if (!s1.bitSet().intersects(s2.bitSet())) { /* Speculate that s1 and s2 have no types in common, i.e., the result is empty. */ return TypeState.forEmpty().forCanBeNull(bb, resultCanBeNull); } @@ -1139,7 +1139,7 @@ private static TypeState doIntersection2(PointsToAnalysis bb, ContextSensitiveMu return new ContextSensitiveSingleTypeState(bb, resultCanBeNull, bb.analysisPolicy().makeProperties(bb, objects), objects[0].type(), objects); } else { /* Logical AND the type bit sets. */ - BitSet resultTypesBitSet = TypeStateUtils.and(s1.typesBitSet(), s2.typesBitSet()); + BitSet resultTypesBitSet = TypeStateUtils.and(s1.bitSet(), s2.bitSet()); MultiTypeState result = new ContextSensitiveMultiTypeState(bb, resultCanBeNull, bb.analysisPolicy().makeProperties(bb, objects), resultTypesBitSet, objects); /* @@ -1200,7 +1200,7 @@ public TypeState doSubtraction(PointsToAnalysis bb, MultiTypeState state1, Singl /* Multiple objects of the same type. */ return new ContextSensitiveSingleTypeState(bb, resultCanBeNull, bb.analysisPolicy().makeProperties(bb, resultObjects), resultObjects[0].type(), resultObjects); } else { - BitSet resultTypesBitSet = TypeStateUtils.clear(s1.typesBitSet(), s2.exactType().getId()); + BitSet resultTypesBitSet = TypeStateUtils.clear(s1.bitSet(), s2.exactType().getId()); return new ContextSensitiveMultiTypeState(bb, resultCanBeNull, bb.analysisPolicy().makeProperties(bb, resultObjects), resultTypesBitSet, resultObjects); } @@ -1229,12 +1229,12 @@ public TypeState doSubtraction(PointsToAnalysis bb, MultiTypeState state1, Multi private static TypeState doSubtraction0(PointsToAnalysis bb, ContextSensitiveMultiTypeState s1, ContextSensitiveMultiTypeState s2, boolean resultCanBeNull) { /* Speculate that s1 and s2 have either the same types, or no types in common. */ - if (s1.typesBitSet().equals(s2.typesBitSet())) { + if (s1.bitSet().equals(s2.bitSet())) { /* Speculate that s1 and s2 have the same types, i.e., the result is empty set. */ return TypeState.forEmpty().forCanBeNull(bb, resultCanBeNull); } - if (!s1.typesBitSet().intersects(s2.typesBitSet())) { + if (!s1.bitSet().intersects(s2.bitSet())) { /* Speculate that s1 and s2 have no types in common, i.e., the result is s1. */ return s1.forCanBeNull(bb, resultCanBeNull); } @@ -1323,7 +1323,7 @@ private static TypeState doSubtraction2(PointsToAnalysis bb, ContextSensitiveMul /* Multiple objects of the same type. */ return new ContextSensitiveSingleTypeState(bb, resultCanBeNull, bb.analysisPolicy().makeProperties(bb, objects), objects[0].type(), objects); } else { - BitSet resultTypesBitSet = TypeStateUtils.andNot(s1.typesBitSet(), s2.typesBitSet()); + BitSet resultTypesBitSet = TypeStateUtils.andNot(s1.bitSet(), s2.bitSet()); /* * Don't need to check if the result is close-to-all-instantiated since result * <= s1. diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/context/bytecode/ContextSensitiveMultiTypeState.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/context/bytecode/ContextSensitiveMultiTypeState.java index 495c424349f2..96a58d226563 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/context/bytecode/ContextSensitiveMultiTypeState.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/context/bytecode/ContextSensitiveMultiTypeState.java @@ -77,6 +77,10 @@ protected ContextSensitiveMultiTypeState(PointsToAnalysis bb, boolean canBeNull, PointsToStats.registerTypeState(bb, this); } + protected BitSet bitSet() { + return typesBitSet; + } + /** * Returns an array of all type ids from the {@link #objects} array. This mitigates the CPU * cache misses when iterating over all AnalysisObject and dereferencing the type field over and diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/context/free/DefaultAnalysisContext.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/DefaultAnalysisContext.java similarity index 96% rename from substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/context/free/DefaultAnalysisContext.java rename to substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/DefaultAnalysisContext.java index be9a8cff0fcf..99946aa97437 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/context/free/DefaultAnalysisContext.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/DefaultAnalysisContext.java @@ -22,7 +22,7 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ -package com.oracle.graal.pointsto.flow.context.free; +package com.oracle.graal.pointsto.typestate; import com.oracle.graal.pointsto.flow.context.AnalysisContext; diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/context/free/DefaultAnalysisContextPolicy.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/DefaultAnalysisContextPolicy.java similarity index 98% rename from substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/context/free/DefaultAnalysisContextPolicy.java rename to substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/DefaultAnalysisContextPolicy.java index de2485cb33f0..6c6f6b086852 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/context/free/DefaultAnalysisContextPolicy.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/DefaultAnalysisContextPolicy.java @@ -22,7 +22,7 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ -package com.oracle.graal.pointsto.flow.context.free; +package com.oracle.graal.pointsto.typestate; import com.oracle.graal.pointsto.PointsToAnalysis; import com.oracle.graal.pointsto.flow.MethodTypeFlow; diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/context/free/DefaultAnalysisPolicy.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/DefaultAnalysisPolicy.java similarity index 98% rename from substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/context/free/DefaultAnalysisPolicy.java rename to substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/DefaultAnalysisPolicy.java index 2dac5ee1e342..f4bcfeac278c 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/context/free/DefaultAnalysisPolicy.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/DefaultAnalysisPolicy.java @@ -22,7 +22,7 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ -package com.oracle.graal.pointsto.flow.context.free; +package com.oracle.graal.pointsto.typestate; import java.lang.reflect.Modifier; import java.util.ArrayList; @@ -52,11 +52,6 @@ import com.oracle.graal.pointsto.meta.AnalysisType; import com.oracle.graal.pointsto.meta.AnalysisUniverse; import com.oracle.graal.pointsto.meta.PointsToAnalysisMethod; -import com.oracle.graal.pointsto.typestate.MultiTypeState; -import com.oracle.graal.pointsto.typestate.PointsToStats; -import com.oracle.graal.pointsto.typestate.SingleTypeState; -import com.oracle.graal.pointsto.typestate.TypeState; -import com.oracle.graal.pointsto.typestate.TypeStateUtils; import com.oracle.graal.pointsto.typestore.ArrayElementsTypeStore; import com.oracle.graal.pointsto.typestore.FieldTypeStore; import com.oracle.graal.pointsto.typestore.UnifiedArrayElementsTypeStore; diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/MultiTypeState.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/MultiTypeState.java index 6f2ce7eca25b..4aee143bb607 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/MultiTypeState.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/MultiTypeState.java @@ -104,7 +104,7 @@ public int typesCount() { return typesCount; } - public BitSet typesBitSet() { + protected BitSet typesBitSet() { return typesBitSet; } 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 61840bee8f9f..245ad0e30b56 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 @@ -131,7 +131,7 @@ import com.oracle.graal.pointsto.AnalysisPolicy; import com.oracle.graal.pointsto.BigBang; import com.oracle.graal.pointsto.flow.context.bytecode.BytecodeSensitiveAnalysisPolicy; -import com.oracle.graal.pointsto.flow.context.free.DefaultAnalysisPolicy; +import com.oracle.graal.pointsto.typestate.DefaultAnalysisPolicy; import com.oracle.graal.pointsto.PointsToAnalysis; import com.oracle.graal.pointsto.api.PointstoOptions; import com.oracle.graal.pointsto.constraints.UnsupportedFeatureException;