Jlm
Loading...
Searching...
No Matches
AggregateAllocaSplitting.cpp
Go to the documentation of this file.
1/*
2 * Copyright 2026 Nico Reißmann <nico.reissmann@gmail.com>
3 * See COPYING for terms of redistribution.
4 */
5
12#include <jlm/llvm/ir/Trace.hpp>
14#include <jlm/rvsdg/delta.hpp>
15#include <jlm/rvsdg/gamma.hpp>
16#include <jlm/rvsdg/lambda.hpp>
18#include <jlm/rvsdg/Phi.hpp>
21#include <jlm/rvsdg/theta.hpp>
23
24#include <deque>
25#include <vector>
26
27namespace jlm::llvm
28{
29
31{
32 const char * numAggregateAllocaNodesLabel_ = "#AggregateAllocaNodes";
33 const char * numAggregateStructAllocaNodesLabel_ = "#AggregateStructAllocaNodes";
34 const char * numSplitableTypeAggregateAllocaNodesLabel_ = "#SplitableTypeAggregateAllocaNodes";
35 const char * numSplitAggregateAllocaNodesLabel_ = "#SplitAggregateAllocaNodes";
36 const char * aggregateAllocaSplittingTimerLabel_ = "AggregateAllocaSplittingTime";
37
38public:
39 ~Statistics() noexcept override = default;
40
41 explicit Statistics(util::FilePath filePath)
42 : util::Statistics(Id::AggregateAllocaSplitting, std::move(filePath))
43 {}
44
45 void
50
51 void
53 const size_t numAggregateAllocaNodes,
54 const size_t numAggregateStructAllocaNodes,
55 const size_t numSplitableTypeAggregateAllocaNodes,
56 const size_t numSplitAggregateAllocaNodes)
57 {
59 AddMeasurement(numAggregateAllocaNodesLabel_, numAggregateAllocaNodes);
60 AddMeasurement(numAggregateStructAllocaNodesLabel_, numAggregateStructAllocaNodes);
63 numSplitableTypeAggregateAllocaNodes);
64 AddMeasurement(numSplitAggregateAllocaNodesLabel_, numSplitAggregateAllocaNodes);
65 }
66
67 static std::unique_ptr<Statistics>
69 {
70 return std::make_unique<Statistics>(std::move(filePath));
71 }
72};
73
81
91
93
97
98bool
100{
101 // FIXME: We currently only look at alloca nodes with a struct type. We might be able
102 // to do something for alloca nodes with array types as well.
103 const auto structType = dynamic_cast<const StructType *>(&type);
104 if (!structType)
105 return false;
106
107 for (const auto & elementType : structType->elementTypes())
108 {
109 if (IsAggregateType(*elementType))
110 {
111 return isSplitableType(*elementType);
112 }
113 }
114
115 return true;
116}
117
118std::optional<AggregateAllocaSplitting::AllocaTraceInfo>
120{
121 [[maybe_unused]] auto allocaOperation =
122 dynamic_cast<const AllocaOperation *>(&allocaNode.GetOperation());
123 JLM_ASSERT(allocaOperation && isSplitableType(*allocaOperation->allocatedType()));
124
125 auto & address = AllocaOperation::getPointerOutput(allocaNode);
126 auto & count = AllocaOperation::getCountInput(allocaNode);
127
128 const auto countOpt = tryGetConstantSignedInteger(*count.origin());
129 if (!countOpt.has_value() || countOpt.value() != 1)
130 {
131 // FIXME: Handle AllocaOperation nodes with a count unequal to 1.
132 return std::nullopt;
133 }
134
135 bool isSplitable = true;
136 AllocaTraceInfo allocaTraceInfo(allocaNode);
137
139 std::deque<rvsdg::Output *> toVisit{ &address };
140 auto addToVisitSet = [&](rvsdg::Output & output)
141 {
142 if (!seen.Contains(&output))
143 {
144 toVisit.push_back(&output);
145 }
146 seen.insert(&output);
147 };
148 auto removeFromVisitSet = [&]()
149 {
150 const auto output = toVisit.front();
151 toVisit.pop_front();
152 return output;
153 };
154
155 while (!toVisit.empty() && isSplitable)
156 {
157 const auto currentOutput = removeFromVisitSet();
158
159 for (auto & user : currentOutput->Users())
160 {
161 if (!isSplitable)
162 {
163 // Stop handling users if the previous user was already not splitable
164 break;
165 }
166
167 if (auto userRegion = rvsdg::TryGetOwnerRegion(user))
168 {
169 // We should never have an alloca connected to a graph export
170 JLM_ASSERT(userRegion->node());
171
173 *userRegion->node(),
174 [&](const rvsdg::GammaNode & gammaNode)
175 {
176 auto & gammaOutput = gammaNode.mapBranchResultToOutput(user);
177 addToVisitSet(gammaOutput);
178 return true;
179 },
180 [&](const rvsdg::ThetaNode & thetaNode)
181 {
182 const auto loopVar = thetaNode.MapPostLoopVar(user);
183 addToVisitSet(*loopVar.pre);
184 addToVisitSet(*loopVar.output);
185 return true;
186 },
187 [&](const rvsdg::LambdaNode &)
188 {
189 return false;
190 },
191 [&]()
192 {
193 throw std::logic_error(util::strfmt(
194 "Unhandled owner region node type: ",
195 userRegion->node()->DebugString()));
196 // Silence compiler
197 return false;
198 });
199 }
200 else if (auto userNode = rvsdg::TryGetOwnerNode<rvsdg::Node>(user))
201 {
203 *userNode,
204 [&](rvsdg::GammaNode & gammaNode)
205 {
206 auto roleVar = gammaNode.MapInput(user);
207 if (auto entryVar = std::get_if<rvsdg::GammaNode::EntryVar>(&roleVar))
208 {
209 for (auto argument : entryVar->branchArgument)
210 {
211 addToVisitSet(*argument);
212 }
213 }
214 else
215 {
216 throw std::logic_error(util::strfmt("Unhandled role variable."));
217 }
218
219 return true;
220 },
221 [&](rvsdg::ThetaNode & thetaNode)
222 {
223 const auto loopVar = thetaNode.MapInputLoopVar(user);
224 addToVisitSet(*loopVar.pre);
225 return true;
226 },
227 [&](rvsdg::SimpleNode & simpleNode)
228 {
229 auto & operation = simpleNode.GetOperation();
231 operation,
232 [&](const GetElementPtrOperation &)
233 {
234 JLM_ASSERT(userNode->input(0) == &user);
236 allocaTraceInfo.allocaConsumers.push_back(&simpleNode);
237 return true;
238 },
239 [&]()
240 {
241 return false;
242 });
243 },
244 [&]()
245 {
246 throw std::logic_error(
247 util::strfmt("Unhandled node type: ", userNode->DebugString()));
248 // Silence compiler
249 return false;
250 });
251 }
252 else
253 {
254 throw std::logic_error("Unhandled owner type");
255 }
256 }
257 }
258
259 if (!isSplitable)
260 return std::nullopt;
261
262 for (const auto allocaConsumer : allocaTraceInfo.allocaConsumers)
263 {
264 if (!checkGetElementPtrUsers(*allocaConsumer))
265 return std::nullopt;
266 }
267
268 return std::make_optional(allocaTraceInfo);
269}
270
271bool
273{
274 [[maybe_unused]] auto gepOperation =
275 dynamic_cast<const GetElementPtrOperation *>(&gepNode.GetOperation());
276 auto & address = *gepNode.output(0);
277
278 bool hasOnlyLoadsAndStores = true;
279
281 std::deque<rvsdg::Output *> toVisit{ &address };
282 auto addToVisitSet = [&](rvsdg::Output & output)
283 {
284 if (!seen.Contains(&output))
285 {
286 toVisit.push_back(&output);
287 }
288 seen.insert(&output);
289 };
290 auto removeFromVisitSet = [&]()
291 {
292 const auto output = toVisit.front();
293 toVisit.pop_front();
294 return output;
295 };
296
297 while (!toVisit.empty() && hasOnlyLoadsAndStores)
298 {
299 const auto currentOutput = removeFromVisitSet();
300 for (auto & user : currentOutput->Users())
301 {
302 if (!hasOnlyLoadsAndStores)
303 {
304 // Stop handling users if the previous user was already not a load or store
305 break;
306 }
307
308 if (auto userRegion = rvsdg::TryGetOwnerRegion(user))
309 {
310 // We should never have a gep node connected to a graph export
311 JLM_ASSERT(userRegion->node());
312
313 hasOnlyLoadsAndStores = rvsdg::MatchTypeWithDefault(
314 *userRegion->node(),
315 [&](const rvsdg::GammaNode & gammaNode)
316 {
317 auto & gammaOutput = gammaNode.mapBranchResultToOutput(user);
318 addToVisitSet(gammaOutput);
319 return true;
320 },
321 [&](const rvsdg::ThetaNode & thetaNode)
322 {
323 const auto loopVar = thetaNode.MapPostLoopVar(user);
324 addToVisitSet(*loopVar.pre);
325 addToVisitSet(*loopVar.output);
326 return true;
327 },
328 [&](const rvsdg::LambdaNode &)
329 {
330 return false;
331 },
332 [&]()
333 {
334 throw std::logic_error(util::strfmt(
335 "Unhandled owner region node type: ",
336 userRegion->node()->DebugString()));
337 // Silence compiler
338 return false;
339 });
340 }
341 else if (auto userNode = rvsdg::TryGetOwnerNode<rvsdg::Node>(user))
342 {
343 hasOnlyLoadsAndStores = rvsdg::MatchTypeWithDefault(
344 *userNode,
345 [&](const rvsdg::GammaNode & gammaNode)
346 {
347 auto roleVar = gammaNode.MapInput(user);
348 if (auto entryVar = std::get_if<rvsdg::GammaNode::EntryVar>(&roleVar))
349 {
350 for (auto argument : entryVar->branchArgument)
351 {
352 addToVisitSet(*argument);
353 }
354 }
355 else
356 {
357 throw std::logic_error(util::strfmt("Unhandled role variable."));
358 }
359
360 return true;
361 },
362 [&](const rvsdg::ThetaNode & thetaNode)
363 {
364 const auto loopVar = thetaNode.MapInputLoopVar(user);
365 addToVisitSet(*loopVar.pre);
366 return true;
367 },
368 [&](const rvsdg::SimpleNode & simpleNode)
369 {
370 auto & operation = simpleNode.GetOperation();
372 operation,
373 [&](const LoadOperation &)
374 {
375 return true;
376 },
377 [&](const StoreOperation &)
378 {
379 if (&user != &StoreOperation::AddressInput(simpleNode))
380 return false;
381
382 return true;
383 },
384 [&](const IOBarrierOperation &)
385 {
386 addToVisitSet(*simpleNode.output(0));
387 return true;
388 },
389 [&]()
390 {
391 return false;
392 });
393 },
394 [&]()
395 {
396 throw std::logic_error(
397 util::strfmt("Unhandled node type: ", userNode->DebugString()));
398 // Silence compiler
399 return false;
400 });
401 }
402 else
403 {
404 throw std::logic_error("Unhandled owner type");
405 }
406 }
407 }
408
409 return hasOnlyLoadsAndStores;
410}
411
412std::vector<AggregateAllocaSplitting::AllocaTraceInfo>
414{
415 std::function<void(rvsdg::Region &, std::vector<AllocaTraceInfo> &)> findAllocaNodes =
416 [&](rvsdg::Region & region, std::vector<AllocaTraceInfo> & traceInfo)
417 {
418 for (auto & node : region.Nodes())
419 {
420 MatchTypeWithDefault(
421 node,
422 [&](rvsdg::GammaNode & gammaNode)
423 {
424 for (auto & subregion : gammaNode.Subregions())
425 findAllocaNodes(subregion, traceInfo);
426 },
427 [&](rvsdg::ThetaNode & thetaNode)
428 {
429 findAllocaNodes(*thetaNode.subregion(), traceInfo);
430 },
431 [&](rvsdg::LambdaNode & lambdaNode)
432 {
433 findAllocaNodes(*lambdaNode.subregion(), traceInfo);
434 },
435 [&](rvsdg::PhiNode & phiNode)
436 {
437 findAllocaNodes(*phiNode.subregion(), traceInfo);
438 },
439 [&](rvsdg::DeltaNode &)
440 {
441 // Nothing needs to be done
442 },
443 [&](rvsdg::SimpleNode & simpleNode)
444 {
445 const auto allocaOperation =
446 dynamic_cast<const AllocaOperation *>(&simpleNode.GetOperation());
447 if (!allocaOperation)
448 return;
449
450 auto & allocaType = *allocaOperation->allocatedType();
451 if (is<StructType>(allocaType))
452 {
453 context_->numAggregateStructAllocaNodes++;
454 context_->numAggregateAllocaNodes++;
455 }
456 else if (IsAggregateType(allocaType))
457 {
458 context_->numAggregateAllocaNodes++;
459 }
460
461 if (isSplitableType(*allocaOperation->allocatedType()))
462 {
463 context_->numSplitableTypeAggregateAllocaNodes++;
464 if (auto allocaTraceInfo = isSplitable(simpleNode))
465 {
466 traceInfo.emplace_back(*allocaTraceInfo);
467 }
468 }
469 },
470 [&]()
471 {
472 throw std::logic_error("Unhandled node type.");
473 });
474 }
475 };
476
477 std::vector<AllocaTraceInfo> traceInfo;
478 findAllocaNodes(region, traceInfo);
479 return traceInfo;
480}
481
483{
484 size_t
485 operator()(const std::vector<uint64_t> & v) const
486 {
487 std::size_t hash = 0;
488 for (auto & index : v)
489 {
490 hash = util::CombineHashes(hash, std::hash<uint64_t>()(index));
491 }
492 return hash;
493 }
494};
495
496using VectorNodeHashMap = std::unordered_map<std::vector<uint64_t>, rvsdg::Node *, VectorHash>;
497
500{
501 const auto allocaOperation =
502 util::assertedCast<const AllocaOperation>(&allocaNode.GetOperation());
503 auto & allocaType = *util::assertedCast<const StructType>(allocaOperation->allocatedType().get());
504 const auto & countInput = AllocaOperation::getCountInput(allocaNode);
505 const auto alignment = allocaOperation->alignment();
506
507 std::function<void(const StructType &, VectorNodeHashMap &, std::vector<uint64_t> &)>
508 createAllocaNodes = [&](const StructType & structType,
509 VectorNodeHashMap & allocaNodes,
510 std::vector<uint64_t> & indices)
511 {
512 size_t index = 0;
513 for (const auto & elementType : structType.elementTypes())
514 {
515 indices.push_back(index++);
516 if (auto structType = std::dynamic_pointer_cast<const StructType>(elementType))
517 {
518 createAllocaNodes(*structType, allocaNodes, indices);
519 }
520 else
521 {
522 auto & elementAlloca =
523 AllocaOperation::createNode(elementType, *countInput.origin(), alignment);
524
525 allocaNodes[indices] = &elementAlloca;
526 }
527
528 indices.pop_back();
529 }
530 };
531
532 VectorNodeHashMap allocaNodes;
533 std::vector<uint64_t> indices(1, 0);
534 createAllocaNodes(allocaType, allocaNodes, indices);
535 return allocaNodes;
536}
537
538void
540{
541 auto & allocaNode = *allocaTraceInfo.allocaNode;
542 const auto allocaOperation = dynamic_cast<const AllocaOperation *>(&allocaNode.GetOperation());
543 JLM_ASSERT(allocaOperation && isSplitableType(*allocaOperation->allocatedType()));
544
545 std::vector<rvsdg::Output *> allocaMemoryStates;
546 auto elementAllocaMap = createElementAllocaNodes(allocaNode);
547 for (auto [_, elementAllocaNode] : elementAllocaMap)
548 {
549 allocaMemoryStates.push_back(&AllocaOperation::getMemoryStateOutput(*elementAllocaNode));
550 }
551
552 // Replace alloca node's memory state output
553 const auto memoryState = MemoryStateMergeOperation::Create(allocaMemoryStates);
555
556 // Replace alloca node consumers
557 for (auto allocaConsumer : allocaTraceInfo.allocaConsumers)
558 {
560 allocaConsumer->GetOperation(),
561 [&](const GetElementPtrOperation &)
562 {
563 JLM_ASSERT(GetElementPtrOperation::numIndices(*allocaConsumer) >= 2);
564 auto & consumerRegion = *allocaConsumer->region();
565 const auto gepConstant =
566 GetElementPtrOperation::tryGetAsConstant(*allocaConsumer).value();
567 JLM_ASSERT(gepConstant.indices[0] == 0);
568
569 auto elementAlloca = elementAllocaMap.at(gepConstant.indices);
570 // FIXME: Introduce caching of routed values to avoid duplicated routing.
571 auto & routedAddress = rvsdg::RouteToRegion(
572 AllocaOperation::getPointerOutput(*elementAlloca),
573 consumerRegion);
574 allocaConsumer->output(0)->divert_users(&routedAddress);
575 },
576 [&]()
577 {
578 throw std::logic_error(
579 util::strfmt("Unhandled node type: ", allocaConsumer->DebugString()));
580 });
581 }
582}
583
584void
586{
587 const auto traceInfo = findSplitableAllocaNodes(rvsdgModule.Rvsdg().GetRootRegion());
588 for (const auto & allocaTraceInfo : traceInfo)
589 {
590 splitAllocaNode(allocaTraceInfo);
591 context_->numSplitAggregateAllocaNodes++;
592 }
593
594 // Remove all nodes that became dead throughout the transformation
595 rvsdgModule.Rvsdg().PruneNodes();
596}
597
598void
600 rvsdg::RvsdgModule & module,
602{
603 context_ = std::make_unique<Context>();
604 auto statistics = Statistics::create(module.SourceFilePath().value());
605
606 statistics->start();
607 splitAllocaNodes(module);
608 statistics->stop(
609 context_->numAggregateAllocaNodes,
610 context_->numAggregateStructAllocaNodes,
611 context_->numSplitableTypeAggregateAllocaNodes,
612 context_->numSplitAggregateAllocaNodes);
613
614 statisticsCollector.CollectDemandedStatistics(std::move(statistics));
615
616 // Discard internal state to free up memory after we are done
617 context_.reset();
618}
619
620}
static jlm::util::StatisticsCollector statisticsCollector
static std::unique_ptr< Statistics > create(util::FilePath filePath)
void stop(const size_t numAggregateAllocaNodes, const size_t numAggregateStructAllocaNodes, const size_t numSplitableTypeAggregateAllocaNodes, const size_t numSplitAggregateAllocaNodes)
Aggregate Alloca Splitting Transformation.
static bool checkGetElementPtrUsers(const rvsdg::SimpleNode &gepNode)
void Run(rvsdg::RvsdgModule &module, util::StatisticsCollector &statisticsCollector) override
Perform RVSDG transformation.
static void splitAllocaNode(const AllocaTraceInfo &allocaTraceInfo)
void splitAllocaNodes(rvsdg::RvsdgModule &rvsdgModule)
static bool isSplitableType(const rvsdg::Type &type)
static std::optional< AllocaTraceInfo > isSplitable(rvsdg::SimpleNode &allocaNode)
~AggregateAllocaSplitting() noexcept override
std::vector< AllocaTraceInfo > findSplitableAllocaNodes(rvsdg::Region &region) const
static rvsdg::SimpleNode & createNode(std::shared_ptr< const rvsdg::Type > allocatedType, rvsdg::Output &count, const size_t alignment)
Definition alloca.hpp:109
static rvsdg::Output & getPointerOutput(rvsdg::Node &node)
Definition alloca.hpp:74
static rvsdg::Output & getMemoryStateOutput(rvsdg::Node &node)
Definition alloca.hpp:81
const std::shared_ptr< const rvsdg::Type > & allocatedType() const noexcept
Definition alloca.hpp:55
static rvsdg::Input & getCountInput(rvsdg::Node &node)
Definition alloca.hpp:67
static std::optional< Constant > tryGetAsConstant(const rvsdg::SimpleNode &gepNode)
static rvsdg::Output * Create(const std::vector< rvsdg::Output * > &operands)
static rvsdg::Input & AddressInput(const rvsdg::Node &node) noexcept
Definition Store.hpp:75
StructType class.
Definition types.hpp:184
ElementTypeConstRange elementTypes() const noexcept
Definition types.hpp:222
Conditional operator / pattern matching.
Definition gamma.hpp:99
std::variant< MatchVar, EntryVar > MapInput(const rvsdg::Input &input) const
Maps gamma input to its role (match variable or entry variable).
Definition gamma.cpp:316
Region & GetRootRegion() const noexcept
Definition graph.hpp:99
void PruneNodes()
Definition graph.hpp:116
void divert_users(jlm::rvsdg::Output *new_origin)
Definition node.hpp:301
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 std::optional< util::FilePath > & SourceFilePath() const noexcept
Graph & Rvsdg() noexcept
const SimpleOperation & GetOperation() const noexcept override
NodeOutput * output(size_t index) const noexcept
SubregionIteratorRange Subregions()
bool insert(ItemType item)
Definition HashSet.hpp:210
bool Contains(const ItemType &item) const noexcept
Definition HashSet.hpp:150
void CollectDemandedStatistics(std::unique_ptr< Statistics > statistics)
Statistics Interface.
util::Timer & GetTimer(const std::string &name)
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
#define JLM_ASSERT(x)
Definition common.hpp:16
Global memory state passed between functions.
static VectorNodeHashMap createElementAllocaNodes(rvsdg::SimpleNode &allocaNode)
std::unordered_map< std::vector< uint64_t >, rvsdg::Node *, VectorHash > VectorNodeHashMap
std::optional< int64_t > tryGetConstantSignedInteger(const rvsdg::Output &output)
Definition Trace.cpp:70
bool IsAggregateType(const jlm::rvsdg::Type &type)
Definition types.hpp:531
void MatchTypeWithDefault(T &obj, const Fns &... fns)
Pattern match over subclass type of given object with default handler.
Region * TryGetOwnerRegion(const rvsdg::Input &input) noexcept
Definition node.hpp:1021
NodeType * TryGetOwnerNode(const rvsdg::Input &input) noexcept
Checks if this is an input to a node of specified type.
Definition node.hpp:872
static std::string strfmt(Args... args)
Definition strfmt.hpp:35
std::size_t CombineHashes(std::size_t hash, Args... args)
Definition Hash.hpp:63
size_t operator()(const std::vector< uint64_t > &v) const