Skip to content

Commit 58ee0ad

Browse files
committed
[GR-43361] Track primitive values in the points-to analysis
PullRequest: graal/14558
2 parents c291aa4 + 1115ad4 commit 58ee0ad

36 files changed

+769
-126
lines changed

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ public boolean forRelocatedPointerFieldValue(JavaConstant receiver, AnalysisFiel
4747
if (!field.isWritten()) {
4848
return field.registerAsWritten(reason);
4949
}
50+
if (fieldValue.isNonNull()) {
51+
FieldTypeFlow fieldTypeFlow = getFieldTypeFlow(field, receiver);
52+
return fieldTypeFlow.addState(getAnalysis(), TypeState.anyPrimitiveState());
53+
}
5054
return false;
5155
}
5256

@@ -71,6 +75,16 @@ public boolean forNonNullFieldValue(JavaConstant receiver, AnalysisField field,
7175
return fieldTypeFlow.addState(analysis, bb.analysisPolicy().constantTypeState(analysis, fieldValue, fieldType));
7276
}
7377

78+
@Override
79+
public boolean forPrimitiveFieldValue(JavaConstant receiver, AnalysisField field, JavaConstant fieldValue, ScanReason reason) {
80+
PointsToAnalysis analysis = getAnalysis();
81+
82+
/* Add the constant value object to the field's type flow. */
83+
FieldTypeFlow fieldTypeFlow = getFieldTypeFlow(field, receiver);
84+
/* Add the new constant to the field's flow state. */
85+
return fieldTypeFlow.addState(analysis, TypeState.forPrimitiveConstant(fieldValue.asLong()));
86+
}
87+
7488
/**
7589
* Get the field type flow give a receiver.
7690
*/

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@ default HostedProviders getProviders(AnalysisMethod method) {
9494

9595
void runAnalysis(DebugContext debug, Function<AnalysisUniverse, Boolean> duringAnalysisAction) throws InterruptedException;
9696

97+
boolean trackPrimitiveValues();
98+
9799
/** You can blacklist certain callees here. */
98100
@SuppressWarnings("unused")
99101
default boolean isCallAllowed(PointsToAnalysis bb, AnalysisMethod caller, AnalysisMethod target, BytecodePosition srcPosition) {

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

Lines changed: 88 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
import com.oracle.graal.pointsto.api.PointstoOptions;
4747
import com.oracle.graal.pointsto.constraints.UnsupportedFeatures;
4848
import com.oracle.graal.pointsto.flow.AllSynchronizedTypeFlow;
49+
import com.oracle.graal.pointsto.flow.AnyPrimitiveSourceTypeFlow;
4950
import com.oracle.graal.pointsto.flow.FieldTypeFlow;
5051
import com.oracle.graal.pointsto.flow.FormalParamTypeFlow;
5152
import com.oracle.graal.pointsto.flow.InvokeTypeFlow;
@@ -61,8 +62,10 @@
6162
import com.oracle.graal.pointsto.meta.AnalysisMethod;
6263
import com.oracle.graal.pointsto.meta.AnalysisType;
6364
import com.oracle.graal.pointsto.meta.AnalysisUniverse;
65+
import com.oracle.graal.pointsto.meta.PointsToAnalysisField;
6466
import com.oracle.graal.pointsto.meta.PointsToAnalysisMethod;
6567
import com.oracle.graal.pointsto.reports.StatisticsPrinter;
68+
import com.oracle.graal.pointsto.typestate.AnyPrimitiveTypeState;
6669
import com.oracle.graal.pointsto.typestate.PointsToStats;
6770
import com.oracle.graal.pointsto.typestate.TypeState;
6871
import com.oracle.graal.pointsto.util.AnalysisError;
@@ -89,6 +92,20 @@
8992
public abstract class PointsToAnalysis extends AbstractAnalysisEngine {
9093
/** The type of {@link java.lang.Object}. */
9194
private final AnalysisType objectType;
95+
/**
96+
* Enables propagating primitive values interproceduraly using the typeflow graph. Only simple
97+
* constants are propagated. Arithmetic operations and merges of different constants result in a
98+
* special {@link AnyPrimitiveTypeState } state that leads to immediate saturation.
99+
* <p>
100+
* This optimization also handles word types, which are essentially primitive values.
101+
* <p>
102+
* Unsafe loads and stores are NOT modeled, because it would lead to merging of primitive and
103+
* objects states (all unsafe fields are merged into a single flow). Instead, all unsafe
104+
* accessed primitive fields are assigned the PrimitiveTypeState state and any unsafe read is
105+
* immediately represented as {@link com.oracle.graal.pointsto.flow.AnyPrimitiveSourceTypeFlow}.
106+
*/
107+
private final boolean trackPrimitiveValues;
108+
private AnyPrimitiveSourceTypeFlow anyPrimitiveSourceTypeFlow;
92109
private TypeFlow<?> allSynchronizedTypeFlow;
93110

94111
protected final boolean trackTypeFlowInputs;
@@ -107,6 +124,8 @@ public PointsToAnalysis(OptionValues options, AnalysisUniverse universe, HostVM
107124
ConstantReflectionProvider constantReflectionProvider, WordTypes wordTypes, UnsupportedFeatures unsupportedFeatures, DebugContext debugContext, TimerCollection timerCollection) {
108125
super(options, universe, hostVM, metaAccess, snippetReflectionProvider, constantReflectionProvider, wordTypes, unsupportedFeatures, debugContext, timerCollection);
109126
this.typeFlowTimer = timerCollection.createTimer("(typeflow)");
127+
this.trackPrimitiveValues = PointstoOptions.TrackPrimitiveValues.getValue(options);
128+
this.anyPrimitiveSourceTypeFlow = new AnyPrimitiveSourceTypeFlow(null, null);
110129

111130
this.objectType = metaAccess.lookupJavaType(Object.class);
112131
/*
@@ -199,15 +218,26 @@ public void forceUnsafeUpdate(AnalysisField field) {
199218
}
200219

201220
@Override
202-
public void registerAsJNIAccessed(AnalysisField field, boolean writable) {
221+
public void registerAsJNIAccessed(AnalysisField f, boolean writable) {
222+
PointsToAnalysisField field = (PointsToAnalysisField) f;
203223
// Same as addRootField() and addRootStaticField():
204224
// create type flows for any subtype of the field's declared type
205225
TypeFlow<?> declaredTypeFlow = field.getType().getTypeFlow(this, true);
206-
if (field.isStatic()) {
207-
declaredTypeFlow.addUse(this, field.getStaticFieldFlow());
208-
} else {
209-
FieldTypeFlow instanceFieldFlow = field.getDeclaringClass().getContextInsensitiveAnalysisObject().getInstanceFieldFlow(this, field, writable);
210-
declaredTypeFlow.addUse(this, instanceFieldFlow);
226+
if (isSupportedJavaKind(field.getStorageKind())) {
227+
if (field.isStatic()) {
228+
if (field.getStorageKind().isObject()) {
229+
declaredTypeFlow.addUse(this, field.getStaticFieldFlow());
230+
} else {
231+
field.saturatePrimitiveField();
232+
}
233+
} else {
234+
FieldTypeFlow instanceFieldFlow = field.getDeclaringClass().getContextInsensitiveAnalysisObject().getInstanceFieldFlow(this, field, writable);
235+
if (field.getStorageKind().isObject()) {
236+
declaredTypeFlow.addUse(this, instanceFieldFlow);
237+
} else {
238+
field.saturatePrimitiveField();
239+
}
240+
}
211241
}
212242
}
213243

@@ -224,6 +254,7 @@ public boolean trackConcreteAnalysisObjects(@SuppressWarnings("unused") Analysis
224254
public void cleanupAfterAnalysis() {
225255
super.cleanupAfterAnalysis();
226256
allSynchronizedTypeFlow = null;
257+
anyPrimitiveSourceTypeFlow = null;
227258
unsafeLoads = null;
228259
unsafeStores = null;
229260

@@ -263,6 +294,10 @@ public TypeFlow<?> getAllSynchronizedTypeFlow() {
263294
return allSynchronizedTypeFlow;
264295
}
265296

297+
public AnyPrimitiveSourceTypeFlow getAnyPrimitiveSourceTypeFlow() {
298+
return anyPrimitiveSourceTypeFlow;
299+
}
300+
266301
@Override
267302
public Iterable<AnalysisType> getAllSynchronizedTypes() {
268303
/*
@@ -306,10 +341,7 @@ public AnalysisMethod addRootMethod(AnalysisMethod aMethod, boolean invokeSpecia
306341
for (int idx = 0; idx < paramCount; idx++) {
307342
AnalysisType declaredParamType = (AnalysisType) signature.getParameterType(idx, declaringClass);
308343
FormalParamTypeFlow parameter = flowInfo.getParameter(idx);
309-
if (declaredParamType.getJavaKind() == JavaKind.Object && parameter != null) {
310-
TypeFlow<?> initialParameterFlow = declaredParamType.getTypeFlow(this, true);
311-
initialParameterFlow.addUse(this, parameter);
312-
}
344+
processParam(declaredParamType, parameter);
313345
}
314346
});
315347
};
@@ -369,17 +401,25 @@ public AnalysisMethod addRootMethod(AnalysisMethod aMethod, boolean invokeSpecia
369401
*/
370402
AnalysisType declaredParamType = (AnalysisType) signature.getParameterType(idx - 1, declaringClass);
371403
TypeFlow<?> actualParameterFlow = invoke.getActualParameter(idx);
372-
if (declaredParamType.getJavaKind() == JavaKind.Object && actualParameterFlow != null) {
373-
TypeFlow<?> initialParameterFlow = declaredParamType.getTypeFlow(this, true);
374-
initialParameterFlow.addUse(this, actualParameterFlow);
375-
}
404+
processParam(declaredParamType, actualParameterFlow);
376405
}
377406
});
378407
}
379408
return aMethod;
380409

