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(),
197 {
198 // It is safe to do invariance caching.
199 // - outputs that are invariant in a structural node are never changed inside the node.
200 // - structural node inputs and outputs are never removed during the duration of this pass.
205 // If load/load forwarding is disabled, make the tracer skip loads
207 }
208
209 // Counters used for statistics
217
218 // Memoization of outputs that have been routed into regions
220 {
221 std::size_t
222 operator()(const std::pair<rvsdg::Output *, rvsdg::Region *> & value) const
223 {
224 return std::hash<rvsdg::Output *>()(value.first) ^ std::hash<rvsdg::Region *>()(value.second);
225 }
226 };
227
228 std::unordered_map<std::pair<rvsdg::Output *, rvsdg::Region *>, rvsdg::Output *, OutputRegionHash>
230
232
234
235 // The AliasAnalysis instance used for all alias queries
237
239};
240
242
246
247void
249{
250 for (auto & node : region.Nodes())
251 {
253 node,
254 [&](rvsdg::PhiNode & phiNode)
255 {
257 },
258 [&](rvsdg::LambdaNode & lambdaNode)
259 {
260 // Output tracing is only done intra-procedural in this pass, and we are about to process
261 // a new lambda node. Clear the tracing cache to free up the memory from the last lambda
262 // we processed.
263 context_->outputTracer.clearInvarianceCache();
264
265 traverseIntraProceduralRegion(*lambdaNode.subregion());
266 },
267 [&]([[maybe_unused]] rvsdg::DeltaNode & deltaNode)
268 {
269 // Do nothing about delta nodes
270 });
271 }
272}
273
274void
276{
277 rvsdg::TopDownTraverser traverser(&region);
278 for (auto node : traverser)
279 {
281 *node,
282 [&](rvsdg::GammaNode & gammaNode)
283 {
284 for (auto & subregion : gammaNode.Subregions())
285 {
287 }
288 },
289 [&](rvsdg::ThetaNode & thetaNode)
290 {
291 traverseIntraProceduralRegion(*thetaNode.subregion());
292 },
293 [&](rvsdg::SimpleNode & simpleNode)
294 {
295 if (is<LoadNonVolatileOperation>(&simpleNode))
296 {
297 processLoad(simpleNode);
298 }
299
300 // For other node types, we don't need to do anything for store value forwarding
301 });
302 }
303
304 // Any forwarded loads are dead at this point, so remove them
305 region.prune(false);
306}
307
308// Enum containing the possible relationships between a load operation and a store node
310{
311 ValueForwarding, // The store can be forwarded to the load node
312 ClobberNoForward, // The store may clobber the load, but the stored value can not be forwarded.
313 // This can for example be because the addresses are not MustAlias,
314 // or that the type of the stored value and the loaded value are not identical.
315 NoClobber // The store is guaranteed to not clobber the loaded value
316};
317
318// Enum containing the possible relationships between a load operation and a previous load
319enum class LoadNodeInfo
320{
321 ValueForwarding, // The load can be forwarded
322 NoClobber, // The load can not be forwarded, but it is not a clobber
323};
324
325// When tracing backwards from a load node through memory state edges, we store points
326// at which a store writes to the load node, an aliasing load is performed,
327// or a structural node causes the loaded value to have multiple possible origins.
328// These points are known as ValueOrigins
330{
331 enum class Kind
332 {
333 Unknown, // Tracing does not lead to a known value origin in all branches
334 Uninitialized, // Tracing leads to uninitialized memory, like an alloca
335 LoadNode, // Tracing leads to exactly one load node, and it is not inside a subregion
336 StoreNode, // Tracing leads to exactly one store node, and it is not inside a subregion
337 GammaNodeOutput, // Tracing leads to a gamma output, but all branches trace to value origins
338 ThetaNodeOutput, // Tracing leads to a theta output, with a value origin inside
339 ThetaNodePre // Tracing leads to the pre value of a loop variable
340 };
341
344
345 // No default constructor
346 ValueOrigin() = delete;
347
348 [[nodiscard]] bool
349 isKnown() const
350 {
351 return kind != Kind::Unknown;
352 }
353
354 [[nodiscard]] bool
355 operator==(const ValueOrigin & other) const noexcept
356 {
357 return kind == other.kind && node == other.node;
358 }
359
360 [[nodiscard]] bool
361 operator!=(const ValueOrigin & other) const noexcept
362 {
363 return !(*this == other);
364 }
365
366 static ValueOrigin
368 {
369 return ValueOrigin{ Kind::Unknown, nullptr };
370 }
371
372 static ValueOrigin
374 {
375 return ValueOrigin{ Kind::Uninitialized, nullptr };
376 }
377
378 static ValueOrigin
380 {
381 return ValueOrigin{ Kind::StoreNode, &storeNode };
382 }
383
384 static ValueOrigin
386 {
387 return ValueOrigin{ Kind::LoadNode, &loadNode };
388 }
389
390 static ValueOrigin
392 {
393 return ValueOrigin{ Kind::GammaNodeOutput, &gammaNode };
394 }
395
396 static ValueOrigin
398 {
399 return ValueOrigin{ Kind::ThetaNodeOutput, &thetaNode };
400 }
401
402 static ValueOrigin
404 {
405 return ValueOrigin{ Kind::ThetaNodePre, &thetaNode };
406 }
407};
408
415{
416public:
432
442 bool
444 {
446
447 // Perform tracing from each memory state input to find exactly what store it leads to
448 for (auto & memoryStateInput : LoadOperation::MemoryStateInputs(loadNode))
449 {
450 // Tracing starts at the load, so no loop back-edges have been taken yet
451 auto lastValueOrigin = getLastValueOriginBeforeInput(memoryStateInput, false);
452
453 // If the memory state input cannot be traced back to value origins,
454 // or different memory state inputs lead to different value origins in the same branch,
455 // forwarding is not possible
456 if (!lastValueOrigin.isKnown())
457 return false;
458 }
459
460 // During tracing, loop back-edges are never followed, but instead added to a list.
461 // Go through the list to ensure all back-edges have been traced as well.
462 while (!loopVarPostsToTrace.IsEmpty())
463 {
464 auto loopVarPost = *loopVarPostsToTrace.Items().begin();
465 // A loop back-edge has been followed, so pass in true
466 auto lastValueOrigin = getLastValueOriginBeforeInput(*loopVarPost, true);
467 if (!lastValueOrigin.isKnown())
468 return false;
469
470 loopVarPostsToTrace.Remove(loopVarPost);
471 }
472
473 return true;
474 }
475
485 std::optional<ValueOrigin>
486 getLastValueOriginBeforeNode(rvsdg::Node & node, bool loopBackEdgeMaybeTaken)
487 {
488 if (loopBackEdgeMaybeTaken)
489 {
490 auto it = lastValueOriginBeforeNode.find({ &node, true });
491 if (it != lastValueOriginBeforeNode.end())
492 return it->second;
493 }
494
495 auto it = lastValueOriginBeforeNode.find({ &node, false });
496 if (it != lastValueOriginBeforeNode.end())
497 return it->second;
498
499 return std::nullopt;
500 }
501
512 std::optional<ValueOrigin>
513 getLastValueOriginInRegion(rvsdg::Region & region, bool loopBackEdgeMaybeTaken)
514 {
515 if (loopBackEdgeMaybeTaken)
516 {
517 auto it = lastValueOriginInRegion.find({ &region, true });
518 if (it != lastValueOriginInRegion.end())
519 return it->second;
520 }
521
522 auto it = lastValueOriginInRegion.find({ &region, false });
523 if (it != lastValueOriginInRegion.end())
524 return it->second;
525
526 return std::nullopt;
527 }
528
529private:
538 {
539 JLM_ASSERT(is<StoreOperation>(&storeNode));
540
541 const auto & storeAddress = *StoreOperation::AddressInput(storeNode).origin();
542 const auto storeType = StoreOperation::StoredValueInput(storeNode).Type();
543 const auto storedSize = GetTypeStoreSize(*storeType);
544
545 // Trace the store address now, to avoid duplicate work when multiple alias analyses are used
546 const auto & tracedStoredAddress = llvm::traceOutput(storeAddress, false);
547
548 // Query the alias analysis
549 const auto response =
550 aliasAnalysis.Query(*loadedAddress, loadedTypeSize, tracedStoredAddress, storedSize);
552
553 return response;
554 }
555
563 {
564 JLM_ASSERT(is<LoadOperation>(&otherLoadNode));
565
566 const auto & otherLoadAddress = *LoadOperation::AddressInput(otherLoadNode).origin();
567 const auto otherLoadType = LoadOperation::LoadedValueOutput(otherLoadNode).Type();
568 const auto otherLoadSize = GetTypeStoreSize(*otherLoadType);
569
570 // Trace the store address now, to avoid duplicate work when multiple alias analyses are used
571 const auto & tracedOtherLoadAddress = llvm::traceOutput(otherLoadAddress, false);
572
573 // Query the alias analysis
574 const auto response =
575 aliasAnalysis.Query(*loadedAddress, loadedTypeSize, tracedOtherLoadAddress, otherLoadSize);
577
578 return response;
579 }
580
597 getLastValueOriginBeforeInput(rvsdg::Input & input, bool loopBackEdgeTaken)
598 {
599 // If the input has already been traced, return the last result
600 if (const auto it = lastValueOriginBeforeInput.find({ &input, loopBackEdgeTaken });
601 it != lastValueOriginBeforeInput.end())
602 return it->second;
603
604 auto result = getLastValueOriginBeforeInputInternal(input, loopBackEdgeTaken);
605
606 // Add the result to the tracing maps
607 const auto [_, inserted] =
608 lastValueOriginBeforeInput.emplace(std::make_pair(&input, loopBackEdgeTaken), result);
609 JLM_ASSERT(inserted);
610
611 // If the input is on a node, add the result to the node map
612 if (auto node = rvsdg::TryGetOwnerNode<rvsdg::Node>(input))
613 {
614 const auto [it, inserted] =
615 lastValueOriginBeforeNode.emplace(std::make_pair(node, loopBackEdgeTaken), result);
616
617 // If the node already had a different last store value, give up
618 if (!inserted && it->second != result)
620 }
621
622 // If the input is a region exit, add the result to the region exit map
623 if (auto regionResult = dynamic_cast<rvsdg::RegionResult *>(&input))
624 {
625 const auto region = regionResult->region();
626 const auto [it, inserted] =
627 lastValueOriginInRegion.emplace(std::make_pair(region, loopBackEdgeTaken), result);
628
629 // If the region already had a different last store value, give up
630 if (!inserted && it->second != result)
632 }
633
634 return result;
635 }
636
639 {
640 // If region predication checking is disabled, always assume loop back-edges have been followed
641 loopBackEdgeTaken |= !ENABLE_REGION_PREDICATE_CHECK;
642
643 auto & tracedOutput = tracer.trace(*input.origin());
644
645 // If tracing reached a store operation, look up its info
646 if (auto [storeNode, storeOp] =
648 storeNode && storeOp)
649 {
650 // Lookup or create a store node info entry
651 auto [it, inserted] = storeNodeInfo.emplace(storeNode, StoreNodeInfo::ClobberNoForward);
652
653 // If the store has not been encountered before, determine forwarding / clobbering
654 if (inserted)
655 {
656 const auto aliasReponse = queryAliasAnalysisWithStore(*storeNode);
657 switch (aliasReponse)
658 {
661 break;
663 it->second = StoreNodeInfo::NoClobber;
664 break;
666 {
667 // MustAlias means a store forwarding candidate was found,
668 // but forwarding is only possible if the type matches
669 auto storedType = StoreOperation::StoredValueInput(*storeNode).Type();
670 if (*storedType == *loadedType)
672 else
674 break;
675 }
676 default:
677 JLM_UNREACHABLE("Unknown AliasAnalysis response");
678 }
679 }
680
681 switch (it->second)
682 {
684 return ValueOrigin::createStoreNode(*storeNode);
688 {
689 // If the store is not clobbering, keep tracing along the memory state chain
690 auto & memoryStateInput = StoreOperation::MapMemoryStateOutputToInput(tracedOutput);
691 return getLastValueOriginBeforeInput(memoryStateInput, loopBackEdgeTaken);
692 }
693
694 default:
695 JLM_UNREACHABLE("Unknown StoreNodeInfo");
696 }
697 }
698
699 // If tracing reached a load operation, check if it is a perfect match
700 if (auto [otherLoadNode, otherLoadOp] =
702 otherLoadNode && otherLoadOp)
703 {
704 // Lookup or create a load node info entry
705 auto [it, inserted] = loadNodeInfo.emplace(otherLoadNode, LoadNodeInfo::NoClobber);
706
707 // If the load has not been encountered before, determine if forwarding is possible
708 if (inserted)
709 {
710 const auto aliasReponse = queryAliasAnalysisWithLoad(*otherLoadNode);
711 switch (aliasReponse)
712 {
715 it->second = LoadNodeInfo::NoClobber;
716 break;
718 {
719 // MustAlias means a forwarding candidate was found,
720 // but forwarding is only possible if the type matches
721 auto otherLoadedType = LoadOperation::LoadedValueOutput(*otherLoadNode).Type();
722 if (*otherLoadedType == *loadedType)
724 else
725 it->second = LoadNodeInfo::NoClobber;
726 break;
727 }
728 default:
729 JLM_UNREACHABLE("Unknown AliasAnalysis response");
730 }
731 }
732
733 switch (it->second)
734 {
736 return ValueOrigin::createLoadNode(*otherLoadNode);
738 {
739 // If the load can not be forwarded, keep tracing along the memory state chain
740 auto & memoryStateInput = LoadOperation::MapMemoryStateOutputToInput(tracedOutput);
741 return getLastValueOriginBeforeInput(memoryStateInput, loopBackEdgeTaken);
742 }
743
744 default:
745 JLM_UNREACHABLE("Unknown StoreNodeInfo");
746 }
747 }
748
749 // For join operations, all the inputs must lead to the same last store
750 if (auto [joinNode, joinOp] =
752 joinNode && joinOp)
753 {
754 if (joinNode->ninputs() == 0)
756
757 for (auto & input : joinNode->Inputs())
758 {
759 auto result = getLastValueOriginBeforeInput(input, loopBackEdgeTaken);
760 if (!result.isKnown())
762 }
763
764 // If none of the calls returned nullptr, there must a shared last store before the join
765 const auto sharedLastValueOrigin =
766 lastValueOriginBeforeNode.find({ joinNode, loopBackEdgeTaken });
767 JLM_ASSERT(sharedLastValueOrigin != lastValueOriginBeforeNode.end());
768 JLM_ASSERT(sharedLastValueOrigin->second.isKnown());
769 return sharedLastValueOrigin->second;
770 }
771
772 // if tracing reaches an alloca, the value is uninitialized, so we can pick our own value
773 if (auto [allocaNode, allocaOp] =
775 allocaNode && allocaOp)
776 {
778 }
779
780 // If we found an exit variable of a gamma node, trace each of its subregions
781 if (auto gammaNode = rvsdg::TryGetOwnerNode<rvsdg::GammaNode>(tracedOutput))
782 {
783 const auto exitVar = gammaNode->MapOutputExitVar(tracedOutput);
784
785 // If all branches lead to the same value origin, return it directly.
786 // If different last value origins have been observed, this becomes unknown
787 std::optional<ValueOrigin> commonValueOrigin;
788 const auto addObservedValueOrigin = [&](ValueOrigin origin)
789 {
790 // Ignore branches that lead to uninitialized
791 if (origin.kind == ValueOrigin::Kind::Uninitialized)
792 return;
793
794 if (!commonValueOrigin.has_value())
795 commonValueOrigin = origin;
796 else if (commonValueOrigin.value() != origin)
797 commonValueOrigin = ValueOrigin::createUnknown();
798 };
799
800 for (auto branchResult : exitVar.branchResult)
801 {
802 // Check if this gamma subregion was provably not taken before reaching the load node
803 // We can only do this check if no back-edges have been taken.
804 if (!loopBackEdgeTaken)
805 {
806 // If region predication checks has been disabled, loopBackEdgeTaken is always true
808
809 auto & valueOriginRegion = *branchResult->region();
810 auto & targetRegion = *loadNode.region();
811 if (!regionPredicateTracer.isReachableFromRegion(targetRegion, valueOriginRegion))
812 {
813 // Mark the region as providing uninitialized memory, since it is never reached
814 auto valueOrigin = ValueOrigin::createUninitialized();
816 std::make_pair(&valueOriginRegion, loopBackEdgeTaken),
817 valueOrigin);
818 addObservedValueOrigin(valueOrigin);
819 continue;
820 }
821 }
822
823 auto lastValueOrigin = getLastValueOriginBeforeInput(*branchResult, loopBackEdgeTaken);
824
825 // If any of the gamma branches is impossible to trace back to a last store,
826 // give up on forwarding entirely
827 if (!lastValueOrigin.isKnown())
829
830 // Keep track if there is a single shared last store in all branches
831 addObservedValueOrigin(lastValueOrigin);
832 }
833
834 // If all branches lead to uninitialized memory
835 if (!commonValueOrigin.has_value())
837
838 // If there is exactly one shared origin for all branches
839 if (commonValueOrigin->isKnown())
840 {
841 // The value origin is neither uninitialized nor unknown, so it must belong to a node
842 JLM_ASSERT(commonValueOrigin->node);
843
844 // Only return the origin if it is not inside one of the subregions
845 if (commonValueOrigin->node->region()->node() != gammaNode)
846 return *commonValueOrigin;
847 }
848
849 // The last value origin differs based on which branch is taken,
850 // or is inside one of the gamma subregions, so return the gamma node itself
851 return ValueOrigin::createGammaNodeOutput(*gammaNode);
852 }
853
854 // If we found an exit variable of a theta node, continue tracing on the inside
855 if (auto thetaNode = rvsdg::TryGetOwnerNode<rvsdg::ThetaNode>(tracedOutput))
856 {
857 const auto loopVar = thetaNode->MapOutputLoopVar(tracedOutput);
858
859 // We continue tracing from the loop var post, but we have not taken a back-edge to get there,
860 // so we keep passing the loopBackEdgeTaken parameter unmodified.
861 auto lastValueOrigin = getLastValueOriginBeforeInput(*loopVar.post, loopBackEdgeTaken);
862 if (!lastValueOrigin.isKnown())
864
865 // if the last value before the end of the theta subregion is the pre of the same theta,
866 // the loaded memory may be loop invariant, and tracing can continue from before the theta.
867 if (lastValueOrigin.kind == ValueOrigin::Kind::ThetaNodePre
868 && lastValueOrigin.node == thetaNode)
869 {
870 // A trace that assumes no back-edges have been taken may skip regions,
871 // so unless loopBackEdgeTaken=true, we must do an additional check
872
873 // No additional check needed
874 if (loopBackEdgeTaken)
875 return getLastValueOriginBeforeInput(*loopVar.input, true);
876
877 // Trace again, this time with loopBackEdgeTaken=true
878 lastValueOrigin = getLastValueOriginBeforeInput(*loopVar.post, true);
879 if (!lastValueOrigin.isKnown())
881
882 if (lastValueOrigin.kind == ValueOrigin::Kind::ThetaNodePre
883 && lastValueOrigin.node == thetaNode)
884 {
885 // The theta was determined to not affect the loaded value, so keep tracing.
886 // Since we are leaving a theta, we still let loopBackEdgeTaken=true
887 return getLastValueOriginBeforeInput(*loopVar.input, true);
888 }
889 }
890
891 // We ended up with some value origin inside the theta, so return theta output
892 // to signal that it needs to be routed out
894 lastValueOrigin.kind == ValueOrigin::Kind::Uninitialized
895 || lastValueOrigin.node->region() == thetaNode->subregion());
896 return ValueOrigin::createThetaNodeOutput(*thetaNode);
897 }
898
899 // If we found a loop pre variable in a theta node, trace both inside and outside
900 if (auto thetaNode = rvsdg::TryGetRegionParentNode<rvsdg::ThetaNode>(tracedOutput))
901 {
902 const auto loopVar = thetaNode->MapPreLoopVar(tracedOutput);
903
904 // Trace from the theta input first.
905 // When tracing from a theta input, we always set loopBackEdgeTaken=true
906 auto inputLastValueOrigin = getLastValueOriginBeforeInput(*loopVar.input, true);
907 if (!inputLastValueOrigin.isKnown())
909
910 // Since the loop value may also originte from a back-edge, add the back-edge to the list.
911 // Using a list prevents visiting the loop body multiple times during recursion.
912 loopVarPostsToTrace.insert(loopVar.post);
913
914 return ValueOrigin::createThetaNodePre(*thetaNode);
915 }
916
917 // Tracing reached something that is not handled, such as a function call
919 }
920
921public:
924 std::shared_ptr<const rvsdg::Type> loadedType;
926
927 // Used for statistics
930
931private:
932 // Variables used during tracing
933
937
938 // Map containing info about each store node relevant to value forwarding.
939 std::unordered_map<rvsdg::SimpleNode *, StoreNodeInfo> storeNodeInfo;
940 // Map containing info about each load node relevant to value forwarding.
941 std::unordered_map<rvsdg::SimpleNode *, LoadNodeInfo> loadNodeInfo;
942
943 /* The last value origin on the memory state chain before the given input.
944 * The boolean in the key is true if any loop back-edges have been taken.
945 */
946 std::unordered_map<
947 std::pair<rvsdg::Input *, bool>,
951
952 /* The last value origin before the given node.
953 * The boolean in the key is true if any loop back-edges have been taken.
954 * @see getLastValueOriginBeforeNode()
955 */
956 std::unordered_map<
957 std::pair<rvsdg::Node *, bool>,
961
962 /* The last value origin before the end of the given region.
963 * Note that it can be outside the region if no clobber occurs inside the region.
964 * The boolean in the key is true if any loop back-edges have been taken.
965 * @see getLastValueOriginInRegion()
966 */
967 std::unordered_map<
968 std::pair<rvsdg::Region *, bool>,
972
973 // When tracing reaches a loop var pre argument, tracing does not continue through the post.
974 // The loop var post result is instead added to this set, to ensure that tracing happens later.
975 // Only loop vars that have yet to be traced are added here.
977
978public:
979 // Variables used during routing
980
981 // During routing, at most one exit variable need to be created per gamma
982 std::unordered_map<rvsdg::GammaNode *, rvsdg::Output *> createdExitVars;
983 // During routing, at most one loop variable needs to be created per theta.
984 std::unordered_map<rvsdg::ThetaNode *, rvsdg::ThetaNode::LoopVar> createdLoopVars;
985 // During routing, loop variable posts are not routed immediately, but added to this queue
986 std::queue<rvsdg::Input *> unroutedLoopVarPosts;
987};
988
989void
991{
992 JLM_ASSERT(is<LoadNonVolatileOperation>(&loadNode));
993
994 if (LoadOperation::numMemoryStates(loadNode) == 0)
995 {
996 context_->numLoadsWithoutMemoryState++;
998 }
999 else
1000 {
1001 context_->numLoadsWithMemoryState++;
1003 }
1004}
1005
1006void
1008{
1009 JLM_ASSERT(is<LoadNonVolatileOperation>(&loadNode));
1011
1012 context_->statistics.startTracing();
1013 LoadTracingInfo loadTracingInfo(
1014 loadNode,
1015 context_->outputTracer,
1016 context_->aliasAnalysis,
1017 context_->regionPredicateTracer);
1018 const auto shouldForwardValueOrigins = loadTracingInfo.traceAllMemoryStateInputs();
1019 context_->statistics.stopTracing();
1020
1021 context_->storeAAResponses.addFromCounter(loadTracingInfo.storeAAResponses);
1022 context_->loadAAResponses.addFromCounter(loadTracingInfo.loadAAResponses);
1023
1024 if (shouldForwardValueOrigins)
1025 {
1026 context_->statistics.startForwarding();
1027 forwardValueOrigins(loadTracingInfo);
1028 context_->statistics.stopForwarding();
1029 }
1030}
1031
1032void
1034{
1035 JLM_ASSERT(is<LoadNonVolatileOperation>(&loadNode));
1037
1038 context_->statistics.startTracing();
1039 const auto tracedDelta = traceLoadWithoutMemoryStates(loadNode);
1040 context_->statistics.stopTracing();
1041 if (!tracedDelta.has_value())
1042 {
1043 return;
1044 }
1045
1046 context_->statistics.startForwarding();
1047 forwardLoadWithoutMemoryStates(loadNode, tracedDelta.value());
1048 context_->statistics.stopForwarding();
1049}
1050
1051std::optional<StoreValueForwarding::TracedDelta>
1053{
1054 JLM_ASSERT(is<LoadNonVolatileOperation>(&loadNode));
1056
1057 const auto & loadAddress = *LoadOperation::AddressInput(loadNode).origin();
1058 const auto [basePointer, gepConstantsOpt] = TracePointerOriginPrecise(loadAddress);
1059 if (!gepConstantsOpt.has_value())
1060 {
1061 return std::nullopt;
1062 }
1063
1064 const auto deltaNode = rvsdg::TryGetOwnerNode<rvsdg::DeltaNode>(*basePointer);
1065 if (!deltaNode)
1066 {
1067 return std::nullopt;
1068 }
1069
1070 context_->numLoadsTracedToDeltaNode++;
1071 return std::optional<TracedDelta>({ deltaNode, gepConstantsOpt.value() });
1072}
1073
1074namespace
1075{
1076
1077struct RegionSlice
1078{
1079 // Nodes are ordered according to their depth. Highest depth first.
1080 std::vector<rvsdg::Node *> nodes;
1081 util::HashSet<rvsdg::Output *> arguments;
1082};
1083
1084}
1085
1086static RegionSlice
1088{
1089 // FIXME: This code works perfectly to visit the nodes of a tree, but does not work if it is a DAG
1090 // as it would not guarantee that the nodes would be ordered according to their depth.
1091 std::function<void(rvsdg::Output &, RegionSlice &, util::HashSet<rvsdg::Node *> &)> compute =
1092 [&compute](
1093 rvsdg::Output & output,
1094 RegionSlice & regionSlice,
1096 {
1097 if (rvsdg::TryGetOwnerRegion(output))
1098 {
1099 regionSlice.arguments.insert(&output);
1100 return;
1101 }
1102
1103 auto & node = rvsdg::AssertGetOwnerNode<rvsdg::Node>(output);
1104 if (visited.Contains(&node))
1105 return;
1106
1107 regionSlice.nodes.push_back(&node);
1108 for (auto & input : node.Inputs())
1109 {
1110 compute(*input.origin(), regionSlice, visited);
1111 }
1112 };
1113
1114 RegionSlice regionSlice;
1116 compute(output, regionSlice, visited);
1117
1118 return regionSlice;
1119}
1120
1121static void
1123 rvsdg::Region & targetRegion,
1124 const RegionSlice & regionSlice,
1125 rvsdg::SubstitutionMap & substitutionMap)
1126{
1127 for (auto it = regionSlice.nodes.rbegin(); it != regionSlice.nodes.rend(); ++it)
1128 {
1129 auto node = *it;
1130 node->copy(&targetRegion, substitutionMap);
1131 }
1132}
1133
1134static rvsdg::Output &
1136{
1137 auto deltaNode = util::assertedCast<rvsdg::DeltaNode>(output.region()->node());
1138
1139 auto regionSlice = computeRegionSlice(output);
1140
1141 rvsdg::SubstitutionMap substitutionMap;
1142 for (auto oldArgument : regionSlice.arguments.Items())
1143 {
1144 auto ctxVar = deltaNode->MapBinderContextVar(*oldArgument);
1145 auto & newArgument = rvsdg::RouteToRegion(*ctxVar.input->origin(), targetRegion);
1146 substitutionMap.insert(oldArgument, &newArgument);
1147 }
1148
1149 copyRegionSlice(targetRegion, regionSlice, substitutionMap);
1150 return substitutionMap.lookup(output);
1151}
1152
1153static rvsdg::Output &
1155 const uint64_t elementOffsetInBytes,
1156 rvsdg::Output & output,
1157 rvsdg::Region & targetRegion,
1158 const std::shared_ptr<const rvsdg::Type> & loadedType)
1159{
1160 if (const auto node = rvsdg::TryGetOwnerNode<rvsdg::SimpleNode>(output))
1161 {
1163 node->GetOperation(),
1164 [&](const IntegerConstantOperation &) -> rvsdg::Output &
1165 {
1166 JLM_ASSERT(elementOffsetInBytes == 0);
1167 auto copiedOutput = &copyDeltaRegionSlice(output, targetRegion);
1168
1169 const auto loadBitType = util::assertedCast<const rvsdg::BitType>(loadedType.get());
1170 const auto copiedBitType =
1171 util::assertedCast<const rvsdg::BitType>(copiedOutput->Type().get());
1172 if (copiedBitType->nbits() == loadBitType->nbits())
1173 {
1174 return *copiedOutput;
1175 }
1176
1177 if (loadBitType->nbits() < copiedBitType->nbits())
1178 {
1179 return *TruncOperation::createNode(*copiedOutput, loadedType).output(0);
1180 }
1181
1182 // FIXME: In this case, we would need to concat multiple integers.
1183 return *copiedOutput;
1184 },
1185 [&](const ConstantFP &) -> rvsdg::Output &
1186 {
1187 JLM_ASSERT(elementOffsetInBytes == 0);
1188 return copyDeltaRegionSlice(output, targetRegion);
1189 },
1191 {
1192 JLM_ASSERT(elementOffsetInBytes == 0);
1193 return copyDeltaRegionSlice(output, targetRegion);
1194 },
1196 {
1197 JLM_ASSERT(elementOffsetInBytes == 0);
1198 return copyDeltaRegionSlice(output, targetRegion);
1199 },
1200 [&](const IntToPtrOperation &) -> rvsdg::Output &
1201 {
1202 JLM_ASSERT(elementOffsetInBytes == 0);
1203 return copyDeltaRegionSlice(output, targetRegion);
1204 },
1205 [&](const GetElementPtrOperation &) -> rvsdg::Output &
1206 {
1207 JLM_ASSERT(elementOffsetInBytes == 0);
1208 return copyDeltaRegionSlice(output, targetRegion);
1209 },
1211 {
1212 if (is<PointerType>(loadedType))
1213 {
1214 return *ConstantPointerNullOperation::createNode(targetRegion).output(0);
1215 }
1216
1217 if (const auto bitType = std::dynamic_pointer_cast<const rvsdg::BitType>(loadedType))
1218 {
1219 return *IntegerConstantOperation::Create(targetRegion, bitType->nbits(), 0).output(0);
1220 }
1221
1222 if (const auto floatType = std::dynamic_pointer_cast<const FloatingPointType>(loadedType))
1223 {
1224 const auto zero = ConstantFP::getZeroRepresentation(floatType->size());
1225 return *ConstantFP::createNode(targetRegion, floatType->size(), zero).output(0);
1226 }
1227
1228 if (const auto vectorType = std::dynamic_pointer_cast<const FixedVectorType>(loadedType))
1229 {
1230 return *ConstantAggregateZeroOperation::createNode(targetRegion, vectorType).output(0);
1231 }
1232
1233 throw std::logic_error("Unsupported load type");
1234 },
1235 [&](const ConstantArrayOperation & constantArrayOperation) -> rvsdg::Output &
1236 {
1237 const auto arrayType = constantArrayOperation.type();
1238 const auto elementSizeInBytes = GetTypeAllocSize(*arrayType->GetElementType());
1239
1240 const auto index = elementOffsetInBytes / elementSizeInBytes;
1241 return copyDeltaElement(
1242 elementOffsetInBytes - (elementSizeInBytes * index),
1243 *node->input(index)->origin(),
1244 targetRegion,
1245 loadedType);
1246 },
1247 [&](const ConstantDataArrayOperation & constantDataArrayOperation) -> rvsdg::Output &
1248 {
1249 const auto arrayType = constantDataArrayOperation.type();
1250 const auto elementSizeInBytes = GetTypeAllocSize(*arrayType->GetElementType());
1251
1252 const auto index = elementOffsetInBytes / elementSizeInBytes;
1253 return copyDeltaElement(
1254 elementOffsetInBytes - (elementSizeInBytes * index),
1255 *node->input(index)->origin(),
1256 targetRegion,
1257 loadedType);
1258 },
1259 [&](const ConstantStructOperation & constantStruct) -> rvsdg::Output &
1260 {
1261 auto & structType = constantStruct.type();
1262
1263 for (size_t n = 0; n < structType.numElements(); ++n)
1264 {
1265 auto fieldOffsetInBytes = structType.GetFieldOffset(n);
1266
1267 if (fieldOffsetInBytes == elementOffsetInBytes)
1268 {
1269 return copyDeltaElement(0, *node->input(n)->origin(), targetRegion, loadedType);
1270 }
1271
1272 if (fieldOffsetInBytes > elementOffsetInBytes)
1273 {
1274 fieldOffsetInBytes = structType.GetFieldOffset(n - 1);
1275 return copyDeltaElement(
1276 elementOffsetInBytes - fieldOffsetInBytes,
1277 *node->input(n - 1)->origin(),
1278 targetRegion,
1279 loadedType);
1280 }
1281 }
1282
1283 const auto lastElementIndex = structType.numElements() - 1;
1284 const auto fieldOffsetInBytes = structType.GetFieldOffset(lastElementIndex);
1285 JLM_ASSERT(fieldOffsetInBytes <= elementOffsetInBytes);
1286 return copyDeltaElement(
1287 elementOffsetInBytes - fieldOffsetInBytes,
1288 *node->input(lastElementIndex)->origin(),
1289 targetRegion,
1290 loadedType);
1291 },
1292 [&]() -> rvsdg::Output &
1293 {
1294 throw std::logic_error("Unsupported operation: " + node->DebugString());
1295 });
1296 }
1297
1299 {
1300 JLM_ASSERT(elementOffsetInBytes == 0);
1301 return copyDeltaRegionSlice(output, targetRegion);
1302 }
1303
1304 throw std::logic_error("Unsupported output owner");
1305}
1306
1307void
1308StoreValueForwarding::forwardLoadWithoutMemoryStates(
1309 rvsdg::SimpleNode & loadNode,
1310 const TracedDelta & tracedDelta)
1311{
1312 JLM_ASSERT(is<LoadNonVolatileOperation>(&loadNode));
1313 JLM_ASSERT(LoadOperation::numMemoryStates(loadNode) == 0);
1314 const auto loadOperation =
1315 dynamic_cast<const LoadNonVolatileOperation *>(&loadNode.GetOperation());
1316 auto & deltaResultOrigin = *tracedDelta.deltaNode->result().origin();
1317
1318 if (tracedDelta.gepConstants.size() > 1)
1319 {
1320 // FIXME:
1321 return;
1322 }
1323
1324 JLM_ASSERT(tracedDelta.gepConstants.size() <= 1);
1325 const uint64_t offsetInBytes =
1326 tracedDelta.gepConstants.empty() ? 0 : tracedDelta.gepConstants.front().getOffsetInBytes();
1327 auto & newOutput = copyDeltaElement(
1328 offsetInBytes,
1329 deltaResultOrigin,
1330 *loadNode.region(),
1331 loadOperation->GetLoadedType());
1332
1333 if (*loadOperation->GetLoadedType() != *newOutput.Type())
1334 {
1335 // FIXME:
1336 return;
1337 }
1338
1339 LoadOperation::LoadedValueOutput(loadNode).divert_users(&newOutput);
1340 context_->numForwardedLoadsWithoutMemoryState++;
1341}
1342
1343// Performs StoreValueForwarding to the load node represented by the tracingInfo.
1344void
1345StoreValueForwarding::forwardValueOrigins(LoadTracingInfo & tracingInfo)
1346{
1347 context_->numForwardedLoadsWithMemoryState++;
1348
1349 auto & loadNode = tracingInfo.loadNode;
1350 auto & loadedValueOutput = LoadOperation::LoadedValueOutput(loadNode);
1351 auto & loadRegion = *loadNode.region();
1352
1353 // Since tracing starts from the load node, we know no loop back-edges have been taken
1354 const auto lastValueOrigin = tracingInfo.getLastValueOriginBeforeNode(loadNode, false);
1355 JLM_ASSERT(lastValueOrigin.has_value() && lastValueOrigin->isKnown());
1356 auto & valueOriginOutput = getValueOriginOutput(*lastValueOrigin, loadRegion, tracingInfo);
1357
1358 // Fixup all loop variables that were created during the above routing
1359 connectUnroutedLoopPosts(tracingInfo);
1360
1361 // Divert users of the load to the routed value origin output
1362 loadedValueOutput.divert_users(&valueOriginOutput);
1363
1364 // Make the load node dead by routing all memory state users around it
1365 for (auto & memoryStateOutput : LoadNonVolatileOperation::MemoryStateOutputs(loadNode))
1366 {
1367 auto & memoryStateInput =
1368 LoadNonVolatileOperation::MapMemoryStateOutputToInput(memoryStateOutput);
1369 memoryStateOutput.divert_users(memoryStateInput.origin());
1370 }
1371}
1372
1373// Gets an rvsdg output providing the output referenced by the value origin.
1375StoreValueForwarding::getValueOriginOutput(
1376 ValueOrigin valueOrigin,
1377 rvsdg::Region & targetRegion,
1378 LoadTracingInfo & tracingInfo)
1379{
1380 JLM_ASSERT(valueOrigin.isKnown());
1381
1382 if (valueOrigin.kind == ValueOrigin::Kind::Uninitialized)
1383 {
1384 // When forwarding uninitialized memory, create an undef node
1385 return *UndefValueOperation::Create(targetRegion, tracingInfo.loadedType);
1386 }
1387
1388 if (valueOrigin.kind == ValueOrigin::Kind::StoreNode)
1389 {
1390 // For store nodes, the stored value is the origin of the node's value input
1391 auto & storedValue = *StoreOperation::StoredValueInput(*valueOrigin.node).origin();
1392 JLM_ASSERT(*storedValue.Type() == *tracingInfo.loadedType);
1393 return routeOutputToRegion(storedValue, targetRegion);
1394 }
1395
1396 if (valueOrigin.kind == ValueOrigin::Kind::LoadNode)
1397 {
1398 // For load nodes, the load output is the value origin
1399 auto & loadedValue = LoadOperation::LoadedValueOutput(*valueOrigin.node);
1400 JLM_ASSERT(*loadedValue.Type() == *tracingInfo.loadedType);
1401 return routeOutputToRegion(loadedValue, targetRegion);
1402 }
1403
1404 // For gamma nodes, create an exit variable by finding the stored value in each of its regions
1405 if (valueOrigin.kind == ValueOrigin::Kind::GammaNodeOutput)
1406 {
1407 auto gammaNode = dynamic_cast<rvsdg::GammaNode *>(valueOrigin.node);
1408 JLM_ASSERT(gammaNode);
1409
1410 // We only need to create at most one exit variable per gamma, so memoize it
1411 auto [it, inserted] = tracingInfo.createdExitVars.emplace(gammaNode, nullptr);
1412 if (inserted)
1413 {
1414 std::vector<rvsdg::Output *> lastValueOriginPerSubregion;
1415 for (auto & subregion : gammaNode->Subregions())
1416 {
1417 // We only create one gamma exit variable for each load,
1418 // so if tracing ever reached the gamma after following a back-edge,
1419 // we can not use value origins traced under the assumption that no back-edges were taken.
1420 // If the gamma output was never reached after tracing through a back-edge,
1421 // the getter function will fall back to using value origins traced under the assumption
1422 // that no back-edges have been followed, which is then a correct assumption.
1423 auto lastValueOrigin = tracingInfo.getLastValueOriginInRegion(subregion, true);
1424 JLM_ASSERT(lastValueOrigin.has_value() && lastValueOrigin->isKnown());
1425 auto & valueOriginOutput = getValueOriginOutput(*lastValueOrigin, subregion, tracingInfo);
1426 lastValueOriginPerSubregion.push_back(&valueOriginOutput);
1427 }
1428
1429 auto exitVar = gammaNode->AddExitVar(lastValueOriginPerSubregion);
1430 it->second = exitVar.output;
1431 }
1432 JLM_ASSERT(it->second);
1433 JLM_ASSERT(*it->second->Type() == *tracingInfo.loadedType);
1434 return routeOutputToRegion(*it->second, targetRegion);
1435 }
1436
1437 // For theta nodes, create a loop variable
1438 if (valueOrigin.kind == ValueOrigin::Kind::ThetaNodeOutput
1439 || valueOrigin.kind == ValueOrigin::Kind::ThetaNodePre)
1440 {
1441 auto thetaNode = dynamic_cast<rvsdg::ThetaNode *>(valueOrigin.node);
1442 JLM_ASSERT(thetaNode);
1443
1444 // If the loop variable has not yet been created in this theta, create it now
1445 auto loopVarSlot = tracingInfo.createdLoopVars.find(thetaNode);
1446 if (loopVarSlot == tracingInfo.createdLoopVars.end())
1447 {
1448 rvsdg::Output * initialValue = nullptr;
1449
1450 // Get the last value origin before the theta.
1451 // Since we only create one loop variable for each load we forward,
1452 // use the conservative assumption that back-edges may have been followed.
1453 // If tracing never left the theta after following a back-edge,
1454 // the getter function falls back to using the value origin found under the asumption
1455 // that no back-edges have been followed, which is the a correct assumption.
1456 auto lastValueOrigin = tracingInfo.getLastValueOriginBeforeNode(*thetaNode, true);
1457 if (lastValueOrigin.has_value())
1458 {
1459 JLM_ASSERT(lastValueOrigin->isKnown());
1460 auto & outerRegion = *thetaNode->region();
1461 initialValue = &getValueOriginOutput(*lastValueOrigin, outerRegion, tracingInfo);
1462 }
1463 else
1464 {
1465 // Tracing never reached the loop entry, so the value must be defined inside the loop.
1466 // The created loop variable can therefore take undef as its input.
1467 initialValue = UndefValueOperation::Create(*thetaNode->region(), tracingInfo.loadedType);
1468 }
1469
1470 // Create the loop variable and add it to the map
1471 JLM_ASSERT(initialValue);
1472 JLM_ASSERT(*initialValue->Type() == *tracingInfo.loadedType);
1473 auto loopVar = thetaNode->AddLoopVar(initialValue);
1474 auto [it, inserted] = tracingInfo.createdLoopVars.emplace(thetaNode, loopVar);
1475 JLM_ASSERT(inserted);
1476 loopVarSlot = it;
1477
1478 // To prevent looping during routing, the created loop variable's post is added to a
1479 // queue of loop variable posts that are routed properly later.
1480 tracingInfo.unroutedLoopVarPosts.push(loopVar.post);
1481 }
1482
1483 // Return the correct output, depending on the query kind
1484 switch (valueOrigin.kind)
1485 {
1486 case ValueOrigin::Kind::ThetaNodePre:
1487 return routeOutputToRegion(*loopVarSlot->second.pre, targetRegion);
1488 case ValueOrigin::Kind::ThetaNodeOutput:
1489 return routeOutputToRegion(*loopVarSlot->second.output, targetRegion);
1490 default:
1491 JLM_UNREACHABLE("Unknown StoreValueOrigin kind");
1492 }
1493 }
1494
1495 JLM_UNREACHABLE("Unknown StoreValueOriginKind");
1496}
1497
1498void
1499StoreValueForwarding::connectUnroutedLoopPosts(LoadTracingInfo & tracingInfo)
1500{
1501 // The process of handling all created loop variables may also create more loop variables,
1502 // so keep going until the queue is empty.
1503 while (!tracingInfo.unroutedLoopVarPosts.empty())
1504 {
1505 auto post = tracingInfo.unroutedLoopVarPosts.front();
1506 tracingInfo.unroutedLoopVarPosts.pop();
1507
1508 // We only create one loop variable per theta,
1509 // so if tracing ever entered the theta after following a back-edge,
1510 // we conservatively use the value origin found with loopBackEdgeTaken=true.
1511 // If the theta subregion was never traced after following a back-edge,
1512 // it falls back to using the value origin found assuming no back-edges have been followed.
1513 auto lastValueOrigin = tracingInfo.getLastValueOriginInRegion(*post->region(), true);
1514 JLM_ASSERT(lastValueOrigin.has_value() && lastValueOrigin->isKnown());
1515 auto & origin = getValueOriginOutput(*lastValueOrigin, *post->region(), tracingInfo);
1516 post->divert_to(&origin);
1517 }
1518}
1519
1521StoreValueForwarding::routeOutputToRegion(rvsdg::Output & output, rvsdg::Region & region)
1522{
1523 if (output.region() == &region)
1524 return output;
1525
1526 JLM_ASSERT(rvsdg::Region::isAncestor(region, *output.region()));
1527
1528 if (region.IsRootRegion())
1529 JLM_UNREACHABLE("root region reached during attempt at routing output into region");
1530
1531 if (auto gammaNode = dynamic_cast<rvsdg::GammaNode *>(region.node()))
1532 {
1533 // Route the output all the way to just outside the gamma first
1534 auto & outerOutput = routeOutputToRegion(output, *gammaNode->region());
1535
1536 // If the outer output already has a corresponding EntryVar, return it
1537 if (auto it = context_->routedOutputs.find({ &outerOutput, &region });
1538 it != context_->routedOutputs.end())
1539 {
1540 // The output in the map key may have been deleted, and had its address re-used, so double
1541 // check
1542 auto & branchArgument = *it->second;
1543 if (gammaNode->mapBranchArgumentToInput(branchArgument).origin() == &outerOutput)
1544 {
1545 JLM_ASSERT(*branchArgument.Type() == *output.Type());
1546 return branchArgument;
1547 }
1548 }
1549
1550 // Create an EntryVar for the output, add all branch arguments to the cache
1551 auto entryVar = gammaNode->AddEntryVar(&outerOutput);
1552 for (auto branchArgument : entryVar.branchArgument)
1553 {
1554 context_->routedOutputs[{ &outerOutput, branchArgument->region() }] = branchArgument;
1555 }
1556
1557 return *entryVar.branchArgument[region.index()];
1558 }
1559
1560 if (auto thetaNode = dynamic_cast<rvsdg::ThetaNode *>(region.node()))
1561 {
1562 // Route the output all the way to just outside the theta first
1563 auto & outerOutput = routeOutputToRegion(output, *thetaNode->region());
1564
1565 // If the outer output already has a corresponding invariant loop variable, return it
1566 if (auto it = context_->routedOutputs.find({ &outerOutput, &region });
1567 it != context_->routedOutputs.end())
1568 {
1569 // The output in the map key may have been deleted, and had its address re-used, so double
1570 // check
1571 auto & loopVarPre = *it->second;
1572 if (thetaNode->MapPreLoopVar(loopVarPre).input->origin() == &outerOutput)
1573 {
1574 JLM_ASSERT(*loopVarPre.Type() == *output.Type());
1575 return loopVarPre;
1576 }
1577 }
1578
1579 // Create an invariant LoopVar for the output and add it to the cache
1580 auto loopVar = thetaNode->AddLoopVar(&outerOutput);
1581 context_->routedOutputs[{ &outerOutput, &region }] = loopVar.pre;
1582 return *loopVar.pre;
1583 }
1584
1585 JLM_UNREACHABLE("routeOutputToRegion reached unhandled structural node");
1586}
1587
1588static std::unique_ptr<aa::AliasAnalysis>
1590{
1591 auto localAA = std::make_unique<aa::LocalAliasAnalysis>();
1592
1594 {
1595 // Setting the trace collection size to 1 limits the analysis to only the most trivial tracing
1596 localAA->setMaxTraceCollectionSize(1);
1597 }
1598
1599 if (!ENABLE_PTGAA)
1600 return localAA;
1601
1602 aa::Andersen andersen;
1603 auto ptg = andersen.Analyze(module, statisticsCollector);
1604 auto ptgAA = std::make_unique<aa::PointsToGraphAliasAnalysis>(std::move(ptg));
1605
1606 return std::make_unique<aa::ChainedAliasAnalysis>(std::move(localAA), std::move(ptgAA));
1607}
1608
1609void
1610StoreValueForwarding::Run(
1611 rvsdg::RvsdgModule & module,
1613{
1614 auto aliasAnalysis = createAliasAnalysis(module, statisticsCollector);
1615 auto statistics = Statistics::Create(module.SourceFilePath().value());
1616
1617 context_ = std::make_unique<Context>(*aliasAnalysis, *statistics);
1618
1619 statistics->StartStatistics();
1620
1621 auto & rvsdg = module.Rvsdg();
1622 traverseInterProceduralRegion(rvsdg.GetRootRegion());
1623
1624 statistics->StopStatistics(
1625 context_->numLoadsWithMemoryState,
1626 context_->numLoadsWithoutMemoryState,
1627 context_->numLoadsTracedToDeltaNode,
1628 context_->numForwardedLoadsWithMemoryState,
1629 context_->numForwardedLoadsWithoutMemoryState,
1630 context_->storeAAResponses,
1631 context_->loadAAResponses);
1632 statisticsCollector.CollectDemandedStatistics(std::move(statistics));
1633
1634 // Discard internal state to free up memory after we are done
1635 context_.reset();
1636}
1637}
static jlm::util::StatisticsCollector statisticsCollector
Definition PullTests.cpp:17
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
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)
rvsdg::AlternativeRegionPredicateTracer & regionPredicateTracer
std::queue< rvsdg::Input * > unroutedLoopVarPosts
aa::AliasAnalysis::AliasQueryResponse queryAliasAnalysisWithLoad(rvsdg::SimpleNode &otherLoadNode)
LoadTracingInfo(rvsdg::SimpleNode &loadNode, OutputTracer &tracer, aa::AliasAnalysis &aliasAnalysis, rvsdg::AlternativeRegionPredicateTracer &regionPredicateTracer)
std::shared_ptr< const rvsdg::Type > loadedType
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(const bool traceThroughLoadedStates)
Definition Trace.hpp:50
static rvsdg::Input & StoredValueInput(const rvsdg::Node &node) noexcept
Definition Store.hpp:99
static rvsdg::Input & MapMemoryStateOutputToInput(const rvsdg::Output &output)
Definition Store.hpp:167
static rvsdg::Input & AddressInput(const rvsdg::Node &node) noexcept
Definition Store.hpp:90
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
class providing guarantees about unreachability of regions from other regions.
bool isReachableFromRegion(Region &targetRegion, Region &originRegion)
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 setRegionPredicateCheckingEnabled(bool value) noexcept
Definition Trace.hpp:165
void setInvarianceCaching(bool value) noexcept
Definition Trace.hpp:194
Output & trace(Output &output)
Definition Trace.cpp:21
void setStructuralNodePolicy(StructuralNodePolicy value) noexcept
Definition Trace.hpp:106
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
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)
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)
rvsdg::Output & traceOutput(rvsdg::Output &output, bool mayEnterSubregions, const rvsdg::Region *withinRegion)
Definition Trace.cpp:84
TracedPointerOrigin TracePointerOriginPrecise(const rvsdg::Output &p)
Definition Trace.cpp:180
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
rvsdg::AlternativeRegionPredicateTracer regionPredicateTracer
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)