Jlm
Loading...
Searching...
No Matches
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
11#include <jlm/rvsdg/Phi.hpp>
14
15#include <fstream>
16#include <map>
17
18namespace 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
47public:
48 ~PrecisionStatistics() override = default;
49
50 explicit PrecisionStatistics(const util::FilePath & sourceFile)
52 {}
53
54 void
66
67 void
72
73 void
78
79 void
84
85 void
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
108void
109AliasAnalysisPrecisionEvaluator::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
162void
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
180void
182 const rvsdg::LambdaNode & function,
183 AliasAnalysis & aliasAnalysis)
184{
185 // Starting a new function, reset previously collected pointer operations
187
188 // Collect all pointer uses and clobbers from this function
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
255void
257{
258 for (auto & node : region.Nodes())
259 {
260 if (auto simpleNode = dynamic_cast<const rvsdg::SimpleNode *>(&node))
261 {
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
275void
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
296void
298 const rvsdg::StructuralNode & node)
299{
300 for (auto & subregion : node.Subregions())
301 {
302 CollectPointersFromRegion(subregion);
303 }
304}
305
306void
308 const rvsdg::Output * value,
309 size_t size,
310 bool isClobber)
311{
313 Context_.PointerOperations.push_back({ value, size, isClobber, 1 });
314}
315
316void
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
326void
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
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
358void
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();
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{
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;
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
440void
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
462void
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 jlm::util::StatisticsCollector statisticsCollector
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)
rvsdg::Region * subregion() const noexcept
Definition lambda.hpp:138
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:375
const SimpleOperation & GetOperation() const noexcept override
SubregionIteratorRange Subregions()
const std::string & to_str() const noexcept
Definition file.hpp:275
const FilePath & path() const noexcept
Definition file.hpp:394
bool IsDemanded(Statistics::Id id) const noexcept
void CollectDemandedStatistics(std::unique_ptr< Statistics > statistics)
File createOutputFile(std::string fileNameSuffix, bool includeCount=false)
Statistics Interface.
util::Timer & GetTimer(const std::string &name)
Statistics(const Statistics::Id &statisticsId, util::FilePath sourceFile)
util::Timer & AddTimer(std::string name)
void AddMeasurement(std::string name, T value)
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, const rvsdg::Region *withinRegion)
Definition Trace.cpp:62
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