381410
}
382411

412+
private void processParam(AnalysisType declaredParamType, TypeFlow<?> actualParameterFlow) {
413+
if (actualParameterFlow != null && isSupportedJavaKind(declaredParamType.getStorageKind())) {
414+
if (declaredParamType.getStorageKind() == JavaKind.Object) {
415+
TypeFlow<?> initialParameterFlow = declaredParamType.getTypeFlow(this, true);
416+
initialParameterFlow.addUse(this, actualParameterFlow);
417+
} else {
418+
actualParameterFlow.addState(this, TypeState.anyPrimitiveState());
419+
}
420+
}
421+
}
422+
383423
public static PointsToAnalysisMethod assertPointsToAnalysisMethod(AnalysisMethod aMethod) {
384424
assert aMethod instanceof PointsToAnalysisMethod : "Only points-to analysis methods are supported";
385425
return ((PointsToAnalysisMethod) aMethod);
@@ -400,12 +440,7 @@ public AnalysisType addRootClass(AnalysisType type, boolean addFields, boolean a
400440
if (addFields) {
401441
field.registerAsAccessed("field of root class");
402442
}
403-
/*
404-
* For system classes any instantiated (sub)type of the declared field type can be
405-
* written to the field flow.
406-
*/
407-
TypeFlow<?> fieldDeclaredTypeFlow = field.getType().getTypeFlow(this, true);
408-
fieldDeclaredTypeFlow.addUse(this, type.getContextInsensitiveAnalysisObject().getInstanceFieldFlow(this, field, true));
443+
processRootField(type, field);
409444
}
410445
if (type.getSuperclass() != null) {
411446
addRootClass(type.getSuperclass(), addFields, addArrayClass);
@@ -424,16 +459,28 @@ public AnalysisType addRootField(Class<?> clazz, String fieldName) {
424459
AnalysisField field = (AnalysisField) javaField;
425460
if (field.getName().equals(fieldName)) {
426461
field.registerAsAccessed("root field");
462+
processRootField(type, field);
463+
return field.getType();
464+
}
465+
}
466+
throw shouldNotReachHere("field not found: " + fieldName);
467+
}
468+
469+
private void processRootField(AnalysisType type, AnalysisField field) {
470+
JavaKind storageKind = field.getStorageKind();
471+
if (isSupportedJavaKind(storageKind)) {
472+
var fieldFlow = type.getContextInsensitiveAnalysisObject().getInstanceFieldFlow(this, field, true);
473+
if (storageKind.isObject()) {
427474
/*
428475
* For system classes any instantiated (sub)type of the declared field type can be
429476
* written to the field flow.
430477
*/
431478
TypeFlow<?> fieldDeclaredTypeFlow = field.getType().getTypeFlow(this, true);
432-
fieldDeclaredTypeFlow.addUse(this, type.getContextInsensitiveAnalysisObject().getInstanceFieldFlow(this, field, true));
433-
return field.getType();
479+
fieldDeclaredTypeFlow.addUse(this, fieldFlow);
480+
} else {
481+
fieldFlow.addState(this, TypeState.anyPrimitiveState());
434482
}
435483
}
436-
throw shouldNotReachHere("field not found: " + fieldName);
437484
}
438485

439486
@SuppressWarnings({"try", "unused"})
@@ -444,8 +491,15 @@ public AnalysisType addRootStaticField(Class<?> clazz, String fieldName) {
444491
reflectField = clazz.getField(fieldName);
445492
AnalysisField field = metaAccess.lookupJavaField(reflectField);
446493
field.registerAsAccessed("static root field");
447-
TypeFlow<?> fieldFlow = field.getType().getTypeFlow(this, true);
448-
fieldFlow.addUse(this, field.getStaticFieldFlow());
494+
JavaKind storageKind = field.getStorageKind();
495+
if (isSupportedJavaKind(storageKind)) {
496+
if (storageKind.isObject()) {
497+
TypeFlow<?> fieldFlow = field.getType().getTypeFlow(this, true);
498+
fieldFlow.addUse(this, field.getStaticFieldFlow());
499+
} else {
500+
field.getStaticFieldFlow().addState(this, TypeState.anyPrimitiveState());
501+
}
502+
}
449503
return field.getType();
450504

451505
} catch (NoSuchFieldException e) {
@@ -457,6 +511,15 @@ public AnalysisType addRootStaticField(Class<?> clazz, String fieldName) {
457511
public void checkUserLimitations() {
458512
}
459513

514+
public boolean isSupportedJavaKind(JavaKind javaKind) {
515+
return javaKind == JavaKind.Object || (trackPrimitiveValues && javaKind.isNumericInteger());
516+
}
517+
518+
@Override
519+
public boolean trackPrimitiveValues() {
520+
return trackPrimitiveValues;
521+
}
522+
460523
public interface TypeFlowRunnable extends DebugContextRunnable {
461524
TypeFlow<?> getTypeFlow();
462525
}

substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/api/PointstoOptions.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@
3333

3434
public class PointstoOptions {
3535

36+
@Option(help = "Track primitive values using the infrastructure of points-to analysis.")//
37+
public static final OptionKey<Boolean> TrackPrimitiveValues = new OptionKey<>(false);
38+
3639
@Option(help = "Use experimental Reachability Analysis instead of points-to.")//
3740
public static final OptionKey<Boolean> UseExperimentalReachabilityAnalysis = new OptionKey<>(false);
3841

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
package com.oracle.graal.pointsto.flow;
26+
27+
import com.oracle.graal.pointsto.meta.AnalysisType;
28+
import com.oracle.graal.pointsto.typestate.TypeState;
29+
30+
import jdk.vm.ci.code.BytecodePosition;
31+
32+
/**
33+
* Produces AnyPrimitive state that leads to immediate saturation of all uses. Used to represent any
34+
* operation that is not modeled by the analysis.
35+
*/
36+
public final class AnyPrimitiveSourceTypeFlow extends TypeFlow<BytecodePosition> implements PrimitiveFlow {
37+
38+
public AnyPrimitiveSourceTypeFlow(BytecodePosition source, AnalysisType type) {
39+
super(source, type, TypeState.anyPrimitiveState());
40+
}
41+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
package com.oracle.graal.pointsto.flow;
26+
27+
import com.oracle.graal.pointsto.PointsToAnalysis;
28+
import com.oracle.graal.pointsto.meta.AnalysisType;
29+
import com.oracle.graal.pointsto.typestate.TypeState;
30+
31+
import jdk.vm.ci.code.BytecodePosition;
32+
33+
public class ConstantPrimitiveSourceTypeFlow extends TypeFlow<BytecodePosition> implements PrimitiveFlow {
34+
35+
public ConstantPrimitiveSourceTypeFlow(BytecodePosition source, AnalysisType type, long value) {
36+
super(source, type, TypeState.forPrimitiveConstant(value));
37+
}
38+
39+
public ConstantPrimitiveSourceTypeFlow(ConstantPrimitiveSourceTypeFlow original, MethodFlowsGraph methodFlows) {
40+
super(original, methodFlows, original.getState());
41+
}
42+
43+
@Override
44+
public TypeFlow<BytecodePosition> copy(PointsToAnalysis bb, MethodFlowsGraph methodFlows) {
45+
return new ConstantPrimitiveSourceTypeFlow(this, methodFlows);
46+
}
47+
}

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,16 @@ public FieldFilterTypeFlow(AnalysisField field) {
4646

4747
@Override
4848
public TypeState filter(PointsToAnalysis bb, TypeState update) {
49-
if (declaredType.equals(bb.getObjectType())) {
49+
if (isPrimitiveFlow) {
50+
if (!update.isPrimitive()) {
51+
/*
52+
* Sources for these inconsistent updates are unsafe flows, but unsafe accessed
53+
* primitive fields are saturated anyway, so we can return any primitive state.
54+
*/
55+
return TypeState.anyPrimitiveState();
56+
}
57+
return update;
58+
} else if (declaredType.equals(bb.getObjectType())) {
5059
/* No need to filter. */
5160
return update;
5261
} else {

0 commit comments

Comments
 (0)