Jlm
Loading...
Searching...
No Matches
CommonNodeElimination.cpp
Go to the documentation of this file.
1/*
2 * Copyright 2017 Nico Reißmann <nico.reissmann@gmail.com>
3 * Copyright 2025 Håvard Krogstie <krogstie.havard@gmail.com>
4 * See COPYING for terms of redistribution.
5 */
6
11#include <jlm/rvsdg/gamma.hpp>
13#include <jlm/rvsdg/Phi.hpp>
14#include <jlm/rvsdg/theta.hpp>
16#include <jlm/util/common.hpp>
17#include <jlm/util/Hash.hpp>
19#include <jlm/util/time.hpp>
20
21#include <map>
22#include <unordered_map>
23
24namespace jlm::llvm
25{
26
28{
29 const char * MarkTimerLabel_ = "MarkTime";
30 const char * DivertTimerLabel_ = "DivertTime";
31
32public:
33 ~Statistics() override = default;
34
35 explicit Statistics(const util::FilePath & sourceFile)
36 : util::Statistics(Statistics::Id::CommonNodeElimination, sourceFile)
37 {}
38
39 void
40 startMarkStatistics(const rvsdg::Graph & graph) noexcept
41 {
42 AddMeasurement(Label::NumRvsdgInputsBefore, rvsdg::ninputs(&graph.GetRootRegion()));
44 }
45
46 void
48 {
50 }
51
52 void
57
58 void
59 endDivertStatistics(const rvsdg::Graph & graph) noexcept
60 {
61 AddMeasurement(Label::NumRvsdgInputsAfter, rvsdg::ninputs(&graph.GetRootRegion()));
63 }
64
65 static std::unique_ptr<Statistics>
66 Create(const util::FilePath & sourceFile)
67 {
68 return std::make_unique<Statistics>(sourceFile);
69 }
70};
71
83{
84public:
100 {
102 : leader(&leader)
103 {}
104
105 // The set leader. Never changes.
106 // Once an output has become a leader, it will never be a follower.
108 // The set of followers. Can both grow and shrink during the marking phase.
109 // Does not include the leader.
111 };
112
113 using CongruenceSetIndex = size_t;
114 static constexpr auto NoCongruenceSetIndex = std::numeric_limits<CongruenceSetIndex>::max();
115
121 [[nodiscard]] CongruenceSetIndex
123 {
124 return sets_.size();
125 }
126
133 [[nodiscard]] CongruenceSetIndex
134 tryGetSetFor(const rvsdg::Output & output) const
135 {
136 if (const auto it = congruenceSetMapping_.find(&output); it != congruenceSetMapping_.end())
137 {
138 return it->second;
139 }
141 }
142
149 [[nodiscard]] CongruenceSetIndex
150 getSetFor(const rvsdg::Output & output) const
151 {
152 const auto index = tryGetSetFor(output);
153 if (index == NoCongruenceSetIndex)
154 throw std::logic_error("Output does not belong to a congruence set");
155 return index;
156 }
157
163 [[nodiscard]] bool
164 hasSet(const rvsdg::Output & output) const
165 {
166 return tryGetSetFor(output) != NoCongruenceSetIndex;
167 }
168
178 {
179 // The index of the new set, if this operation actually creates one
180 auto nextSet = sets_.size();
181 auto [it, added] = congruenceSetMapping_.try_emplace(&leader, nextSet);
182
183 if (!added)
184 {
185 // If the leader already has its own set, we are done
186 if (sets_[it->second].leader == &leader)
187 {
188 return it->second;
189 }
190
191 // Remove the output from the congruence set it is following
192 sets_[it->second].followers.Remove(&leader);
193 it->second = nextSet;
194 }
195
196 // Create the new set, lead by \p leader
197 sets_.emplace_back(leader);
198 return nextSet;
199 }
200
206 const rvsdg::Output &
208 {
209 JLM_ASSERT(index < sets_.size());
210 return *sets_[index].leader;
211 }
212
221 void
223 {
224 JLM_ASSERT(index < sets_.size());
225
226 const bool newFollower = sets_[index].followers.insert(&follower);
227
228 // If the follower is already following the correct set, do nothing
229 if (!newFollower)
230 return;
231
232 const auto [it, added] = congruenceSetMapping_.try_emplace(&follower, index);
233
234 // If the follower already belonged to a congruence set, remove it from the old set
235 if (!added)
236 {
237 JLM_ASSERT(it->second != index);
238
239 if (sets_[it->second].leader == &follower)
240 throw std::logic_error("Cannot turn a leader into a follower");
241
242 const bool removed = sets_[it->second].followers.Remove(&follower);
243 JLM_ASSERT(removed);
244
245 it->second = index;
246 }
247 }
248
256 {
257 JLM_ASSERT(index < sets_.size());
258 return sets_[index].followers;
259 }
260
261private:
262 // The list of congruence sets
263 std::vector<CongruenceSet> sets_;
264 // A mapping from each output to the congruence set it belongs to, either as leader or follower
265 std::unordered_map<const rvsdg::Output *, CongruenceSetIndex> congruenceSetMapping_;
266};
267
277static bool
279 const rvsdg::Output & o1,
280 const rvsdg::Output & o2,
282{
283 if (*o1.Type() != *o2.Type())
284 return false;
285
286 const auto o1Set = context.getSetFor(o1);
287 const auto o2Set = context.getSetFor(o2);
288
289 return o1Set == o2Set;
290}
291
304static bool
306 const rvsdg::Node & node1,
307 const rvsdg::Node & node2,
309{
310 const auto simpleNode1 = dynamic_cast<const rvsdg::SimpleNode *>(&node1);
311 const auto simpleNode2 = dynamic_cast<const rvsdg::SimpleNode *>(&node2);
312 if (!simpleNode1 || !simpleNode2)
313 return false;
314
315 if (simpleNode1->ninputs() != simpleNode2->ninputs())
316 return false;
317
318 if (simpleNode1->GetOperation() != simpleNode2->GetOperation())
319 return false;
320
321 // For each pair of corresponding inputs of simpleNode1 and simpleNode2,
322 // they must have origins that are congruent
323 for (auto & input : simpleNode1->Inputs())
324 {
325 const auto origin1 = input.origin();
326 const auto origin2 = simpleNode2->input(input.index())->origin();
327 if (!areOutputsCongruent(*origin1, *origin2, context))
328 return false;
329 }
330
331 return true;
332}
333
339void
341{
342 for (auto & output : leader.Outputs())
343 {
344 context.getOrCreateSetForLeader(output);
345 }
346}
347
358void
360 const rvsdg::Node & leader,
361 const rvsdg::Node & follower,
363{
364 JLM_ASSERT(leader.noutputs() == follower.noutputs());
365 JLM_ASSERT(leader.region() == follower.region());
366
367 for (size_t i = 0; i < leader.noutputs(); i++)
368 {
369 const auto & leaderOutput = *leader.output(i);
370 const auto & followerOutput = *follower.output(i);
371 const auto leaderSet = context.getSetFor(leaderOutput);
372 context.addFollower(leaderSet, followerOutput);
373 }
374}
375
384[[nodiscard]] const rvsdg::SimpleNode *
386{
387 // Nodes with 0 outputs are never congruent with anything, so let the node be its own leader.
388 if (node.noutputs() == 0)
389 return &node;
390
391 // Check the congruence set of the first output
392 const auto output0Set = context.tryGetSetFor(*node.output(0));
393 // If the output has not gotten a congruence set yet, the node has yet to be marked
395 return nullptr;
396
397 // A simple node can only be congruent with other simple nodes
398 const auto & output0Leader = context.getLeader(output0Set);
399 const auto & leaderNode = rvsdg::AssertGetOwnerNode<rvsdg::SimpleNode>(output0Leader);
400 JLM_ASSERT(leaderNode.noutputs() == node.noutputs());
401 return &leaderNode;
402}
403
404using TopNodeLeaderList = std::vector<const rvsdg::Node *>;
405
414static void
416 const rvsdg::SimpleNode & node,
417 TopNodeLeaderList & leaders,
419{
420 // If the node has already been marked, check if it is still congruent with its leader
421 if (const auto existingLeaderNode = tryGetLeaderNode(node, context))
422 {
423 // We are our own leader, nothing to check
424 if (existingLeaderNode == &node)
425 {
426 leaders.push_back(&node);
427 return;
428 }
429
430 // Check if we are still congruent with the leader
431 if (checkNodesCongruent(*existingLeaderNode, node, context))
432 {
433 return;
434 }
435
436 // This node is no longer congruent with its leader, continue looking for a new leader
437 }
438
439 // TODO: Use some sort of hashing to make this not O(n * m)
440 // where n is the number of outputs and m is the number of congruence sets
441
442 // Check if the node is congruent with any existing leader in the TopNodeLeaderList
443 for (auto leader : leaders)
444 {
445 if (checkNodesCongruent(node, *leader, context))
446 {
447 markNodesAsCongruent(*leader, node, context);
448 return;
449 }
450 }
451
452 // No existing leader top node found, create new congruence sets for each of node's outputs
453 markNodeAsLeader(node, context);
454 leaders.push_back(&node);
455}
456
465static void
467{
468 // This function should never be called for TopNodes
469 JLM_ASSERT(node.ninputs() > 0);
470
471 // If node is already in a congruence set, check that it actually belongs there
472 if (const auto leaderNode = tryGetLeaderNode(node, context))
473 {
474 // If node is its own leader, it definitely belongs, and we are done
475 if (leaderNode == &node)
476 return;
477
478 // Double-check that we are congruent with our leader
479 if (checkNodesCongruent(node, *leaderNode, context))
480 return;
481
482 // Otherwise we need to continue looking for a new leader
483 }
484
485 // This function looks at all nodes that take the given output as the origin for its first input.
486 // If the other node is its own leader, and the current node is congruent with it,
487 // the current node becomes a follower.
488 const auto tryFindCongruentUserOf = [&](const rvsdg::Output & output) -> bool
489 {
490 // TODO: It would be possible to maintain a list of only users that are leader nodes,
491 // to avoid needing to check every user
492 for (auto & user : output.Users())
493 {
494 if (user.index() != 0)
495 continue;
496
497 const auto otherNode = rvsdg::TryGetOwnerNode<rvsdg::SimpleNode>(user);
498 if (!otherNode)
499 continue;
500
501 // Do not compare against ourselves
502 if (otherNode == &node)
503 continue;
504
505 // Only consider other nodes that are leaders
506 auto otherNodeLeader = tryGetLeaderNode(*otherNode, context);
507 if (otherNode != otherNodeLeader)
508 continue;
509
510 if (checkNodesCongruent(node, *otherNode, context))
511 {
512 // When nodes are congruent, they should always have the same amount of outputs
513 JLM_ASSERT(node.noutputs() == otherNode->noutputs());
514
515 markNodesAsCongruent(*otherNode, node, context);
516 return true;
517 }
518 }
519 return false;
520 };
521
522 // Use the origin of the first input to find other potential candidates
523 const auto origin = node.input(0)->origin();
524 const auto origin0Set = context.getSetFor(*origin);
525 const auto & origin0Leader = context.getLeader(origin0Set);
526 const auto & origin0Followers = context.getFollowers(origin0Set);
527 if (tryFindCongruentUserOf(origin0Leader))
528 return;
529 for (auto follower : origin0Followers.Items())
530 {
531 if (tryFindCongruentUserOf(*follower))
532 return;
533 }
534
535 // If we got here, no congruent node was found, so make the node its own leader
536 markNodeAsLeader(node, context);
537}
538
539static void
540markRegion(const rvsdg::Region &, CommonNodeElimination::Context & context);
541
547static void
549{
550 // FIXME: Multiple imports with identical names could in theory be aliases.
551 // For now this function ignores that and makes all imports distinct. This is sound
552 for (auto argument : region.Arguments())
553 {
554 context.getOrCreateSetForLeader(*argument);
555 }
556}
557
571static bool
573 const rvsdg::Region & region,
574 const std::vector<CommonNodeElimination::Context::CongruenceSetIndex> & partitions,
576{
577 JLM_ASSERT(region.narguments() == partitions.size());
578
579 const auto numCongruenceSets = context.numCongruenceSets();
580
581 // Keys in the map are (old congruence set index, provided partition key)
582 // Values in the map are the new congruence set indices
583 std::map<
584 std::pair<
588 newSets;
589 for (auto argument : region.Arguments())
590 {
591 const auto currentPartition = context.tryGetSetFor(*argument);
592 const auto key = std::make_pair(currentPartition, partitions[argument->index()]);
593
594 // If this argument is the first with the given key, it should be a leader
595 // otherwise it should be a follower
596
597 if (const auto it = newSets.find(key); it != newSets.end())
598 {
599 // This argument should be a follower of the given congruence set
600 const auto toFollow = it->second;
601
602 // If we are already a follower, we are done
603 if (currentPartition == toFollow)
604 continue;
605
606 // Start following our leader
607 context.addFollower(toFollow, *argument);
608 }
609 else
610 {
611 // This argument should be the leader of its congruence set
612 newSets.emplace(key, context.getOrCreateSetForLeader(*argument));
613 }
614 }
615
616 // Return true iff any new congruence sets were created
617 return context.numCongruenceSets() != numCongruenceSets;
618}
619
637static bool
639 const rvsdg::StructuralNode & node,
641{
642 bool anyChanges = false;
643
644 for (auto & subregion : node.Subregions())
645 {
646 if (subregion.narguments() == 0)
647 {
648 markRegion(subregion, context);
649 }
650 else
651 {
652 // create a partitioning of the region arguments
653 std::vector<size_t> partitions(subregion.narguments());
654 // Arguments that do not belong to any input are given partition keys higher than any real
655 // index
656 size_t nextUniquePartitionKey = context.numCongruenceSets();
657
658 for (const auto argument : subregion.Arguments())
659 {
660 if (const auto input = argument->input())
661 {
662 // If the argument corresponds to an input, use the partition key of the input
663 partitions[argument->index()] = context.getSetFor(*input->origin());
664 }
665 else
666 {
667 // Otherwise make sure the argument is not partitioned with any other argument
668 partitions[argument->index()] = nextUniquePartitionKey++;
669 }
670 }
671
672 if (partitionArguments(subregion, partitions, context))
673 {
674 anyChanges = true;
675 markRegion(subregion, context);
676 }
677 }
678 }
679
680 return anyChanges;
681}
682
693static std::optional<CommonNodeElimination::Context::CongruenceSetIndex>
697{
698 std::optional<CommonNodeElimination::Context::CongruenceSetIndex> sharedCongruenceSet;
699
700 for (auto result : exitVar.branchResult)
701 {
702 if (const auto argument = dynamic_cast<rvsdg::RegionArgument *>(result->origin()))
703 {
704 const auto inputCongruenceSet = context.getSetFor(*argument->input()->origin());
705 if (!sharedCongruenceSet.has_value())
706 {
707 sharedCongruenceSet = inputCongruenceSet;
708 }
709 else if (*sharedCongruenceSet != inputCongruenceSet)
710 {
711 // We have multiple different non-congruent origins
712 return std::nullopt;
713 }
714 }
715 else
716 {
717 // The branch result was not invariant
718 return std::nullopt;
719 }
720 }
721
722 return sharedCongruenceSet;
723}
724
734[[nodiscard]] static size_t
736 const rvsdg::GammaNode::ExitVar & exitVar,
738{
739 size_t hash = 1;
740 for (auto branchResult : exitVar.branchResult)
741 {
742 const auto set = context.getSetFor(*branchResult->origin());
743 hash = util::CombineHashes(hash, set);
744 }
745 return hash;
746}
747
760[[nodiscard]] static bool
762 const rvsdg::GammaNode::ExitVar & first,
763 const rvsdg::GammaNode::ExitVar & second,
765{
769 const auto numResults = first.branchResult.size();
770 for (size_t i = 0; i < numResults; i++)
771 {
772 const auto firstSet = context.getSetFor(*first.branchResult[i]->origin());
773 const auto secondSet = context.getSetFor(*second.branchResult[i]->origin());
774 if (firstSet != secondSet)
775 return false;
776 }
777 return true;
778}
779
789static void
793 std::unordered_map<size_t, CommonNodeElimination::Context::CongruenceSetIndex> & leaderHashes,
795{
796 JLM_ASSERT(&context.getLeader(congruenceSet) == exitVar.output);
797
798 size_t hash = getGammaExitVariableHash(exitVar, context);
799 do
800 {
801 const auto [_, inserted] = leaderHashes.emplace(hash, congruenceSet);
802 if (inserted)
803 return;
804
805 // Open addressing, try next hash
806 hash++;
807 } while (true);
808}
809
823static void
825 const rvsdg::GammaNode::ExitVar & exitVar,
826 const rvsdg::GammaNode & gamma,
827 std::unordered_map<size_t, CommonNodeElimination::Context::CongruenceSetIndex> & leaderHashes,
829{
830 size_t hash = getGammaExitVariableHash(exitVar, context);
831 do
832 {
833 // If no leader has the same hash already, insert a new leader exit variable
834 const auto [it, inserted] = leaderHashes.emplace(hash, 0);
835 if (inserted)
836 {
837 it->second = context.getOrCreateSetForLeader(*exitVar.output);
838 return;
839 }
840
841 // There is already an exit variable with the same hash, check if it is a match
842 auto & otherLeader = context.getLeader(it->second);
843 auto otherExitVar = gamma.MapOutputExitVar(otherLeader);
844 if (areGammaExitVariablesCongruent(exitVar, otherExitVar, context))
845 {
846 context.addFollower(it->second, *exitVar.output);
847 return;
848 }
849
850 // Open addressing, try next hash
851 hash++;
852 } while (true);
853}
854
863static void
865{
866 markSubregionsFromInputs(gamma, context);
867
868 // Mapping from exit variable hash to the leader of its congruence set.
869 // The hash function is not collision free, and the table uses open addressing.
870 std::unordered_map<size_t, CommonNodeElimination::Context::CongruenceSetIndex> leaderHashes;
871
872 // Go through the outputs of the gamma node and create congruence sets for them.
873 for (auto exitVar : gamma.GetExitVars())
874 {
875 // If the exit variable has previously been found to not be invariant, we skip trying again
876 bool skipInvarianceCheck = false;
877
878 // First check if the exit variable already belongs to a congruence set
879 const auto existingSet = context.tryGetSetFor(*exitVar.output);
881 {
882 // The output already belongs to a congruence set
883
884 // Check if the output is the leader
885 const auto & exisitingLeader = context.getLeader(existingSet);
886 if (&exisitingLeader == exitVar.output)
887 {
888 // Add the exit var to the hashmap of exit variable leaders
889 insertGammaExitVarInHashmap(exitVar, existingSet, leaderHashes, context);
890
891 // A leader can never become a follower, so we are done
892 continue;
893 }
894
895 // Check if the leader is another exit variable of the same gamma
896 if (rvsdg::TryGetOwnerNode<rvsdg::GammaNode>(exisitingLeader) == &gamma)
897 {
898 const auto otherExitVar = gamma.MapOutputExitVar(exisitingLeader);
899 // Double check that the output still belongs to the same congurence set
900 if (areGammaExitVariablesCongruent(exitVar, otherExitVar, context))
901 {
902 // The exit variable should continue to be a follower
903 continue;
904 }
905
906 // exitVar should find a new leader, but we can skip checking if it is invariant
907 skipInvarianceCheck = true;
908 }
909 }
910
911 if (!skipInvarianceCheck)
912 {
913 // First check if the exit variable is gamma invariant
914 if (const auto entryVarCongruenceSet = tryGetGammaExitVarCongruenceSet(exitVar, context);
915 entryVarCongruenceSet.has_value())
916 {
917 context.addFollower(*entryVarCongruenceSet, *exitVar.output);
918 continue;
919 }
920 }
921
922 // This function either finds a new congruent leader for the exitVar,
923 // or makes exitVar itself the leader of its own congruence set.
924 lookupOrInsertGammaExitVarInHashmap(exitVar, gamma, leaderHashes, context);
925 }
926}
927
939static void
941{
942 bool anyChanges = markSubregionsFromInputs(theta, context);
943 const auto loopVars = theta.GetLoopVars();
944
945 // If anyChanges is false, it means no congruence sets changed within the theta node.
946 // However, the outputs of invariant loop variables belong to the congruence set of their input,
947 // which may have changed since the last time this function was called.
948 bool onlyUpdateLoopInvariants = !anyChanges;
949
950 // Use the loop variable post results to refine partitioning of loop variable arguments
951 while (anyChanges)
952 {
953 // Create partition keys for each loop variable
954 std::vector<CommonNodeElimination::Context::CongruenceSetIndex> partitions;
955 for (const auto & loopVar : loopVars)
956 {
957 partitions.push_back(context.getSetFor(*loopVar.post->origin()));
958 }
959
960 anyChanges = partitionArguments(*theta.subregion(), partitions, context);
961 if (anyChanges)
962 {
963 // Propagate refinement of argument congruence sets into the region
964 markRegion(*theta.subregion(), context);
965 }
966 }
967
968 // Check if the given loop variable's output is loop invariant.
969 // If it is, the origin of the invariant value is returned.
970 // If it is not, nullptr is returned.
971 const auto isOutputInvariant = [&](const auto & loopVar) -> rvsdg::Output *
972 {
973 // First perform a trivial check
975 {
976 return loopVar.input->origin();
977 }
978
979 const auto resultSet = context.getSetFor(*loopVar.post->origin());
980 const auto & resultSetLeader = context.getLeader(resultSet);
981 // If the origin's leader is the loop variable's pre, the result is also invariant
982 if (&resultSetLeader == loopVar.pre)
983 {
984 return loopVar.input->origin();
985 }
986
987 // Finally check if the loop variable post takes its value from another loop variable,
988 // and if that other loop variable is invariant
989 if (rvsdg::TryGetRegionParentNode<rvsdg::ThetaNode>(resultSetLeader) == &theta)
990 {
991 const auto otherLoopVar = theta.MapPreLoopVar(resultSetLeader);
992 // Check if the other loop variable is trivially invariant
993 if (rvsdg::ThetaLoopVarIsInvariant(otherLoopVar))
994 {
995 return otherLoopVar.input->origin();
996 }
997
998 // Use the CNE context to check if the other loop variable is invariant
999 const auto otherLoopVarPostSet = context.getSetFor(*otherLoopVar.post->origin());
1000 const auto & otherLoopVarPostLeader = context.getLeader(otherLoopVarPostSet);
1001 if (&otherLoopVarPostLeader == otherLoopVar.pre)
1002 {
1003 return otherLoopVar.input->origin();
1004 }
1005 }
1006
1007 return nullptr;
1008 };
1009
1010 // Partition theta outputs
1011 std::unordered_map<
1014 resultToOutputSetMapping;
1015 for (auto & loopVar : loopVars)
1016 {
1017 // If a loop variable is invariant, or its post result is congruent with
1018 // another loop variable that is invariant, the output becomes a follower of the input origin.
1019 if (const auto origin = isOutputInvariant(loopVar))
1020 {
1021 auto inputCongruenceSet = context.getSetFor(*origin);
1022 context.addFollower(inputCongruenceSet, *loopVar.output);
1023 continue;
1024 }
1025
1026 if (onlyUpdateLoopInvariants)
1027 continue;
1028
1029 // Other loop variable outputs are partitioned based on the origins of the post results.
1030 const auto resultSet = context.getSetFor(*loopVar.post->origin());
1031 const auto it = resultToOutputSetMapping.find(resultSet);
1032 if (it != resultToOutputSetMapping.end())
1033 {
1034 context.addFollower(it->second, *loopVar.output);
1035 }
1036 else
1037 {
1038 const auto outputSet = context.getOrCreateSetForLeader(*loopVar.output);
1039 resultToOutputSetMapping.emplace(resultSet, outputSet);
1040 }
1041 }
1042}
1043
1044static void
1046{
1048 node,
1049 [&](const rvsdg::GammaNode & gamma)
1050 {
1051 markGamma(gamma, context);
1052 },
1053 [&](const rvsdg::ThetaNode & theta)
1054 {
1055 markTheta(theta, context);
1056 },
1057 [&](const rvsdg::LambdaNode & lambda)
1058 {
1059 // Context variables are congruent if their origins are congruent.
1060 // All other arguments are given distinct congruence sets.
1061 markSubregionsFromInputs(lambda, context);
1062
1063 // A lambda output is always unique
1064 markNodeAsLeader(lambda, context);
1065 },
1066 [&](const rvsdg::PhiNode & phi)
1067 {
1068 // Context variables are congruent if their origins are congruent.
1069 // Recursion variables are given distinct congruence sets.
1070 markSubregionsFromInputs(phi, context);
1071
1072 // A phi node is always unique
1073 markNodeAsLeader(phi, context);
1074 },
1075 [&](const rvsdg::DeltaNode & delta)
1076 {
1077 // We skip doing CNE inside delta nodes
1078
1079 // A delta node is always unique
1080 markNodeAsLeader(delta, context);
1081 });
1082}
1083
1091static void
1093{
1094 TopNodeLeaderList leaders;
1095
1096 for (const auto node : rvsdg::TopDownConstTraverser(&region))
1097 {
1099 *node,
1100 [&](const rvsdg::SimpleNode & simple)
1101 {
1102 // Handle top nodes as a special case
1103 if (node->ninputs() == 0)
1104 {
1105 markSimpleTopNode(simple, leaders, context);
1106 }
1107 else
1108 {
1109 markSimpleNode(simple, context);
1110 }
1111 },
1112 [&](const rvsdg::StructuralNode & structural)
1113 {
1114 markStructuralNode(structural, context);
1115 });
1116 }
1117}
1118
1119/* divert phase */
1120
1121static void
1123{
1124 const auto outputSet = context.getSetFor(output);
1125
1126 auto & leader = context.getLeader(outputSet);
1127 if (&leader == &output)
1128 return;
1129
1130 output.divert_users(const_cast<rvsdg::Output *>(&leader));
1131}
1132
1133static void
1134divertInRegion(rvsdg::Region &, CommonNodeElimination::Context &);
1135
1136static void
1138{
1139 bool divertInSubregions = false;
1141 node,
1142 [&]([[maybe_unused]] rvsdg::GammaNode & gamma)
1143 {
1144 divertInSubregions = true;
1145 },
1146 [&]([[maybe_unused]] rvsdg::ThetaNode & theta)
1147 {
1148 divertInSubregions = true;
1149 },
1150 [&]([[maybe_unused]] rvsdg::LambdaNode & lambda)
1151 {
1152 divertInSubregions = true;
1153 },
1154 [&]([[maybe_unused]] rvsdg::PhiNode & phi)
1155 {
1156 divertInSubregions = true;
1157 },
1158 [&]([[maybe_unused]] rvsdg::DeltaNode & delta)
1159 {
1160 // Inside a delta node we can not perform diverting,
1161 // since we never marked the outputs there
1162 });
1163
1164 if (divertInSubregions)
1165 {
1166 for (auto & subregion : node.Subregions())
1167 {
1168 divertInRegion(subregion, context);
1169 }
1170 }
1171}
1172
1173static void
1175{
1176 // First divert all region arguments
1177 for (auto argument : region.Arguments())
1178 {
1179 divertOutput(*argument, context);
1180 }
1181
1182 // Divert all nodes
1183 for (const auto node : rvsdg::TopDownTraverser(&region))
1184 {
1185 // When diverting structural nodes, also recurse into their subregions
1187 *node,
1188 [&](rvsdg::StructuralNode & structural)
1189 {
1190 divertInStructuralNode(structural, context);
1191 });
1192
1193 // Divert all outputs of the node
1194 for (auto & output : node->Outputs())
1195 {
1196 divertOutput(output, context);
1197 }
1198 }
1199}
1200
1202
1203void
1205 rvsdg::RvsdgModule & module,
1206 util::StatisticsCollector & statisticsCollector)
1207{
1208 const auto & rvsdg = module.Rvsdg();
1209 auto & rootRegion = rvsdg.GetRootRegion();
1210
1211 Context context;
1212 auto statistics = Statistics::Create(module.SourceFilePath().value());
1213
1214 statistics->startMarkStatistics(rvsdg);
1215 markGraphImports(rootRegion, context);
1216 markRegion(rootRegion, context);
1217 statistics->endMarkStatistics();
1218
1219 statistics->startDivertStatistics();
1220 divertInRegion(rootRegion, context);
1221 statistics->endDivertStatistics(rvsdg);
1222
1223 statisticsCollector.CollectDemandedStatistics(std::move(statistics));
1224}
1225}
static jlm::util::StatisticsCollector statisticsCollector
CongruenceSetIndex getOrCreateSetForLeader(const rvsdg::Output &leader)
const util::HashSet< const rvsdg::Output * > & getFollowers(CongruenceSetIndex index) const
void addFollower(CongruenceSetIndex index, const rvsdg::Output &follower)
bool hasSet(const rvsdg::Output &output) const
CongruenceSetIndex getSetFor(const rvsdg::Output &output) const
CongruenceSetIndex tryGetSetFor(const rvsdg::Output &output) const
const rvsdg::Output & getLeader(CongruenceSetIndex index) const
std::unordered_map< const rvsdg::Output *, CongruenceSetIndex > congruenceSetMapping_
void startMarkStatistics(const rvsdg::Graph &graph) noexcept
void endDivertStatistics(const rvsdg::Graph &graph) noexcept
static std::unique_ptr< Statistics > Create(const util::FilePath &sourceFile)
Common Node Elimination Discovers simple nodes, region arguments and structural node outputs that are...
~CommonNodeElimination() noexcept override
Conditional operator / pattern matching.
Definition gamma.hpp:99
ExitVar MapOutputExitVar(const rvsdg::Output &output) const
Maps gamma output to exit variable description.
Definition gamma.cpp:397
std::vector< ExitVar > GetExitVars() const
Gets all exit variables for this gamma.
Definition gamma.cpp:381
Output * origin() const noexcept
Definition node.hpp:58
NodeOutput * output(size_t index) const noexcept
Definition node.hpp:650
OutputIteratorRange Outputs() noexcept
Definition node.hpp:657
rvsdg::Region * region() const noexcept
Definition node.hpp:761
size_t ninputs() const noexcept
Definition node.hpp:609
size_t noutputs() const noexcept
Definition node.hpp:644
void divert_users(jlm::rvsdg::Output *new_origin)
Definition node.hpp:301
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
Represents the argument of a region.
Definition region.hpp:41
Represent acyclic RVSDG subgraphs.
Definition region.hpp:213
RegionArgumentRange Arguments() noexcept
Definition region.hpp:319
size_t narguments() const noexcept
Definition region.hpp:460
NodeInput * input(size_t index) const noexcept
NodeOutput * output(size_t index) const noexcept
SubregionIteratorRange Subregions()
LoopVar MapPreLoopVar(const rvsdg::Output &argument) const
Maps variable at start of loop iteration to full varibale description.
Definition theta.cpp:140
std::vector< LoopVar > GetLoopVars() const
Returns all loop variables.
Definition theta.cpp:176
rvsdg::Region * subregion() const noexcept
Definition theta.hpp:79
void CollectDemandedStatistics(std::unique_ptr< Statistics > statistics)
Statistics Interface.
util::Timer & GetTimer(const std::string &name)
util::Timer & AddTimer(std::string name)
void AddMeasurement(std::string name, T value)
void start() noexcept
Definition time.hpp:54
void stop() noexcept
Definition time.hpp:67
#define JLM_ASSERT(x)
Definition common.hpp:16
Global memory state passed between functions.
static void lookupOrInsertGammaExitVarInHashmap(const rvsdg::GammaNode::ExitVar &exitVar, const rvsdg::GammaNode &gamma, std::unordered_map< size_t, CommonNodeElimination::Context::CongruenceSetIndex > &leaderHashes, CommonNodeElimination::Context &context)
static bool partitionArguments(const rvsdg::Region &region, const std::vector< CommonNodeElimination::Context::CongruenceSetIndex > &partitions, CommonNodeElimination::Context &context)
static void markSimpleTopNode(const rvsdg::SimpleNode &node, TopNodeLeaderList &leaders, CommonNodeElimination::Context &context)
static bool markSubregionsFromInputs(const rvsdg::StructuralNode &node, CommonNodeElimination::Context &context)
static void markGamma(const rvsdg::GammaNode &gamma, CommonNodeElimination::Context &context)
static void markGraphImports(const rvsdg::Region &region, CommonNodeElimination::Context &context)
static bool checkNodesCongruent(const rvsdg::Node &node1, const rvsdg::Node &node2, CommonNodeElimination::Context &context)
static void divertOutput(rvsdg::Output &output, CommonNodeElimination::Context &context)
static void divertInRegion(rvsdg::Region &, CommonNodeElimination::Context &)
void markNodeAsLeader(const rvsdg::Node &leader, CommonNodeElimination::Context &context)
static void insertGammaExitVarInHashmap(rvsdg::GammaNode::ExitVar &exitVar, CommonNodeElimination::Context::CongruenceSetIndex congruenceSet, std::unordered_map< size_t, CommonNodeElimination::Context::CongruenceSetIndex > &leaderHashes, CommonNodeElimination::Context &context)
static size_t getGammaExitVariableHash(const rvsdg::GammaNode::ExitVar &exitVar, CommonNodeElimination::Context &context)
std::vector< const rvsdg::Node * > TopNodeLeaderList
static void markRegion(const rvsdg::Region &, CommonNodeElimination::Context &context)
static std::optional< CommonNodeElimination::Context::CongruenceSetIndex > tryGetGammaExitVarCongruenceSet(rvsdg::GammaNode::ExitVar &exitVar, CommonNodeElimination::Context &context)
static bool areOutputsCongruent(const rvsdg::Output &o1, const rvsdg::Output &o2, CommonNodeElimination::Context &context)
void markNodesAsCongruent(const rvsdg::Node &leader, const rvsdg::Node &follower, CommonNodeElimination::Context &context)
static void divertInStructuralNode(rvsdg::StructuralNode &node, CommonNodeElimination::Context &context)
static void markTheta(const rvsdg::ThetaNode &theta, CommonNodeElimination::Context &context)
static bool areGammaExitVariablesCongruent(const rvsdg::GammaNode::ExitVar &first, const rvsdg::GammaNode::ExitVar &second, CommonNodeElimination::Context &context)
static void markStructuralNode(const rvsdg::StructuralNode &node, CommonNodeElimination::Context &context)
static void markSimpleNode(const rvsdg::SimpleNode &node, CommonNodeElimination::Context &context)
const rvsdg::SimpleNode * tryGetLeaderNode(const rvsdg::SimpleNode &node, CommonNodeElimination::Context &context)
void MatchTypeOrFail(T &obj, const Fns &... fns)
Pattern match over subclass type of given object.
static bool ThetaLoopVarIsInvariant(const ThetaNode::LoopVar &loopVar) noexcept
Definition theta.hpp:227
void MatchType(T &obj, const Fns &... fns)
Pattern match over subclass type of given object.
detail::TopDownTraverserGeneric< true > TopDownConstTraverser
Traverser for visiting every node in a const region in a top down order.
size_t ninputs(const rvsdg::Region *region) noexcept
Definition region.cpp:861
NodeType * TryGetOwnerNode(const rvsdg::Input &input) noexcept
Checks if this is an input to a node of specified type.
Definition node.hpp:872
detail::TopDownTraverserGeneric< false > TopDownTraverser
Traverser for visiting every node in a region in a top down order.
std::size_t CombineHashes(std::size_t hash, Args... args)
Definition Hash.hpp:63
A variable routed out of all gamma regions as result.
Definition gamma.hpp:146
rvsdg::Output * output
Output of gamma.
Definition gamma.hpp:154
std::vector< rvsdg::Input * > branchResult
Variable exit points (results per subregion).
Definition gamma.hpp:150
rvsdg::Input * input
Variable at loop entry (input to theta).
Definition theta.hpp:54