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
3434import java .time .format .DateTimeFormatter ;
3535import java .util .Comparator ;
3636import java .util .List ;
37+ import java .util .Map ;
3738import java .util .Map .Entry ;
3839import java .util .Objects ;
3940import java .util .concurrent .ConcurrentHashMap ;
4041import java .util .concurrent .CopyOnWriteArrayList ;
4142import java .util .concurrent .atomic .AtomicInteger ;
43+ import java .util .concurrent .atomic .AtomicLong ;
4244import java .util .function .Consumer ;
4345import java .util .function .Function ;
4446import java .util .stream .Collectors ;
7274import com .oracle .graal .pointsto .flow .StoreFieldTypeFlow .StoreStaticFieldTypeFlow ;
7375import com .oracle .graal .pointsto .flow .TypeFlow ;
7476import 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 ;
7579import com .oracle .graal .pointsto .meta .AnalysisField ;
7680import com .oracle .graal .pointsto .meta .AnalysisType ;
7781import com .oracle .graal .pointsto .util .AnalysisError ;
82+ import com .oracle .graal .pointsto .util .GraalAccess ;
7883import com .oracle .svm .util .ClassUtil ;
7984
8085import jdk .graal .compiler .graph .NodeSourcePosition ;
8489import jdk .vm .ci .meta .JavaType ;
8590import 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+ */
87102public 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 ;
0 commit comments