Jlm
Loading...
Searching...
No Matches
StoreValueForwarding.cpp
Go to the documentation of this file.
1/*
2 * Copyright 2026 HÃ¥vard Krogstie <krogstie.havard@gmail.com>
3 * See COPYING for terms of redistribution.
4 */
5
14#include <jlm/llvm/ir/Trace.hpp>
15#include <jlm/llvm/ir/types.hpp>
21#include <jlm/rvsdg/delta.hpp>
22#include <jlm/rvsdg/gamma.hpp>
24#include <jlm/rvsdg/node.hpp>
25#include <jlm/rvsdg/Phi.hpp>
26#include <jlm/rvsdg/region.hpp>
30#include <jlm/rvsdg/theta.hpp>
32#include <jlm/util/common.hpp>
33#include <jlm/util/Hash.hpp>
35#include <jlm/util/time.hpp>
36
37#include <memory>
38#include <optional>
39#include <queue>
40
41namespace jlm::llvm
42{
43
44// Makes the LocalAA give up earlier
45static const bool USE_TRIVIAL_LOCALAA = std::getenv("JLM_SVF_USE_TRIVIAL_LOCALAA");
46
47// Enables the use of the PointsToGraphAliasAnalysis.
48// Runs Andersen to make the PointsToGraph, and queries it if LocalAA yields MayAlias.
49static const bool ENABLE_PTGAA = std::getenv("JLM_ENABLE_SVF_PTGAA");
50
51// Enables the use of region predication checking when tracing origins of loaded values
53 !std::getenv("JLM_DISABLE_REGION_PREDICATE_CHECK");
54
55// By default, loads whose memory states can be traced to other loads attempt to forward
56// the previously loaded value, if the types match, and the addresses are the same (MustAlias).
57// When disabled, loads are skipped during tracing, and never considered for value forwarding.
58static const bool DISABLE_LOAD_LOAD_FORWARDING = std::getenv("JLM_DISABLE_LOAD_LOAD_FORWARDING");
59
64{
68
69 void
71 {
72 switch (response)
73 {
76 break;
79 break;
82 break;
83 default:
84 throw std::logic_error("Unhandled alias analysis query response!");
85 }
86 }
87
88 void
95};
96
101{
102 static constexpr auto NumLoadsWithMemoryStateLabel_ = "#LoadsWithMemoryState";
103 static constexpr auto NumLoadsWithoutMemoryStateLabel_ = "#LoadsWithoutMemoryState";
104 static constexpr auto NumLoadsTracedToDeltaNodeLabel_ = "#LoadsTracedToDeltaNode";
105 static constexpr auto NumForwardedLoadsWithMemoryStateLabel_ = "#ForwardedLoadsWithMemoryState";
107 "#ForwardedLoadsWithoutMemoryState";
108 static constexpr auto numNoAliasStoreLabel_ = "#NoAliasStore";
109 static constexpr auto numMayAliasStoreLabel_ = "#MayAliasStore";
110 static constexpr auto numMustAliasStoreLabel_ = "#MustAliasStore";
111 static constexpr auto numNoAliasLoadLabel_ = "#NoAliasLoad";
112 static constexpr auto numMayAliasLoadLabel_ = "#MayAliasLoad";
113 static constexpr auto numMustAliasLoadLabel_ = "#MustAliasLoad";
114 static constexpr auto TracingLabel_ = "TracingTime";
115 static constexpr auto ForwardingLabel_ = "ForwardingTime";
116
117public:
118 ~Statistics() override = default;
119
120 explicit Statistics(const util::FilePath & sourceFile)
121 : util::Statistics(Id::StoreValueForwarding, sourceFile)
122 {
125 }
126
127 void
129 {
130 AddTimer(Label::Timer).start();
131 }
132
133 void
135 const size_t numLoadsWithMemoryState,
136 const size_t numLoadsWithoutMemoryState,
137 const size_t numLoadsTracedtoDeltaNode,
138 const size_t numForwardedLoadsWithMemoryState,
139 const size_t numForwardedLoadsWithoutMemoryState,
140 const AliasQueryResponseCounter & storeAAResponses,
141 const AliasQueryResponseCounter & loadAAResponses) noexcept
142 {
143 GetTimer(Label::Timer).stop();
144 AddMeasurement(NumLoadsWithMemoryStateLabel_, numLoadsWithMemoryState);
145 AddMeasurement(NumLoadsWithoutMemoryStateLabel_, numLoadsWithoutMemoryState);
146 AddMeasurement(NumLoadsTracedToDeltaNodeLabel_, numLoadsTracedtoDeltaNode);
147 AddMeasurement(NumForwardedLoadsWithMemoryStateLabel_, numForwardedLoadsWithMemoryState);
148 AddMeasurement(NumForwardedLoadsWithoutMemoryStateLabel_, numForwardedLoadsWithoutMemoryState);
149 AddMeasurement(numNoAliasStoreLabel_, storeAAResponses.numNoAliasAnalysisQueries);
150 AddMeasurement(numMayAliasStoreLabel_, storeAAResponses.numMayAliasAnalysisQueries);
151 AddMeasurement(numMustAliasStoreLabel_, storeAAResponses.numMustAliasAnalysisQueries);
152 AddMeasurement(numNoAliasLoadLabel_, loadAAResponses.numNoAliasAnalysisQueries);
153 AddMeasurement(numMayAliasLoadLabel_, loadAAResponses.numMayAliasAnalysisQueries);
154 AddMeasurement(numMustAliasLoadLabel_, loadAAResponses.numMustAliasAnalysisQueries);
155 }
156
157 void
158 startTracing() noexcept
159 {
161 }
162
163 void
164 stopTracing() noexcept
165 {
167 }
168
169 void
171 {
173 }
174
175 void
176 stopForwarding() noexcept
177 {
179 }
180
181 static std::unique_ptr<Statistics>
182 Create(const util::FilePath & sourceFile)
183 {
184 return std::make_unique<Statistics>(sourceFile);
185 }
186};
187
192{
194 : outputTracer(true),
197 {
199 // If load/load forwarding is disabled, make the tracer skip loads
201 }
202
203 // Counters used for statistics
211
212 // Memoization of outputs that have been routed into regions
214 {
215 std::size_t
216 operator()(const std::pair<rvsdg::Output *, rvsdg::Region *> & value) const
217 {
218 return std::hash<rvsdg::Output *>()(value.first) ^ std::hash<rvsdg::Region *>()(value.second);
219 }
220 };
221
222 std::unordered_map<std::pair<rvsdg::Output *, rvsdg::Region *>, rvsdg::Output *, OutputRegionHash>
224
226
228
229 // The AliasAnalysis instance used for all alias queries
231
233};
234
236
240
241void
243{
244 for (auto & node : region.Nodes())
245 {
247 node,
248 [&](rvsdg::PhiNode & phiNode)
249 {
251 },
252 [&](rvsdg::LambdaNode & lambdaNode)
253 {
254 // Output tracing is only done intra-procedural in this pass, and we are about to process
255 // a new lambda node. Clear the tracing cache to free up the memory from the last lambda
256 // we processed.
257 context_->outputTracer.clearCache();
258
259 traverseIntraProceduralRegion(*lambdaNode.subregion());
260 },
261 [&]([[maybe_unused]] rvsdg::DeltaNode & deltaNode)
262 {
263 // Do nothing about delta nodes
264 });
265 }
266}
267
268void
270{
271 rvsdg::TopDownTraverser traverser(&region);
272 for (auto node : traverser)
273 {
275 *node,
276 [&](rvsdg::GammaNode & gammaNode)
277 {
278 for (auto & subregion : gammaNode.Subregions())
279 {
281 }
282 },
283 [&](rvsdg::ThetaNode & thetaNode)
284 {
285 traverseIntraProceduralRegion(*thetaNode.subregion());
286 },
287 [&](rvsdg::SimpleNode & simpleNode)
288 {
289 if (is<LoadNonVolatileOperation>(&simpleNode))
290 {
291 processLoad(simpleNode);
292 }
293
294 // For other node types, we don't need to do anything for store value forwarding
295 });
296 }
297
298 // Any forwarded loads are dead at this point, so remove them
299 region.prune(false);
300}
301
302// Enum containing the possible relationships between a load operation and a store node
304{
305 ValueForwarding, // The store can be forwarded to the load node
306 ClobberNoForward, // The store may clobber the load, but the stored value can not be forwarded.
307 // This can for example be because the addresses are not MustAlias,
308 // or that the type of the stored value and the loaded value are not identical.
309 NoClobber // The store is guaranteed to not clobber the loaded value
310};
311
312// Enum containing the possible relationships between a load operation and a previous load
313enum class LoadNodeInfo
314{
315 ValueForwarding, // The load can be forwarded
316 NoClobber, // The load can not be forwarded, but it is not a clobber
317};
318
319// When tracing backwards from a load node through memory state edges, we store points
320// at which a store writes to the load node, an aliasing load is performed,
321// or a structural node causes the loaded value to have multiple possible origins.
322// These points are known as ValueOrigins
324{
325 enum class Kind
326 {
327 Unknown, // Tracing does not lead to a known value origin in all branches
328 Uninitialized, // Tracing leads to uninitialized memory, like an alloca
329 LoadNode, // Tracing leads to exactly one load node, and it is not inside a subregion
330 StoreNode, // Tracing leads to exactly one store node, and it is not inside a subregion
331 GammaNodeOutput, // Tracing leads to a gamma output, but all branches trace to value origins
332 ThetaNodeOutput, // Tracing leads to a theta output, with a value origin inside
333 ThetaNodePre // Tracing leads to the pre value of a loop variable
334 };
335
338
339 // No default constructor
340 ValueOrigin() = delete;
341
342 [[nodiscard]] bool
343 isKnown() const
344 {
345 return kind != Kind::Unknown;
346 }
347
348 [[nodiscard]] bool
349 operator==(const ValueOrigin & other) const noexcept
350 {
351 return kind == other.kind && node == other.node;
352 }
353
354 [[nodiscard]] bool
355 operator!=(const ValueOrigin & other) const noexcept
356 {
357 return !(*this == other);
358 }
359
360 static ValueOrigin
362 {
363 return ValueOrigin{ Kind::Unknown, nullptr };
364 }
365
366 static ValueOrigin
368 {
369 return ValueOrigin{ Kind::Uninitialized, nullptr };
370 }
371
372 static ValueOrigin
374 {
375 return ValueOrigin{ Kind::StoreNode, &storeNode };
376 }
377
378 static ValueOrigin
380 {
381 return ValueOrigin{ Kind::LoadNode, &loadNode };
382 }
383
384 static ValueOrigin
386 {
387 return ValueOrigin{ Kind::GammaNodeOutput, &gammaNode };
388 }
389
390 static ValueOrigin
392 {
393 return ValueOrigin{ Kind::ThetaNodeOutput, &thetaNode };
394 }
395
396 static ValueOrigin
398 {
399 return ValueOrigin{ Kind::ThetaNodePre, &thetaNode };
400 }
401};
402
409{
410public:
426
436 bool
438 {
440
441 // Perform tracing from each memory state input to find exactly what store it leads to
442 for (auto & memoryStateInput : LoadOperation::MemoryStateInputs(loadNode))
443 {
444 // Tracing starts at the load, so no loop back-edges have been taken yet
445 auto lastValueOrigin = getLastValueOriginBeforeInput(memoryStateInput, false);
446
447 // If the memory state input cannot be traced back to value origins,
448 // or different memory state inputs lead to different value origins in the same branch,
449 // forwarding is not possible
450 if (!lastValueOrigin.isKnown())
451 return false;
452 }
453
454 // During tracing, loop back-edges are never followed, but instead added to a list.
455 // Go through the list to ensure all back-edges have been traced as well.
456 while (!loopVarPostsToTrace.IsEmpty())
457 {
458 auto loopVarPost = *loopVarPostsToTrace.Items().begin();
459 // A loop back-edge has been followed, so pass in true
460 auto lastValueOrigin = getLastValueOriginBeforeInput(*loopVarPost, true);
461 if (!lastValueOrigin.isKnown())
462 return false;
463
464 loopVarPostsToTrace.Remove(loopVarPost);
465 }
466
467 return true;
468 }
469
479 std::optional<ValueOrigin>
480 getLastValueOriginBeforeNode(rvsdg::Node & node, bool loopBackEdgeMaybeTaken)
481 {
482 if (loopBackEdgeMaybeTaken)
483 {
484 auto it = lastValueOriginBeforeNode.find({ &node, true });
485 if (it != lastValueOriginBeforeNode.end())
486 return it->second;
487 }
488
489 auto it = lastValueOriginBeforeNode.find({ &node, false });
490 if (it != lastValueOriginBeforeNode.end())
491 return it->second;
492
493 return std::nullopt;
494 }
495
506 std::optional<ValueOrigin>
507 getLastValueOriginInRegion(rvsdg::Region & region, bool loopBackEdgeMaybeTaken)
508 {
509 if (loopBackEdgeMaybeTaken)
510 {
511 auto it = lastValueOriginInRegion.find({ &region, true });
512 if (it != lastValueOriginInRegion.end())
513 return it->second;
514 }
515
516 auto it = lastValueOriginInRegion.find({ &region, false });
517 if (it != lastValueOriginInRegion.end())
518 return it->second;
519
520 return std::nullopt;
521 }
522
523private:
532 {
533 JLM_ASSERT(is<StoreOperation>(&storeNode));
534
535 const auto & storeAddress = *StoreOperation::AddressInput(storeNode).origin();
536 const auto storeType = StoreOperation::StoredValueInput(storeNode).Type();
537 const auto storedSize = GetTypeStoreSize(*storeType);
538
539 // Trace the store address now, to avoid duplicate work when multiple alias analyses are used
540 const auto & tracedStoredAddress = llvm::traceOutput(storeAddress);
541
542 // Query the alias analysis
543 const auto response =
544 aliasAnalysis.Query(*loadedAddress, loadedTypeSize, tracedStoredAddress, storedSize);
546
547 return response;
548 }
549
557 {
558 JLM_ASSERT(is<LoadOperation>(&otherLoadNode));
559
560 const auto & otherLoadAddress = *LoadOperation::AddressInput(otherLoadNode).origin();
561 const auto otherLoadType = LoadOperation::LoadedValueOutput(otherLoadNode).Type();
562 const auto otherLoadSize = GetTypeStoreSize(*otherLoadType);
563
564 // Trace the store address now, to avoid duplicate work when multiple alias analyses are used
565 const auto & tracedOtherLoadAddress = llvm::traceOutput(otherLoadAddress);
566
567 // Query the alias analysis
568 const auto response =
569 aliasAnalysis.Query(*loadedAddress, loadedTypeSize, tracedOtherLoadAddress, otherLoadSize);
571
572 return response;
573 }
574
591 getLastValueOriginBeforeInput(rvsdg::Input & input, bool loopBackEdgeTaken)
592 {
593 // If the input has already been traced, return the last result
594 if (const auto it = lastValueOriginBeforeInput.find({ &input, loopBackEdgeTaken });
595 it != lastValueOriginBeforeInput.end())
596 return it->second;
597
598 auto result = getLastValueOriginBeforeInputInternal(input, loopBackEdgeTaken);
599
600 // Add the result to the tracing maps
601 const auto [_, inserted] =
602 lastValueOriginBeforeInput.emplace(std::make_pair(&input, loopBackEdgeTaken), result);
603 JLM_ASSERT(inserted);
604
605 // If the input is on a node, add the result to the node map
606 if (auto node = rvsdg::TryGetOwnerNode<rvsdg::Node>(input))
607 {
608 const auto [it, inserted] =
609 lastValueOriginBeforeNode.emplace(std::make_pair(node, loopBackEdgeTaken), result);
610
611 // If the node already had a different last store value, give up
612 if (!inserted && it->second != result)
614 }
615
616 // If the input is a region exit, add the result to the region exit map
617 if (auto regionResult = dynamic_cast<rvsdg::RegionResult *>(&input))
618 {
619 const auto region = regionResult->region();
620 const auto [it, inserted] =
621 lastValueOriginInRegion.emplace(std::make_pair(region, loopBackEdgeTaken), result);
622
623 // If the region already had a different last store value, give up
624 if (!inserted && it->second != result)
626 }
627
628 return result;
629 }
630
633 {
634 // If region predication checking is disabled, always assume loop back-edges have been followed
635 loopBackEdgeTaken |= !ENABLE_REGION_PREDICATE_CHECK;
636
637 auto & tracedOutput = tracer.trace(*input.origin());
638
639 // If tracing reached a store operation, look up its info
640 if (auto [storeNode, storeOp] =
642 storeNode && storeOp)
643 {
644 // Lookup or create a store node info entry
645 auto [it, inserted] = storeNodeInfo.emplace(storeNode, StoreNodeInfo::ClobberNoForward);
646
647 // If the store has not been encountered before, determine forwarding / clobbering
648 if (inserted)
649 {
650 const auto aliasReponse = queryAliasAnalysisWithStore(*storeNode);
651 switch (aliasReponse)
652 {
655 break;
657 it->second = StoreNodeInfo::NoClobber;
658 break;
660 {
661 // MustAlias means a store forwarding candidate was found,
662 // but forwarding is only possible if the type matches
663 auto storedType = StoreOperation::StoredValueInput(*storeNode).Type();
664 if (*storedType == *loadedType)
666 else
668 break;
669 }
670 default:
671 JLM_UNREACHABLE("Unknown AliasAnalysis response");
672 }
673 }
674
675 switch (it->second)
676 {
678 return ValueOrigin::createStoreNode(*storeNode);
682 {
683 // If the store is not clobbering, keep tracing along the memory state chain
684 auto & memoryStateInput = StoreOperation::MapMemoryStateOutputToInput(tracedOutput);
685 return getLastValueOriginBeforeInput(memoryStateInput, loopBackEdgeTaken);
686 }
687
688 default:
689 JLM_UNREACHABLE("Unknown StoreNodeInfo");
690 }
691 }
692
693 // If tracing reached a load operation, check if it is a perfect match
694 if (auto [otherLoadNode, otherLoadOp] =
696 otherLoadNode && otherLoadOp)
697 {
698 // Lookup or create a load node info entry
699 auto [it, inserted] = loadNodeInfo.emplace(otherLoadNode, LoadNodeInfo::NoClobber);
700
701 // If the load has not been encountered before, determine if forwarding is possible
702 if (inserted)
703 {
704 const auto aliasReponse = queryAliasAnalysisWithLoad(*otherLoadNode);
705 switch (aliasReponse)
706 {
709 it->second = LoadNodeInfo::NoClobber;
710 break;
712 {
713 // MustAlias means a forwarding candidate was found,
714 // but forwarding is only possible if the type matches
715 auto otherLoadedType = LoadOperation::LoadedValueOutput(*otherLoadNode).Type();
716 if (*otherLoadedType == *loadedType)
718 else
719 it->second = LoadNodeInfo::NoClobber;
720 break;
721 }
722 default:
723 JLM_UNREACHABLE("Unknown AliasAnalysis response");
724 }
725 }
726
727 switch (it->second)
728 {
730 return ValueOrigin::createLoadNode(*otherLoadNode);
732 {
733 // If the load can not be forwarded, keep tracing along the memory state chain
734 auto & memoryStateInput = LoadOperation::MapMemoryStateOutputToInput(tracedOutput);
735 return getLastValueOriginBeforeInput(memoryStateInput, loopBackEdgeTaken);
736 }
737
738 default:
739 JLM_UNREACHABLE("Unknown StoreNodeInfo");
740 }
741 }
742
743 // For join operations, all the inputs must lead to the same last store
744 if (auto [joinNode, joinOp] =
746 joinNode && joinOp)
747 {
748 if (joinNode->ninputs() == 0)
750
751 for (auto & input : joinNode->Inputs())
752 {
753 auto result = getLastValueOriginBeforeInput(input, loopBackEdgeTaken);
754 if (!result.isKnown())
756 }
757
758 // If none of the calls returned nullptr, there must a shared last store before the join
759 const auto sharedLastValueOrigin =
760 lastValueOriginBeforeNode.find({ joinNode, loopBackEdgeTaken });
761 JLM_ASSERT(sharedLastValueOrigin != lastValueOriginBeforeNode.end());
762 JLM_ASSERT(sharedLastValueOrigin->second.isKnown());
763 return sharedLastValueOrigin->second;
764 }
765
766 // if tracing reaches an alloca, the value is uninitialized, so we can pick our own value
767 if (auto [allocaNode, allocaOp] =
769 allocaNode && allocaOp)
770 {
772 }
773
774 // If we found an exit variable of a gamma node, trace each of its subregions
775 if (auto gammaNode = rvsdg::TryGetOwnerNode<rvsdg::GammaNode>(tracedOutput))
776 {
777 const auto exitVar = gammaNode->MapOutputExitVar(tracedOutput);
778
779 // If all branches lead to the same value origin, return it directly.
780 // If different last value origins have been observed, this becomes unknown
781 std::optional<ValueOrigin> commonValueOrigin;
782 const auto addObservedValueOrigin = [&](ValueOrigin origin)
783 {
784 // Ignore branches that lead to uninitialized
785 if (origin.kind == ValueOrigin::Kind::Uninitialized)
786 return;
787
788 if (!commonValueOrigin.has_value())
789 commonValueOrigin = origin;
790 else if (commonValueOrigin.value() != origin)
791 commonValueOrigin = ValueOrigin::createUnknown();
792 };
793
794 for (auto branchResult : exitVar.branchResult)
795 {
796 // Check if this gamma subregion was provably not taken before reaching the load node
797 // We can only do this check if no back-edges have been taken.
798 if (!loopBackEdgeTaken)
799 {
800 // If region predication checks has been disabled, loopBackEdgeTaken is always true
802
803 auto & fromRegion = *branchResult->region();
805 {
806 // Mark the region as providing uninitialized memory, since it is never reached
807 auto valueOrigin = ValueOrigin::createUninitialized();
809 std::make_pair(&fromRegion, loopBackEdgeTaken),
810 valueOrigin);
811 addObservedValueOrigin(valueOrigin);
812 continue;
813 }
814 }
815
816 auto lastValueOrigin = getLastValueOriginBeforeInput(*branchResult, loopBackEdgeTaken);
817
818 // If any of the gamma branches is impossible to trace back to a last store,
819 // give up on forwarding entirely
820 if (!lastValueOrigin.isKnown())
822
823 // Keep track if there is a single shared last store in all branches
824 addObservedValueOrigin(lastValueOrigin);
825 }
826
827 // If all branches lead to uninitialized memory
828 if (!commonValueOrigin.has_value())
830
831 // If there is exactly one shared origin for all branches
832 if (commonValueOrigin->isKnown())
833 {
834 // The value origin is neither uninitialized nor unknown, so it must belong to a node
835 JLM_ASSERT(commonValueOrigin->node);
836
837 // Only return the origin if it is not inside one of the subregions
838 if (commonValueOrigin->node->region()->node() != gammaNode)
839 return *commonValueOrigin;
840 }
841
842 // The last value origin differs based on which branch is taken,
843 // or is inside one of the gamma subregions, so return the gamma node itself
844 return ValueOrigin::createGammaNodeOutput(*gammaNode);
845 }
846
847 // If we found an exit variable of a theta node, continue tracing on the inside
848 if (auto thetaNode = rvsdg::TryGetOwnerNode<rvsdg::ThetaNode>(tracedOutput))
849 {
850 const auto loopVar = thetaNode->MapOutputLoopVar(tracedOutput);
851
852 // We continue tracing from the loop var post, but we have not taken a back-edge to get there,
853 // so we keep passing the loopBackEdgeTaken parameter unmodified.
854 auto lastValueOrigin = getLastValueOriginBeforeInput(*loopVar.post, loopBackEdgeTaken);
855 if (!lastValueOrigin.isKnown())
857
858 // if the last value before the end of the theta subregion is the pre of the same theta,
859 // the loaded memory may be loop invariant, and tracing can continue from before the theta.
860 if (lastValueOrigin.kind == ValueOrigin::Kind::ThetaNodePre
861 && lastValueOrigin.node == thetaNode)
862 {
863 // A trace that assumes no back-edges have been taken may skip regions,
864 // so unless loopBackEdgeTaken=true, we must do an additional check
865
866 // No additional check needed
867 if (loopBackEdgeTaken)
868 return getLastValueOriginBeforeInput(*loopVar.input, true);
869
870 // Trace again, this time with loopBackEdgeTaken=true
871 lastValueOrigin = getLastValueOriginBeforeInput(*loopVar.post, true);
872 if (!lastValueOrigin.isKnown())
874
875 if (lastValueOrigin.kind == ValueOrigin::Kind::ThetaNodePre
876 && lastValueOrigin.node == thetaNode)
877 {
878 // The theta was determined to not affect the loaded value, so keep tracing.
879 // Since we are leaving a theta, we still let loopBackEdgeTaken=true
880 return getLastValueOriginBeforeInput(*loopVar.input, true);
881 }
882 }
883
884 // We ended up with some value origin inside the theta, so return theta output
885 // to signal that it needs to be routed out
887 lastValueOrigin.kind == ValueOrigin::Kind::Uninitialized
888 || lastValueOrigin.node->region() == thetaNode->subregion());
889 return ValueOrigin::createThetaNodeOutput(*thetaNode);
890 }
891
892 // If we found a loop pre variable in a theta node, trace both inside and outside
893 if (auto thetaNode = rvsdg::TryGetRegionParentNode<rvsdg::ThetaNode>(tracedOutput))
894 {
895 const auto loopVar = thetaNode->MapPreLoopVar(tracedOutput);
896
897 // Trace from the theta input first.
898 // When tracing from a theta input, we always set loopBackEdgeTaken=true
899 auto inputLastValueOrigin = getLastValueOriginBeforeInput(*loopVar.input, true);
900 if (!inputLastValueOrigin.isKnown())
902
903 // Since the loop value may also originte from a back-edge, add the back-edge to the list.
904 // Using a list prevents visiting the loop body multiple times during recursion.
905 loopVarPostsToTrace.insert(loopVar.post);
906
907 return ValueOrigin::createThetaNodePre(*thetaNode);
908 }
909
910 // Tracing reached something that is not handled, such as a function call
912 }
913
914public:
917 std::shared_ptr<const rvsdg::Type> loadedType;
919
920 // Used for statistics
923
924private:
925 // Variables used during tracing
926
930
931 // Map containing info about each store node relevant to value forwarding.
932 std::unordered_map<rvsdg::SimpleNode *, StoreNodeInfo> storeNodeInfo;
933 // Map containing info about each load node relevant to value forwarding.
934 std::unordered_map<rvsdg::SimpleNode *, LoadNodeInfo> loadNodeInfo;
935
936 /* The last value origin on the memory state chain before the given input.
937 * The boolean in the key is true if any loop back-edges have been taken.
938 */
939 std::unordered_map<
940 std::pair<rvsdg::Input *, bool>,
944
945 /* The last value origin before the given node.
946 * The boolean in the key is true if any loop back-edges have been taken.
947 * @see getLastValueOriginBeforeNode()
948 */
949 std::unordered_map<
950 std::pair<rvsdg::Node *, bool>,
954
955 /* The last value origin before the end of the given region.
956 * Note that it can be outside the region if no clobber occurs inside the region.
957 * The boolean in the key is true if any loop back-edges have been taken.
958 * @see getLastValueOriginInRegion()
959 */
960 std::unordered_map<
961 std::pair<rvsdg::Region *, bool>,
965
966 // When tracing reaches a loop var pre argument, tracing does not continue through the post.
967 // The loop var post result is instead added to this set, to ensure that tracing happens later.
968 // Only loop vars that have yet to be traced are added here.
970
971public:
972 // Variables used during routing
973
974 // During routing, at most one exit variable need to be created per gamma
975 std::unordered_map<rvsdg::GammaNode *, rvsdg::Output *> createdExitVars;
976 // During routing, at most one loop variable needs to be created per theta.
977 std::unordered_map<rvsdg::ThetaNode *, rvsdg::ThetaNode::LoopVar> createdLoopVars;
978 // During routing, loop variable posts are not routed immediately, but added to this queue
979 std::queue<rvsdg::Input *> unroutedLoopVarPosts;
980};
981
982void
984{
985 JLM_ASSERT(is<LoadNonVolatileOperation>(&loadNode));
986
987 if (LoadOperation::numMemoryStates(loadNode) == 0)
988 {
989 context_->numLoadsWithoutMemoryState++;
991 }
992 else
993 {
994 context_->numLoadsWithMemoryState++;
996 }
997}
998
999void
1001{
1002 JLM_ASSERT(is<LoadNonVolatileOperation>(&loadNode));
1004
1005 context_->statistics.startTracing();
1006 LoadTracingInfo loadTracingInfo(
1007 loadNode,
1008 context_->outputTracer,
1009 context_->aliasAnalysis,
1010 context_->regionPredicateTrace);
1011 const auto shouldForwardValueOrigins = loadTracingInfo.traceAllMemoryStateInputs();
1012 context_->statistics.stopTracing();
1013
1014 context_->storeAAResponses.addFromCounter(loadTracingInfo.storeAAResponses);
1015 context_->loadAAResponses.addFromCounter(loadTracingInfo.loadAAResponses);
1016
1017 if (shouldForwardValueOrigins)
1018 {
1019 context_->statistics.startForwarding();
1020 forwardValueOrigins(loadTracingInfo);
1021 context_->statistics.stopForwarding();
1022 }
1023}
1024
1025void
1027{
1028 JLM_ASSERT(is<LoadNonVolatileOperation>(&loadNode));
1030
1031 context_->statistics.startTracing();
1032 const auto tracedDelta = traceLoadWithoutMemoryStates(loadNode);
1033 context_->statistics.stopTracing();
1034 if (!tracedDelta.has_value())
1035 {
1036 return;
1037 }
1038
1039 context_->statistics.startForwarding();
1040 forwardLoadWithoutMemoryStates(loadNode, tracedDelta.value());
1041 context_->statistics.stopForwarding();
1042}
1043
1044std::optional<StoreValueForwarding::TracedDelta>
1046{
1047 JLM_ASSERT(is<LoadNonVolatileOperation>(&loadNode));
1049
1050 const auto & loadAddress = *LoadOperation::AddressInput(loadNode).origin();
1051 const auto [basePointer, gepConstantsOpt] = TracePointerOriginPrecise(loadAddress);
1052 if (!gepConstantsOpt.has_value())
1053 {
1054 return std::nullopt;
1055 }
1056
1057 const auto deltaNode = rvsdg::TryGetOwnerNode<rvsdg::DeltaNode>(*basePointer);
1058 if (!deltaNode)
1059 {
1060 return std::nullopt;
1061 }
1062
1063 context_->numLoadsTracedToDeltaNode++;
1064 return std::optional<TracedDelta>({ deltaNode, gepConstantsOpt.value() });
1065}
1066
1067namespace
1068{
1069
1070struct RegionSlice
1071{
1072 // Nodes are ordered according to their depth. Highest depth first.
1073 std::vector<rvsdg::Node *> nodes;
1074 util::HashSet<rvsdg::Output *> arguments;
1075};
1076
1077}
1078
1079static RegionSlice
1081{
1082 // FIXME: This code works perfectly to visit the nodes of a tree, but does not work if it is a DAG
1083 // as it would not guarantee that the nodes would be ordered according to their depth.
1084 std::function<void(rvsdg::Output &, RegionSlice &, util::HashSet<rvsdg::Node *> &)> compute =
1085 [&compute](
1086 rvsdg::Output & output,
1087 RegionSlice & regionSlice,
1089 {
1090 if (rvsdg::TryGetOwnerRegion(output))
1091 {
1092 regionSlice.arguments.insert(&output);
1093 return;
1094 }
1095
1096 auto & node = rvsdg::AssertGetOwnerNode<rvsdg::Node>(output);
1097 if (visited.Contains(&node))
1098 return;
1099
1100 regionSlice.nodes.push_back(&node);
1101 for (auto & input : node.Inputs())
1102 {
1103 compute(*input.origin(), regionSlice, visited);
1104 }
1105 };
1106
1107 RegionSlice regionSlice;
1109 compute(output, regionSlice, visited);
1110
1111 return regionSlice;
1112}
1113
1114static void
1116 rvsdg::Region & targetRegion,
1117 const RegionSlice & regionSlice,
1118 rvsdg::SubstitutionMap & substitutionMap)
1119{
1120 for (auto it = regionSlice.nodes.rbegin(); it != regionSlice.nodes.rend(); ++it)
1121 {
1122 auto node = *it;
1123 node->copy(&targetRegion, substitutionMap);
1124 }
1125}
1126
1127static rvsdg::Output &
1129{
1130 auto deltaNode = util::assertedCast<rvsdg::DeltaNode>(output.region()->node());
1131
1132 auto regionSlice = computeRegionSlice(output);
1133
1134 rvsdg::SubstitutionMap substitutionMap;
1135 for (auto oldArgument : regionSlice.arguments.Items())
1136 {
1137 auto ctxVar = deltaNode->MapBinderContextVar(*oldArgument);
1138 auto & newArgument = rvsdg::RouteToRegion(*ctxVar.input->origin(), targetRegion);
1139 substitutionMap.insert(oldArgument, &newArgument);
1140 }
1141
1142 copyRegionSlice(targetRegion, regionSlice, substitutionMap);
1143 return substitutionMap.lookup(output);
1144}
1145
1146static rvsdg::Output &
1148 const uint64_t elementOffsetInBytes,
1149 rvsdg::Output & output,
1150 rvsdg::Region & targetRegion,
1151 const std::shared_ptr<const rvsdg::Type> & loadedType)
1152{
1153 if (const auto node = rvsdg::TryGetOwnerNode<rvsdg::SimpleNode>(output))
1154 {
1156 node->GetOperation(),
1157 [&](const IntegerConstantOperation &) -> rvsdg::Output &
1158 {
1159 JLM_ASSERT(elementOffsetInBytes == 0);
1160 auto copiedOutput = &copyDeltaRegionSlice(output, targetRegion);
1161
1162 const auto loadBitType = util::assertedCast<const rvsdg::BitType>(loadedType.get());
1163 const auto copiedBitType =
1164 util::assertedCast<const rvsdg::BitType>(copiedOutput->Type().get());
1165 if (copiedBitType->nbits() == loadBitType->nbits())
1166 {
1167 return *copiedOutput;
1168 }
1169
1170 if (loadBitType->nbits() < copiedBitType->nbits())
1171 {
1172 return *TruncOperation::createNode(*copiedOutput, loadedType).output(0);
1173 }
1174
1175 // FIXME: In this case, we would need to concat multiple integers.
1176 return *copiedOutput;
1177 },
1178 [&](const ConstantFP &) -> rvsdg::Output &
1179 {
1180 JLM_ASSERT(elementOffsetInBytes == 0);
1181 return copyDeltaRegionSlice(output, targetRegion);
1182 },
1184 {
1185 JLM_ASSERT(elementOffsetInBytes == 0);
1186 return copyDeltaRegionSlice(output, targetRegion);
1187 },
1189 {
1190 JLM_ASSERT(elementOffsetInBytes == 0);
1191 return copyDeltaRegionSlice(output, targetRegion);
1192 },
1193 [&](const IntToPtrOperation &) -> rvsdg::Output &
1194 {
1195 JLM_ASSERT(elementOffsetInBytes == 0);
1196 return copyDeltaRegionSlice(output, targetRegion);
1197 },
1198 [&](const GetElementPtrOperation &) -> rvsdg::Output &
1199 {
1200 JLM_ASSERT(elementOffsetInBytes == 0);
1201 return copyDeltaRegionSlice(output, targetRegion);
1202 },
1204 {
1205 if (is<PointerType>(loadedType))
1206 {
1207 return *ConstantPointerNullOperation::createNode(targetRegion).output(0);
1208 }
1209
1210 if (const auto bitType = std::dynamic_pointer_cast<const rvsdg::BitType>(loadedType))
1211 {
1212 return *IntegerConstantOperation::Create(targetRegion, bitType->nbits(), 0).output(0);
1213 }
1214
1215 if (const auto floatType = std::dynamic_pointer_cast<const FloatingPointType>(loadedType))
1216 {
1217 const auto zero = ConstantFP::getZeroRepresentation(floatType->size());
1218 return *ConstantFP::createNode(targetRegion, floatType->size(), zero).output(0);
1219 }
1220
1221 if (const auto vectorType = std::dynamic_pointer_cast<const FixedVectorType>(loadedType))
1222 {
1223 return *ConstantAggregateZeroOperation::createNode(targetRegion, vectorType).output(0);
1224 }
1225
1226 throw std::logic_error("Unsupported load type");
1227 },
1228 [&](const ConstantArrayOperation & constantArrayOperation) -> rvsdg::Output &
1229 {
1230 const auto arrayType = constantArrayOperation.type();
1231 const auto elementSizeInBytes = GetTypeAllocSize(*arrayType->GetElementType());
1232
1233 const auto index = elementOffsetInBytes / elementSizeInBytes;
1234 return copyDeltaElement(
1235 elementOffsetInBytes - (elementSizeInBytes * index),
1236 *node->input(index)->origin(),
1237 targetRegion,
1238 loadedType);
1239 },
1240 [&](const ConstantDataArrayOperation & constantDataArrayOperation) -> rvsdg::Output &
1241 {
1242 const auto arrayType = constantDataArrayOperation.type();
1243 const auto elementSizeInBytes = GetTypeAllocSize(*arrayType->GetElementType());
1244
1245 const auto index = elementOffsetInBytes / elementSizeInBytes;
1246 return copyDeltaElement(
1247 elementOffsetInBytes - (elementSizeInBytes * index),
1248 *node->input(index)->origin(),
1249 targetRegion,
1250 loadedType);
1251 },
1252 [&](const ConstantStructOperation & constantStruct) -> rvsdg::Output &
1253 {
1254 auto & structType = constantStruct.type();
1255
1256 for (size_t n = 0; n < structType.numElements(); ++n)
1257 {
1258 auto fieldOffsetInBytes = structType.GetFieldOffset(n);
1259
1260 if (fieldOffsetInBytes == elementOffsetInBytes)
1261 {
1262 return copyDeltaElement(0, *node->input(n)->origin(), targetRegion, loadedType);
1263 }
1264
1265 if (fieldOffsetInBytes > elementOffsetInBytes)
1266 {
1267 fieldOffsetInBytes = structType.GetFieldOffset(n - 1);
1268 return copyDeltaElement(
1269 elementOffsetInBytes - fieldOffsetInBytes,
1270 *node->input(n - 1)->origin(),
1271 targetRegion,
1272 loadedType);
1273 }
1274 }
1275
1276 const auto lastElementIndex = structType.numElements() - 1;
1277 const auto fieldOffsetInBytes = structType.GetFieldOffset(lastElementIndex);
1278 JLM_ASSERT(fieldOffsetInBytes <= elementOffsetInBytes);
1279 return copyDeltaElement(
1280 elementOffsetInBytes - fieldOffsetInBytes,
1281 *node->input(lastElementIndex)->origin(),
1282 targetRegion,
1283 loadedType);
1284 },
1285 [&]() -> rvsdg::Output &
1286 {
1287 throw std::logic_error("Unsupported operation: " + node->DebugString());
1288 });
1289 }
1290
1292 {
1293 JLM_ASSERT(elementOffsetInBytes == 0);
1294 return copyDeltaRegionSlice(output, targetRegion);
1295 }
1296
1297 throw std::logic_error("Unsupported output owner");
1298}
1299
1300void
1301StoreValueForwarding::forwardLoadWithoutMemoryStates(
1302 rvsdg::SimpleNode & loadNode,
1303 const TracedDelta & tracedDelta)
1304{
1305 JLM_ASSERT(is<LoadNonVolatileOperation>(&loadNode));
1306 JLM_ASSERT(LoadOperation::numMemoryStates(loadNode) == 0);
1307 const auto loadOperation =
1308 dynamic_cast<const LoadNonVolatileOperation *>(&loadNode.GetOperation());
1309 auto & deltaResultOrigin = *tracedDelta.deltaNode->result().origin();
1310
1311 if (tracedDelta.gepConstants.size() > 1)
1312 {
1313 // FIXME:
1314 return;
1315 }
1316
1317 JLM_ASSERT(tracedDelta.gepConstants.size() <= 1);
1318 const uint64_t offsetInBytes =
1319 tracedDelta.gepConstants.empty() ? 0 : tracedDelta.gepConstants.front().getOffsetInBytes();
1320 auto & newOutput = copyDeltaElement(
1321 offsetInBytes,
1322 deltaResultOrigin,
1323 *loadNode.region(),
1324 loadOperation->GetLoadedType());
1325
1326 if (*loadOperation->GetLoadedType() != *newOutput.Type())
1327 {
1328 // FIXME:
1329 return;
1330 }
1331
1332 LoadOperation::LoadedValueOutput(loadNode).divert_users(&newOutput);
1333 context_->numForwardedLoadsWithoutMemoryState++;
1334}
1335
1336// Performs StoreValueForwarding to the load node represented by the tracingInfo.
1337void
1338StoreValueForwarding::forwardValueOrigins(LoadTracingInfo & tracingInfo)
1339{
1340 context_->numForwardedLoadsWithMemoryState++;
1341
1342 auto & loadNode = tracingInfo.loadNode;
1343 auto & loadedValueOutput = LoadOperation::LoadedValueOutput(loadNode);
1344 auto & loadRegion = *loadNode.region();
1345
1346 // Since tracing starts from the load node, we know no loop back-edges have been taken
1347 const auto lastValueOrigin = tracingInfo.getLastValueOriginBeforeNode(loadNode, false);
1348 JLM_ASSERT(lastValueOrigin.has_value() && lastValueOrigin->isKnown());
1349 auto & valueOriginOutput = getValueOriginOutput(*lastValueOrigin, loadRegion, tracingInfo);
1350
1351 // Fixup all loop variables that were created during the above routing
1352 connectUnroutedLoopPosts(tracingInfo);
1353
1354 // Divert users of the load to the routed value origin output
1355 loadedValueOutput.divert_users(&valueOriginOutput);
1356
1357 // Make the load node dead by routing all memory state users around it
1358 for (auto & memoryStateOutput : LoadNonVolatileOperation::MemoryStateOutputs(loadNode))
1359 {
1360 auto & memoryStateInput =
1361 LoadNonVolatileOperation::MapMemoryStateOutputToInput(memoryStateOutput);
1362 memoryStateOutput.divert_users(memoryStateInput.origin());
1363 }
1364}
1365
1366// Gets an rvsdg output providing the output referenced by the value origin.
1368StoreValueForwarding::getValueOriginOutput(
1369 ValueOrigin valueOrigin,
1370 rvsdg::Region & targetRegion,
1371 LoadTracingInfo & tracingInfo)
1372{
1373 JLM_ASSERT(valueOrigin.isKnown());
1374
1375 if (valueOrigin.kind == ValueOrigin::Kind::Uninitialized)
1376 {
1377 // When forwarding uninitialized memory, create an undef node
1378 return *UndefValueOperation::Create(targetRegion, tracingInfo.loadedType);
1379 }
1380
1381 if (valueOrigin.kind == ValueOrigin::Kind::StoreNode)
1382 {
1383 // For store nodes, the stored value is the origin of the node's value input
1384 auto & storedValue = *StoreOperation::StoredValueInput(*valueOrigin.node).origin();
1385 JLM_ASSERT(*storedValue.Type() == *tracingInfo.loadedType);
1386 return routeOutputToRegion(storedValue, targetRegion);
1387 }
1388
1389 if (valueOrigin.kind == ValueOrigin::Kind::LoadNode)
1390 {
1391 // For load nodes, the load output is the value origin
1392 auto & loadedValue = LoadOperation::LoadedValueOutput(*valueOrigin.node);
1393 JLM_ASSERT(*loadedValue.Type() == *tracingInfo.loadedType);
1394 return routeOutputToRegion(loadedValue, targetRegion);
1395 }
1396
1397 // For gamma nodes, create an exit variable by finding the stored value in each of its regions
1398 if (valueOrigin.kind == ValueOrigin::Kind::GammaNodeOutput)
1399 {
1400 auto gammaNode = dynamic_cast<rvsdg::GammaNode *>(valueOrigin.node);
1401 JLM_ASSERT(gammaNode);
1402
1403 // We only need to create at most one exit variable per gamma, so memoize it
1404 auto [it, inserted] = tracingInfo.createdExitVars.emplace(gammaNode, nullptr);
1405 if (inserted)
1406 {
1407 std::vector<rvsdg::Output *> lastValueOriginPerSubregion;
1408 for (auto & subregion : gammaNode->Subregions())
1409 {
1410 // We only create one gamma exit variable for each load,
1411 // so if tracing ever reached the gamma after following a back-edge,
1412 // we can not use value origins traced under the assumption that no back-edges were taken.
1413 // If the gamma output was never reached after tracing through a back-edge,
1414 // the getter function will fall back to using value origins traced under the assumption
1415 // that no back-edges have been followed, which is then a correct assumption.
1416 auto lastValueOrigin = tracingInfo.getLastValueOriginInRegion(subregion, true);
1417 JLM_ASSERT(lastValueOrigin.has_value() && lastValueOrigin->isKnown());
1418 auto & valueOriginOutput = getValueOriginOutput(*lastValueOrigin, subregion, tracingInfo);
1419 lastValueOriginPerSubregion.push_back(&valueOriginOutput);
1420 }
1421
1422 auto exitVar = gammaNode->AddExitVar(lastValueOriginPerSubregion);
1423 it->second = exitVar.output;
1424 }
1425 JLM_ASSERT(it->second);
1426 JLM_ASSERT(*it->second->Type() == *tracingInfo.loadedType);
1427 return routeOutputToRegion(*it->second, targetRegion);
1428 }
1429
1430 // For theta nodes, create a loop variable
1431 if (valueOrigin.kind == ValueOrigin::Kind::ThetaNodeOutput
1432 || valueOrigin.kind == ValueOrigin::Kind::ThetaNodePre)
1433 {
1434 auto thetaNode = dynamic_cast<rvsdg::ThetaNode *>(valueOrigin.node);
1435 JLM_ASSERT(thetaNode);
1436
1437 // If the loop variable has not yet been created in this theta, create it now
1438 auto loopVarSlot = tracingInfo.createdLoopVars.find(thetaNode);
1439 if (loopVarSlot == tracingInfo.createdLoopVars.end())
1440 {
1441 rvsdg::Output * initialValue = nullptr;
1442
1443 // Get the last value origin before the theta.
1444 // Since we only create one loop variable for each load we forward,
1445 // use the conservative assumption that back-edges may have been followed.
1446 // If tracing never left the theta after following a back-edge,
1447 // the getter function falls back to using the value origin found under the asumption
1448 // that no back-edges have been followed, which is the a correct assumption.
1449 auto lastValueOrigin = tracingInfo.getLastValueOriginBeforeNode(*thetaNode, true);
1450 if (lastValueOrigin.has_value())
1451 {
1452 JLM_ASSERT(lastValueOrigin->isKnown());
1453 auto & outerRegion = *thetaNode->region();
1454 initialValue = &getValueOriginOutput(*lastValueOrigin, outerRegion, tracingInfo);
1455 }
1456 else
1457 {
1458 // Tracing never reached the loop entry, so the value must be defined inside the loop.
1459 // The created loop variable can therefore take undef as its input.
1460 initialValue = UndefValueOperation::Create(*thetaNode->region(), tracingInfo.loadedType);
1461 }
1462
1463 // Create the loop variable and add it to the map
1464 JLM_ASSERT(initialValue);
1465 JLM_ASSERT(*initialValue->Type() == *tracingInfo.loadedType);
1466 auto loopVar = thetaNode->AddLoopVar(initialValue);
1467 auto [it, inserted] = tracingInfo.createdLoopVars.emplace(thetaNode, loopVar);
1468 JLM_ASSERT(inserted);
1469 loopVarSlot = it;
1470
1471 // To prevent looping during routing, the created loop variable's post is added to a
1472 // queue of loop variable posts that are routed properly later.
1473 tracingInfo.unroutedLoopVarPosts.push(loopVar.post);
1474 }
1475
1476 // Return the correct output, depending on the query kind
1477 switch (valueOrigin.kind)
1478 {
1479 case ValueOrigin::Kind::ThetaNodePre:
1480 return routeOutputToRegion(*loopVarSlot->second.pre, targetRegion);
1481 case ValueOrigin::Kind::ThetaNodeOutput:
1482 return routeOutputToRegion(*loopVarSlot->second.output, targetRegion);
1483 default:
1484 JLM_UNREACHABLE("Unknown StoreValueOrigin kind");
1485 }
1486 }
1487
1488 JLM_UNREACHABLE("Unknown StoreValueOriginKind");
1489}
1490
1491void
1492StoreValueForwarding::connectUnroutedLoopPosts(LoadTracingInfo & tracingInfo)
1493{
1494 // The process of handling all created loop variables may also create more loop variables,
1495 // so keep going until the queue is empty.
1496 while (!tracingInfo.unroutedLoopVarPosts.empty())
1497 {
1498 auto post = tracingInfo.unroutedLoopVarPosts.front();
1499 tracingInfo.unroutedLoopVarPosts.pop();
1500
1501 // We only create one loop variable per theta,
1502 // so if tracing ever entered the theta after following a back-edge,
1503 // we conservatively use the value origin found with loopBackEdgeTaken=true.
1504 // If the theta subregion was never traced after following a back-edge,
1505 // it falls back to using the value origin found assuming no back-edges have been followed.
1506 auto lastValueOrigin = tracingInfo.getLastValueOriginInRegion(*post->region(), true);
1507 JLM_ASSERT(lastValueOrigin.has_value() && lastValueOrigin->isKnown());
1508 auto & origin = getValueOriginOutput(*lastValueOrigin, *post->region(), tracingInfo);
1509 post->divert_to(&origin);
1510 }
1511}
1512
1514StoreValueForwarding::routeOutputToRegion(rvsdg::Output & output, rvsdg::Region & region)
1515{
1516 if (output.region() == &region)
1517 return output;
1518
1519 JLM_ASSERT(rvsdg::Region::isAncestor(region, *output.region()));
1520
1521 if (region.IsRootRegion())
1522 JLM_UNREACHABLE("root region reached during attempt at routing output into region");
1523
1524 if (auto gammaNode = dynamic_cast<rvsdg::GammaNode *>(region.node()))
1525 {
1526 // Route the output all the way to just outside the gamma first
1527 auto & outerOutput = routeOutputToRegion(output, *gammaNode->region());
1528
1529 // If the outer output already has a corresponding EntryVar, return it
1530 if (auto it = context_->routedOutputs.find({ &outerOutput, &region });
1531 it != context_->routedOutputs.end())
1532 {
1533 // The output in the map key may have been deleted, and had its address re-used, so double
1534 // check
1535 auto & branchArgument = *it->second;
1536 if (gammaNode->mapBranchArgumentToInput(branchArgument).origin() == &outerOutput)
1537 {
1538 JLM_ASSERT(*branchArgument.Type() == *output.Type());
1539 return branchArgument;
1540 }
1541 }
1542
1543 // Create an EntryVar for the output, add all branch arguments to the cache
1544 auto entryVar = gammaNode->AddEntryVar(&outerOutput);
1545 for (auto branchArgument : entryVar.branchArgument)
1546 {
1547 context_->routedOutputs[{ &outerOutput, branchArgument->region() }] = branchArgument;
1548 }
1549
1550 return *entryVar.branchArgument[region.index()];
1551 }
1552
1553 if (auto thetaNode = dynamic_cast<rvsdg::ThetaNode *>(region.node()))
1554 {
1555 // Route the output all the way to just outside the theta first
1556 auto & outerOutput = routeOutputToRegion(output, *thetaNode->region());
1557
1558 // If the outer output already has a corresponding invariant loop variable, return it
1559 if (auto it = context_->routedOutputs.find({ &outerOutput, &region });
1560 it != context_->routedOutputs.end())
1561 {
1562 // The output in the map key may have been deleted, and had its address re-used, so double
1563 // check
1564 auto & loopVarPre = *it->second;
1565 if (thetaNode->MapPreLoopVar(loopVarPre).input->origin() == &outerOutput)
1566 {
1567 JLM_ASSERT(*loopVarPre.Type() == *output.Type());
1568 return loopVarPre;
1569 }
1570 }
1571
1572 // Create an invariant LoopVar for the output and add it to the cache
1573 auto loopVar = thetaNode->AddLoopVar(&outerOutput);
1574 context_->routedOutputs[{ &outerOutput, &region }] = loopVar.pre;
1575 return *loopVar.pre;
1576 }
1577
1578 JLM_UNREACHABLE("routeOutputToRegion reached unhandled structural node");
1579}
1580
1581static std::unique_ptr<aa::AliasAnalysis>
1583{
1584 auto localAA = std::make_unique<aa::LocalAliasAnalysis>();
1585
1587 {
1588 // Setting the trace collection size to 1 limits the analysis to only the most trivial tracing
1589 localAA->setMaxTraceCollectionSize(1);
1590 }
1591
1592 if (!ENABLE_PTGAA)
1593 return localAA;
1594
1595 aa::Andersen andersen;
1596 auto ptg = andersen.Analyze(module, statisticsCollector);
1597 auto ptgAA = std::make_unique<aa::PointsToGraphAliasAnalysis>(std::move(ptg));
1598
1599 return std::make_unique<aa::ChainedAliasAnalysis>(std::move(localAA), std::move(ptgAA));
1600}
1601
1602void
1603StoreValueForwarding::Run(
1604 rvsdg::RvsdgModule & module,
1606{
1607 auto aliasAnalysis = createAliasAnalysis(module, statisticsCollector);
1608 auto statistics = Statistics::Create(module.SourceFilePath().value());
1609
1610 context_ = std::make_unique<Context>(*aliasAnalysis, *statistics);
1611
1612 statistics->StartStatistics();
1613
1614 auto & rvsdg = module.Rvsdg();
1615 traverseInterProceduralRegion(rvsdg.GetRootRegion());
1616
1617 statistics->StopStatistics(
1618 context_->numLoadsWithMemoryState,
1619 context_->numLoadsWithoutMemoryState,
1620 context_->numLoadsTracedToDeltaNode,
1621 context_->numForwardedLoadsWithMemoryState,
1622 context_->numForwardedLoadsWithoutMemoryState,
1623 context_->storeAAResponses,
1624 context_->loadAAResponses);
1625 statisticsCollector.CollectDemandedStatistics(std::move(statistics));
1626
1627 // Discard internal state to free up memory after we are done
1628 context_.reset();
1629}
1630}
static jlm::util::StatisticsCollector statisticsCollector
std::vector< rvsdg::Node * > nodes
util::HashSet< rvsdg::Output * > arguments
static rvsdg::SimpleNode & createNode(rvsdg::Region &region, std::shared_ptr< const rvsdg::Type > type)
static rvsdg::Node & createNode(rvsdg::Region &region, fpsize size, const ::llvm::APFloat &constant)
static ::llvm::APFloat getZeroRepresentation(fpsize size)
ConstantPointerNullOperation class.
static rvsdg::Node & createNode(rvsdg::Region &region)
Get address of compiled function object.
static rvsdg::Node & Create(rvsdg::Region &region, IntegerValueRepresentation representation)
static size_t numMemoryStates(const rvsdg::SimpleNode &node) noexcept
Definition Load.hpp:101
static rvsdg::Output & LoadedValueOutput(const rvsdg::Node &node)
Definition Load.hpp:84
static rvsdg::Input & AddressInput(const rvsdg::Node &node) noexcept
Definition Load.hpp:75
static rvsdg::Node::InputIteratorRange MemoryStateInputs(const rvsdg::Node &node) noexcept
Definition Load.hpp:139
static rvsdg::Input & MapMemoryStateOutputToInput(const rvsdg::Output &output)
Definition Load.hpp:157
std::optional< ValueOrigin > getLastValueOriginBeforeNode(rvsdg::Node &node, bool loopBackEdgeMaybeTaken)
ValueOrigin getLastValueOriginBeforeInput(rvsdg::Input &input, bool loopBackEdgeTaken)
std::unordered_map< std::pair< rvsdg::Region *, bool >, ValueOrigin, util::Hash< std::pair< rvsdg::Region *, bool > > > lastValueOriginInRegion
std::unordered_map< rvsdg::GammaNode *, rvsdg::Output * > createdExitVars
std::unordered_map< rvsdg::SimpleNode *, StoreNodeInfo > storeNodeInfo
LoadTracingInfo(rvsdg::SimpleNode &loadNode, OutputTracer &tracer, aa::AliasAnalysis &aliasAnalysis, rvsdg::RegionPredicateTrace &regionPredicateTrace)
std::unordered_map< std::pair< rvsdg::Input *, bool >, ValueOrigin, util::Hash< std::pair< rvsdg::Input *, bool > > > lastValueOriginBeforeInput
AliasQueryResponseCounter loadAAResponses
std::unordered_map< rvsdg::SimpleNode *, LoadNodeInfo > loadNodeInfo
ValueOrigin getLastValueOriginBeforeInputInternal(rvsdg::Input &input, bool loopBackEdgeTaken)
std::unordered_map< rvsdg::ThetaNode *, rvsdg::ThetaNode::LoopVar > createdLoopVars
std::optional< ValueOrigin > getLastValueOriginInRegion(rvsdg::Region &region, bool loopBackEdgeMaybeTaken)
std::queue< rvsdg::Input * > unroutedLoopVarPosts
aa::AliasAnalysis::AliasQueryResponse queryAliasAnalysisWithLoad(rvsdg::SimpleNode &otherLoadNode)
std::shared_ptr< const rvsdg::Type > loadedType
rvsdg::RegionPredicateTrace & regionPredicateTrace
AliasQueryResponseCounter storeAAResponses
std::unordered_map< std::pair< rvsdg::Node *, bool >, ValueOrigin, util::Hash< std::pair< rvsdg::Node *, bool > > > lastValueOriginBeforeNode
aa::AliasAnalysis::AliasQueryResponse queryAliasAnalysisWithStore(rvsdg::SimpleNode &storeNode)
util::HashSet< rvsdg::Input * > loopVarPostsToTrace
void setTraceThroughLoadedStates(bool traceThroughLoadedStates)
Definition Trace.hpp:39
static rvsdg::Input & StoredValueInput(const rvsdg::Node &node) noexcept
Definition Store.hpp:84
static rvsdg::Input & MapMemoryStateOutputToInput(const rvsdg::Output &output)
Definition Store.hpp:152
static rvsdg::Input & AddressInput(const rvsdg::Node &node) noexcept
Definition Store.hpp:75
Store Value Forwarding Statistics class.
void StopStatistics(const size_t numLoadsWithMemoryState, const size_t numLoadsWithoutMemoryState, const size_t numLoadsTracedtoDeltaNode, const size_t numForwardedLoadsWithMemoryState, const size_t numForwardedLoadsWithoutMemoryState, const AliasQueryResponseCounter &storeAAResponses, const AliasQueryResponseCounter &loadAAResponses) noexcept
static std::unique_ptr< Statistics > Create(const util::FilePath &sourceFile)
Store Value Forwarding Optimization.
void processLoadWithoutMemoryStates(rvsdg::SimpleNode &loadNode)
void processLoad(rvsdg::SimpleNode &loadNode)
~StoreValueForwarding() noexcept override
std::unique_ptr< Context > context_
void forwardValueOrigins(LoadTracingInfo &tracingInfo)
void processLoadWithMemoryStates(rvsdg::SimpleNode &loadNode)
void forwardLoadWithoutMemoryStates(rvsdg::SimpleNode &loadNode, const TracedDelta &tracedDelta)
std::optional< TracedDelta > traceLoadWithoutMemoryStates(const rvsdg::SimpleNode &loadNode)
void traverseInterProceduralRegion(rvsdg::Region &region)
void traverseIntraProceduralRegion(rvsdg::Region &region)
virtual AliasQueryResponse Query(const rvsdg::Output &p1, size_t s1, const rvsdg::Output &p2, size_t s2)=0
std::unique_ptr< PointsToGraph > Analyze(const rvsdg::RvsdgModule &module, util::StatisticsCollector &statisticsCollector) override
rvsdg::Input & result() const noexcept
Definition delta.cpp:116
Conditional operator / pattern matching.
Definition gamma.hpp:99
Output * origin() const noexcept
Definition node.hpp:58
const std::shared_ptr< const rvsdg::Type > & Type() const noexcept
Definition node.hpp:67
NodeOutput * output(size_t index) const noexcept
Definition node.hpp:650
rvsdg::Region * region() const noexcept
Definition node.hpp:761
void setTraceThroughStructuralNodes(bool value) noexcept
Definition Trace.hpp:59
Output & trace(Output &output)
Definition Trace.cpp:22
rvsdg::Region * region() const noexcept
Definition node.cpp:151
const std::shared_ptr< const rvsdg::Type > & Type() const noexcept
Definition node.hpp:366
A phi node represents the fixpoint of mutually recursive definitions.
Definition Phi.hpp:46
rvsdg::Region * subregion() const noexcept
Definition Phi.hpp:320
Traces region reachability by predicate assertions.
bool CheckPredicatesSatisfiable(Region &originRegion, Region &targetRegion)
Checks for dynamic reachability between two regions.
Represents the result of a region.
Definition region.hpp:120
Represent acyclic RVSDG subgraphs.
Definition region.hpp:213
void prune(bool recursive)
Definition region.cpp:326
size_t index() const noexcept
Definition region.hpp:310
bool IsRootRegion() const noexcept
Definition region.cpp:173
rvsdg::StructuralNode * node() const noexcept
Definition region.hpp:301
static bool isAncestor(const rvsdg::Region &region, const rvsdg::Region &ancestor) noexcept
Definition region.cpp:474
NodeRange Nodes() noexcept
Definition region.hpp:375
const std::optional< util::FilePath > & SourceFilePath() const noexcept
const SimpleOperation & GetOperation() const noexcept override
NodeOutput * output(size_t index) const noexcept
SubregionIteratorRange Subregions()
void insert(const Output *original, Output *substitute)
Output & lookup(const Output &original) const
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
#define JLM_UNREACHABLE(msg)
Definition common.hpp:43
Global memory state passed between functions.
static const bool USE_TRIVIAL_LOCALAA
size_t GetTypeAllocSize(const rvsdg::Type &type)
Definition types.cpp:473
static rvsdg::Output & copyDeltaElement(const uint64_t elementOffsetInBytes, rvsdg::Output &output, rvsdg::Region &targetRegion, const std::shared_ptr< const rvsdg::Type > &loadedType)
rvsdg::Output & traceOutput(rvsdg::Output &output, const rvsdg::Region *withinRegion)
Definition Trace.cpp:62
static const bool ENABLE_REGION_PREDICATE_CHECK
static const bool DISABLE_LOAD_LOAD_FORWARDING
static std::unique_ptr< aa::AliasAnalysis > createAliasAnalysis(rvsdg::RvsdgModule &module, util::StatisticsCollector &statisticsCollector)
static void copyRegionSlice(rvsdg::Region &targetRegion, const RegionSlice &regionSlice, rvsdg::SubstitutionMap &substitutionMap)
TracedPointerOrigin TracePointerOriginPrecise(const rvsdg::Output &p)
Definition Trace.cpp:153
static RegionSlice computeRegionSlice(rvsdg::Output &output)
static rvsdg::Output & copyDeltaRegionSlice(rvsdg::Output &output, rvsdg::Region &targetRegion)
size_t GetTypeStoreSize(const rvsdg::Type &type)
Definition types.cpp:386
static const bool ENABLE_PTGAA
void MatchTypeWithDefault(T &obj, const Fns &... fns)
Pattern match over subclass type of given object with default handler.
void MatchTypeOrFail(T &obj, const Fns &... fns)
Pattern match over subclass type of given object.
Output & RouteToRegion(Output &output, Region &region)
Definition node.cpp:381
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
void addResponse(aa::AliasAnalysis::AliasQueryResponse response)
void addFromCounter(const AliasQueryResponseCounter &other)
std::size_t operator()(const std::pair< rvsdg::Output *, rvsdg::Region * > &value) const
Context(aa::AliasAnalysis &aliasAnalysis, Statistics &statistics) noexcept
std::unordered_map< std::pair< rvsdg::Output *, rvsdg::Region * >, rvsdg::Output *, OutputRegionHash > routedOutputs
std::vector< GetElementPtrOperation::Constant > gepConstants
static ValueOrigin createUninitialized()
static ValueOrigin createGammaNodeOutput(rvsdg::GammaNode &gammaNode)
static ValueOrigin createThetaNodeOutput(rvsdg::ThetaNode &thetaNode)
bool operator!=(const ValueOrigin &other) const noexcept
static ValueOrigin createLoadNode(rvsdg::SimpleNode &loadNode)
static ValueOrigin createStoreNode(rvsdg::SimpleNode &storeNode)
static ValueOrigin createUnknown()
bool operator==(const ValueOrigin &other) const noexcept
static ValueOrigin createThetaNodePre(rvsdg::ThetaNode &thetaNode)