Skip to content

Commit 3fdf22d

Browse files
committed
[GR-69548] Introduce -H:+PrintTypeStateMemoryFootprint
PullRequest: graal/22091
2 parents 3184ba5 + 43b7b86 commit 3fdf22d

File tree

7 files changed

+166
-21
lines changed

7 files changed

+166
-21
lines changed

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

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2012, 2023, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -113,6 +113,7 @@ public abstract class PointsToAnalysis extends AbstractAnalysisEngine {
113113

114114
protected final boolean trackTypeFlowInputs;
115115
protected final boolean reportAnalysisStatistics;
116+
protected final boolean reportTypeStateMemoryFootprint;
116117

117118
private ConcurrentMap<UnsafeLoadTypeFlow, Boolean> unsafeLoads;
118119
private ConcurrentMap<UnsafeStoreTypeFlow, Boolean> unsafeStores;
@@ -146,7 +147,8 @@ public PointsToAnalysis(OptionValues options, AnalysisUniverse universe, HostVM
146147

147148
trackTypeFlowInputs = PointstoOptions.TrackInputFlows.getValue(options);
148149
reportAnalysisStatistics = PointstoOptions.PrintPointsToStatistics.getValue(options);
149-
if (reportAnalysisStatistics) {
150+
reportTypeStateMemoryFootprint = PointstoOptions.PrintTypeStateMemoryFootprint.getValue(options);
151+
if (reportAnalysisStatistics || reportTypeStateMemoryFootprint) {
150152
PointsToStats.init(this);
151153
}
152154

@@ -201,6 +203,10 @@ public boolean reportAnalysisStatistics() {
201203
return reportAnalysisStatistics;
202204
}
203205

206+
public boolean reportTypeStateMemoryFootprint() {
207+
return reportTypeStateMemoryFootprint;
208+
}
209+
204210
public MethodTypeFlowBuilder createMethodTypeFlowBuilder(PointsToAnalysis bb, PointsToAnalysisMethod method, MethodFlowsGraph flowsGraph, MethodFlowsGraph.GraphKind graphKind) {
205211
return new MethodTypeFlowBuilder(bb, method, flowsGraph, graphKind);
206212
}
@@ -299,6 +305,7 @@ public void cleanupAfterAnalysis() {
299305
unsafeStores = null;
300306

301307
ConstantObjectsProfiler.constantTypes.clear();
308+
PointsToStats.cleanupAfterAnalysis();
302309
}
303310

304311
public AnalysisType lookup(JavaType type) {

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

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2017, 2017, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -31,6 +31,8 @@
3131

3232
import org.graalvm.collections.EconomicMap;
3333

34+
import com.oracle.graal.pointsto.typestate.TypeState;
35+
3436
import jdk.graal.compiler.options.Option;
3537
import jdk.graal.compiler.options.OptionKey;
3638

@@ -149,6 +151,16 @@ protected void onValueUpdate(EconomicMap<OptionKey<?>, Object> values, Boolean o
149151
@Option(help = "Report analysis statistics.")//
150152
public static final OptionKey<Boolean> PrintPointsToStatistics = new OptionKey<>(false);
151153

154+
/**
155+
* The {@link TypeState} memory footprint report enabled by this option is also generated as a
156+
* part of {@link #PrintPointsToStatistics}. However, running the full
157+
* {@link #PrintPointsToStatistics} is resource-intensive, so we expose
158+
* {@code PrintTypeStateMemoryFootprint} to allow computing just the footprint if the rest is
159+
* not required.
160+
*/
161+
@Option(help = "Report the memory footprint of TypeState objects used by the analysis.")//
162+
public static final OptionKey<Boolean> PrintTypeStateMemoryFootprint = new OptionKey<>(false);
163+
152164
@Option(help = "Path to the contents of the Inspect web server.")//
153165
public static final OptionKey<String> InspectServerContentPath = new OptionKey<>("inspect");
154166

substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/reports/AnalysisReporter.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/*
2-
* Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved.
3-
* Copyright (c) 2021, 2021, Alibaba Group Holding Limited. All rights reserved.
2+
* Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved.
3+
* Copyright (c) 2021, 2025, Alibaba Group Holding Limited. All rights reserved.
44
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
55
*
66
* This code is free software; you can redistribute it and/or modify it
@@ -29,14 +29,14 @@
2929
import java.util.List;
3030
import java.util.stream.Collectors;
3131

32-
import jdk.graal.compiler.options.OptionValues;
33-
3432
import com.oracle.graal.pointsto.BigBang;
3533
import com.oracle.graal.pointsto.api.PointstoOptions;
3634
import com.oracle.graal.pointsto.meta.AnalysisType;
3735
import com.oracle.graal.pointsto.typestate.PointsToStats;
3836
import com.oracle.graal.pointsto.typestate.TypeStateUtils;
3937

38+
import jdk.graal.compiler.options.OptionValues;
39+
4040
public class AnalysisReporter {
4141
public static void printAnalysisReports(String imageName, OptionValues options, String reportsPath, BigBang bb) {
4242
if (bb != null) {
@@ -55,7 +55,7 @@ public static void printAnalysisReports(String imageName, OptionValues options,
5555
AnalysisHeapHistogramPrinter.print(bb, reportsPath, baseImageName);
5656
}
5757

58-
if (PointstoOptions.PrintPointsToStatistics.getValue(options)) {
58+
if (PointstoOptions.PrintPointsToStatistics.getValue(options) || PointstoOptions.PrintTypeStateMemoryFootprint.getValue(options)) {
5959
PointsToStats.report(bb, baseImageName);
6060
}
6161

substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/MultiTypeState.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2013, 2021, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2013, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -33,7 +33,7 @@
3333
import com.oracle.graal.pointsto.meta.AnalysisType;
3434
import com.oracle.graal.pointsto.util.AnalysisError;
3535

36-
public class MultiTypeState extends TypeState {
36+
public non-sealed class MultiTypeState extends TypeState {
3737

3838
/**
3939
* Keep a bit set for types to easily answer queries like contains type or types count, and

substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/PointsToStats.java

Lines changed: 123 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2013, 2023, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2013, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -34,11 +34,13 @@
3434
import java.time.format.DateTimeFormatter;
3535
import java.util.Comparator;
3636
import java.util.List;
37+
import java.util.Map;
3738
import java.util.Map.Entry;
3839
import java.util.Objects;
3940
import java.util.concurrent.ConcurrentHashMap;
4041
import java.util.concurrent.CopyOnWriteArrayList;
4142
import java.util.concurrent.atomic.AtomicInteger;
43+
import java.util.concurrent.atomic.AtomicLong;
4244
import java.util.function.Consumer;
4345
import java.util.function.Function;
4446
import java.util.stream.Collectors;
@@ -72,9 +74,12 @@
7274
import com.oracle.graal.pointsto.flow.StoreFieldTypeFlow.StoreStaticFieldTypeFlow;
7375
import com.oracle.graal.pointsto.flow.TypeFlow;
7476
import com.oracle.graal.pointsto.flow.builder.TypeFlowBuilder;
77+
import com.oracle.graal.pointsto.flow.context.bytecode.ContextSensitiveMultiTypeState;
78+
import com.oracle.graal.pointsto.flow.context.bytecode.ContextSensitiveSingleTypeState;
7579
import com.oracle.graal.pointsto.meta.AnalysisField;
7680
import com.oracle.graal.pointsto.meta.AnalysisType;
7781
import com.oracle.graal.pointsto.util.AnalysisError;
82+
import com.oracle.graal.pointsto.util.GraalAccess;
7883
import com.oracle.svm.util.ClassUtil;
7984

8085
import jdk.graal.compiler.graph.NodeSourcePosition;
@@ -84,29 +89,46 @@
8489
import jdk.vm.ci.meta.JavaType;
8590
import jdk.vm.ci.meta.ResolvedJavaMethod;
8691

92+
/**
93+
* This class provides methods for collecting and reporting statistics about
94+
* {@link PointsToAnalysis}. It tracks various metrics such as {@link TypeState} memory footprint,
95+
* {@link TypeFlow} statistics, and union operation statistics. If the {@link TypeFlow} or
96+
* {@link TypeState} hierarchy changes, this class might have to be updated to reflect that.
97+
*
98+
* @see PointsToAnalysis
99+
* @see TypeFlow
100+
* @see TypeState
101+
*/
87102
public class PointsToStats {
88103

89104
static boolean reportStatistics;
105+
static boolean reportTypeStateMemoryFootPrint;
90106

91107
public static void init(PointsToAnalysis bb) {
108+
reportStatistics = bb.reportAnalysisStatistics();
109+
reportTypeStateMemoryFootPrint = bb.reportTypeStateMemoryFootprint();
92110
registerTypeState(bb, EmptyTypeState.SINGLETON);
93111
registerTypeState(bb, NullTypeState.SINGLETON);
94112
registerTypeState(bb, AnyPrimitiveTypeState.SINGLETON);
95113
PrimitiveConstantTypeState.registerCachedTypeStates(bb);
96-
reportStatistics = bb.reportAnalysisStatistics();
97114
}
98115

99116
public static void report(@SuppressWarnings("unused") BigBang bb, String reportNameRoot) {
100-
117+
assert reportStatistics || reportTypeStateMemoryFootPrint : "At least one of these options should be selected.";
101118
try {
102119
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss");
103120
String timeStamp = LocalDateTime.now().format(formatter);
104121
Path statsDirectory = Files.createDirectories(FileSystems.getDefault().getPath("svmbuild").resolve("stats"));
105122

106-
doReport(statsDirectory, reportNameRoot, "type state stats", timeStamp, PointsToStats::reportTypeStateStats);
107-
doReport(statsDirectory, reportNameRoot, "union operation stats", timeStamp, PointsToStats::reportUnionOpertationsStats);
108-
doReport(statsDirectory, reportNameRoot, "type flow stats", timeStamp, PointsToStats::reportTypeFlowStats);
109-
doReport(statsDirectory, reportNameRoot, "pruned type flow stats", timeStamp, PointsToStats::reportPrunedTypeFlows);
123+
/* Both report option include the footprint, so generate it unconditionally. */
124+
doReport(statsDirectory, reportNameRoot, "type state memory footprint", timeStamp, PointsToStats::reportTypeStateMemoryFootprint);
125+
if (reportStatistics) {
126+
/* The rest of reports should only be generated if reportStatistics was enabled. */
127+
doReport(statsDirectory, reportNameRoot, "detailed type state stats", timeStamp, PointsToStats::reportTypeStateStats);
128+
doReport(statsDirectory, reportNameRoot, "union operation stats", timeStamp, PointsToStats::reportUnionOpertationsStats);
129+
doReport(statsDirectory, reportNameRoot, "type flow stats", timeStamp, PointsToStats::reportTypeFlowStats);
130+
doReport(statsDirectory, reportNameRoot, "pruned type flow stats", timeStamp, PointsToStats::reportPrunedTypeFlows);
131+
}
110132

111133
} catch (IOException e) {
112134
throw JVMCIError.shouldNotReachHere(e);
@@ -316,7 +338,25 @@ private static void reportTypeFlowStats(BufferedWriter out) {
316338
private static final AtomicInteger nextStateId = new AtomicInteger();
317339
private static ConcurrentHashMap<TypeState, AtomicInteger> typeStateStats = new ConcurrentHashMap<>();
318340

341+
/**
342+
* Contains the count and total size of the given TypeState class.
343+
*
344+
* @see #typeStateFootprint
345+
* @see #reportTypeStateMemoryFootprint
346+
* @see #registerTypeStateSize
347+
*/
348+
private static final class TypeStateMemoryStats {
349+
AtomicInteger frequency = new AtomicInteger();
350+
AtomicLong size = new AtomicLong();
351+
}
352+
353+
private static Map<Class<? extends TypeState>, TypeStateMemoryStats> typeStateFootprint = new ConcurrentHashMap<>();
354+
319355
public static <T extends TypeState> T registerTypeState(PointsToAnalysis bb, T state) {
356+
if (bb.reportAnalysisStatistics() || bb.reportTypeStateMemoryFootprint()) {
357+
/* TypeState memory footprint is measured in both cases. */
358+
registerTypeStateSize(state);
359+
}
320360

321361
if (!bb.reportAnalysisStatistics()) {
322362
return state;
@@ -343,6 +383,77 @@ private static int typesCount(TypeState state) {
343383
return state.typesCount();
344384
}
345385

386+
/**
387+
* This method is used to track the memory footprint of {@link TypeState} classes. It updates
388+
* the frequency and total size of the given {@link TypeState} class in the
389+
* {@link #typeStateFootprint} map.
390+
*
391+
* @param <T> the type of the {@link TypeState} instance
392+
* @param state the {@link TypeState} instance to register
393+
*/
394+
private static <T extends TypeState> void registerTypeStateSize(T state) {
395+
var stats = typeStateFootprint.computeIfAbsent(state.getClass(), __ -> new TypeStateMemoryStats());
396+
stats.frequency.incrementAndGet();
397+
stats.size.addAndGet(getTypeStateMemorySize(state));
398+
}
399+
400+
/**
401+
* In most cases, we use just the shallow size of the object as obtained from the heap dump.
402+
* However, {@link MultiTypeState} is an exception, because it represents a set of values, so we
403+
* consider the size of the underlying collection as well.
404+
*/
405+
private static long getTypeStateMemorySize(TypeState typeState) {
406+
var shallowSize = getObjectSize(typeState);
407+
if (!(typeState instanceof MultiTypeState multi)) {
408+
return shallowSize;
409+
}
410+
var bitsetSize = getObjectSize(multi.typesBitSet);
411+
var wordArraySize = getObjectSize(TypeStateUtils.extractBitSetField(multi.typesBitSet));
412+
return shallowSize + bitsetSize + wordArraySize;
413+
}
414+
415+
private static long getObjectSize(Object object) {
416+
return GraalAccess.getOriginalProviders().getMetaAccess().getMemorySize(GraalAccess.getOriginalProviders().getSnippetReflection().forObject(object));
417+
}
418+
419+
/**
420+
* Reports the memory footprint of {@link TypeState} classes used by {@link PointsToAnalysis}.
421+
* <p>
422+
* This method writes a report to the provided {@link BufferedWriter} containing the frequency
423+
* and total size of each allocated {@link TypeState} class.
424+
* <p>
425+
* The report includes the following information:
426+
* <ul>
427+
* <li>Type: the class name of the {@link TypeState}</li>
428+
* <li>Frequency: the number of instances of the {@link TypeState} class</li>
429+
* <li>Total Size: the total memory size of all instances of the {@link TypeState} class</li>
430+
* </ul>
431+
* <p>
432+
* The report is written in a tabular format with the columns "Type", "Frequency", and "Total
433+
* Size".
434+
*
435+
* @param out the {@link BufferedWriter} to write the report to
436+
*/
437+
private static void reportTypeStateMemoryFootprint(BufferedWriter out) {
438+
doWrite(out, String.format("%30s\t%15s\t%15s%n", "Type", "Frequency", "Total Size"));
439+
/* Use explicit order for the final report. */
440+
var typeStateOrder = List.of(EmptyTypeState.class, NullTypeState.class, PrimitiveConstantTypeState.class, AnyPrimitiveTypeState.class, SingleTypeState.class,
441+
ContextSensitiveSingleTypeState.class, ConstantTypeState.class,
442+
MultiTypeState.class, ContextSensitiveMultiTypeState.class);
443+
var totalFreq = 0L;
444+
var totalSize = 0L;
445+
for (var typeStateClass : typeStateOrder) {
446+
var stats = typeStateFootprint.remove(typeStateClass);
447+
if (stats != null) {
448+
doWrite(out, String.format("%30s\t%15d\t%15d%n", ClassUtil.getUnqualifiedName(typeStateClass), stats.frequency.get(), stats.size.get()));
449+
totalFreq += stats.frequency.get();
450+
totalSize += stats.size.get();
451+
}
452+
}
453+
AnalysisError.guarantee(typeStateFootprint.isEmpty(), "Missing elements in the typeStateOrder list: %s, please update it.", typeStateFootprint.keySet());
454+
doWrite(out, String.format("%30s\t%15d\t%15d%n", "TOTAL", totalFreq, totalSize));
455+
}
456+
346457
private static void reportTypeStateStats(BufferedWriter out) {
347458

348459
doWrite(out, String.format("%10s\t%10s\t%10s\t%10s\t%10s%n", "Id", "Frequency", "Types#", "Object#", "Types"));
@@ -391,6 +502,11 @@ private static void reportUnionOpertationsStats(BufferedWriter out) {
391502
});
392503
}
393504

505+
public static void cleanupAfterAnalysis() {
506+
typeStateStats = null;
507+
typeStateFootprint = null;
508+
}
509+
394510
static class UnionOperation {
395511
int state1Id;
396512
int state2Id;

substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/SingleTypeState.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2013, 2017, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2013, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -32,7 +32,7 @@
3232
import com.oracle.graal.pointsto.flow.context.object.AnalysisObject;
3333
import com.oracle.graal.pointsto.meta.AnalysisType;
3434

35-
public class SingleTypeState extends TypeState {
35+
public non-sealed class SingleTypeState extends TypeState {
3636
protected final AnalysisType type;
3737
/** Can this type state represent the null value? */
3838
protected final boolean canBeNull;

substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/TypeState.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2013, 2023, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2013, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -33,13 +33,23 @@
3333
import com.oracle.graal.pointsto.BigBang;
3434
import com.oracle.graal.pointsto.PointsToAnalysis;
3535
import com.oracle.graal.pointsto.flow.PrimitiveComparison;
36+
import com.oracle.graal.pointsto.flow.TypeFlow;
3637
import com.oracle.graal.pointsto.flow.context.object.AnalysisObject;
3738
import com.oracle.graal.pointsto.meta.AnalysisType;
3839

3940
import jdk.vm.ci.meta.JavaConstant;
4041
import jdk.vm.ci.meta.JavaKind;
4142

42-
public abstract class TypeState {
43+
/**
44+
* This class and its subclasses represent the sets of objects/types/primitive values propagated
45+
* through {@link TypeFlow} nodes during the run of {@link PointsToAnalysis}. If the
46+
* {@link TypeState} hierarchy is changed, {@link PointsToStats} might have to be updated to reflect
47+
* that.
48+
*
49+
* @see TypeFlow
50+
* @see PointsToStats
51+
*/
52+
public abstract sealed class TypeState permits EmptyTypeState, NullTypeState, PrimitiveTypeState, SingleTypeState, MultiTypeState {
4353

4454
/** Get the number of types. */
4555
public abstract int typesCount();

0 commit comments

Comments
 (0)