Skip to content

Commit befb4fb

Browse files
committed
[GR-43619] Refactor points-to analysis.
PullRequest: graal/13711
2 parents ddbb112 + 19e27bd commit befb4fb

File tree

12 files changed

+270
-142
lines changed

12 files changed

+270
-142
lines changed

substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/AnalysisPolicy.java

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,14 @@
2727
import java.util.BitSet;
2828

2929
import org.graalvm.compiler.options.OptionValues;
30+
import org.graalvm.compiler.replacements.nodes.BasicArrayCopyNode;
3031

3132
import com.oracle.graal.pointsto.api.PointstoOptions;
3233
import com.oracle.graal.pointsto.flow.AbstractSpecialInvokeTypeFlow;
3334
import com.oracle.graal.pointsto.flow.AbstractStaticInvokeTypeFlow;
3435
import com.oracle.graal.pointsto.flow.AbstractVirtualInvokeTypeFlow;
3536
import com.oracle.graal.pointsto.flow.ActualReturnTypeFlow;
37+
import com.oracle.graal.pointsto.flow.ArrayElementsTypeFlow;
3638
import com.oracle.graal.pointsto.flow.CloneTypeFlow;
3739
import com.oracle.graal.pointsto.flow.InvokeTypeFlow;
3840
import com.oracle.graal.pointsto.flow.MethodFlowsGraph;
@@ -68,6 +70,7 @@ public abstract class AnalysisPolicy {
6870
protected final int maxHeapContextDepth;
6971
protected final boolean limitObjectArrayLength;
7072
protected final int maxObjectSetSize;
73+
protected final boolean hybridStaticContext;
7174

7275
public AnalysisPolicy(OptionValues options) {
7376
this.options = options;
@@ -80,7 +83,7 @@ public AnalysisPolicy(OptionValues options) {
8083
maxHeapContextDepth = PointstoOptions.MaxHeapContextDepth.getValue(options);
8184
limitObjectArrayLength = PointstoOptions.LimitObjectArrayLength.getValue(options);
8285
maxObjectSetSize = PointstoOptions.MaxObjectSetSize.getValue(options);
83-
86+
hybridStaticContext = PointstoOptions.HybridStaticContext.getValue(options);
8487
}
8588

8689
public abstract boolean isContextSensitiveAnalysis();
@@ -113,6 +116,10 @@ public int maxObjectSetSize() {
113116
return maxObjectSetSize;
114117
}
115118

119+
public boolean useHybridStaticContext() {
120+
return hybridStaticContext;
121+
}
122+
116123
public abstract MethodTypeFlow createMethodTypeFlow(PointsToAnalysisMethod method);
117124

118125
/**
@@ -272,4 +279,32 @@ public final TypeState doSubtraction(PointsToAnalysis bb, SingleTypeState s1, Mu
272279
public abstract TypeState doSubtraction(PointsToAnalysis bb, MultiTypeState s1, SingleTypeState s2);
273280

274281
public abstract TypeState doSubtraction(PointsToAnalysis bb, MultiTypeState s1, MultiTypeState s2);
282+
283+
public abstract void processArrayCopyStates(PointsToAnalysis bb, TypeState srcArrayState, TypeState dstArrayState);
284+
285+
/**
286+
* System.arraycopy() type compatibility is defined as: can elements of the source array be
287+
* converted to the component type of the destination array by assignment conversion.
288+
* <p>
289+
* System.arraycopy() semantics doesn't check the compatibility of the source and destination
290+
* arrays statically, it instead relies on runtime checks to verify the compatibility between
291+
* the copied objects and the destination array. For example System.arraycopy() can copy from an
292+
* Object[] to SomeOtherObject[]. That's why {@link ArrayElementsTypeFlow} tests each individual
293+
* copied object for compatibility with the defined type of the destination array and filters
294+
* out those not assignable. From System.arraycopy() javadoc: "...if any actual component of the
295+
* source array [...] cannot be converted to the component type of the destination array by
296+
* assignment conversion, an ArrayStoreException is thrown."
297+
* <p>
298+
* Here we detect incompatible types eagerly, i.e., array types whose elements would be filtered
299+
* out by ArrayElementsTypeFlow anyway, by checking
300+
* {@code dstType.isAssignableFrom(srcType) || srcType.isAssignableFrom(dstType)}.
301+
* <p>
302+
* By skipping incompatible types when modeling an {@link BasicArrayCopyNode} we avoid adding
303+
* any use links between ArrayElementsTypeFlow that would filter out all elements anyway. (Note
304+
* that the filter in ArrayElementsTypeFlow is still necessary for partially compatible copying,
305+
* e.g., when copying from an array to another array of one of its subtypes.)
306+
*/
307+
protected static boolean areTypesCompatibleForSystemArraycopy(AnalysisType srcType, AnalysisType dstType) {
308+
return dstType.isAssignableFrom(srcType) || srcType.isAssignableFrom(dstType);
309+
}
275310
}

substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/ArrayCopyTypeFlow.java

Lines changed: 2 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
package com.oracle.graal.pointsto.flow;
2626

2727
import com.oracle.graal.pointsto.PointsToAnalysis;
28-
import com.oracle.graal.pointsto.flow.context.object.AnalysisObject;
2928
import com.oracle.graal.pointsto.meta.AnalysisType;
3029
import com.oracle.graal.pointsto.typestate.TypeState;
3130
import com.oracle.graal.pointsto.util.AnalysisError;
@@ -123,61 +122,7 @@ public void onObservedUpdate(PointsToAnalysis bb) {
123122
}
124123

125124
private static void processStates(PointsToAnalysis bb, TypeState srcArrayState, TypeState dstArrayState) {
126-
/*
127-
* The source and destination array can have reference types which, although must be
128-
* compatible, can be different.
129-
*/
130-
for (AnalysisObject srcArrayObject : srcArrayState.objects(bb)) {
131-
if (!srcArrayObject.type().isArray()) {
132-
/*
133-
* Ignore non-array type. Sometimes the analysis cannot filter out non-array types
134-
* flowing into array copy, however this will fail at runtime.
135-
*/
136-
continue;
137-
}
138-
assert srcArrayObject.type().isArray();
139-
140-
if (srcArrayObject.isPrimitiveArray() || srcArrayObject.isEmptyObjectArrayConstant(bb)) {
141-
/* Nothing to read from a primitive array or an empty array constant. */
142-
continue;
143-
}
144-
145-
ArrayElementsTypeFlow srcArrayElementsFlow = srcArrayObject.getArrayElementsFlow(bb, false);
146-
147-
for (AnalysisObject dstArrayObject : dstArrayState.objects(bb)) {
148-
if (!dstArrayObject.type().isArray()) {
149-
/* Ignore non-array type. */
150-
continue;
151-
}
152-
153-
assert dstArrayObject.type().isArray();
154-
155-
if (dstArrayObject.isPrimitiveArray() || dstArrayObject.isEmptyObjectArrayConstant(bb)) {
156-
/* Cannot write to a primitive array or an empty array constant. */
157-
continue;
158-
}
159-
160-
/*
161-
* As far as the ArrayCopyTypeFlow is concerned the source and destination types can
162-
* be compatible or not, where compatibility is defined as: the component of the
163-
* source array can be converted to the component type of the destination array by
164-
* assignment conversion. System.arraycopy() semantics doesn't check the
165-
* compatibility of the source and destination arrays, it instead relies on runtime
166-
* checks of the compatibility of the copied objects and the destination array. For
167-
* example System.arraycopy() can copy from an Object[] to SomeOtherObject[]. In
168-
* this case a check dstArrayObject.type().isAssignableFrom(srcArrayObject.type()
169-
* would fail but it is actually a valid use. That's why ArrayElementsTypeFlow will
170-
* test each individual copied object for compatibility with the defined type of the
171-
* destination array and filter out those not assignable. From System.arraycopy()
172-
* javadoc: "...if any actual component of the source array from position srcPos
173-
* through srcPos+length-1 cannot be converted to the component type of the
174-
* destination array by assignment conversion, an ArrayStoreException is thrown."
175-
*/
176-
177-
ArrayElementsTypeFlow dstArrayElementsFlow = dstArrayObject.getArrayElementsFlow(bb, true);
178-
srcArrayElementsFlow.addUse(bb, dstArrayElementsFlow);
179-
}
180-
}
125+
bb.analysisPolicy().processArrayCopyStates(bb, srcArrayState, dstArrayState);
181126
}
182127

183128
@Override
@@ -195,8 +140,6 @@ public TypeFlow<?> destination() {
195140

196141
@Override
197142
public String toString() {
198-
StringBuilder str = new StringBuilder();
199-
str.append("ArrayCopyTypeFlow<").append(getState()).append(">");
200-
return str.toString();
143+
return "ArrayCopyTypeFlow<" + getState() + ">";
201144
}
202145
}

substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/context/bytecode/BytecodeAnalysisContextPolicy.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ public BytecodeAnalysisContext staticCalleeContext(PointsToAnalysis bb, Bytecode
117117
* the invoke location, otherwise just reuse the caller context.
118118
*/
119119

120-
if (!PointstoOptions.HybridStaticContext.getValue(bb.getOptions())) {
120+
if (!bb.analysisPolicy().useHybridStaticContext()) {
121121
return callerContext;
122122
}
123123

substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/context/bytecode/BytecodeSensitiveAnalysisPolicy.java

Lines changed: 70 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@
7777

7878
import jdk.vm.ci.code.BytecodePosition;
7979
import jdk.vm.ci.meta.JavaConstant;
80+
import jdk.vm.ci.meta.JavaKind;
8081

8182
public final class BytecodeSensitiveAnalysisPolicy extends AnalysisPolicy {
8283

@@ -539,9 +540,9 @@ public TypeState doUnion(PointsToAnalysis bb, MultiTypeState state1, SingleTypeS
539540
return result;
540541
} else {
541542
AnalysisObject[] resultObjects;
542-
if (s2.exactType().getId() < s1.firstType().getId()) {
543+
if (s2.exactType().getId() < s1.firstTypeId()) {
543544
resultObjects = TypeStateUtils.concat(so2, so1);
544-
} else if (s2.exactType().getId() > s1.lastType().getId()) {
545+
} else if (s2.exactType().getId() > s1.lastTypeId()) {
545546
resultObjects = TypeStateUtils.concat(so1, so2);
546547
} else {
547548

@@ -598,7 +599,7 @@ private TypeState doUnion0(PointsToAnalysis bb, ContextSensitiveMultiTypeState s
598599

599600
/* Speculate that s1 and s2 are distinct sets. */
600601

601-
if (s1.lastType().getId() < s2.firstType().getId()) {
602+
if (s1.lastTypeId() < s2.firstTypeId()) {
602603
/* Speculate that objects in s2 follow after objects in s1. */
603604

604605
/* Concatenate the objects. */
@@ -612,7 +613,7 @@ private TypeState doUnion0(PointsToAnalysis bb, ContextSensitiveMultiTypeState s
612613
PointsToStats.registerUnionOperation(bb, s1, s2, result);
613614
return result;
614615

615-
} else if (s2.lastType().getId() < s1.firstType().getId()) {
616+
} else if (s2.lastTypeId() < s1.firstTypeId()) {
616617
/* Speculate that objects in s1 follow after objects in s2. */
617618

618619
/* Concatenate the objects. */
@@ -1178,4 +1179,69 @@ private static TypeState doSubtraction2(PointsToAnalysis bb, ContextSensitiveMul
11781179
}
11791180
}
11801181
}
1182+
1183+
@Override
1184+
public void processArrayCopyStates(PointsToAnalysis bb, TypeState srcArrayState, TypeState dstArrayState) {
1185+
/*
1186+
* The types-objects iterator uses a sliding window to iterate over the objects of a type,
1187+
* and can quickly skip over the objects of specific types. This iterator is ideal since we
1188+
* want to quickly skip over the objects in the source whose type is not compatible with the
1189+
* destination (see AnalysisPolicy.areTypesCompatibleForSystemArraycopy() for details). This
1190+
* iterator also has the ability to reset back to the beginning of a type's partition, so
1191+
* after processing all objects in a pair of source-destination compatible types you can
1192+
* reset the source type partition back to the beginning and look for the next compatible
1193+
* destination type.
1194+
*
1195+
* Although the resulting code is still quadratic from an algorithmic complexity standpoint,
1196+
* in practice you can never reach the asymptotic complexity given that in a usual type
1197+
* hierarchy most types are not compatible with most other types for System.arraycopy().
1198+
*/
1199+
TypesObjectsIterator srcIterator = new TypesObjectsIterator(srcArrayState);
1200+
while (srcIterator.hasNextType()) {
1201+
AnalysisType srcType = srcIterator.nextType();
1202+
if (!isObjectArrayType(srcType)) {
1203+
srcIterator.skipObjects(srcType);
1204+
continue;
1205+
}
1206+
srcIterator.memoizePosition();
1207+
TypesObjectsIterator dstIterator = new TypesObjectsIterator(dstArrayState);
1208+
while (dstIterator.hasNextType()) {
1209+
AnalysisType dstType = dstIterator.nextType();
1210+
if (!isObjectArrayType(dstType)) {
1211+
dstIterator.skipObjects(dstType);
1212+
continue;
1213+
}
1214+
if (areTypesCompatibleForSystemArraycopy(srcType, dstType)) {
1215+
srcIterator.reset();
1216+
dstIterator.memoizePosition();
1217+
while (srcIterator.hasNextObject(srcType)) {
1218+
AnalysisObject srcObject = srcIterator.nextObject(srcType);
1219+
if (srcObject.isEmptyObjectArrayConstant(bb)) {
1220+
continue;
1221+
}
1222+
var srcArrayElements = srcObject.getArrayElementsFlow(bb, true);
1223+
dstIterator.reset();
1224+
while (dstIterator.hasNextObject(dstType)) {
1225+
AnalysisObject dstObject = dstIterator.nextObject(dstType);
1226+
if (dstObject.isEmptyObjectArrayConstant(bb)) {
1227+
continue;
1228+
}
1229+
var dstArrayElements = dstObject.getArrayElementsFlow(bb, true);
1230+
srcArrayElements.addUse(bb, dstArrayElements);
1231+
}
1232+
}
1233+
} else {
1234+
dstIterator.skipObjects(dstType);
1235+
}
1236+
}
1237+
}
1238+
}
1239+
1240+
/*
1241+
* We ignore non-array types. Sometimes the analysis cannot filter out non-array types flowing
1242+
* into array copy, however this will fail at runtime.
1243+
*/
1244+
private static boolean isObjectArrayType(AnalysisType type) {
1245+
return type.isArray() && type.getComponentType().getJavaKind() == JavaKind.Object;
1246+
}
11811247
}

substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/context/bytecode/BytecodeSensitiveVirtualInvokeTypeFlow.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -106,10 +106,7 @@ public void onObservedUpdate(PointsToAnalysis bb) {
106106
* Type states can be conservative, i.e., we can have receiver types that do not
107107
* implement the method. Just ignore such types.
108108
*/
109-
while (toi.hasNextObject(type)) {
110-
// skip the rest of the objects of the same type
111-
toi.nextObject(type);
112-
}
109+
toi.skipObjects(type);
113110
continue;
114111
}
115112

substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/context/bytecode/ContextSensitiveMultiTypeState.java

Lines changed: 10 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,7 @@
3333
import com.oracle.graal.pointsto.flow.context.object.AnalysisObject;
3434
import com.oracle.graal.pointsto.meta.AnalysisType;
3535
import com.oracle.graal.pointsto.typestate.MultiTypeState;
36-
import com.oracle.graal.pointsto.typestate.PointsToStats;
3736
import com.oracle.graal.pointsto.typestate.TypeState;
38-
import com.oracle.graal.pointsto.typestate.TypeStateUtils;
3937

4038
public class ContextSensitiveMultiTypeState extends MultiTypeState {
4139

@@ -48,33 +46,16 @@ public class ContextSensitiveMultiTypeState extends MultiTypeState {
4846
public ContextSensitiveMultiTypeState(PointsToAnalysis bb, boolean canBeNull, int properties, BitSet typesBitSet, AnalysisObject... objects) {
4947
super(bb, canBeNull, properties, typesBitSet);
5048
this.objects = objects;
51-
/*
52-
* Trim the typesBitSet to size eagerly. The typesBitSet is effectively immutable, i.e., no
53-
* calls to mutating methods are made on it after it is set in the MultiTypeState, thus we
54-
* don't need to use any external synchronization. However, to keep it immutable we use
55-
* BitSet.clone() when deriving a new BitSet since the set operations (and, or, etc.) mutate
56-
* the original object. The problem is that BitSet.clone() breaks the informal contract that
57-
* the clone method should not modify the original object; it calls trimToSize() before
58-
* creating a copy. Thus, trimming the bit set here ensures that cloning does not modify the
59-
* typesBitSet. Since BitSet is not thread safe mutating it during cloning is problematic in
60-
* a multithreaded environment. If for example you iterate over the bits at the same time as
61-
* another thread calls clone() the words[] array can be in an inconsistent state.
62-
*/
63-
TypeStateUtils.trimBitSetToSize(typesBitSet);
64-
long cardinality = typesBitSet.cardinality();
65-
assert cardinality < Integer.MAX_VALUE : "We don't expect so much types.";
6649
assert typesCount > 1 : "Multi type state with single type.";
6750
assert objects.length > 1 : "Multi type state with single object.";
6851
assert !bb.extendedAsserts() || checkObjects(bb);
69-
PointsToStats.registerTypeState(bb, this);
7052
}
7153

7254
/** Create a type state with the same content and a reversed canBeNull value. */
7355
protected ContextSensitiveMultiTypeState(PointsToAnalysis bb, boolean canBeNull, ContextSensitiveMultiTypeState other) {
7456
super(bb, canBeNull, other);
7557
this.objects = other.objects;
7658
this.merged = other.merged;
77-
PointsToStats.registerTypeState(bb, this);
7859
}
7960

8061
protected BitSet bitSet() {
@@ -130,14 +111,18 @@ protected Iterator<AnalysisObject> objectsIterator(BigBang bb) {
130111
return Arrays.asList(objects).iterator();
131112
}
132113

133-
/** Get the type of the first object group. */
134-
public AnalysisType firstType() {
135-
return objects[0].type();
114+
/**
115+
* Get the type of the first object group.
116+
*/
117+
public int firstTypeId() {
118+
return objects[0].getTypeId();
136119
}
137120

138-
/** Get the type of the last object group. */
139-
public AnalysisType lastType() {
140-
return objects[objects.length - 1].type();
121+
/**
122+
* Get the type of the last object group.
123+
*/
124+
public int lastTypeId() {
125+
return objects[objects.length - 1].getTypeId();
141126
}
142127

143128
@Override

substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/context/bytecode/ContextSensitiveSingleTypeState.java

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,13 @@
2424
*/
2525
package com.oracle.graal.pointsto.flow.context.bytecode;
2626

27-
import java.util.ArrayList;
2827
import java.util.Arrays;
2928
import java.util.Iterator;
3029

3130
import com.oracle.graal.pointsto.BigBang;
3231
import com.oracle.graal.pointsto.PointsToAnalysis;
3332
import com.oracle.graal.pointsto.flow.context.object.AnalysisObject;
3433
import com.oracle.graal.pointsto.meta.AnalysisType;
35-
import com.oracle.graal.pointsto.typestate.PointsToStats;
3634
import com.oracle.graal.pointsto.typestate.SingleTypeState;
3735
import com.oracle.graal.pointsto.typestate.TypeState;
3836

@@ -42,26 +40,17 @@ public class ContextSensitiveSingleTypeState extends SingleTypeState {
4240
/** The objects of this type state. */
4341
protected final AnalysisObject[] objects;
4442

45-
public ContextSensitiveSingleTypeState(PointsToAnalysis bb, boolean canBeNull, int properties, AnalysisType type, ArrayList<AnalysisObject> objects) {
46-
this(bb, canBeNull, properties, type, objects.toArray(AnalysisObject.EMPTY_ARRAY));
47-
assert objects.size() > 0 : "Single type state with no objects.";
48-
}
49-
5043
/** Creates a new type state from incoming objects. */
5144
public ContextSensitiveSingleTypeState(PointsToAnalysis bb, boolean canBeNull, int properties, AnalysisType type, AnalysisObject... objects) {
5245
super(bb, canBeNull, properties, type);
5346
this.objects = objects;
5447
assert !bb.extendedAsserts() || checkObjects(bb);
55-
56-
PointsToStats.registerTypeState(bb, this);
5748
}
5849

5950
/** Create a type state with the same content and a reversed canBeNull value. */
6051
protected ContextSensitiveSingleTypeState(PointsToAnalysis bb, boolean canBeNull, ContextSensitiveSingleTypeState other) {
6152
super(bb, canBeNull, other);
6253
this.objects = other.objects;
63-
64-
PointsToStats.registerTypeState(bb, this);
6554
}
6655

6756
protected boolean checkObjects(BigBang bb) {

0 commit comments

Comments
 (0)