Jlm
AliasAnalysisPrecisionEvaluator.cpp
Go to the documentation of this file.
1 /*
2  * Copyright 2025 HÃ¥vard Krogstie <krogstie.havard@gmail.com>
3  * See COPYING for terms of redistribution.
4  */
5 
6 #include <jlm/llvm/DotWriter.hpp>
9 #include <jlm/llvm/ir/Trace.hpp>
11 #include <jlm/rvsdg/Phi.hpp>
13 #include <jlm/util/GraphWriter.hpp>
14 
15 #include <fstream>
16 #include <map>
17 
18 namespace jlm::llvm::aa
19 {
20 
22 {
23  // The output from calling ToString on the AliasAnalysis
24  static constexpr auto PairwiseAliasAnalysisType_ = "PairwiseAliasAnalysisType";
25 
26  static constexpr auto LoadsConsideredClobbers_ = "LoadsConsideredClobbers";
27  static constexpr auto DeduplicatingPointers_ = "DeduplicatingPointers";
28  // The path of the file where per-function statistics are written, if enabled
29  static constexpr auto PerFunctionOutputFile_ = "PerFunctionOutputFile";
30  // The path of the file where the aliasing graph is written, if enabled
31  static constexpr auto AliasingGraphOutputFile_ = "AliasingGraphOutputFile";
32 
33  // The total number of clobbering operations considered by the precision evaluator.
34  // If deduplication is enabled, only unique clobbers are counted.
35  static constexpr auto ModuleNumClobbers_ = "ModuleNumClobbers";
36  // The rate of each response type, for the average clobbering operations. Should sum up to 1
37  static constexpr auto ClobberAverageNoAlias = "ClobberAverageNoAlias";
38  static constexpr auto ClobberAverageMayAlias = "ClobberAverageMayAlias";
39  static constexpr auto ClobberAverageMustAlias = "ClobberAverageMustAlias";
40  // The total number of alias query responses given, of each kind
41  static constexpr auto NumTotalNoAlias_ = "#TotalNoAlias";
42  static constexpr auto NumTotalMayAlias_ = "#TotalMayAlias";
43  static constexpr auto NumTotalMustAlias_ = "#TotalMustAlias";
44 
45  static constexpr auto PrecisionEvaluationTimer_ = "PrecisionEvaluationTimer";
46 
47 public:
48  ~PrecisionStatistics() override = default;
49 
50  explicit PrecisionStatistics(const util::FilePath & sourceFile)
51  : Statistics(Id::AliasAnalysisPrecisionEvaluation, sourceFile)
52  {}
53 
54  void
56  const AliasAnalysisPrecisionEvaluator & evaluator,
57  const AliasAnalysis & aliasAnalysis)
58  {
63  evaluator.AreLoadsConsideredClobbers() ? "true" : "false");
64  AddMeasurement(DeduplicatingPointers_, evaluator.IsDeduplicatingPointers() ? "true" : "false");
65  }
66 
67  void
69  {
71  }
72 
73  void
75  {
77  }
78 
79  void
81  {
83  }
84 
85  void
87  {
95  }
96 
97  static std::unique_ptr<PrecisionStatistics>
98  Create(const util::FilePath & sourceFile)
99  {
100  return std::make_unique<PrecisionStatistics>(sourceFile);
101  }
102 };
103 
105 
107 
108 void
109 AliasAnalysisPrecisionEvaluator::EvaluateAliasAnalysisClient(
110  const rvsdg::RvsdgModule & rvsdgModule,
111  AliasAnalysis & aliasAnalysis,
112  util::StatisticsCollector & statisticsCollector)
113 {
114  auto statistics = PrecisionStatistics::Create(rvsdgModule.SourceFilePath().value());
115 
116  // If precision evaluation statistics are not demanded, skip doing it
117  if (!statisticsCollector.IsDemanded(*statistics))
118  return;
119 
120  Context_ = Context{};
121 
122  // If creating an aliasing graph is enabled, initialize an empty graph
124  if (IsAliasingGraphEnabled())
125  {
126  Context_.AliasingGraph_ = &gw.CreateGraph();
127 
128  // Also emit the RVSDG to the graph writer
129  LlvmDotWriter writer;
130  writer.WriteGraphs(gw, rvsdgModule.Rvsdg().GetRootRegion(), true);
131  }
132 
133  // Do the pairwise alias queries
134  statistics->StartEvaluatingPrecision(*this, aliasAnalysis);
135  EvaluateAllFunctions(rvsdgModule.Rvsdg().GetRootRegion(), aliasAnalysis);
136  statistics->StopEvaluatingPrecision();
137 
138  // If an aliasing graph was constructed during the evaluation, print it out now
139  if (IsAliasingGraphEnabled())
140  {
141  auto out = statisticsCollector.createOutputFile("AliasingGraph.dot", true);
142  std::ofstream fd(out.path().to_str());
144  statistics->AddAliasingGraphOutputFile(out.path());
145  }
146 
147  // If precision metrics are demanded per function, create the output file
148  std::optional<util::FilePath> perFunctionOutputFile;
149  if (IsPerFunctionOutputEnabled())
150  {
151  perFunctionOutputFile =
152  statisticsCollector.createOutputFile("AAPrecisionEvaluation.log", true).path();
153  statistics->AddPerFunctionOutputFile(*perFunctionOutputFile);
154  }
155 
156  // Calculate total and average precision statistics
157  CalculateResults(perFunctionOutputFile, *statistics);
158 
159  statisticsCollector.CollectDemandedStatistics(std::move(statistics));
160 }
161 
162 void
164  const rvsdg::Region & region,
165  AliasAnalysis & aliasAnalysis)
166 {
167  for (auto & node : region.Nodes())
168  {
169  if (auto lambda = dynamic_cast<const rvsdg::LambdaNode *>(&node))
170  {
171  EvaluateFunction(*lambda, aliasAnalysis);
172  }
173  else if (auto phi = dynamic_cast<const rvsdg::PhiNode *>(&node))
174  {
175  EvaluateAllFunctions(*phi->subregion(), aliasAnalysis);
176  }
177  }
178 }
179 
180 void
182  const rvsdg::LambdaNode & function,
183  AliasAnalysis & aliasAnalysis)
184 {
185  // Starting a new function, reset previously collected pointer operations
186  Context_.PointerOperations.clear();
187 
188  // Collect all pointer uses and clobbers from this function
189  CollectPointersFromRegion(*function.subregion());
190 
191  // Pointers are normalized, to prevent pointers that trivially originate
192  // from the same output to be regarded as different.
194 
195  // If IsDeduplicatingPointers() is true, duplicate pointers are discarded
196  // Even if deduplicating is disabled, duplicates are still grouped, but only for performance.
198 
199  // Create a PrecisionInfo instance for this function
200  auto & precisionEvaluation = Context_.PerFunctionPrecision[&function];
201 
202  // Go over all pointer usages, find the ratio of clobbering points that may alias with it
203  for (size_t i = 0; i < Context_.PointerOperations.size(); i++)
204  {
205  auto [p1, s1, p1IsClobber, p1Multiplier] = Context_.PointerOperations[i];
206 
207  precisionEvaluation.NumOperations += p1Multiplier;
208 
209  if (!p1IsClobber)
210  continue;
211 
212  precisionEvaluation.NumClobberOperations += p1Multiplier;
213 
214  PrecisionInfo::ClobberInfo clobberInfo;
215  clobberInfo.Multiplier = p1Multiplier;
216 
217  // If p1 represents more than one concrete clobber operation,
218  // add the result of querying against all the other clobber operations
219  if (p1Multiplier > 1)
220  clobberInfo.NumMustAlias += p1Multiplier - 1;
221 
222  for (size_t j = 0; j < Context_.PointerOperations.size(); j++)
223  {
224  if (i == j)
225  continue;
226 
227  auto [p2, s2, p2IsClobber, p2Multiplier] = Context_.PointerOperations[j];
228 
229  auto response = aliasAnalysis.Query(*p1, s1, *p2, s2);
230 
231  // Queries should always be symmetric, so double check that in debug builds
232  // Note: LLVM's own alias analyses are not always symmetric, but ours should be
233  JLM_ASSERT(response == aliasAnalysis.Query(*p2, s2, *p1, s1));
234 
235  // Add edge to aliasing graph if dumping a graph of alias analysis response edges is requested
237  {
238  AddToAliasingGraph(*p1, s1, *p2, s2, response);
239  }
240 
241  if (response == AliasAnalysis::NoAlias)
242  clobberInfo.NumNoAlias += p2Multiplier;
243  else if (response == AliasAnalysis::MayAlias)
244  clobberInfo.NumMayAlias += p2Multiplier;
245  else if (response == AliasAnalysis::MustAlias)
246  clobberInfo.NumMustAlias += p2Multiplier;
247  else
248  JLM_UNREACHABLE("Unknown AliasAnalysis query response");
249  }
250 
251  precisionEvaluation.ClobberOperations.push_back(clobberInfo);
252  }
253 }
254 
255 void
257 {
258  for (auto & node : region.Nodes())
259  {
260  if (auto simpleNode = dynamic_cast<const rvsdg::SimpleNode *>(&node))
261  {
262  CollectPointersFromSimpleNode(*simpleNode);
263  }
264  else if (auto structuralNode = dynamic_cast<const rvsdg::StructuralNode *>(&node))
265  {
266  CollectPointersFromStructuralNode(*structuralNode);
267  }
268  else
269  {
270  JLM_UNREACHABLE("Unknown node type");
271  }
272  }
273 }
274 
275 void
277 {
278  if (const auto load = dynamic_cast<const LoadOperation *>(&node.GetOperation()))
279  {
280  // At the time of writing, these are the only loads we know about
281  JLM_ASSERT(is<LoadNonVolatileOperation>(*load) || is<LoadVolatileOperation>(*load));
282 
283  const auto size = GetTypeStoreSize(*load->GetLoadedType());
285  }
286  else if (auto store = dynamic_cast<const StoreOperation *>(&node.GetOperation()))
287  {
288  // At the time of writing, these are the only stores we know about
289  JLM_ASSERT(is<StoreNonVolatileOperation>(*store) || is<StoreVolatileOperation>(*store));
290 
291  const auto size = GetTypeStoreSize(store->GetStoredType());
292  CollectPointer(StoreOperation::AddressInput(node).origin(), size, true);
293  }
294 }
295 
296 void
298  const rvsdg::StructuralNode & node)
299 {
300  for (auto & subregion : node.Subregions())
301  {
302  CollectPointersFromRegion(subregion);
303  }
304 }
305 
306 void
308  const rvsdg::Output * value,
309  size_t size,
310  bool isClobber)
311 {
313  Context_.PointerOperations.push_back({ value, size, isClobber, 1 });
314 }
315 
316 void
318 {
319  for (size_t i = 0; i < Context_.PointerOperations.size(); i++)
320  {
321  auto & pointer = std::get<0>(Context_.PointerOperations[i]);
322  pointer = &llvm::traceOutput(*pointer);
323  }
324 }
325 
326 void
328 {
329  // Add up the multipliers of all (pointer, size, isClobber) values
330  std::map<std::tuple<const rvsdg::Output *, size_t, bool>, size_t> pointerOpMultipliers;
331 
332  for (const auto & [pointer, size, isClobber, multiplier] : Context_.PointerOperations)
333  {
334  pointerOpMultipliers[{ pointer, size, isClobber }] += multiplier;
335  }
336 
337  Context_.PointerOperations.clear();
338  for (const auto & [pointerOp, multiplier] : pointerOpMultipliers)
339  {
340  const auto [pointer, size, isClobber] = pointerOp;
341 
343  {
344  // If Deduplication is being done, set all multipliers to 1
345  // Also, if the operation exists both as a clobber and not a clobber, only add the former
346  if (!isClobber && pointerOpMultipliers.count({ pointer, size, true }) > 0)
347  continue;
348  Context_.PointerOperations.push_back({ pointer, size, isClobber, 1 });
349  }
350  else
351  {
352  // When deduplication is not enabled, use the sum of multipliers as the final multiplier
353  Context_.PointerOperations.push_back({ pointer, size, isClobber, multiplier });
354  }
355  }
356 }
357 
358 void
360  const rvsdg::Output & p1,
361  size_t s1,
362  const rvsdg::Output & p2,
363  size_t s2,
365 {
366  // Create a node associated with the given output
367  // but also attach it to the GraphElement that already represents it
368  const auto GetOrCreateAliasGraphNode = [&](const rvsdg::Output & p) -> util::graph::Node &
369  {
370  const auto element = Context_.AliasingGraph_->GetElementFromProgramObject(p);
371  const auto node = dynamic_cast<util::graph::Node *>(element);
372  if (node)
373  return *node;
374 
375  auto & newNode = Context_.AliasingGraph_->CreateNode();
376  auto existingElement = Context_.AliasingGraph_->GetWriter().GetElementFromProgramObject(p);
377  newNode.SetAttributeGraphElement("output", *existingElement);
378  newNode.SetProgramObject(p);
379  return newNode;
380  };
381 
382  auto & p1Node = GetOrCreateAliasGraphNode(p1);
383  auto & p2Node = GetOrCreateAliasGraphNode(p2);
384 
385  // Only create edges for MayAlias and MustAlias
386  std::optional<std::string> edgeColor;
387  if (response == AliasAnalysis::MayAlias)
388  edgeColor = util::graph::Colors::Purple;
389  else if (response == AliasAnalysis::MustAlias)
390  edgeColor = util::graph::Colors::Orange;
391 
392  if (edgeColor)
393  {
394  auto & edge = Context_.AliasingGraph_->CreateEdge(p1Node, p2Node, false);
395  edge.SetAttribute("s1", util::strfmt(s1));
396  edge.SetAttribute("s2", util::strfmt(s2));
397  edge.SetAttribute("color", *edgeColor);
398  }
399 }
400 
403  const std::vector<PrecisionInfo::ClobberInfo> & clobberInfos)
404 {
405  AggregatedClobberInfos result;
406 
407  for (auto & clobber : clobberInfos)
408  {
409  size_t total = clobber.NumNoAlias + clobber.NumMayAlias + clobber.NumMustAlias;
410 
411  // Avoid division by 0 by skipping clobbers that are alone in their function
412  if (total == 0)
413  continue;
414 
415  result.NumClobberOperations += clobber.Multiplier;
416 
417  result.ClobberAverageNoAlias +=
418  static_cast<double>(clobber.NumNoAlias) / total * clobber.Multiplier;
419  result.ClobberAverageMayAlias +=
420  static_cast<double>(clobber.NumMayAlias) / total * clobber.Multiplier;
421  result.ClobberAverageMustAlias +=
422  static_cast<double>(clobber.NumMustAlias) / total * clobber.Multiplier;
423  result.TotalNoAlias += clobber.NumNoAlias * clobber.Multiplier;
424  result.TotalMayAlias += clobber.NumMayAlias * clobber.Multiplier;
425  result.TotalMustAlias += clobber.NumMustAlias * clobber.Multiplier;
426  }
427 
428  if (result.NumClobberOperations > 0)
429  {
430  // Perform the final division to get the average across all clobbers
433  ;
435  }
436 
437  return result;
438 }
439 
440 void
442  const AggregatedClobberInfos & clobberInfos,
443  std::ostream & out)
444 {
445  if (clobberInfos.NumClobberOperations == 0)
446  {
447  out << "No clobber operations" << std::endl;
448  return;
449  }
450 
451  out << "Number of clobbering operations: " << clobberInfos.NumClobberOperations << std::endl;
452  out << "The average clobber has: " << (clobberInfos.ClobberAverageNoAlias * 100)
453  << " % NoAlias \t";
454  out << (clobberInfos.ClobberAverageMayAlias * 100) << " % MayAlias \t";
455  out << (clobberInfos.ClobberAverageMustAlias * 100) << " % MustAlias" << std::endl;
456  out << "Total responses: " << clobberInfos.TotalNoAlias << " NoAlias \t";
457  out << clobberInfos.TotalMayAlias << " MayAlias \t";
458  out << clobberInfos.TotalMustAlias << " MustAlias" << std::endl;
459  out << std::endl;
460 }
461 
462 void
464  std::optional<util::FilePath> perFunctionOutputFile,
465  PrecisionStatistics & statistics) const
466 {
467  // Adds up the total set of clobber operations in the entire module
468  std::vector<PrecisionInfo::ClobberInfo> allClobberInfos;
469 
470  // Write precision info about each function to an output file
471  std::ofstream out;
472  if (perFunctionOutputFile)
473  out.open(perFunctionOutputFile->to_str());
474 
475  for (auto [function, precision] : Context_.PerFunctionPrecision)
476  {
477  // Calculate and print information about the clobbers in this function
478  if (perFunctionOutputFile)
479  {
480  const auto aggregated = AggregateClobberInfos(precision.ClobberOperations);
481  out << function->GetOperation().debug_string() << " [";
482  out << precision.NumOperations << " pointer operations]:" << std::endl;
483  PrintAggregatedClobberInfos(aggregated, out);
484  }
485 
486  // Add the clobber operations to the list of all clobbers
487  for (auto & clobberInfo : precision.ClobberOperations)
488  allClobberInfos.push_back(clobberInfo);
489  }
490 
491  const auto aggregated = AggregateClobberInfos(allClobberInfos);
492 
493  if (perFunctionOutputFile)
494  {
495  out << "Module total:" << std::endl;
496  PrintAggregatedClobberInfos(aggregated, out);
497  out.close();
498  }
499 
500  statistics.AddPrecisionSummaryStatistics(aggregated);
501 }
502 
503 }
static rvsdg::Input & AddressInput(const rvsdg::Node &node) noexcept
Definition: Load.hpp:75
static rvsdg::Input & AddressInput(const rvsdg::Node &node) noexcept
Definition: Store.hpp:75
void StartEvaluatingPrecision(const AliasAnalysisPrecisionEvaluator &evaluator, const AliasAnalysis &aliasAnalysis)
static std::unique_ptr< PrecisionStatistics > Create(const util::FilePath &sourceFile)
static void PrintAggregatedClobberInfos(const AggregatedClobberInfos &clobberInfos, std::ostream &out)
static AggregatedClobberInfos AggregateClobberInfos(const std::vector< PrecisionInfo::ClobberInfo > &clobberInfos)
void EvaluateFunction(const rvsdg::LambdaNode &function, AliasAnalysis &aliasAnalysis)
void CalculateResults(std::optional< util::FilePath > perFunctionOutput, PrecisionStatistics &statistics) const
void CollectPointersFromStructuralNode(const rvsdg::StructuralNode &node)
void AddToAliasingGraph(const rvsdg::Output &p1, size_t s1, const rvsdg::Output &p2, size_t s2, AliasAnalysis::AliasQueryResponse response)
void CollectPointer(const rvsdg::Output *value, size_t size, bool isClobber)
void EvaluateAllFunctions(const rvsdg::Region &region, AliasAnalysis &aliasAnalysis)
virtual std::string ToString() const =0
virtual AliasQueryResponse Query(const rvsdg::Output &p1, size_t s1, const rvsdg::Output &p2, size_t s2)=0
util::graph::Graph & WriteGraphs(util::graph::Writer &writer, const Region &region, bool emitTypeGraph)
Definition: DotWriter.cpp:198
Lambda node.
Definition: lambda.hpp:83
A phi node represents the fixpoint of mutually recursive definitions.
Definition: Phi.hpp:46
Represent acyclic RVSDG subgraphs.
Definition: region.hpp:213
NodeRange Nodes() noexcept
Definition: region.hpp:328
const SimpleOperation & GetOperation() const noexcept override
Definition: simple-node.cpp:48
SubregionIteratorRange Subregions()
const std::string & to_str() const noexcept
Definition: file.hpp:275
Statistics Interface.
Definition: Statistics.hpp:31
util::Timer & GetTimer(const std::string &name)
Definition: Statistics.cpp:134
Statistics(const Statistics::Id &statisticsId, util::FilePath sourceFile)
Definition: Statistics.hpp:73
util::Timer & AddTimer(std::string name)
Definition: Statistics.cpp:155
void AddMeasurement(std::string name, T value)
Definition: Statistics.hpp:174
void start() noexcept
Definition: time.hpp:54
void stop() noexcept
Definition: time.hpp:67
void SetAttribute(const std::string &attribute, std::string value)
void SetAttributeGraphElement(const std::string &attribute, const GraphElement &element)
GraphElement * GetElementFromProgramObject(uintptr_t object) const
Edge & CreateEdge(Port &from, Port &to, bool directed)
GraphElement * GetElementFromProgramObject(uintptr_t object) const
void outputAllGraphs(std::ostream &out, OutputFormat format)
#define JLM_ASSERT(x)
Definition: common.hpp:16
#define JLM_UNREACHABLE(msg)
Definition: common.hpp:43
bool IsPointerCompatible(const rvsdg::Output &value)
rvsdg::Output & traceOutput(rvsdg::Output &output)
Definition: Trace.cpp:39
size_t GetTypeStoreSize(const rvsdg::Type &type)
Definition: types.cpp:386
const char *const Purple
const char *const Orange
static std::string strfmt(Args... args)
Definition: strfmt.hpp:35
std::unordered_map< const rvsdg::LambdaNode *, PrecisionInfo > PerFunctionPrecision
std::vector< std::tuple< const rvsdg::Output *, size_t, bool, size_t > > PointerOperations