Jlm
Loading...
Searching...
No Matches
PointerObjectSet.cpp
Go to the documentation of this file.
1/*
2 * Copyright 2023, 2024 HÃ¥vard Krogstie <krogstie.havard@gmail.com>
3 * See COPYING for terms of redistribution.
4 */
5
7
11#include <jlm/util/Worklist.hpp>
12
13#include <limits>
14#include <queue>
15#include <variant>
16
17namespace jlm::llvm::aa
18{
19
25static constexpr bool ENABLE_UNIFICATION = true;
26
29{
30 JLM_ASSERT(PointerObjects_.size() < std::numeric_limits<PointerObjectIndex>::max());
32
33 PointerObjects_.emplace_back(kind, canPoint);
34 if constexpr (ENABLE_UNIFICATION)
35 {
36 PointerObjectParents_.push_back(index);
37 PointerObjectRank_.push_back(0);
38 }
39 PointsToSets_.emplace_back(); // Add empty points-to set
40 return PointerObjects_.size() - 1;
41}
42
43size_t
45{
46 return PointerObjects_.size();
47}
48
49size_t
51{
52 size_t count = 0;
53 for (auto & pointerObject : PointerObjects_)
54 {
55 count += pointerObject.Kind == kind;
56 }
57 return count;
58}
59
60size_t
65
66size_t
71
72size_t
74{
75 size_t count = 0;
76 for (auto & pointerObject : PointerObjects_)
77 {
78 count += !pointerObject.IsRegister() && pointerObject.CanPoint();
79 }
80 return count;
81}
82
85{
86 JLM_ASSERT(RegisterMap_.count(&rvsdgOutput) == 0);
87 return RegisterMap_[&rvsdgOutput] = AddPointerObject(PointerObjectKind::Register, true);
88}
89
92{
93 const auto it = RegisterMap_.find(&rvsdgOutput);
94 JLM_ASSERT(it != RegisterMap_.end());
95 return it->second;
96}
97
98std::optional<PointerObjectIndex>
100{
101 if (const auto it = RegisterMap_.find(&rvsdgOutput); it != RegisterMap_.end())
102 return it->second;
103 return std::nullopt;
104}
105
106void
108 const rvsdg::Output & rvsdgOutput,
109 PointerObjectIndex pointerObject)
110{
111 JLM_ASSERT(RegisterMap_.count(&rvsdgOutput) == 0);
113 RegisterMap_[&rvsdgOutput] = pointerObject;
114}
115
121
124{
125 JLM_ASSERT(AllocaMap_.count(&allocaNode) == 0);
126 return AllocaMap_[&allocaNode] =
128}
129
132{
133 JLM_ASSERT(MallocMap_.count(&mallocNode) == 0);
134 return MallocMap_[&mallocNode] =
136}
137
140{
141 JLM_ASSERT(GlobalMap_.count(&deltaNode) == 0);
143}
144
147{
148 JLM_ASSERT(!FunctionMap_.HasKey(&lambdaNode));
149 const auto pointerObject = AddPointerObject(PointerObjectKind::FunctionMemoryObject, false);
150 FunctionMap_.Insert(&lambdaNode, pointerObject);
151 return pointerObject;
152}
153
156{
157 JLM_ASSERT(FunctionMap_.HasKey(&lambdaNode));
158 return FunctionMap_.LookupKey(&lambdaNode);
159}
160
161const rvsdg::LambdaNode &
163{
164 JLM_ASSERT(FunctionMap_.HasValue(index));
165 return *FunctionMap_.LookupValue(index);
166}
167
170{
171 JLM_ASSERT(ImportMap_.count(&importNode) == 0);
172
173 auto importMemoryObject = AddPointerObject(PointerObjectKind::ImportMemoryObject, canPoint);
174 ImportMap_[&importNode] = importMemoryObject;
175
176 // Memory objects defined in other modules are definitely not private to this module
177 MarkAsEscaped(importMemoryObject);
178 return importMemoryObject;
179}
180
181const std::unordered_map<const rvsdg::Output *, PointerObjectIndex> &
183{
184 return RegisterMap_;
185}
186
187const std::unordered_map<const rvsdg::SimpleNode *, PointerObjectIndex> &
189{
190 return AllocaMap_;
191}
192
193const std::unordered_map<const rvsdg::SimpleNode *, PointerObjectIndex> &
195{
196 return MallocMap_;
197}
198
199const std::unordered_map<const rvsdg::DeltaNode *, PointerObjectIndex> &
201{
202 return GlobalMap_;
203}
204
207{
208 return FunctionMap_;
209}
210
211const std::unordered_map<const LlvmGraphImport *, PointerObjectIndex> &
213{
214 return ImportMap_;
215}
216
219{
220 JLM_ASSERT(index < NumPointerObjects());
221 return PointerObjects_[index].Kind;
222}
223
224bool
226{
227 JLM_ASSERT(index < NumPointerObjects());
228 return PointerObjects_[index].CanPoint();
229}
230
231bool
233{
234 JLM_ASSERT(index < NumPointerObjects());
235 return PointerObjects_[index].IsRegister();
236}
237
238bool
240{
241 JLM_ASSERT(index < NumPointerObjects());
242 return PointerObjects_[index].HasEscaped;
243}
244
245bool
247{
248 // Registers do not have addresses, and can as such not escape
250 if (PointerObjects_[index].HasEscaped)
251 return false;
252
253 PointerObjects_[index].HasEscaped = true;
254
255 // Flags implied by escaping
258
259 return true;
260}
261
262[[nodiscard]] bool
264{
265 return PointerObjects_[GetUnificationRoot(index)].PointeesEscaping;
266}
267
268bool
270{
271 auto root = GetUnificationRoot(index);
272 if (PointerObjects_[root].PointeesEscaping)
273 return false;
274 PointerObjects_[root].PointeesEscaping = true;
275 return true;
276}
277
278bool
280{
281 return PointerObjects_[GetUnificationRoot(index)].PointsToExternal;
282}
283
284bool
286{
287 auto root = GetUnificationRoot(index);
288 if (PointerObjects_[root].PointsToExternal)
289 return false;
290 PointerObjects_[root].PointsToExternal = true;
291 return true;
292}
293
294bool
296{
297 auto root = GetUnificationRoot(index);
298 return PointerObjects_[root].CanTrackPointeesImplicitly();
299}
300
301bool
303{
304 auto root = GetUnificationRoot(index);
305 if (PointerObjects_[root].StoredAsScalar)
306 return false;
307 PointerObjects_[root].StoredAsScalar = true;
308 return true;
309}
310
311[[nodiscard]] bool
313{
314 return PointerObjects_[GetUnificationRoot(index)].StoredAsScalar;
315}
316
317bool
319{
320 auto root = GetUnificationRoot(index);
321 if (PointerObjects_[root].LoadedAsScalar)
322 return false;
323 PointerObjects_[root].LoadedAsScalar = true;
324 return true;
325}
326
327[[nodiscard]] bool
329{
330 return PointerObjects_[GetUnificationRoot(index)].LoadedAsScalar;
331}
332
335{
336 JLM_ASSERT(index < NumPointerObjects());
337
338 if constexpr (ENABLE_UNIFICATION)
339 {
340 // Technique known as path halving, gives same asymptotic performance as full path compression
341 while (PointerObjectParents_[index] != index)
342 {
343 auto & parent = PointerObjectParents_[index];
344 auto grandparent = PointerObjectParents_[parent];
345 index = parent = grandparent;
346 }
347 }
348
349 return index;
350}
351
352bool
354{
355 return GetUnificationRoot(index) == index;
356}
357
360{
361 if constexpr (!ENABLE_UNIFICATION)
362 JLM_UNREACHABLE("Unification is not enabled");
363
364 PointerObjectIndex newRoot = GetUnificationRoot(object1);
365 PointerObjectIndex oldRoot = GetUnificationRoot(object2);
366
367 if (newRoot == oldRoot)
368 return newRoot;
369
370 // Make sure the rank continues to be an upper bound for height.
371 // If they have different rank, the root should be the one with the highest rank.
372 // Equal rank forces the new root to increase its rank
373 if (PointerObjectRank_[newRoot] < PointerObjectRank_[oldRoot])
374 std::swap(newRoot, oldRoot);
375 else if (PointerObjectRank_[newRoot] == PointerObjectRank_[oldRoot])
376 PointerObjectRank_[newRoot]++;
377
378 // Ensure any flags set on the points-to set continue to be set in the new unification
379 if (IsPointingToExternal(oldRoot))
381 if (HasPointeesEscaping(oldRoot))
382 MarkAsPointeesEscaping(newRoot);
383 if (IsStoredAsScalar(oldRoot))
384 MarkAsStoringAsScalar(newRoot);
385 if (IsLoadedAsScalar(oldRoot))
386 MarkAsLoadingAsScalar(newRoot);
387
388 // Perform the actual unification
389 PointerObjectParents_[oldRoot] = newRoot;
390
391 // Copy over all pointees, and clean the pointee set from the old root
392 auto & oldRootPointees = PointsToSets_[oldRoot];
393
394 NumSetInsertionAttempts_ += oldRootPointees.Size();
395 NumExplicitPointeesRemoved_ += oldRootPointees.Size();
396
397 PointsToSets_[newRoot].UnionWithAndClear(oldRootPointees);
398
399 return newRoot;
400}
401
407
408// Makes pointee a member of P(pointer)
409bool
411{
412 JLM_ASSERT(pointer < NumPointerObjects());
413 JLM_ASSERT(pointee < NumPointerObjects());
414 // Assert we are not trying to point to a register
416
417 const auto pointerRoot = GetUnificationRoot(pointer);
418
420 return PointsToSets_[pointerRoot].insert(pointee);
421}
422
423// Makes P(superset) a superset of P(subset)
424template<typename NewPointeeFunctor>
425bool
427 PointerObjectIndex superset,
428 PointerObjectIndex subset,
429 NewPointeeFunctor & onNewPointee)
430{
431 auto supersetRoot = GetUnificationRoot(superset);
432 auto subsetRoot = GetUnificationRoot(subset);
433
434 if (supersetRoot == subsetRoot)
435 return false;
436
437 auto & P_super = PointsToSets_[supersetRoot];
438 auto & P_sub = PointsToSets_[subsetRoot];
439
440 NumSetInsertionAttempts_ += P_sub.Size();
441
442 bool modified = false;
443 for (PointerObjectIndex pointee : P_sub.Items())
444 {
445 if (P_super.insert(pointee))
446 {
447 onNewPointee(pointee);
448 modified = true;
449 }
450 }
451
452 // If the external node is in the subset, it must also be part of the superset
453 if (IsPointingToExternal(subsetRoot))
454 modified |= MarkAsPointingToExternal(supersetRoot);
455
456 return modified;
457}
458
459bool
461{
462 // NewPointee is a no-op
463 const auto & NewPointee = [](PointerObjectIndex)
464 {
465 };
466 return PropagateNewPointees(superset, subset, NewPointee);
467}
468
469bool
471 PointerObjectIndex superset,
472 PointerObjectIndex subset,
474{
475 const auto & NewPointee = [&](PointerObjectIndex pointee)
476 {
477 newPointees.insert(pointee);
478 };
479 return PropagateNewPointees(superset, subset, NewPointee);
480}
481
482void
484{
485 auto root = GetUnificationRoot(index);
487 PointsToSets_[root].Clear();
488}
489
490bool
492{
493 // Check if it is an implicit pointee
494 if (IsPointingToExternal(pointer) && HasEscaped(pointee))
495 {
496 return true;
497 }
498
499 // Otherwise, check if it is an explicit pointee
500 if (GetPointsToSet(pointer).Contains(pointee))
501 {
502 return true;
503 }
504
505 return false;
506}
507
508std::unique_ptr<PointerObjectSet>
510{
511 return std::make_unique<PointerObjectSet>(*this);
512}
513
514bool
516{
517 if (NumPointerObjects() != other.NumPointerObjects())
518 return false;
519
520 // Check that each pointer object has the same Sol set in both sets
521 for (PointerObjectIndex i = 0; i < NumPointerObjects(); i++)
522 {
523 // Either i escapes in both sets, or in neither set
524 if (HasEscaped(i) != other.HasEscaped(i))
525 return false;
526
527 // Either i points to external in both sets, or in neither set
529 return false;
530
531 // Each explicit pointee of i in one set, should also be a pointee of i in the opposite set
532 for (auto thisPointee : GetPointsToSet(i).Items())
533 {
534 if (!other.IsPointingTo(i, thisPointee))
535 return false;
536 }
537 for (auto otherPointee : other.GetPointsToSet(i).Items())
538 {
539 if (!IsPointingTo(i, otherPointee))
540 return false;
541 }
542 }
543 return true;
544}
545
546size_t
551
552size_t
557
558// Makes P(superset) a superset of P(subset)
559bool
564
565// for all x in P(pointer1), make P(x) a superset of P(pointer2)
566bool
568{
569 bool modified = false;
571 modified |= set.MakePointsToSetSuperset(x, Value_);
572
573 // If external in P(pointer), P(external) should become a superset of P(value)
574 // In practice, this means everything in P(value) escapes
576 modified |= set.MarkAsPointeesEscaping(Value_);
577
578 return modified;
579}
580
581// Make P(loaded) a superset of P(x) for all x in P(pointer)
582bool
584{
585 bool modified = false;
587 modified |= set.MakePointsToSetSuperset(Value_, x);
588
589 // P(pointer) "contains" external, then P(loaded) should also "contain" it
591 modified |= set.MarkAsPointingToExternal(Value_);
592
593 return modified;
594}
595
604template<typename MarkAsPointeesEscaping, typename MarkAsPointsToExternal>
605void
607 PointerObjectSet & set,
608 const rvsdg::SimpleNode & callNode,
609 MarkAsPointeesEscaping & markAsPointeesEscaping,
610 MarkAsPointsToExternal & markAsPointsToExternal)
611{
612 JLM_ASSERT(is<CallOperation>(callNode.GetOperation()));
613
614 // Mark all the call's inputs as escaped, and all the outputs as pointing to external
615 for (size_t n = 0; n < CallOperation::NumArguments(callNode); n++)
616 {
617 const auto & inputRegister = *CallOperation::Argument(callNode, n)->origin();
618 const auto inputRegisterPO = set.TryGetRegisterPointerObject(inputRegister);
619
620 if (inputRegisterPO)
621 markAsPointeesEscaping(inputRegisterPO.value());
622 }
623
624 for (size_t n = 0; n < callNode.noutputs(); n++)
625 {
626 const auto & outputRegister = *callNode.output(n);
627 const auto outputRegisterPO = set.TryGetRegisterPointerObject(outputRegister);
628 if (outputRegisterPO)
629 markAsPointsToExternal(outputRegisterPO.value());
630 }
631}
632
642template<typename MarkAsPointeesEscaping, typename MarkAsPointsToExternal>
643static void
645 PointerObjectSet & set,
646 const rvsdg::SimpleNode & callNode,
647 [[maybe_unused]] PointerObjectIndex imported,
648 MarkAsPointeesEscaping & markAsPointeesEscaping,
649 MarkAsPointsToExternal & markAsPointsToExternal)
650{
651 JLM_ASSERT(is<CallOperation>(callNode.GetOperation()));
652
653 // FIXME: Add special handling of common library functions
654 // Otherwise we don't know anything about the function
656 set,
657 callNode,
658 markAsPointeesEscaping,
659 markAsPointsToExternal);
660}
661
666template<typename MakeSupersetFunctor>
667static void
669 PointerObjectSet & set,
670 const rvsdg::SimpleNode & callNode,
671 const rvsdg::LambdaNode & lambdaNode,
672 MakeSupersetFunctor & makeSuperset)
673{
674 JLM_ASSERT(is<CallOperation>(callNode.GetOperation()));
675
676 auto lambdaArgs = lambdaNode.GetFunctionArguments();
677 for (size_t n = 0; n < CallOperation::NumArguments(callNode) && n < lambdaArgs.size(); n++)
678 {
679 const auto & inputRegister = *CallOperation::Argument(callNode, n)->origin();
680 const auto & argumentRegister = *lambdaArgs[n];
681
682 const auto inputRegisterPO = set.TryGetRegisterPointerObject(inputRegister);
683 const auto argumentRegisterPO = set.TryGetRegisterPointerObject(argumentRegister);
684 if (!inputRegisterPO || !argumentRegisterPO)
685 continue;
686
687 makeSuperset(*argumentRegisterPO, *inputRegisterPO);
688 }
689}
690
695template<typename MakeSupersetFunctor>
696static void
698 PointerObjectSet & set,
699 const rvsdg::SimpleNode & callNode,
700 const rvsdg::LambdaNode & lambdaNode,
701 MakeSupersetFunctor & makeSuperset)
702{
703 JLM_ASSERT(is<CallOperation>(callNode.GetOperation()));
704
705 auto lambdaResults = lambdaNode.GetFunctionResults();
706 for (size_t n = 0; n < callNode.noutputs() && n < lambdaResults.size(); n++)
707 {
708 const auto & outputRegister = *callNode.output(n);
709 const auto & resultRegister = *lambdaResults[n]->origin();
710
711 const auto outputRegisterPO = set.TryGetRegisterPointerObject(outputRegister);
712 const auto resultRegisterPO = set.TryGetRegisterPointerObject(resultRegister);
713 if (!outputRegisterPO || !resultRegisterPO)
714 continue;
715
716 makeSuperset(*outputRegisterPO, *resultRegisterPO);
717 }
718}
719
731template<typename MakeSupersetFunctor>
732static void
734 PointerObjectSet & set,
735 const rvsdg::SimpleNode & callNode,
736 PointerObjectIndex lambda,
737 MakeSupersetFunctor & makeSuperset)
738{
739 JLM_ASSERT(is<CallOperation>(callNode.GetOperation()));
740 auto & lambdaNode = set.GetLambdaNodeFromFunctionMemoryObject(lambda);
741
742 // LLVM allows calling functions even when the number of arguments don't match,
743 // so we instead pair up as many parameters and return values as possible
744
745 // Pass all call node inputs to the function's subregion
746 HandleLambdaCallParameters(set, callNode, lambdaNode, makeSuperset);
747
748 // Pass the function's subregion results to the output of the call node
749 HandleLambdaCallReturnValues(set, callNode, lambdaNode, makeSuperset);
750}
751
752// Connects function calls to every possible target function
753bool
755{
756 bool modified = false;
757
758 const auto MakeSuperset = [&](PointerObjectIndex superset, PointerObjectIndex subset)
759 {
760 modified |= set.MakePointsToSetSuperset(superset, subset);
761 };
762
763 const auto MarkAsPointeesEscaping = [&](PointerObjectIndex index)
764 {
765 modified |= set.MarkAsPointeesEscaping(index);
766 };
767
768 const auto MarkAsPointsToExternal = [&](PointerObjectIndex index)
769 {
770 modified |= set.MarkAsPointingToExternal(index);
771 };
772
773 // For each possible function target, connect parameters and return values to the call node
774 for (const auto target : set.GetPointsToSet(Pointer_).Items())
775 {
776 const auto kind = set.GetPointerObjectKind(target);
779 set,
780 CallNode_,
781 target,
782 MarkAsPointeesEscaping,
783 MarkAsPointsToExternal);
785 HandleCallingLambdaFunction(set, CallNode_, target, MakeSuperset);
786 }
787
788 // If we might be calling an external function
790 HandleCallingExternalFunction(set, CallNode_, MarkAsPointeesEscaping, MarkAsPointsToExternal);
791
792 return modified;
793}
794
795bool
797{
798 bool modified = false;
799
800 // First handle all unification roots marked as storing or loading scalars
801 for (PointerObjectIndex idx = 0; idx < set.NumPointerObjects(); idx++)
802 {
803 if (!set.IsUnificationRoot(idx))
804 continue;
805
806 if (set.IsStoredAsScalar(idx))
807 {
808 for (auto pointee : set.GetPointsToSet(idx).Items())
809 {
810 modified |= set.MarkAsPointingToExternal(pointee);
811 }
812 }
813 if (set.IsLoadedAsScalar(idx))
814 {
815 for (auto pointee : set.GetPointsToSet(idx).Items())
816 {
817 modified |= set.MarkAsPointeesEscaping(pointee);
818 }
819 }
820 }
821
822 std::queue<PointerObjectIndex> pointeeEscapers;
823 // Add all unification roots marked as PointeesEscaping to the queue
824 for (PointerObjectIndex idx = 0; idx < set.NumPointerObjects(); idx++)
825 {
826 if (set.IsUnificationRoot(idx) && set.HasPointeesEscaping(idx))
827 pointeeEscapers.push(idx);
828 }
829
830 // For all pointee escapers, check if they point to any PointerObjects not marked as escaped
831 while (!pointeeEscapers.empty())
832 {
833 const PointerObjectIndex pointeeEscaper = pointeeEscapers.front();
834 pointeeEscapers.pop();
835
836 for (PointerObjectIndex pointee : set.GetPointsToSet(pointeeEscaper).Items())
837 {
838 const auto unificationRoot = set.GetUnificationRoot(pointee);
839 const bool prevHasPointeesEscaping = set.HasPointeesEscaping(unificationRoot);
840
841 modified |= set.MarkAsEscaped(pointee);
842
843 // If the pointee's unification root previously didn't have the PointeesEscaping flag,
844 // add it to the queue
845 if (!prevHasPointeesEscaping)
846 {
847 JLM_ASSERT(set.HasPointeesEscaping(unificationRoot));
848 pointeeEscapers.push(unificationRoot);
849 }
850 }
851 }
852
853 return modified;
854}
855
867template<typename MarkAsPointeesEscapingFunctor, typename MarkAsPointsToExternalFunctor>
868static void
870 PointerObjectSet & set,
871 PointerObjectIndex lambda,
872 MarkAsPointeesEscapingFunctor & markAsPointeesEscaping,
873 MarkAsPointsToExternalFunctor & markAsPointsToExternal)
874{
876 JLM_ASSERT(set.HasEscaped(lambda));
877
878 // We now go through the lambda's inner region and apply the necessary flags
879 auto & lambdaNode = set.GetLambdaNodeFromFunctionMemoryObject(lambda);
880
881 // All the function's arguments need to be flagged as PointsToExternal
882 for (auto argument : lambdaNode.GetFunctionArguments())
883 {
884 // Argument registers that are mapped to a register pointer object should point to external
885 const auto argumentPO = set.TryGetRegisterPointerObject(*argument);
886 if (!argumentPO)
887 continue;
888
889 // Nothing to be done if it is already marked as points to external
890 if (set.IsPointingToExternal(argumentPO.value()))
891 continue;
892
893 markAsPointsToExternal(argumentPO.value());
894 }
895
896 // All results of pointer type need to be flagged as pointees escaping
897 for (auto result : lambdaNode.GetFunctionResults())
898 {
899 const auto resultPO = set.TryGetRegisterPointerObject(*result->origin());
900 if (!resultPO)
901 continue;
902
903 // Nothing to be done if it is already marked as pointees escaping
904 if (set.HasPointeesEscaping(resultPO.value()))
905 continue;
906
907 // Mark the result register as making any pointees it may have escape
908 markAsPointeesEscaping(resultPO.value());
909 }
910}
911
912bool
914{
915 bool modified = false;
916
917 const auto markAsPointeesEscaping = [&](PointerObjectIndex index)
918 {
919 modified |= set.MarkAsPointeesEscaping(index);
920 };
921
922 const auto markAsPointsToExternal = [&](PointerObjectIndex index)
923 {
924 modified |= set.MarkAsPointingToExternal(index);
925 };
926
927 for (const auto & [lambda, lambdaPO] : set.GetFunctionMap())
928 {
929 if (set.HasEscaped(lambdaPO))
930 HandleEscapedFunction(set, lambdaPO, markAsPointeesEscaping, markAsPointsToExternal);
931 }
932
933 return modified;
934}
935
936bool
938{
940}
941
942void
944 PointerObjectIndex pointer,
945 PointerObjectIndex pointee)
946{
948 // All set constraints are additive, so simple constraints like this can be directly applied.
949 Set_.AddToPointsToSet(pointer, pointee);
950}
951
952void
954{
956 // Flags are never removed, so adding the flag now ensures it will be included.
958}
959
960void
967
968void
974
975const std::vector<PointerObjectConstraintSet::ConstraintVariant> &
977{
978 return Constraints_;
979}
980
981size_t
983{
984 size_t numBaseConstraints = 0;
985 for (PointerObjectIndex i = 0; i < Set_.NumPointerObjects(); i++)
986 {
987 if (Set_.IsUnificationRoot(i))
988 numBaseConstraints += Set_.GetPointsToSet(i).Size();
989 }
990 return numBaseConstraints;
991}
992
993std::pair<size_t, size_t>
995{
996 size_t numScalarFlagConstraints = 0;
997 size_t numOtherFlagConstraints = 0;
998 for (PointerObjectIndex i = 0; i < Set_.NumPointerObjects(); i++)
999 {
1000 if (Set_.HasEscaped(i))
1001 numOtherFlagConstraints++;
1002
1003 if (!Set_.IsUnificationRoot(i))
1004 continue;
1005
1007 numOtherFlagConstraints++;
1009 numOtherFlagConstraints++;
1010 if (Set_.IsStoredAsScalar(i))
1011 numScalarFlagConstraints++;
1012 if (Set_.IsLoadedAsScalar(i))
1013 numScalarFlagConstraints++;
1014 }
1015 return { numScalarFlagConstraints, numOtherFlagConstraints };
1016}
1017
1024static std::string
1026{
1027 std::ostringstream label;
1028 label << index;
1029
1030 auto kind = set.GetPointerObjectKind(index);
1032 label << " A";
1034 label << " M";
1036 label << " F";
1038 label << " G";
1040 label << " I";
1041 else if (kind != PointerObjectKind::Register)
1042 JLM_UNREACHABLE("Unknown PointerObject kind");
1043
1044 label << "\n";
1045
1046 if (set.IsUnificationRoot(index))
1047 {
1048 label << "{";
1049 bool sep = false;
1050 for (auto pointee : set.GetPointsToSet(index).Items())
1051 {
1052 if (sep)
1053 label << ", ";
1054 sep = true;
1055 label << pointee;
1056 }
1057 // Add a + if pointing to external
1058 if (set.IsPointingToExternal(index))
1059 label << (sep ? ", +" : "+");
1060 label << "}";
1061
1062 if (set.HasPointeesEscaping(index))
1063 label << "e";
1064 }
1065 else
1066 {
1067 label << "#" << set.GetUnificationRoot(index);
1068 }
1069
1070 if (!set.CanPoint(index))
1071 label << "\nCantPoint";
1072
1073 return label.str();
1074}
1075
1081static void
1083{
1084 // Ensure the index of nodes line up with the index of the corresponding PointerObject
1085 JLM_ASSERT(graph.NumNodes() == 0);
1086
1087 // Create nodes for each PointerObject
1088 for (PointerObjectIndex i = 0; i < set.NumPointerObjects(); i++)
1089 {
1090 auto & node = graph.CreateNode();
1092
1093 if (set.IsPointerObjectRegister(i))
1094 node.SetShape(util::graph::Node::Shape::Oval);
1095 else
1097
1098 if (set.HasEscaped(i))
1099 node.SetFillColor("#FFFF99");
1100 }
1101
1102 // Associate PointerObjects nodes with their associated RVSDG nodes / outputs
1103 for (auto [allocaNode, index] : set.GetAllocaMap())
1104 graph.GetNode(index).SetAttributeObject("rvsdgAlloca", *allocaNode);
1105
1106 for (auto [mallocNode, index] : set.GetMallocMap())
1107 graph.GetNode(index).SetAttributeObject("rvsdgMalloc", *mallocNode);
1108
1109 for (auto [deltaNode, index] : set.GetGlobalMap())
1110 graph.GetNode(index).SetAttributeObject("rvsdgDelta", *deltaNode);
1111
1112 for (auto [lambdaNode, index] : set.GetFunctionMap())
1113 graph.GetNode(index).SetAttributeObject("rvsdgLambda", *lambdaNode);
1114
1115 for (auto [importArgument, index] : set.GetImportMap())
1116 graph.GetNode(index).SetAttributeObject("rvsdgImport", *importArgument);
1117
1118 // Multiple registers can be associated with the same register PointerObject, so add a suffix
1119 std::unordered_map<PointerObjectIndex, size_t> associationSuffix;
1120 for (auto [rvsdgOutput, index] : set.GetRegisterMap())
1121 {
1122 auto suffix = associationSuffix[index]++;
1123 graph.GetNode(index).SetAttributeObject(util::strfmt("rvsdgOutput", suffix), *rvsdgOutput);
1124 }
1125}
1126
1134static void
1136 const PointerObjectSet & set,
1137 const std::vector<PointerObjectConstraintSet::ConstraintVariant> & constraints,
1138 util::graph::Graph & graph)
1139{
1140 // Draw edges for constraints
1141 size_t nextCallConstraintIndex = 0;
1142 for (auto & constraint : constraints)
1143 {
1144 if (auto * supersetConstraint = std::get_if<SupersetConstraint>(&constraint))
1145 {
1146 graph.CreateDirectedEdge(
1147 graph.GetNode(supersetConstraint->GetSubset()),
1148 graph.GetNode(supersetConstraint->GetSuperset()));
1149 }
1150 else if (auto * storeConstraint = std::get_if<StoreConstraint>(&constraint))
1151 {
1152 auto & edge = graph.CreateDirectedEdge(
1153 graph.GetNode(storeConstraint->GetValue()),
1154 graph.GetNode(storeConstraint->GetPointer()));
1156 edge.SetArrowHead("normalodot");
1157 }
1158 else if (auto * loadConstraint = std::get_if<LoadConstraint>(&constraint))
1159 {
1160 auto & edge = graph.CreateDirectedEdge(
1161 graph.GetNode(loadConstraint->GetPointer()),
1162 graph.GetNode(loadConstraint->GetValue()));
1164 edge.SetArrowTail("odot");
1165 }
1166 else if (auto * callConstraint = std::get_if<FunctionCallConstraint>(&constraint))
1167 {
1168 auto callConstraintIndex = nextCallConstraintIndex++;
1169 auto & pointerNode = graph.GetNode(callConstraint->GetPointer());
1170 pointerNode.AppendToLabel(util::strfmt("call", callConstraintIndex, " target"));
1171
1172 // Connect all registers that correspond to inputs and outputs of the call, to the call target
1173 auto & callNode = callConstraint->GetCallNode();
1174 for (size_t i = 0; i < CallOperation::NumArguments(callNode); i++)
1175 {
1176 if (auto inputRegister =
1178 {
1179 const auto label = util::strfmt("call", callConstraintIndex, " input", i);
1180 graph.GetNode(*inputRegister).AppendToLabel(label);
1181 }
1182 }
1183 for (size_t i = 0; i < callNode.noutputs(); i++)
1184 {
1185 if (auto outputRegister = set.TryGetRegisterPointerObject(*callNode.output(i)))
1186 {
1187 const auto label = util::strfmt("call", callConstraintIndex, " output", i);
1188 graph.GetNode(*outputRegister).AppendToLabel(label);
1189 }
1190 }
1191 }
1192 else
1193 {
1194 JLM_UNREACHABLE("Unknown constraint type");
1195 }
1196 }
1197}
1198
1206static void
1208{
1209 size_t nextFunctionIndex = 0;
1210 for (auto [function, pointerObject] : set.GetFunctionMap())
1211 {
1213 const auto functionIndex = nextFunctionIndex++;
1214 graph.GetNode(pointerObject).AppendToLabel(util::strfmt("function", functionIndex));
1215
1216 // Add labels to registers corresponding to arguments and results of the function
1217 auto args = function->GetFunctionArguments();
1218 for (size_t i = 0; i < args.size(); i++)
1219 {
1220 if (auto argumentRegister = set.TryGetRegisterPointerObject(*args[i]))
1221 {
1222 const auto label = util::strfmt("function", functionIndex, " arg", i);
1223 graph.GetNode(*argumentRegister).AppendToLabel(label);
1224 }
1225 }
1226 auto results = function->GetFunctionResults();
1227 for (size_t i = 0; i < results.size(); i++)
1228 {
1229 if (auto resultRegister = set.TryGetRegisterPointerObject(*results[i]->origin()))
1230 {
1231 const auto label = util::strfmt("function", functionIndex, " res", i);
1232 graph.GetNode(*resultRegister).AppendToLabel(label);
1233 }
1234 }
1235 }
1236}
1237
1240{
1241 auto & graph = writer.CreateGraph();
1242 graph.SetLabel("Andersen subset graph");
1243
1247
1248 return graph;
1249}
1250
1251// Helper function for DoOfflineVariableSubstitution()
1252std::tuple<size_t, std::vector<util::HashSet<PointerObjectIndex>>, std::vector<bool>>
1254{
1255 // The index of n(v) is the index of the PointerObject v
1256 // The index of n(*v) is the index of v + derefNodeOffset
1257 const size_t derefNodeOffset = Set_.NumPointerObjects();
1258 const size_t totalNodeCount = Set_.NumPointerObjects() * 2;
1259 std::vector<util::HashSet<PointerObjectIndex>> successors(totalNodeCount);
1260 std::vector<bool> isDirectNode(totalNodeCount, false);
1261
1262 // Nodes representing registers can be direct nodes, but only if they have empty points-to sets
1263 for (auto [_, index] : Set_.GetRegisterMap())
1264 isDirectNode[index] = Set_.GetPointsToSet(index).IsEmpty();
1265
1266 // Mark all function argument register nodes as not direct
1267 for (auto [lambda, _] : Set_.GetFunctionMap())
1268 {
1269 for (auto arg : lambda->GetFunctionArguments())
1270 {
1271 if (auto argumentPO = Set_.TryGetRegisterPointerObject(*arg))
1272 isDirectNode[*argumentPO] = false;
1273 }
1274 }
1275
1276 // Create the offline subset graph, and mark all registers that are not direct
1277 for (auto constraint : Constraints_)
1278 {
1279 if (auto * supersetConstraint = std::get_if<SupersetConstraint>(&constraint))
1280 {
1281 auto subset = Set_.GetUnificationRoot(supersetConstraint->GetSubset());
1282 auto superset = Set_.GetUnificationRoot(supersetConstraint->GetSuperset());
1283
1284 successors[subset].insert(superset);
1285 // Also add an edge for *subset -> *superset, from the original OVS paper.
1286 // It is not mentioned in Hardekopf and Lin, 2007: The Ant and the Grasshopper.
1287 successors[subset + derefNodeOffset].insert(superset + derefNodeOffset);
1288 }
1289 else if (auto * storeConstraint = std::get_if<StoreConstraint>(&constraint))
1290 {
1291 auto pointer = Set_.GetUnificationRoot(storeConstraint->GetPointer());
1292 auto value = Set_.GetUnificationRoot(storeConstraint->GetValue());
1293
1294 // Add an edge for value -> *pointer
1295 successors[value].insert(pointer + derefNodeOffset);
1296 }
1297 else if (auto * loadConstraint = std::get_if<LoadConstraint>(&constraint))
1298 {
1299 auto value = Set_.GetUnificationRoot(loadConstraint->GetValue());
1300 auto pointer = Set_.GetUnificationRoot(loadConstraint->GetPointer());
1301
1302 // Add an edge for *pointer -> value
1303 successors[pointer + derefNodeOffset].insert(value);
1304 }
1305 else if (auto * callConstraint = std::get_if<FunctionCallConstraint>(&constraint))
1306 {
1307 auto & callNode = callConstraint->GetCallNode();
1308 // Mark all results of function calls as non-direct nodes
1309 for (size_t n = 0; n < callNode.noutputs(); n++)
1310 {
1311 if (auto resultPO = Set_.TryGetRegisterPointerObject(*callNode.output(n)))
1312 isDirectNode[*resultPO] = false;
1313 }
1314 }
1315 else
1316 JLM_UNREACHABLE("Unknown constraint variant");
1317 }
1318
1319 return { totalNodeCount, std::move(successors), std::move(isDirectNode) };
1320}
1321
1329static std::vector<int64_t>
1331 std::vector<util::HashSet<PointerObjectIndex>> & successors,
1332 size_t numSccs,
1333 std::vector<size_t> & sccIndex,
1334 std::vector<size_t> & reverseTopologicalOrder,
1335 std::vector<bool> & sccHasDirectNodesOnly)
1336{
1337 // Visit all SCCs in topological order and assign equivalence set labels
1338 int64_t nextEquivalenceSetLabel = 0;
1339 const int64_t NO_EQUIVALENCE_SET_LABEL = -1;
1340 std::vector<int64_t> equivalenceSetLabels(numSccs, NO_EQUIVALENCE_SET_LABEL);
1341
1342 // If all predecessors of a direct SCC share equivalence set label, use that label.
1343 const int64_t NO_PREDECESSOR_YET = -1; // Value used when no predecessor label has been seen
1344 const int64_t SEVERAL_PREDECESSORS = -2; // Value used when different predecessors have been seen
1345 std::vector<int64_t> predecessorEquivalenceLabels(numSccs, NO_PREDECESSOR_YET);
1346
1347 // Iterate over each SCC in topological order, and each node within the SCC.
1348 // This ensures all predecessor SCCs are known before visiting each SCC.
1349 for (auto it = reverseTopologicalOrder.rbegin(); it != reverseTopologicalOrder.rend(); ++it)
1350 {
1351 const auto node = *it;
1352 const auto scc = sccIndex[node];
1353
1354 // If this SCC has not been visited in the topological order traversal, give it a label
1355 if (equivalenceSetLabels[scc] == NO_EQUIVALENCE_SET_LABEL)
1356 {
1357 // Check if the SCC is direct, and all predecessors share a single equivalence label.
1358 // Otherwise, give it a new unique equivalence set label.
1359 if (sccHasDirectNodesOnly[scc] && predecessorEquivalenceLabels[scc] != NO_PREDECESSOR_YET
1360 && predecessorEquivalenceLabels[scc] != SEVERAL_PREDECESSORS)
1361 {
1362 equivalenceSetLabels[scc] = predecessorEquivalenceLabels[scc];
1363 }
1364 else
1365 {
1366 equivalenceSetLabels[scc] = nextEquivalenceSetLabel++;
1367 }
1368 }
1369
1370 // Inform all successors of this node about this SCC's equivalence label
1371 for (auto successor : successors[node].Items())
1372 {
1373 const auto successorSCC = sccIndex[successor];
1374 if (predecessorEquivalenceLabels[successorSCC] == SEVERAL_PREDECESSORS)
1375 continue;
1376
1377 if (predecessorEquivalenceLabels[successorSCC] == NO_PREDECESSOR_YET)
1378 {
1379 predecessorEquivalenceLabels[successorSCC] = equivalenceSetLabels[scc];
1380 }
1381 else if (predecessorEquivalenceLabels[successorSCC] != equivalenceSetLabels[scc])
1382 {
1383 predecessorEquivalenceLabels[successorSCC] = SEVERAL_PREDECESSORS;
1384 }
1385 }
1386 }
1387
1388 return equivalenceSetLabels;
1389}
1390
1391size_t
1393{
1394 // Performing unification on direct nodes relies on all subset edges being known offline.
1395 // This is only safe if no more constraints are added to the node in the future.
1396 ConstraintSetFrozen_ = true;
1397
1398 // For each PointerObject v, creates two nodes: n(v) and n(*v), and creates edges between them
1399 auto subsetGraph = CreateOvsSubsetGraph();
1400 auto totalNodeCount = std::get<0>(subsetGraph);
1401 auto & successors = std::get<1>(subsetGraph);
1402 auto & isDirectNode = std::get<2>(subsetGraph);
1403
1404 auto GetSuccessors = [&](PointerObjectIndex node)
1405 {
1406 return successors[node].Items();
1407 };
1408
1409 // Output vectors from Tarjan's SCC algorithm
1410 std::vector<size_t> sccIndex;
1411 std::vector<size_t> reverseTopologicalOrder;
1412 auto numSccs = util::FindStronglyConnectedComponents<size_t>(
1413 totalNodeCount,
1414 GetSuccessors,
1415 sccIndex,
1416 reverseTopologicalOrder);
1417
1418 // Find out which SCCs contain only direct nodes, as described in CreateOvsSubsetGraph()
1419 std::vector<bool> sccHasDirectNodesOnly(numSccs, true);
1420 for (size_t node = 0; node < totalNodeCount; node++)
1421 {
1422 if (!isDirectNode[node])
1423 sccHasDirectNodesOnly[sccIndex[node]] = false;
1424 }
1425
1426 // Give each SCC an equivalence set label
1427 auto equivalenceSetLabels = AssignOvsEquivalenceSetLabels(
1428 successors,
1429 numSccs,
1430 sccIndex,
1431 reverseTopologicalOrder,
1432 sccHasDirectNodesOnly);
1433
1434 // Finally unify all PointerObjects with equal equivalence label
1435 size_t numUnifications = 0;
1436 std::vector<std::optional<PointerObjectIndex>> unificationRoot(
1437 equivalenceSetLabels.size(),
1438 std::nullopt);
1439
1440 for (PointerObjectIndex i = 0; i < Set_.NumPointerObjects(); i++)
1441 {
1442 if (!Set_.IsUnificationRoot(i))
1443 continue;
1444
1445 const auto equivalenceSetLabel = equivalenceSetLabels[sccIndex[i]];
1446
1447 // If other nodes with the same equivalence set label have been seen, unify it with i
1448 if (unificationRoot[equivalenceSetLabel])
1449 {
1450 unificationRoot[equivalenceSetLabel] =
1451 Set_.UnifyPointerObjects(i, *unificationRoot[equivalenceSetLabel]);
1452 numUnifications++;
1453 }
1454 else
1455 unificationRoot[equivalenceSetLabel] = i;
1456 }
1457
1458 // If hybrid cycle detection is enabled, it requires some information to be kept from OVS
1459 if (storeRefCycleUnificationRoot)
1460 {
1461 // For each ref node that is in a cycle with a regular node, store it for hybrid cycle detection
1462 // The idea: Any pointee of p should be unified with a, if *p and a are in the same SCC
1463 // NOTE: We do not use equivalence set labels here, as they represent more than just cycles
1464
1465 // First find one unification root representing each SCC
1466 std::vector<std::optional<PointerObjectIndex>> unificationRootPerSCC(numSccs, std::nullopt);
1467 for (PointerObjectIndex i = 0; i < Set_.NumPointerObjects(); i++)
1468 {
1469 if (!unificationRootPerSCC[sccIndex[i]])
1470 unificationRootPerSCC[sccIndex[i]] = Set_.GetUnificationRoot(i);
1471 }
1472
1473 // Assign unification roots to ref nodes that belong to SCCs with at least one regular node
1474 const size_t derefNodeOffset = Set_.NumPointerObjects();
1475 for (PointerObjectIndex i = 0; i < Set_.NumPointerObjects(); i++)
1476 {
1477 if (auto optRoot = unificationRootPerSCC[sccIndex[i + derefNodeOffset]])
1478 {
1479 RefNodeUnificationRoot_[i] = *optRoot;
1480 }
1481 }
1482 }
1483
1484 return numUnifications;
1485}
1486
1487size_t
1489{
1490 // The new list of constraints, preserving the order of constraints that are not deleted.
1491 std::vector<ConstraintVariant> newConstraints;
1492
1493 // Sets used to avoid adding duplicates of any constraints
1498
1499 for (auto constraint : Constraints_)
1500 {
1501 // Update all PointerObjectIndex fields to point to unification roots
1502 if (auto * supersetConstraint = std::get_if<SupersetConstraint>(&constraint))
1503 {
1504 auto supersetRoot = Set_.GetUnificationRoot(supersetConstraint->GetSuperset());
1505 auto subsetRoot = Set_.GetUnificationRoot(supersetConstraint->GetSubset());
1506
1507 // Skip no-op constraints
1508 if (supersetRoot == subsetRoot)
1509 continue;
1510
1511 if (addedSupersetConstraints.insert({ supersetRoot, subsetRoot }))
1512 newConstraints.emplace_back(SupersetConstraint(supersetRoot, subsetRoot));
1513 }
1514 else if (auto * storeConstraint = std::get_if<StoreConstraint>(&constraint))
1515 {
1516 auto pointerRoot = Set_.GetUnificationRoot(storeConstraint->GetPointer());
1517 auto valueRoot = Set_.GetUnificationRoot(storeConstraint->GetValue());
1518
1519 if (addedStoreConstraints.insert({ pointerRoot, valueRoot }))
1520 newConstraints.emplace_back(StoreConstraint(pointerRoot, valueRoot));
1521 }
1522 else if (auto * loadConstraint = std::get_if<LoadConstraint>(&constraint))
1523 {
1524 auto valueRoot = Set_.GetUnificationRoot(loadConstraint->GetValue());
1525 auto pointerRoot = Set_.GetUnificationRoot(loadConstraint->GetPointer());
1526
1527 if (addedLoadConstraints.insert({ pointerRoot, valueRoot }))
1528 newConstraints.emplace_back(LoadConstraint(valueRoot, pointerRoot));
1529 }
1530 else if (auto * functionCallConstraint = std::get_if<FunctionCallConstraint>(&constraint))
1531 {
1532 auto pointerRoot = Set_.GetUnificationRoot(functionCallConstraint->GetPointer());
1533 auto & callNode = functionCallConstraint->GetCallNode();
1534
1535 if (addedCallConstraints.insert({ pointerRoot, &callNode }))
1536 newConstraints.emplace_back(FunctionCallConstraint(pointerRoot, callNode));
1537 }
1538 else
1539 JLM_UNREACHABLE("Unknown Constraint variant");
1540 }
1541
1542 size_t reduction = Constraints_.size() - newConstraints.size();
1543 Constraints_ = std::move(newConstraints);
1544 return reduction;
1545}
1546
1547template<
1548 typename Worklist,
1549 bool EnableOnlineCycleDetection,
1550 bool EnableHybridCycleDetection,
1551 bool EnableLazyCycleDetection,
1552 bool EnableDifferencePropagation,
1553 bool EnablePreferImplicitPointees>
1554void
1556{
1557 // Check that the provided worklist implementation inherits from Worklist
1558 static_assert(std::is_base_of_v<util::Worklist<PointerObjectIndex>, Worklist>);
1559
1560 // Online cycle detections detects all cycles immediately, so there is no point in enabling others
1561 if constexpr (EnableOnlineCycleDetection)
1562 {
1563 static_assert(!EnableHybridCycleDetection, "OnlineCD can not be combined with HybridCD");
1564 static_assert(!EnableLazyCycleDetection, "OnlineCD can not be combined with LazyCD");
1565 }
1566
1567 // Create auxiliary subset graph.
1568 // All edges must have their tail be a unification root (non-root nodes have no successors).
1569 // If supersetEdges[x] contains y, (x -> y), that means P(y) supseteq P(x)
1570 std::vector<util::HashSet<PointerObjectIndex>> supersetEdges(Set_.NumPointerObjects());
1571
1572 // Create quick lookup tables for Load, Store and function call constraints.
1573 // Lookup is indexed by the constraint's pointer.
1574 // The constraints need to be added to the unification root, as only unification roots
1575 // are allowed on the worklist. The sets are empty for all non-root nodes.
1576 std::vector<util::HashSet<PointerObjectIndex>> storeConstraints(Set_.NumPointerObjects());
1577 std::vector<util::HashSet<PointerObjectIndex>> loadConstraints(Set_.NumPointerObjects());
1578 std::vector<util::HashSet<const rvsdg::SimpleNode *>> callConstraints(Set_.NumPointerObjects());
1579
1580 for (const auto & constraint : Constraints_)
1581 {
1582 if (const auto * ssConstraint = std::get_if<SupersetConstraint>(&constraint))
1583 {
1584 // Superset constraints become edges in the subset graph
1585 // When initializing the set of superset edges, normalize them as well
1586 auto superset = Set_.GetUnificationRoot(ssConstraint->GetSuperset());
1587 auto subset = Set_.GetUnificationRoot(ssConstraint->GetSubset());
1588
1589 if (superset != subset) // Ignore self-edges
1590 supersetEdges[subset].insert(superset);
1591 }
1592 else if (const auto * storeConstraint = std::get_if<StoreConstraint>(&constraint))
1593 {
1594 auto pointer = Set_.GetUnificationRoot(storeConstraint->GetPointer());
1595 auto value = Set_.GetUnificationRoot(storeConstraint->GetValue());
1596
1597 storeConstraints[pointer].insert(value);
1598 }
1599 else if (const auto * loadConstraint = std::get_if<LoadConstraint>(&constraint))
1600 {
1601 auto pointer = Set_.GetUnificationRoot(loadConstraint->GetPointer());
1602 auto value = Set_.GetUnificationRoot(loadConstraint->GetValue());
1603
1604 loadConstraints[pointer].insert(value);
1605 }
1606 else if (const auto * callConstraint = std::get_if<FunctionCallConstraint>(&constraint))
1607 {
1608 auto pointer = Set_.GetUnificationRoot(callConstraint->GetPointer());
1609 const auto & callNode = callConstraint->GetCallNode();
1610
1611 callConstraints[pointer].insert(&callNode);
1612 }
1613 }
1614
1615 DifferencePropagation differencePropagation(Set_);
1616 if constexpr (EnableDifferencePropagation)
1617 differencePropagation.Initialize();
1618
1619 // Makes pointer point to pointee.
1620 // Returns true if the pointee was new. Does not add pointer to the worklist.
1621 const auto & AddToPointsToSet = [&](PointerObjectIndex pointer,
1622 PointerObjectIndex pointee) -> bool
1623 {
1624 if constexpr (EnableDifferencePropagation)
1625 return differencePropagation.AddToPointsToSet(pointer, pointee);
1626 else
1627 return Set_.AddToPointsToSet(pointer, pointee);
1628 };
1629
1630 // Makes superset point to everything subset points to, and propagates the PointsToEscaped flag.
1631 // Returns true if any pointees were new, or the flag was new.
1632 // Does not add superset to the worklist.
1633 const auto & MakePointsToSetSuperset = [&](PointerObjectIndex superset,
1634 PointerObjectIndex subset) -> bool
1635 {
1636 if constexpr (EnableDifferencePropagation)
1637 return differencePropagation.MakePointsToSetSuperset(superset, subset);
1638 else
1639 return Set_.MakePointsToSetSuperset(superset, subset);
1640 };
1641
1642 // Performs unification safely while the worklist algorithm is running.
1643 // Ensures all constraints end up being owned by the new root.
1644 // It does NOT redirect constraints owned by other nodes, referencing a or b.
1645 // If a and b already belong to the same unification root, this is a no-op.
1646 // This operation does not add the unification result to the worklist.
1647 // Returns the root of the new unification, or the existing root if a and b were already unified.
1648 const auto UnifyPointerObjects = [&](PointerObjectIndex a,
1650 {
1651 const auto aRoot = Set_.GetUnificationRoot(a);
1652 const auto bRoot = Set_.GetUnificationRoot(b);
1653
1654 if (aRoot == bRoot)
1655 return aRoot;
1656
1657 const auto root = Set_.UnifyPointerObjects(aRoot, bRoot);
1658 // The root among the two original roots that did NOT end up as the new root
1659 const auto nonRoot = root == aRoot ? bRoot : aRoot;
1660
1661 // Move constraints owned by the non-root to the root
1662 supersetEdges[root].UnionWithAndClear(supersetEdges[nonRoot]);
1663
1664 // Try to avoid self-edges, but indirect self-edges can still exist
1665 supersetEdges[root].Remove(root);
1666 supersetEdges[root].Remove(nonRoot);
1667
1668 storeConstraints[root].UnionWithAndClear(storeConstraints[nonRoot]);
1669
1670 loadConstraints[root].UnionWithAndClear(loadConstraints[nonRoot]);
1671
1672 callConstraints[root].UnionWithAndClear(callConstraints[nonRoot]);
1673
1674 if constexpr (EnableDifferencePropagation)
1675 differencePropagation.OnPointerObjectsUnified(root, nonRoot);
1676
1677 if constexpr (EnableHybridCycleDetection)
1678 {
1679 // If the new root did not have a ref node unification target, check if the other node has one
1680 if (RefNodeUnificationRoot_.count(root) == 0)
1681 {
1682 const auto nonRootRefUnification = RefNodeUnificationRoot_.find(nonRoot);
1683 if (nonRootRefUnification != RefNodeUnificationRoot_.end())
1684 RefNodeUnificationRoot_[root] = nonRootRefUnification->second;
1685 }
1686 }
1687
1688 return root;
1689 };
1690
1691 // Removes all explicit pointees from the given PointerObject
1692 const auto RemoveAllPointees = [&](PointerObjectIndex index)
1693 {
1695 Set_.RemoveAllPointees(index);
1696
1697 // Prevent the difference propagation from keeping any pointees after the removal
1698 if constexpr (EnableDifferencePropagation)
1699 differencePropagation.OnRemoveAllPointees(index);
1700 };
1701
1702 // Lambda for getting all superset edge successors of a given pointer object in the subset graph.
1703 // The node must be a unification root
1704 const auto GetSupersetEdgeSuccessors = [&](PointerObjectIndex node)
1705 {
1707 return supersetEdges[node].Items();
1708 };
1709
1710 // If online cycle detection is enabled, perform the initial topological sorting now,
1711 // which may detect and eliminate cycles
1712 OnlineCycleDetector onlineCycleDetector(Set_, GetSupersetEdgeSuccessors, UnifyPointerObjects);
1713 if constexpr (EnableOnlineCycleDetection)
1714 onlineCycleDetector.InitializeTopologicalOrdering();
1715
1716 // If lazy cycle detection is enabled, initialize it here
1717 LazyCycleDetector lazyCycleDetector(Set_, GetSupersetEdgeSuccessors, UnifyPointerObjects);
1718 if constexpr (EnableLazyCycleDetection)
1719 lazyCycleDetector.Initialize();
1720
1721 if constexpr (EnableHybridCycleDetection)
1722 statistics.NumHybridCycleUnifications = 0;
1723
1724 if constexpr (EnablePreferImplicitPointees)
1725 statistics.NumPipExplicitPointeesRemoved = 0;
1726
1727 // The worklist, initialized with every unification root
1728 Worklist worklist;
1729 for (PointerObjectIndex i = 0; i < Set_.NumPointerObjects(); i++)
1730 {
1731 if (Set_.IsUnificationRoot(i))
1732 worklist.PushWorkItem(i);
1733 }
1734
1735 // Helper function for marking a PointerObject such that all its pointees will escape
1736 const auto MarkAsPointeesEscaping = [&](PointerObjectIndex index)
1737 {
1738 index = Set_.GetUnificationRoot(index);
1739 if (Set_.MarkAsPointeesEscaping(index))
1740 worklist.PushWorkItem(index);
1741 };
1742
1743 // Helper function for flagging a pointer as pointing to external. Adds to the worklist if changed
1744 const auto MarkAsPointsToExternal = [&](PointerObjectIndex index)
1745 {
1746 index = Set_.GetUnificationRoot(index);
1747 if (Set_.MarkAsPointingToExternal(index))
1748 worklist.PushWorkItem(index);
1749 };
1750
1751 // Helper function for adding superset edges, propagating everything currently in the subset.
1752 // The superset's root is added to the worklist if its points-to set or flags are changed.
1753 const auto AddSupersetEdge = [&](PointerObjectIndex superset, PointerObjectIndex subset)
1754 {
1755 superset = Set_.GetUnificationRoot(superset);
1756 subset = Set_.GetUnificationRoot(subset);
1757
1758 // If this is a self-edge, ignore
1759 if (superset == subset)
1760 return;
1761
1762 if constexpr (EnablePreferImplicitPointees)
1763 {
1764 // No need to add edges when all pointees propagate implicitly either way
1765 if (Set_.IsPointingToExternal(superset) && Set_.HasPointeesEscaping(subset))
1766 {
1767 return;
1768 }
1769
1770 // Ignore adding simple edges to nodes that should only have implicit pointees
1771 if (Set_.CanTrackPointeesImplicitly(superset))
1772 {
1773 MarkAsPointeesEscaping(subset);
1774 return;
1775 }
1776 }
1777
1778 // If the edge already exists, ignore
1779 if (!supersetEdges[subset].insert(superset))
1780 return;
1781
1782 // The edge is now added. If OCD is enabled, check if it broke the topological order, and fix it
1783 if constexpr (EnableOnlineCycleDetection)
1784 {
1785 // If a cycle is detected, this function eliminates it by unifying, and returns the root
1786 auto optUnificationRoot = onlineCycleDetector.MaintainTopologicalOrder(subset, superset);
1787 if (optUnificationRoot)
1788 {
1789 worklist.PushWorkItem(*optUnificationRoot);
1790 return;
1791 }
1792 }
1793
1794 // A new edge was added, propagate points to-sets. If the superset changes, add to the worklist
1795 bool anyPropagation = MakePointsToSetSuperset(superset, subset);
1796 if (anyPropagation)
1797 worklist.PushWorkItem(superset);
1798
1799 // If nothing was propagated by adding the edge, try lazy cycle detection
1800 if (EnableLazyCycleDetection && !Set_.GetPointsToSet(subset).IsEmpty() && !anyPropagation)
1801 {
1802 const auto optUnificationRoot = lazyCycleDetector.OnPropagatedNothing(subset, superset);
1803 if (optUnificationRoot)
1804 worklist.PushWorkItem(*optUnificationRoot);
1805 }
1806 };
1807
1808 // A temporary place to store new subset edges, to avoid modifying sets while they are iterated
1810 const auto QueueNewSupersetEdge = [&](PointerObjectIndex superset, PointerObjectIndex subset)
1811 {
1812 superset = Set_.GetUnificationRoot(superset);
1813 subset = Set_.GetUnificationRoot(subset);
1814 if (superset == subset || supersetEdges[subset].Contains(superset))
1815 return;
1816 newSupersetEdges.insert({ superset, subset });
1817 };
1818
1819 const auto FlushNewSupersetEdges = [&]()
1820 {
1821 for (auto [superset, subset] : newSupersetEdges.Items())
1822 AddSupersetEdge(superset, subset);
1823 newSupersetEdges.Clear();
1824 };
1825
1826 // Ensure that all functions that have already escaped have informed their arguments and results
1827 // The worklist will only inform functions if their HasEscaped flag changes
1829
1830 // The main work item handler. A work item can be in the worklist for the following reasons:
1831 // - It has never been fired
1832 // - It has pointees added since the last time it was fired
1833 // - It has been marked as pointing to external since last time it was fired
1834 // - It has been marked as escaping all pointees since last time it was fired
1835 // - It is the unification root of a new unification
1836 // Work items should be unification roots. If a work item is not a root when popped, it is skipped
1837 const auto HandleWorkItem = [&](PointerObjectIndex node)
1838 {
1839 statistics.NumWorkItemsPopped++;
1840
1841 // Skip visiting unification roots.
1842 // All unification operations are responsible for adding the new root to the worklist if needed
1843 if (!Set_.IsUnificationRoot(node))
1844 return;
1845
1846 // If difference propagation is enabled, this set contains only pointees that have been added
1847 // since the last time this work item was popped. Otherwise, it contains all pointees.
1848 const auto & newPointees = EnableDifferencePropagation
1849 ? differencePropagation.GetNewPointees(node)
1850 : Set_.GetPointsToSet(node);
1851 statistics.NumWorkItemNewPointees += newPointees.Size();
1852
1853 // If difference propagation is enabled, this bool is true if this is the first time node
1854 // is being visited by the worklist with the PointsToExternal flag set
1855 const auto newPointsToExternal = EnableDifferencePropagation
1856 ? differencePropagation.PointsToExternalIsNew(node)
1857 : Set_.IsPointingToExternal(node);
1858
1859 // Perform hybrid cycle detection if all pointees of node should be unified
1860 if constexpr (EnableHybridCycleDetection)
1861 {
1862 // If all pointees of node should be unified, do it now
1863 const auto & refUnificationRootIt = RefNodeUnificationRoot_.find(node);
1864 if (refUnificationRootIt != RefNodeUnificationRoot_.end())
1865 {
1866 auto & refUnificationRoot = refUnificationRootIt->second;
1867 // The ref unification root may no longer be a root, so update it first
1868 refUnificationRoot = Set_.GetUnificationRoot(refUnificationRoot);
1869
1870 // if any unification happens, the result must be added to the worklist
1871 bool anyUnification = false;
1872
1873 // Make a copy of the set, as the node itself may be unified, invalidating newPointees
1874 auto unificationMembers = newPointees;
1875 for (const auto pointee : unificationMembers.Items())
1876 {
1877 const auto pointeeRoot = Set_.GetUnificationRoot(pointee);
1878 if (pointeeRoot == refUnificationRoot)
1879 continue;
1880
1881 (*statistics.NumHybridCycleUnifications)++;
1882 anyUnification = true;
1883 refUnificationRoot = UnifyPointerObjects(refUnificationRoot, pointeeRoot);
1884 }
1885
1886 if (anyUnification)
1887 {
1888 JLM_ASSERT(Set_.IsUnificationRoot(refUnificationRoot));
1889 worklist.PushWorkItem(refUnificationRoot);
1890 // If the node itself was unified, the new root has been added to the worklist, so exit
1891 if (refUnificationRoot == Set_.GetUnificationRoot(node))
1892 return;
1893 }
1894 }
1895 }
1896
1897 // If propagating to any node with AllPointeesEscape, we should have AllPointeesEscape
1898 if (EnablePreferImplicitPointees && !Set_.HasPointeesEscaping(node))
1899 {
1900 for (auto superset : supersetEdges[node].Items())
1901 {
1902 if (Set_.HasPointeesEscaping(superset))
1903 {
1904 // Mark the current node.
1905 // This is the beginning of the work item visit, so node does not need to be added again
1907 break;
1908 }
1909 }
1910 }
1911
1912 const auto pointeesEscaping = Set_.HasPointeesEscaping(node);
1913 // If difference propagation is enabled, this bool is true if this is the first time node
1914 // is being visited by the worklist with the PointeesEscaping flag set
1915 const auto newPointeesEscaping = EnableDifferencePropagation
1916 ? differencePropagation.PointeesEscapeIsNew(node)
1917 : pointeesEscaping;
1918
1919 // Mark pointees as escaping, if node has the PointeesEscaping flag
1920 if (pointeesEscaping)
1921 {
1922 // If this is the first time node is being visited with the PointeesEscaping flag set,
1923 // add the escaped flag to all pointees. Otherwise, only add it to new pointees.
1924 const auto & newEscapingPointees =
1925 newPointeesEscaping ? Set_.GetPointsToSet(node) : newPointees;
1926 for (const auto pointee : newEscapingPointees.Items())
1927 {
1928 const auto pointeeRoot = Set_.GetUnificationRoot(pointee);
1929
1930 // Marking a node as escaped will imply two flags on the unification root:
1931 // - PointeesEscaping
1932 // - PointsToExternal
1933 const bool rootAlreadyHasFlags =
1934 Set_.HasPointeesEscaping(pointeeRoot) && Set_.IsPointingToExternal(pointeeRoot);
1935
1936 // Mark the pointee itself as escaped, not the pointee's unifiction root!
1937 if (!Set_.MarkAsEscaped(pointee))
1938 continue;
1939
1940 // If the PointerObject we just marked as escaped is a function, inform it about escaping
1942 HandleEscapedFunction(Set_, pointee, MarkAsPointeesEscaping, MarkAsPointsToExternal);
1943
1944 // If the pointee's unification root previously didn't have both the flags implied by
1945 // having one of the unification members escaping, add the root to the worklist
1946 if (!rootAlreadyHasFlags)
1947 {
1948 JLM_ASSERT(Set_.HasPointeesEscaping(pointeeRoot));
1949 JLM_ASSERT(Set_.IsPointingToExternal(pointeeRoot));
1950 worklist.PushWorkItem(pointeeRoot);
1951 }
1952 }
1953 }
1954
1955 // If this node can track all pointees implicitly, remove its explicit nodes
1956 if (EnablePreferImplicitPointees && Set_.CanTrackPointeesImplicitly(node))
1957 {
1958 *(statistics.NumPipExplicitPointeesRemoved) += Set_.GetPointsToSet(node).Size();
1959 // This also causes newPointees to become empty
1960 RemoveAllPointees(node);
1961 }
1962
1963 // Propagate P(n) along all edges n -> superset
1964 auto supersets = supersetEdges[node].Items();
1965 for (auto it = supersets.begin(); it != supersets.end();)
1966 {
1967 const auto supersetParent = Set_.GetUnificationRoot(*it);
1968
1969 // Remove self-edges
1970 if (supersetParent == node)
1971 {
1972 it = supersetEdges[node].Erase(it);
1973 continue;
1974 }
1975
1976 // Remove edges from nodes with "all pointees escape" to nodes with "points to all escaped"
1977 if (EnablePreferImplicitPointees && pointeesEscaping
1978 && Set_.IsPointingToExternal(supersetParent))
1979 {
1980 it = supersetEdges[node].Erase(it);
1981 continue;
1982 }
1983
1984 // The current it-edge should be kept as is, prepare "it" for the next iteration.
1985 ++it;
1986
1987 bool modified = false;
1988 for (const auto pointee : newPointees.Items())
1989 modified |= AddToPointsToSet(supersetParent, pointee);
1990
1991 if (newPointsToExternal)
1992 modified |= Set_.MarkAsPointingToExternal(supersetParent);
1993
1994 if (modified)
1995 worklist.PushWorkItem(supersetParent);
1996
1997 if (EnableLazyCycleDetection && !newPointees.IsEmpty() && !modified)
1998 {
1999 // If nothing was propagated along this edge, check if there is a cycle
2000 // If a cycle is detected, this function eliminates it by unifying, and returns the root
2001 auto optUnificationRoot = lazyCycleDetector.OnPropagatedNothing(node, supersetParent);
2002 if (optUnificationRoot)
2003 {
2004 // The new unification root is pushed, and handling of the current work item is aborted.
2005 worklist.PushWorkItem(*optUnificationRoot);
2006 return;
2007 }
2008 }
2009 }
2010
2011 // Stores on the form *n = value.
2012 for (const auto value : storeConstraints[node].Items())
2013 {
2014 // This loop ensures *P(n) supseteq P(value)
2015 for (const auto pointee : newPointees.Items())
2016 QueueNewSupersetEdge(pointee, value);
2017
2018 // If P(n) contains "external", the contents of the written value escapes
2019 if (newPointsToExternal)
2020 MarkAsPointeesEscaping(value);
2021 }
2022
2023 // If node has the stored as scalar constraint, but does not make its pointees escape outright
2024 if (Set_.IsStoredAsScalar(node) && !Set_.HasPointeesEscaping(node))
2025 {
2026 for (const auto pointee : newPointees.Items())
2027 {
2028 MarkAsPointsToExternal(pointee);
2029 }
2030 }
2031
2032 // Loads on the form value = *n.
2033 for (const auto value : loadConstraints[node].Items())
2034 {
2035 // This loop ensures P(value) supseteq *P(n)
2036 for (const auto pointee : newPointees.Items())
2037 QueueNewSupersetEdge(value, pointee);
2038
2039 // If P(n) contains "external", the loaded value may also point to external
2040 if (newPointsToExternal)
2041 MarkAsPointsToExternal(value);
2042 }
2043
2044 // If node has the loaded as scalar constraint, but does not make its pointees escape outright
2045 if (Set_.IsLoadedAsScalar(node) && !Set_.HasPointeesEscaping(node))
2046 {
2047 for (const auto pointee : newPointees.Items())
2048 {
2049 MarkAsPointeesEscaping(pointee);
2050 }
2051 }
2052
2053 // Function calls on the form (*n)()
2054 for (const auto callNode : callConstraints[node].Items())
2055 {
2056 // Connect the inputs and outputs of the callNode to every possible function pointee
2057 for (const auto pointee : newPointees.Items())
2058 {
2059 const auto kind = Set_.GetPointerObjectKind(pointee);
2062 Set_,
2063 *callNode,
2064 pointee,
2065 MarkAsPointeesEscaping,
2066 MarkAsPointsToExternal);
2068 HandleCallingLambdaFunction(Set_, *callNode, pointee, QueueNewSupersetEdge);
2069 }
2070
2071 // If P(n) contains "external", handle calling external functions
2072 if (newPointsToExternal)
2074 Set_,
2075 *callNode,
2076 MarkAsPointeesEscaping,
2077 MarkAsPointsToExternal);
2078 }
2079
2080 // No pointees have been added to P(node) while visiting node thus far in the handler.
2081 // All new flags have also been handled, or caused this node to be on the worklist again.
2082 if constexpr (EnableDifferencePropagation)
2083 {
2084 differencePropagation.ClearNewPointees(node);
2085 if (newPointsToExternal)
2086 differencePropagation.MarkPointsToExternalAsHandled(node);
2087 if (newPointeesEscaping)
2088 differencePropagation.MarkPointeesEscapeAsHandled(node);
2089 }
2090
2091 // Add all new superset edges, which also propagates points-to sets immediately
2092 // and possibly performs unifications to eliminate cycles.
2093 // Any unified nodes, or nodes with updated points-to sets, are added to the worklist.
2094 FlushNewSupersetEdges();
2095 };
2096
2097 // The Workset worklist only remembers which work items have been pushed.
2098 // It does not provide an iteration order, so if any work item need to be revisited,
2099 // we do a topological traversal over all work items instead, visiting ones in the Workset.
2100 // Performing topological sorting also detects all cycles, which are unified away.
2101 constexpr bool useTopologicalTraversal =
2102 std::is_same_v<Worklist, util::Workset<PointerObjectIndex>>;
2103
2104 if constexpr (useTopologicalTraversal)
2105 {
2106 std::vector<PointerObjectIndex> sccIndex;
2107 std::vector<PointerObjectIndex> reverseTopologicalOrder;
2108
2109 statistics.NumTopologicalWorklistSweeps = 0;
2110
2111 while (worklist.HasMoreWorkItems())
2112 {
2113 (*statistics.NumTopologicalWorklistSweeps)++;
2114
2115 // Used during topological sorting to avoid traversing non-roots
2116 const auto GetUnificationRoot = [&](PointerObjectIndex node)
2117 {
2118 return Set_.GetUnificationRoot(node);
2119 };
2120
2121 // First perform a topological sort of the entire subset graph, with respect to simple edges
2122 util::FindStronglyConnectedComponents<PointerObjectIndex>(
2124 GetUnificationRoot,
2125 GetSupersetEdgeSuccessors,
2126 sccIndex,
2127 reverseTopologicalOrder);
2128
2129 // Visit nodes in topological order, if they are in the workset.
2130 // cycles will result in neighbouring nodes in the topological order sharing sccIndex
2131 for (auto it = reverseTopologicalOrder.rbegin(); it != reverseTopologicalOrder.rend(); ++it)
2132 {
2133 const auto node = *it;
2134
2135 // Check if node can be unified with the next node in the topological order
2136 const auto nextIt = it + 1;
2137 if (nextIt != reverseTopologicalOrder.rend())
2138 {
2139 auto & nextNode = *nextIt;
2140 if (sccIndex[node] == sccIndex[nextNode])
2141 {
2142 // This node is in a cycle with the next node, unify them
2143 auto unifiedNode = UnifyPointerObjects(node, nextNode);
2144 auto oldNode = unifiedNode == node ? nextNode : node;
2145 // Make sure only unification roots are in the worklist
2146 worklist.RemoveWorkItem(oldNode);
2147 // Make sure the new root is visited
2148 worklist.PushWorkItem(unifiedNode);
2149
2150 // Update the nextNode to the unification root, to make sure it is visited
2151 nextNode = unifiedNode;
2152 continue;
2153 }
2154 }
2155
2156 // If this work item is in the workset, handle it. Repeat immediately if it gets re-added.
2157 while (worklist.HasWorkItem(node))
2158 {
2159 worklist.RemoveWorkItem(node);
2160 HandleWorkItem(node);
2161 }
2162 }
2163 }
2164 }
2165 else
2166 {
2167 // The worklist is a normal worklist
2168 while (worklist.HasMoreWorkItems())
2169 HandleWorkItem(worklist.PopWorkItem());
2170 }
2171
2172 if constexpr (EnableOnlineCycleDetection)
2173 {
2174 statistics.NumOnlineCyclesDetected = onlineCycleDetector.NumOnlineCyclesDetected();
2175 statistics.NumOnlineCycleUnifications = onlineCycleDetector.NumOnlineCycleUnifications();
2176 }
2177
2178 if constexpr (EnableLazyCycleDetection)
2179 {
2180 statistics.NumLazyCyclesDetectionAttempts = lazyCycleDetector.NumCycleDetectionAttempts();
2181 statistics.NumLazyCyclesDetected = lazyCycleDetector.NumCyclesDetected();
2182 statistics.NumLazyCycleUnifications = lazyCycleDetector.NumCycleUnifications();
2183 }
2184}
2185
2188 WorklistSolverPolicy policy,
2189 bool enableOnlineCycleDetection,
2190 bool enableHybridCycleDetection,
2191 bool enableLazyCycleDetection,
2192 bool enableDifferencePropagation,
2193 bool enablePreferImplicitPointees)
2194{
2195
2196 // Takes all parameters as compile time types.
2197 // tWorklist is a pointer to one of the Worklist implementations.
2198 // the rest are instances of std::bool_constant, either std::true_type or std::false_type
2199 const auto Dispatch = [&](auto tWorklist,
2200 auto tOnlineCycleDetection,
2201 auto tHybridCycleDetection,
2202 auto tLazyCycleDetection,
2203 auto tDifferencePropagation,
2204 auto tPreferImplicitPointees) -> WorklistStatistics
2205 {
2206 using Worklist = std::remove_pointer_t<decltype(tWorklist)>;
2207 constexpr bool vOnlineCycleDetection = decltype(tOnlineCycleDetection)::value;
2208 constexpr bool vHybridCycleDetection = decltype(tHybridCycleDetection)::value;
2209 constexpr bool vLazyCycleDetection = decltype(tLazyCycleDetection)::value;
2210 constexpr bool vDifferencePropagation = decltype(tDifferencePropagation)::value;
2211 constexpr bool vPreferImplicitPointees = decltype(tPreferImplicitPointees)::value;
2212
2213 if constexpr (
2214 std::is_same_v<Worklist, util::Workset<PointerObjectIndex>>
2215 && (vOnlineCycleDetection || vHybridCycleDetection || vLazyCycleDetection))
2216 {
2217 JLM_UNREACHABLE("Can not enable online, hybrid or lazy cycle detection with the topo policy");
2218 }
2219 if constexpr (vOnlineCycleDetection && (vHybridCycleDetection || vLazyCycleDetection))
2220 {
2221 JLM_UNREACHABLE("Can not enable hybrid or lazy cycle detection with online cycle detection");
2222 }
2223 else
2224 {
2225 WorklistStatistics statistics(policy);
2227 Worklist,
2228 vOnlineCycleDetection,
2229 vHybridCycleDetection,
2230 vLazyCycleDetection,
2231 vDifferencePropagation,
2232 vPreferImplicitPointees>(statistics);
2233 return statistics;
2234 }
2235 };
2236
2237 std::variant<
2243 policyVariant;
2244
2246 policyVariant = static_cast<util::LrfWorklist<PointerObjectIndex> *>(nullptr);
2248 policyVariant = static_cast<util::TwoPhaseLrfWorklist<PointerObjectIndex> *>(nullptr);
2249 else if (policy == WorklistSolverPolicy::TopologicalSort)
2250 policyVariant = static_cast<util::Workset<PointerObjectIndex> *>(nullptr);
2251 else if (policy == WorklistSolverPolicy::LastInFirstOut)
2252 policyVariant = static_cast<util::LifoWorklist<PointerObjectIndex> *>(nullptr);
2253 else if (policy == WorklistSolverPolicy::FirstInFirstOut)
2254 policyVariant = static_cast<util::FifoWorklist<PointerObjectIndex> *>(nullptr);
2255 else
2256 JLM_UNREACHABLE("Unknown worklist policy");
2257
2258 std::variant<std::true_type, std::false_type> onlineCycleDetectionVariant;
2259 if (enableOnlineCycleDetection)
2260 onlineCycleDetectionVariant = std::true_type{};
2261 else
2262 onlineCycleDetectionVariant = std::false_type{};
2263
2264 std::variant<std::true_type, std::false_type> hybridCycleDetectionVariant;
2265 if (enableHybridCycleDetection)
2266 hybridCycleDetectionVariant = std::true_type{};
2267 else
2268 hybridCycleDetectionVariant = std::false_type{};
2269
2270 std::variant<std::true_type, std::false_type> lazyCycleDetectionVariant;
2271 if (enableLazyCycleDetection)
2272 lazyCycleDetectionVariant = std::true_type{};
2273 else
2274 lazyCycleDetectionVariant = std::false_type{};
2275
2276 std::variant<std::true_type, std::false_type> differencePropagationVariant;
2277 if (enableDifferencePropagation)
2278 differencePropagationVariant = std::true_type{};
2279 else
2280 differencePropagationVariant = std::false_type{};
2281
2282 std::variant<std::true_type, std::false_type> preferImplicitPropagationVariant;
2283 if (enablePreferImplicitPointees)
2284 preferImplicitPropagationVariant = std::true_type{};
2285 else
2286 preferImplicitPropagationVariant = std::false_type{};
2287
2288 return std::visit(
2289 Dispatch,
2290 policyVariant,
2291 onlineCycleDetectionVariant,
2292 hybridCycleDetectionVariant,
2293 lazyCycleDetectionVariant,
2294 differencePropagationVariant,
2295 preferImplicitPropagationVariant);
2296}
2297
2298const char *
2300{
2301 switch (policy)
2302 {
2304 return "LeastRecentlyFired";
2306 return "TwoPhaseLeastRecentlyFired";
2308 return "TopologicalSort";
2310 return "FirstInFirstOut";
2312 return "LastInFirstOut";
2313 default:
2314 JLM_UNREACHABLE("Unknown WorklistSolverPolicy");
2315 }
2316}
2317
2318size_t
2320{
2321 size_t numIterations = 0;
2322
2323 // Keep applying constraints until no sets are modified
2324 bool modified = true;
2325
2326 while (modified)
2327 {
2328 numIterations++;
2329 modified = false;
2330
2331 for (auto & constraint : Constraints_)
2332 {
2333 std::visit(
2334 [&](auto & constraint)
2335 {
2336 modified |= constraint.ApplyDirectly(Set_);
2337 },
2338 constraint);
2339 }
2340
2343 }
2344
2345 return numIterations;
2346}
2347
2348std::pair<std::unique_ptr<PointerObjectSet>, std::unique_ptr<PointerObjectConstraintSet>>
2350{
2351 auto setClone = Set_.Clone();
2352 auto constraintClone = std::make_unique<PointerObjectConstraintSet>(*setClone);
2353 for (auto constraint : Constraints_)
2354 constraintClone->AddConstraint(constraint);
2355 constraintClone->ConstraintSetFrozen_ = ConstraintSetFrozen_;
2356 return std::make_pair(std::move(setClone), std::move(constraintClone));
2357}
2358
2359} // namespace jlm::llvm::aa
static bool Contains(const jlm::llvm::InterProceduralGraphModule &module, const std::string &)
Definition FNegTests.cpp:19
static rvsdg::Input * Argument(const rvsdg::Node &node, const size_t n)
Definition call.hpp:310
static size_t NumArguments(const rvsdg::Node &node) noexcept
Definition call.hpp:298
bool AddToPointsToSet(PointerObjectIndex pointer, PointerObjectIndex pointee)
bool PointeesEscapeIsNew(PointerObjectIndex index)
void OnPointerObjectsUnified(PointerObjectIndex root, PointerObjectIndex nonRoot)
bool PointsToExternalIsNew(PointerObjectIndex index)
const util::HashSet< PointerObjectIndex > & GetNewPointees(PointerObjectIndex index) const
void MarkPointsToExternalAsHandled(PointerObjectIndex index)
bool MakePointsToSetSuperset(PointerObjectIndex superset, PointerObjectIndex subset)
void MarkPointeesEscapeAsHandled(PointerObjectIndex index)
void OnRemoveAllPointees(PointerObjectIndex index)
void ClearNewPointees(PointerObjectIndex index)
static bool PropagateEscapedFlagsDirectly(PointerObjectSet &set)
static bool PropagateEscapedFunctionsDirectly(PointerObjectSet &set)
bool ApplyDirectly(PointerObjectSet &set)
size_t NumCycleUnifications() const noexcept
size_t NumCyclesDetected() const noexcept
size_t NumCycleDetectionAttempts() const noexcept
std::optional< PointerObjectIndex > OnPropagatedNothing(PointerObjectIndex subset, PointerObjectIndex superset)
bool ApplyDirectly(PointerObjectSet &set)
size_t NumOnlineCyclesDetected() const noexcept
std::optional< PointerObjectIndex > MaintainTopologicalOrder(PointerObjectIndex subset, PointerObjectIndex superset)
size_t NumOnlineCycleUnifications() const noexcept
void RunWorklistSolver(WorklistStatistics &statistics)
std::unordered_map< PointerObjectIndex, PointerObjectIndex > RefNodeUnificationRoot_
std::pair< size_t, size_t > NumFlagConstraints() const noexcept
void AddPointerPointeeConstraint(PointerObjectIndex pointer, PointerObjectIndex pointee)
std::vector< ConstraintVariant > Constraints_
size_t PerformOfflineVariableSubstitution(bool storeRefCycleUnificationRoot)
util::graph::Graph & DrawSubsetGraph(util::graph::Writer &writer) const
std::pair< std::unique_ptr< PointerObjectSet >, std::unique_ptr< PointerObjectConstraintSet > > Clone() const
std::tuple< size_t, std::vector< util::HashSet< PointerObjectIndex > >, std::vector< bool > > CreateOvsSubsetGraph()
WorklistStatistics SolveUsingWorklist(WorklistSolverPolicy policy, bool enableOnlineCycleDetection, bool enableHybridCycleDetection, bool enableLazyCycleDetection, bool enableDifferencePropagation, bool enablePreferImplicitPropation)
const std::vector< ConstraintVariant > & GetConstraints() const noexcept
std::variant< SupersetConstraint, StoreConstraint, LoadConstraint, FunctionCallConstraint > ConstraintVariant
void AddPointsToExternalConstraint(PointerObjectIndex pointer)
void AddRegisterContentEscapedConstraint(PointerObjectIndex registerIndex)
static const char * WorklistSolverPolicyToString(WorklistSolverPolicy policy)
const util::BijectiveMap< const rvsdg::LambdaNode *, PointerObjectIndex > & GetFunctionMap() const noexcept
PointerObjectIndex CreateMallocMemoryObject(const rvsdg::SimpleNode &mallocNode, bool canPoint)
PointerObjectIndex UnifyPointerObjects(PointerObjectIndex object1, PointerObjectIndex object2)
PointerObjectIndex AddPointerObject(PointerObjectKind kind, bool canPoint)
size_t NumMemoryPointerObjects() const noexcept
bool IsPointingTo(PointerObjectIndex pointer, PointerObjectIndex pointee) const
PointerObjectIndex CreateDummyRegisterPointerObject()
PointerObjectKind GetPointerObjectKind(PointerObjectIndex index) const noexcept
size_t GetNumSetInsertionAttempts() const noexcept
std::vector< PointerObjectIndex > PointerObjectParents_
const std::unordered_map< const rvsdg::SimpleNode *, PointerObjectIndex > & GetMallocMap() const noexcept
bool IsPointerObjectRegister(PointerObjectIndex index) const noexcept
size_t GetNumExplicitPointeesRemoved() const noexcept
std::unordered_map< const rvsdg::Output *, PointerObjectIndex > RegisterMap_
PointerObjectIndex CreateGlobalMemoryObject(const rvsdg::DeltaNode &deltaNode, bool canPoint)
void MapRegisterToExistingPointerObject(const rvsdg::Output &rvsdgOutput, PointerObjectIndex pointerObject)
size_t NumRegisterPointerObjects() const noexcept
const util::HashSet< PointerObjectIndex > & GetPointsToSet(PointerObjectIndex index) const
bool HasPointeesEscaping(PointerObjectIndex index) const noexcept
bool PropagateNewPointees(PointerObjectIndex superset, PointerObjectIndex subset, NewPointeeFunctor &onNewPointee)
bool IsLoadedAsScalar(PointerObjectIndex index) const noexcept
void RemoveAllPointees(PointerObjectIndex index)
size_t NumMemoryPointerObjectsCanPoint() const noexcept
std::unordered_map< const rvsdg::SimpleNode *, PointerObjectIndex > AllocaMap_
PointerObjectIndex CreateRegisterPointerObject(const rvsdg::Output &rvsdgOutput)
bool CanPoint(PointerObjectIndex index) const noexcept
bool AddToPointsToSet(PointerObjectIndex pointer, PointerObjectIndex pointee)
PointerObjectIndex GetRegisterPointerObject(const rvsdg::Output &rvsdgOutput) const
std::vector< uint8_t > PointerObjectRank_
bool MarkAsLoadingAsScalar(PointerObjectIndex index)
const std::unordered_map< const rvsdg::SimpleNode *, PointerObjectIndex > & GetAllocaMap() const noexcept
std::unordered_map< const rvsdg::DeltaNode *, PointerObjectIndex > GlobalMap_
std::unique_ptr< PointerObjectSet > Clone() const
bool IsPointingToExternal(PointerObjectIndex index) const noexcept
std::unordered_map< const rvsdg::SimpleNode *, PointerObjectIndex > MallocMap_
bool HasIdenticalSolAs(const PointerObjectSet &other) const
bool IsStoredAsScalar(PointerObjectIndex index) const noexcept
bool HasEscaped(PointerObjectIndex index) const noexcept
bool MarkAsPointeesEscaping(PointerObjectIndex index)
const std::unordered_map< const LlvmGraphImport *, PointerObjectIndex > & GetImportMap() const noexcept
const std::unordered_map< const rvsdg::DeltaNode *, PointerObjectIndex > & GetGlobalMap() const noexcept
std::vector< util::HashSet< PointerObjectIndex > > PointsToSets_
PointerObjectIndex CreateAllocaMemoryObject(const rvsdg::SimpleNode &allocaNode, bool canPoint)
bool CanTrackPointeesImplicitly(PointerObjectIndex index) const noexcept
bool MarkAsStoringAsScalar(PointerObjectIndex index)
bool MarkAsPointingToExternal(PointerObjectIndex index)
PointerObjectIndex GetUnificationRoot(PointerObjectIndex index) const noexcept
bool IsUnificationRoot(PointerObjectIndex index) const noexcept
PointerObjectIndex CreateFunctionMemoryObject(const rvsdg::LambdaNode &lambdaNode)
std::vector< PointerObject > PointerObjects_
size_t NumPointerObjects() const noexcept
const rvsdg::LambdaNode & GetLambdaNodeFromFunctionMemoryObject(PointerObjectIndex index) const
const std::unordered_map< const rvsdg::Output *, PointerObjectIndex > & GetRegisterMap() const noexcept
PointerObjectIndex CreateImportMemoryObject(const LlvmGraphImport &importNode, bool canPoint)
bool MarkAsEscaped(PointerObjectIndex index)
util::BijectiveMap< const rvsdg::LambdaNode *, PointerObjectIndex > FunctionMap_
PointerObjectIndex GetFunctionMemoryObject(const rvsdg::LambdaNode &lambdaNode) const
bool MakePointsToSetSuperset(PointerObjectIndex superset, PointerObjectIndex subset)
std::optional< PointerObjectIndex > TryGetRegisterPointerObject(const rvsdg::Output &rvsdgOutput) const
std::unordered_map< const LlvmGraphImport *, PointerObjectIndex > ImportMap_
size_t NumPointerObjectsOfKind(PointerObjectKind kind) const noexcept
bool ApplyDirectly(PointerObjectSet &set)
bool ApplyDirectly(PointerObjectSet &set)
Output * origin() const noexcept
Definition node.hpp:58
std::vector< rvsdg::Output * > GetFunctionArguments() const
Definition lambda.cpp:58
std::vector< rvsdg::Input * > GetFunctionResults() const
Definition lambda.cpp:70
size_t noutputs() const noexcept
Definition node.hpp:644
const SimpleOperation & GetOperation() const noexcept override
NodeOutput * output(size_t index) const noexcept
void Clear() noexcept
Definition HashSet.hpp:138
bool insert(ItemType item)
Definition HashSet.hpp:210
std::size_t Size() const noexcept
Definition HashSet.hpp:187
IteratorRange< ItemConstIterator > Items() const noexcept
Definition HashSet.hpp:223
bool IsEmpty() const noexcept
Definition HashSet.hpp:198
void SetStyle(std::string style)
void SetLabel(std::string label)
void SetAttributeObject(const std::string &attribute, uintptr_t object)
void AppendToLabel(std::string_view text, std::string_view sep="\n")
Node & GetNode(size_t index)
Edge & CreateDirectedEdge(Port &from, Port &to)
size_t NumNodes() const noexcept
#define JLM_ASSERT(x)
Definition common.hpp:16
#define JLM_UNREACHABLE(msg)
Definition common.hpp:43
static void HandleLambdaCallParameters(PointerObjectSet &set, const rvsdg::SimpleNode &callNode, const rvsdg::LambdaNode &lambdaNode, MakeSupersetFunctor &makeSuperset)
static std::string CreateSubsetGraphNodeLabel(PointerObjectSet &set, PointerObjectIndex index)
static void CreateSubsetGraphNodes(PointerObjectSet &set, util::graph::Graph &graph)
static void HandleLambdaCallReturnValues(PointerObjectSet &set, const rvsdg::SimpleNode &callNode, const rvsdg::LambdaNode &lambdaNode, MakeSupersetFunctor &makeSuperset)
static void HandleEscapedFunction(PointerObjectSet &set, PointerObjectIndex lambda, MarkAsPointeesEscapingFunctor &markAsPointeesEscaping, MarkAsPointsToExternalFunctor &markAsPointsToExternal)
static void LabelFunctionsArgumentsAndReturnValues(PointerObjectSet &set, util::graph::Graph &graph)
void HandleCallingExternalFunction(PointerObjectSet &set, const rvsdg::SimpleNode &callNode, MarkAsPointeesEscaping &markAsPointeesEscaping, MarkAsPointsToExternal &markAsPointsToExternal)
uint32_t PointerObjectIndex
static constexpr bool ENABLE_UNIFICATION
static void HandleCallingImportedFunction(PointerObjectSet &set, const rvsdg::SimpleNode &callNode, PointerObjectIndex imported, MarkAsPointeesEscaping &markAsPointeesEscaping, MarkAsPointsToExternal &markAsPointsToExternal)
static void CreateSubsetGraphEdges(const PointerObjectSet &set, const std::vector< PointerObjectConstraintSet::ConstraintVariant > &constraints, util::graph::Graph &graph)
static std::vector< int64_t > AssignOvsEquivalenceSetLabels(std::vector< util::HashSet< PointerObjectIndex > > &successors, size_t numSccs, std::vector< size_t > &sccIndex, std::vector< size_t > &reverseTopologicalOrder, std::vector< bool > &sccHasDirectNodesOnly)
static void HandleCallingLambdaFunction(PointerObjectSet &set, const rvsdg::SimpleNode &callNode, PointerObjectIndex lambda, MakeSupersetFunctor &makeSuperset)
static std::string strfmt(Args... args)
Definition strfmt.hpp:35
static const char *const Dashed
static const char *const Rectangle
static const char *const Oval