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 const char * PruneTimerLabel_ = "PruneTime";
32
33public:
34 ~Statistics() override = default;
35
36 explicit Statistics(const util::FilePath & sourceFile)
37 : util::Statistics(Statistics::Id::CommonNodeElimination, sourceFile)
38 {}
39
40 void
41 startMarkStatistics(const rvsdg::Graph & graph) noexcept
42 {
43 AddMeasurement(Label::NumRvsdgInputsBefore, rvsdg::ninputs(&graph.GetRootRegion()));
45 }
46
47 void
49 {
51 }
52
53 void
58
59 void
60 endDivertStatistics(const rvsdg::Graph & graph) noexcept
61 {
62 AddMeasurement(Label::NumRvsdgInputsAfter, rvsdg::ninputs(&graph.GetRootRegion()));
64 }
65
66 void
71
72 void
77
78 static std::unique_ptr<Statistics>
79 Create(const util::FilePath & sourceFile)
80 {
81 return std::make_unique<Statistics>(sourceFile);
82 }
83};
84
96{
97public:
113 {
115 : leader(&leader)
116 {}
117
118 // The set leader. Never changes.
119 // Once an output has become a leader, it will never be a follower.
121 // The set of followers. Can both grow and shrink during the marking phase.
122 // Does not include the leader.
124 };
125
126 using CongruenceSetIndex = size_t;
127 static constexpr auto NoCongruenceSetIndex = std::numeric_limits<CongruenceSetIndex>::max();
128
134 [[nodiscard]] CongruenceSetIndex
136 {
137 return sets_.size();
138 }
139
146 [[nodiscard]] CongruenceSetIndex
147 tryGetSetFor(const rvsdg::Output & output) const
148 {
149 if (const auto it = congruenceSetMapping_.find(&output); it != congruenceSetMapping_.end())
150 {
151 return it->second;
152 }
154 }
155
162 [[nodiscard]] CongruenceSetIndex
163 getSetFor(const rvsdg::Output & output) const
164 {
165 const auto index = tryGetSetFor(output);
166 if (index == NoCongruenceSetIndex)
167 throw std::logic_error("Output does not belong to a congruence set");
168 return index;
169 }
170
176 [[nodiscard]] bool
177 hasSet(const rvsdg::Output & output) const
178 {
179 return tryGetSetFor(output) != NoCongruenceSetIndex;
180 }
181
191 {
192 // The index of the new set, if this operation actually creates one
193 auto nextSet = sets_.size();
194 auto [it, added] = congruenceSetMapping_.try_emplace(&leader, nextSet);
195
196 if (!added)
197 {
198 // If the leader already has its own set, we are done
199 if (sets_[it->second].leader == &leader)
200 {
201 return it->second;
202 }
203
204 // Remove the output from the congruence set it is following
205 sets_[it->second].followers.Remove(&leader);
206 it->second = nextSet;
207 }
208
209 // Create the new set, lead by \p leader
210 sets_.emplace_back(leader);
211 return nextSet;
212 }
213
219 const rvsdg::Output &
221 {
222 JLM_ASSERT(index < sets_.size());
223 return *sets_[index].leader;
224 }
225
234 void
236 {
237 JLM_ASSERT(index < sets_.size());
238
239 const bool newFollower = sets_[index].followers.insert(&follower);
240
241 // If the follower is already following the correct set, do nothing
242 if (!newFollower)
243 return;
244
245 const auto [it, added] = congruenceSetMapping_.try_emplace(&follower, index);
246
247 // If the follower already belonged to a congruence set, remove it from the old set
248 if (!added)
249 {
250 JLM_ASSERT(it->second != index);
251
252 if (sets_[it->second].leader == &follower)
253 throw std::logic_error("Cannot turn a leader into a follower");
254
255 const bool removed = sets_[it->second].followers.Remove(&follower);
256 JLM_ASSERT(removed);
257
258 it->second = index;
259 }
260 }
261
269 {
270 JLM_ASSERT(index < sets_.size());
271 return sets_[index].followers;
272 }
273
274private:
275 // The list of congruence sets
276 std::vector<CongruenceSet> sets_;
277 // A mapping from each output to the congruence set it belongs to, either as leader or follower
278 std::unordered_map<const rvsdg::Output *, CongruenceSetIndex> congruenceSetMapping_;
279};
280
290static bool
292 const rvsdg::Output & o1,
293 const rvsdg::Output & o2,
295{
296 if (*o1.Type() != *o2.Type())
297 return false;
298
299 const auto o1Set = context.getSetFor(o1);
300 const auto o2Set = context.getSetFor(o2);
301
302 return o1Set == o2Set;
303}
304
317static bool
319 const rvsdg::Node & node1,
320 const rvsdg::Node & node2,
322{
323 const auto simpleNode1 = dynamic_cast<const rvsdg::SimpleNode *>(&node1);
324 const auto simpleNode2 = dynamic_cast<const rvsdg::SimpleNode *>(&node2);
325 if (!simpleNode1 || !simpleNode2)
326 return false;
327
328 if (simpleNode1->ninputs() != simpleNode2->ninputs())
329 return false;
330
331 if (simpleNode1->GetOperation() != simpleNode2->GetOperation())
332 return false;
333
334 // For each pair of corresponding inputs of simpleNode1 and simpleNode2,
335 // they must have origins that are congruent
336 for (auto & input : simpleNode1->Inputs())
337 {
338 const auto origin1 = input.origin();
339 const auto origin2 = simpleNode2->input(input.index())->origin();
340 if (!areOutputsCongruent(*origin1, *origin2, context))
341 return false;
342 }
343
344 return true;
345}
346
352void
354{
355 for (auto & output : leader.Outputs())
356 {
357 context.getOrCreateSetForLeader(output);
358 }
359}
360
371void
373 const rvsdg::Node & leader,
374 const rvsdg::Node & follower,
376{
377 JLM_ASSERT(leader.noutputs() == follower.noutputs());
378 JLM_ASSERT(leader.region() == follower.region());
379
380 for (size_t i = 0; i < leader.noutputs(); i++)
381 {
382 const auto & leaderOutput = *leader.output(i);
383 const auto & followerOutput = *follower.output(i);
384 const auto leaderSet = context.getSetFor(leaderOutput);
385 context.addFollower(leaderSet, followerOutput);
386 }
387}
388
397[[nodiscard]] const rvsdg::SimpleNode *
399{
400 // Nodes with 0 outputs are never congruent with anything, so let the node be its own leader.
401 if (node.noutputs() == 0)
402 return &node;
403
404 // Check the congruence set of the first output
405 const auto output0Set = context.tryGetSetFor(*node.output(0));
406 // If the output has not gotten a congruence set yet, the node has yet to be marked
408 return nullptr;
409
410 // A simple node can only be congruent with other simple nodes
411 const auto & output0Leader = context.getLeader(output0Set);
412 const auto & leaderNode = rvsdg::AssertGetOwnerNode<rvsdg::SimpleNode>(output0Leader);
413 JLM_ASSERT(leaderNode.noutputs() == node.noutputs());
414 return &leaderNode;
415}
416
417using TopNodeLeaderList = std::vector<const rvsdg::Node *>;
418
427static void
429 const rvsdg::SimpleNode & node,
430 TopNodeLeaderList & leaders,
432{
433 // If the node has already been marked, check if it is still congruent with its leader
434 if (const auto existingLeaderNode = tryGetLeaderNode(node, context))
435 {
436 // We are our own leader, nothing to check
437 if (existingLeaderNode == &node)
438 {
439 leaders.push_back(&node);
440 return;
441 }
442
443 // Check if we are still congruent with the leader
444 if (checkNodesCongruent(*existingLeaderNode, node, context))
445 {
446 return;
447 }
448
449 // This node is no longer congruent with its leader, continue looking for a new leader
450 }
451
452 // TODO: Use some sort of hashing to make this not O(n * m)
453 // where n is the number of outputs and m is the number of congruence sets
454
455 // Check if the node is congruent with any existing leader in the TopNodeLeaderList
456 for (auto leader : leaders)
457 {
458 if (checkNodesCongruent(node, *leader, context))
459 {
460 markNodesAsCongruent(*leader, node, context);
461 return;
462 }
463 }
464
465 // No existing leader top node found, create new congruence sets for each of node's outputs
466 markNodeAsLeader(node, context);
467 leaders.push_back(&node);
468}
469
478static void
480{
481 // This function should never be called for TopNodes
482 JLM_ASSERT(node.ninputs() > 0);
483
484 // If node is already in a congruence set, check that it actually belongs there
485 if (const auto leaderNode = tryGetLeaderNode(node, context))
486 {
487 // If node is its own leader, it definitely belongs, and we are done
488 if (leaderNode == &node)
489 return;
490
491 // Double-check that we are congruent with our leader
492 if (checkNodesCongruent(node, *leaderNode, context))
493 return;
494
495 // Otherwise we need to continue looking for a new leader
496 }
497
498 // This function looks at all nodes that take the given output as the origin for its first input.
499 // If the other node is its own leader, and the current node is congruent with it,
500 // the current node becomes a follower.
501 const auto tryFindCongruentUserOf = [&](const rvsdg::Output & output) -> bool
502 {
503 // TODO: It would be possible to maintain a list of only users that are leader nodes,
504 // to avoid needing to check every user
505 for (auto & user : output.Users())
506 {
507 if (user.index() != 0)
508 continue;
509
510 const auto otherNode = rvsdg::TryGetOwnerNode<rvsdg::SimpleNode>(user);
511 if (!otherNode)
512 continue;
513
514 // Do not compare against ourselves
515 if (otherNode == &node)
516 continue;
517
518 // Only consider other nodes that are leaders
519 auto otherNodeLeader = tryGetLeaderNode(*otherNode, context);
520 if (otherNode != otherNodeLeader)
521 continue;
522
523 if (checkNodesCongruent(node, *otherNode, context))
524 {
525 // When nodes are congruent, they should always have the same amount of outputs
526 JLM_ASSERT(node.noutputs() == otherNode->noutputs());
527
528 markNodesAsCongruent(*otherNode, node, context);
529 return true;
530 }
531 }
532 return false;
533 };
534
535 // Use the origin of the first input to find other potential candidates
536 const auto origin = node.input(0)->origin();
537 const auto origin0Set = context.getSetFor(*origin);
538 const auto & origin0Leader = context.getLeader(origin0Set);
539 const auto & origin0Followers = context.getFollowers(origin0Set);
540 if (tryFindCongruentUserOf(origin0Leader))
541 return;
542 for (auto follower : origin0Followers.Items())
543 {
544 if (tryFindCongruentUserOf(*follower))
545 return;
546 }
547
548 // If we got here, no congruent node was found, so make the node its own leader
549 markNodeAsLeader(node, context);
550}
551
552static void
553markRegion(const rvsdg::Region &, CommonNodeElimination::Context & context);
554
560static void
562{
563 // FIXME: Multiple imports with identical names could in theory be aliases.
564 // For now this function ignores that and makes all imports distinct. This is sound
565 for (auto argument : region.Arguments())
566 {
567 context.getOrCreateSetForLeader(*argument);
568 }
569}
570
584static bool
586 const rvsdg::Region & region,
587 const std::vector<CommonNodeElimination::Context::CongruenceSetIndex> & partitions,
589{
590 JLM_ASSERT(region.narguments() == partitions.size());
591
592 const auto numCongruenceSets = context.numCongruenceSets();
593
594 // Keys in the map are (old congruence set index, provided partition key)
595 // Values in the map are the new congruence set indices
596 std::map<
597 std::pair<
601 newSets;
602 for (auto argument : region.Arguments())
603 {
604 const auto currentPartition = context.tryGetSetFor(*argument);
605 const auto key = std::make_pair(currentPartition, partitions[argument->index()]);
606
607 // If this argument is the first with the given key, it should be a leader
608 // otherwise it should be a follower
609
610 if (const auto it = newSets.find(key); it != newSets.end())
611 {
612 // This argument should be a follower of the given congruence set
613 const auto toFollow = it->second;
614
615 // If we are already a follower, we are done
616 if (currentPartition == toFollow)
617 continue;
618
619 // Start following our leader
620 context.addFollower(toFollow, *argument);
621 }
622 else
623 {
624 // This argument should be the leader of its congruence set
625 newSets.emplace(key, context.getOrCreateSetForLeader(*argument));
626 }
627 }
628
629 // Return true iff any new congruence sets were created
630 return context.numCongruenceSets() != numCongruenceSets;
631}
632
650static bool
652 const rvsdg::StructuralNode & node,
654{
655 bool anyChanges = false;
656
657 for (auto & subregion : node.Subregions())
658 {
659 if (subregion.narguments() == 0)
660 {
661 markRegion(subregion, context);
662 }
663 else
664 {
665 // create a partitioning of the region arguments
666 std::vector<size_t> partitions(subregion.narguments());
667 // Arguments that do not belong to any input are given partition keys higher than any real
668 // index
669 size_t nextUniquePartitionKey = context.numCongruenceSets();
670
671 for (const auto argument : subregion.Arguments())
672 {
673 if (const auto input = argument->input())
674 {
675 // If the argument corresponds to an input, use the partition key of the input
676 partitions[argument->index()] = context.getSetFor(*input->origin());
677 }
678 else
679 {
680 // Otherwise make sure the argument is not partitioned with any other argument
681 partitions[argument->index()] = nextUniquePartitionKey++;
682 }
683 }
684
685 if (partitionArguments(subregion, partitions, context))
686 {
687 anyChanges = true;
688 markRegion(subregion, context);
689 }
690 }
691 }
692
693 return anyChanges;
694}
695
706static std::optional<CommonNodeElimination::Context::CongruenceSetIndex>
710{
711 std::optional<CommonNodeElimination::Context::CongruenceSetIndex> sharedCongruenceSet;
712
713 for (auto result : exitVar.branchResult)
714 {
715 if (const auto argument = dynamic_cast<rvsdg::RegionArgument *>(result->origin()))
716 {
717 const auto inputCongruenceSet = context.getSetFor(*argument->input()->origin());
718 if (!sharedCongruenceSet.has_value())
719 {
720 sharedCongruenceSet = inputCongruenceSet;
721 }
722 else if (*sharedCongruenceSet != inputCongruenceSet)
723 {
724 // We have multiple different non-congruent origins
725 return std::nullopt;
726 }
727 }
728 else
729 {
730 // The branch result was not invariant
731 return std::nullopt;
732 }
733 }
734
735 return sharedCongruenceSet;
736}
737
747[[nodiscard]] static size_t
749 const rvsdg::GammaNode::ExitVar & exitVar,
751{
752 size_t hash = 1;
753 for (auto branchResult : exitVar.branchResult)
754 {
755 const auto set = context.getSetFor(*branchResult->origin());
756 hash = util::CombineHashes(hash, set);
757 }
758 return hash;
759}
760
773[[nodiscard]] static bool
775 const rvsdg::GammaNode::ExitVar & first,
776 const rvsdg::GammaNode::ExitVar & second,
778{
782 const auto numResults = first.branchResult.size();
783 for (size_t i = 0; i < numResults; i++)
784 {
785 const auto firstSet = context.getSetFor(*first.branchResult[i]->origin());
786 const auto secondSet = context.getSetFor(*second.branchResult[i]->origin());
787 if (firstSet != secondSet)
788 return false;
789 }
790 return true;
791}
792
802static void
806 std::unordered_map<size_t, CommonNodeElimination::Context::CongruenceSetIndex> & leaderHashes,
808{
809 JLM_ASSERT(&context.getLeader(congruenceSet) == exitVar.output);
810
811 size_t hash = getGammaExitVariableHash(exitVar, context);
812 do
813 {
814 const auto [_, inserted] = leaderHashes.emplace(hash, congruenceSet);
815 if (inserted)
816 return;
817
818 // Open addressing, try next hash
819 hash++;
820 } while (true);
821}
822
836static void
838 const rvsdg::GammaNode::ExitVar & exitVar,
839 const rvsdg::GammaNode & gamma,
840 std::unordered_map<size_t, CommonNodeElimination::Context::CongruenceSetIndex> & leaderHashes,
842{
843 size_t hash = getGammaExitVariableHash(exitVar, context);
844 do
845 {
846 // If no leader has the same hash already, insert a new leader exit variable
847 const auto [it, inserted] = leaderHashes.emplace(hash, 0);
848 if (inserted)
849 {
850 it->second = context.getOrCreateSetForLeader(*exitVar.output);
851 return;
852 }
853
854 // There is already an exit variable with the same hash, check if it is a match
855 auto & otherLeader = context.getLeader(it->second);
856 auto otherExitVar = gamma.MapOutputExitVar(otherLeader);
857 if (areGammaExitVariablesCongruent(exitVar, otherExitVar, context))
858 {
859 context.addFollower(it->second, *exitVar.output);
860 return;
861 }
862
863 // Open addressing, try next hash
864 hash++;
865 } while (true);
866}
867
876static void
878{
879 markSubregionsFromInputs(gamma, context);
880
881 // Mapping from exit variable hash to the leader of its congruence set.
882 // The hash function is not collision free, and the table uses open addressing.
883 std::unordered_map<size_t, CommonNodeElimination::Context::CongruenceSetIndex> leaderHashes;
884
885 // Go through the outputs of the gamma node and create congruence sets for them.
886 for (auto exitVar : gamma.GetExitVars())
887 {
888 // If the exit variable has previously been found to not be invariant, we skip trying again
889 bool skipInvarianceCheck = false;
890
891 // First check if the exit variable already belongs to a congruence set
892 const auto existingSet = context.tryGetSetFor(*exitVar.output);
894 {
895 // The output already belongs to a congruence set
896
897 // Check if the output is the leader
898 const auto & exisitingLeader = context.getLeader(existingSet);
899 if (&exisitingLeader == exitVar.output)
900 {
901 // Add the exit var to the hashmap of exit variable leaders
902 insertGammaExitVarInHashmap(exitVar, existingSet, leaderHashes, context);
903
904 // A leader can never become a follower, so we are done
905 continue;
906 }
907
908 // Check if the leader is another exit variable of the same gamma
909 if (rvsdg::TryGetOwnerNode<rvsdg::GammaNode>(exisitingLeader) == &gamma)
910 {
911 const auto otherExitVar = gamma.MapOutputExitVar(exisitingLeader);
912 // Double check that the output still belongs to the same congurence set
913 if (areGammaExitVariablesCongruent(exitVar, otherExitVar, context))
914 {
915 // The exit variable should continue to be a follower
916 continue;
917 }
918
919 // exitVar should find a new leader, but we can skip checking if it is invariant
920 skipInvarianceCheck = true;
921 }
922 }
923
924 if (!skipInvarianceCheck)
925 {
926 // First check if the exit variable is gamma invariant
927 if (const auto entryVarCongruenceSet = tryGetGammaExitVarCongruenceSet(exitVar, context);
928 entryVarCongruenceSet.has_value())
929 {
930 context.addFollower(*entryVarCongruenceSet, *exitVar.output);
931 continue;
932 }
933 }
934
935 // This function either finds a new congruent leader for the exitVar,
936 // or makes exitVar itself the leader of its own congruence set.
937 lookupOrInsertGammaExitVarInHashmap(exitVar, gamma, leaderHashes, context);
938 }
939}
940
952static void
954{
955 bool anyChanges = markSubregionsFromInputs(theta, context);
956 const auto loopVars = theta.GetLoopVars();
957
958 // If anyChanges is false, it means no congruence sets changed within the theta node.
959 // However, the outputs of invariant loop variables belong to the congruence set of their input,
960 // which may have changed since the last time this function was called.
961 bool onlyUpdateLoopInvariants = !anyChanges;
962
963 // Use the loop variable post results to refine partitioning of loop variable arguments
964 while (anyChanges)
965 {
966 // Create partition keys for each loop variable
967 std::vector<CommonNodeElimination::Context::CongruenceSetIndex> partitions;
968 for (const auto & loopVar : loopVars)
969 {
970 partitions.push_back(context.getSetFor(*loopVar.post->origin()));
971 }
972
973 anyChanges = partitionArguments(*theta.subregion(), partitions, context);
974 if (anyChanges)
975 {
976 // Propagate refinement of argument congruence sets into the region
977 markRegion(*theta.subregion(), context);
978 }
979 }
980
981 // Check if the given loop variable's output is loop invariant.
982 // If it is, the origin of the invariant value is returned.
983 // If it is not, nullptr is returned.
984 const auto isOutputInvariant = [&](const auto & loopVar) -> rvsdg::Output *
985 {
986 // First perform a trivial check
988 {
989 return loopVar.input->origin();
990 }
991
992 const auto resultSet = context.getSetFor(*loopVar.post->origin());
993 const auto & resultSetLeader = context.getLeader(resultSet);
994 // If the origin's leader is the loop variable's pre, the result is also invariant
995 if (&resultSetLeader == loopVar.pre)
996 {
997 return loopVar.input->origin();
998 }
999
1000 // Finally check if the loop variable post takes its value from another loop variable,
1001 // and if that other loop variable is invariant
1002 if (rvsdg::TryGetRegionParentNode<rvsdg::ThetaNode>(resultSetLeader) == &theta)
1003 {
1004 const auto otherLoopVar = theta.MapPreLoopVar(resultSetLeader);
1005 // Check if the other loop variable is trivially invariant
1006 if (rvsdg::ThetaLoopVarIsInvariant(otherLoopVar))
1007 {
1008 return otherLoopVar.input->origin();
1009 }
1010
1011 // Use the CNE context to check if the other loop variable is invariant
1012 const auto otherLoopVarPostSet = context.getSetFor(*otherLoopVar.post->origin());
1013 const auto & otherLoopVarPostLeader = context.getLeader(otherLoopVarPostSet);
1014 if (&otherLoopVarPostLeader == otherLoopVar.pre)
1015 {
1016 return otherLoopVar.input->origin();
1017 }
1018 }
1019
1020 return nullptr;
1021 };
1022
1023 // Partition theta outputs
1024 std::unordered_map<
1027 resultToOutputSetMapping;
1028 for (auto & loopVar : loopVars)
1029 {
1030 // If a loop variable is invariant, or its post result is congruent with
1031 // another loop variable that is invariant, the output becomes a follower of the input origin.
1032 if (const auto origin = isOutputInvariant(loopVar))
1033 {
1034 auto inputCongruenceSet = context.getSetFor(*origin);
1035 context.addFollower(inputCongruenceSet, *loopVar.output);
1036 continue;
1037 }
1038
1039 if (onlyUpdateLoopInvariants)
1040 continue;
1041
1042 // Other loop variable outputs are partitioned based on the origins of the post results.
1043 const auto resultSet = context.getSetFor(*loopVar.post->origin());
1044 const auto it = resultToOutputSetMapping.find(resultSet);
1045 if (it != resultToOutputSetMapping.end())
1046 {
1047 context.addFollower(it->second, *loopVar.output);
1048 }
1049 else
1050 {
1051 const auto outputSet = context.getOrCreateSetForLeader(*loopVar.output);
1052 resultToOutputSetMapping.emplace(resultSet, outputSet);
1053 }
1054 }
1055}
1056
1057static void
1059{
1061 node,
1062 [&](const rvsdg::GammaNode & gamma)
1063 {
1064 markGamma(gamma, context);
1065 },
1066 [&](const rvsdg::ThetaNode & theta)
1067 {
1068 markTheta(theta, context);
1069 },
1070 [&](const rvsdg::LambdaNode & lambda)
1071 {
1072 // Context variables are congruent if their origins are congruent.
1073 // All other arguments are given distinct congruence sets.
1074 markSubregionsFromInputs(lambda, context);
1075
1076 // A lambda output is always unique
1077 markNodeAsLeader(lambda, context);
1078 },
1079 [&](const rvsdg::PhiNode & phi)
1080 {
1081 // Context variables are congruent if their origins are congruent.
1082 // Recursion variables are given distinct congruence sets.
1083 markSubregionsFromInputs(phi, context);
1084
1085 // A phi node is always unique
1086 markNodeAsLeader(phi, context);
1087 },
1088 [&](const rvsdg::DeltaNode & delta)
1089 {
1090 // We skip doing CNE inside delta nodes
1091
1092 // A delta node is always unique
1093 markNodeAsLeader(delta, context);
1094 });
1095}
1096
1104static void
1106{
1107 TopNodeLeaderList leaders;
1108
1109 for (const auto node : rvsdg::TopDownConstTraverser(&region))
1110 {
1112 *node,
1113 [&](const rvsdg::SimpleNode & simple)
1114 {
1115 // Handle top nodes as a special case
1116 if (node->ninputs() == 0)
1117 {
1118 markSimpleTopNode(simple, leaders, context);
1119 }
1120 else
1121 {
1122 markSimpleNode(simple, context);
1123 }
1124 },
1125 [&](const rvsdg::StructuralNode & structural)
1126 {
1127 markStructuralNode(structural, context);
1128 });
1129 }
1130}
1131
1132/* divert phase */
1133
1134static void
1136{
1137 const auto outputSet = context.getSetFor(output);
1138
1139 auto & leader = context.getLeader(outputSet);
1140 if (&leader == &output)
1141 return;
1142
1143 output.divert_users(const_cast<rvsdg::Output *>(&leader));
1144}
1145
1146static void
1147divertInRegion(rvsdg::Region &, CommonNodeElimination::Context &);
1148
1149static void
1151{
1152 bool divertInSubregions = false;
1154 node,
1155 [&]([[maybe_unused]] rvsdg::GammaNode & gamma)
1156 {
1157 divertInSubregions = true;
1158 },
1159 [&]([[maybe_unused]] rvsdg::ThetaNode & theta)
1160 {
1161 divertInSubregions = true;
1162 },
1163 [&]([[maybe_unused]] rvsdg::LambdaNode & lambda)
1164 {
1165 divertInSubregions = true;
1166 },
1167 [&]([[maybe_unused]] rvsdg::PhiNode & phi)
1168 {
1169 divertInSubregions = true;
1170 },
1171 [&]([[maybe_unused]] rvsdg::DeltaNode & delta)
1172 {
1173 // Inside a delta node we can not perform diverting,
1174 // since we never marked the outputs there
1175 });
1176
1177 if (divertInSubregions)
1178 {
1179 for (auto & subregion : node.Subregions())
1180 {
1181 divertInRegion(subregion, context);
1182 }
1183 }
1184}
1185
1186static void
1188{
1189 // First divert all region arguments
1190 for (auto argument : region.Arguments())
1191 {
1192 divertOutput(*argument, context);
1193 }
1194
1195 // Divert all nodes
1196 for (const auto node : rvsdg::TopDownTraverser(&region))
1197 {
1198 // When diverting structural nodes, also recurse into their subregions
1200 *node,
1201 [&](rvsdg::StructuralNode & structural)
1202 {
1203 divertInStructuralNode(structural, context);
1204 });
1205
1206 // Divert all outputs of the node
1207 for (auto & output : node->Outputs())
1208 {
1209 divertOutput(output, context);
1210 }
1211 }
1212
1213 region.prune(false);
1214}
1215
1217
1218void
1220 rvsdg::RvsdgModule & module,
1221 util::StatisticsCollector & statisticsCollector)
1222{
1223 auto & rvsdg = module.Rvsdg();
1224 auto & rootRegion = rvsdg.GetRootRegion();
1225
1226 Context context;
1227 auto statistics = Statistics::Create(module.SourceFilePath().value());
1228
1229 statistics->startMarkStatistics(rvsdg);
1230 markGraphImports(rootRegion, context);
1231 markRegion(rootRegion, context);
1232 statistics->endMarkStatistics();
1233
1234 statistics->startDivertStatistics();
1235 divertInRegion(rootRegion, context);
1236 statistics->endDivertStatistics(rvsdg);
1237
1238 // Pruning nodes as an extra stage instead of at the end of divertInRegion() was a deliberate
1239 // choice. The divertInRegion() method traverses the region tree to the innermost regions first
1240 // before handling the outermost region, which means we would prune in the innermost regions
1241 // before pruning in the outermost regions. However, it could be that CNE renders a structural
1242 // node dead. Thus, this innermost-first strategy would mean that we prune in the dead structural
1243 // node's subregion before removing the structural node itself, which is not desirable. In
1244 // contrast, the Graph::PruneNodes() method prunes the outermost regions first before traversing
1245 // into the subregions of the (leftover) structural nodes.
1246 statistics->startPruneStatistics();
1247 rvsdg.PruneNodes();
1248 statistics->stopPruneStatistics();
1249
1250 statisticsCollector.CollectDemandedStatistics(std::move(statistics));
1251}
1252}
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
void prune(bool recursive)
Definition region.cpp:326
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:193
rvsdg::Region * subregion() const noexcept
Definition theta.hpp:90
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 util::StatisticsCollector statisticsCollector
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:266
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