Jlm
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 
17 namespace jlm::llvm::aa
18 {
19 
25 static constexpr bool ENABLE_UNIFICATION = true;
26 
29 {
30  JLM_ASSERT(PointerObjects_.size() < std::numeric_limits<PointerObjectIndex>::max());
31  PointerObjectIndex index = PointerObjects_.size();
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 
43 size_t
45 {
46  return PointerObjects_.size();
47 }
48 
49 size_t
51 {
52  size_t count = 0;
53  for (auto & pointerObject : PointerObjects_)
54  {
55  count += pointerObject.Kind == kind;
56  }
57  return count;
58 }
59 
60 size_t
62 {
64 }
65 
66 size_t
68 {
70 }
71 
72 size_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 
98 std::optional<PointerObjectIndex>
100 {
101  if (const auto it = RegisterMap_.find(&rvsdgOutput); it != RegisterMap_.end())
102  return it->second;
103  return std::nullopt;
104 }
105 
106 void
108  const rvsdg::Output & rvsdgOutput,
109  PointerObjectIndex pointerObject)
110 {
111  JLM_ASSERT(RegisterMap_.count(&rvsdgOutput) == 0);
113  RegisterMap_[&rvsdgOutput] = pointerObject;
114 }
115 
118 {
120 }
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);
142  return GlobalMap_[&deltaNode] = AddPointerObject(PointerObjectKind::GlobalMemoryObject, canPoint);
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 
161 const 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 
181 const std::unordered_map<const rvsdg::Output *, PointerObjectIndex> &
183 {
184  return RegisterMap_;
185 }
186 
187 const std::unordered_map<const rvsdg::SimpleNode *, PointerObjectIndex> &
189 {
190  return AllocaMap_;
191 }
192 
193 const std::unordered_map<const rvsdg::SimpleNode *, PointerObjectIndex> &
195 {
196  return MallocMap_;
197 }
198 
199 const std::unordered_map<const rvsdg::DeltaNode *, PointerObjectIndex> &
201 {
202  return GlobalMap_;
203 }
204 
207 {
208  return FunctionMap_;
209 }
210 
211 const 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 
224 bool
226 {
227  JLM_ASSERT(index < NumPointerObjects());
228  return PointerObjects_[index].CanPoint();
229 }
230 
231 bool
233 {
234  JLM_ASSERT(index < NumPointerObjects());
235  return PointerObjects_[index].IsRegister();
236 }
237 
238 bool
240 {
241  JLM_ASSERT(index < NumPointerObjects());
242  return PointerObjects_[index].HasEscaped;
243 }
244 
245 bool
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
256  MarkAsPointeesEscaping(index);
258 
259  return true;
260 }
261 
262 [[nodiscard]] bool
264 {
265  return PointerObjects_[GetUnificationRoot(index)].PointeesEscaping;
266 }
267 
268 bool
270 {
271  auto root = GetUnificationRoot(index);
272  if (PointerObjects_[root].PointeesEscaping)
273  return false;
274  PointerObjects_[root].PointeesEscaping = true;
275  return true;
276 }
277 
278 bool
280 {
281  return PointerObjects_[GetUnificationRoot(index)].PointsToExternal;
282 }
283 
284 bool
286 {
287  auto root = GetUnificationRoot(index);
288  if (PointerObjects_[root].PointsToExternal)
289  return false;
290  PointerObjects_[root].PointsToExternal = true;
291  return true;
292 }
293 
294 bool
296 {
297  auto root = GetUnificationRoot(index);
298  return PointerObjects_[root].CanTrackPointeesImplicitly();
299 }
300 
301 bool
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 
317 bool
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 
352 bool
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))
380  MarkAsPointingToExternal(newRoot);
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 
404 {
405  return PointsToSets_[GetUnificationRoot(index)];
406 }
407 
408 // Makes pointee a member of P(pointer)
409 bool
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)
424 template<typename NewPointeeFunctor>
425 bool
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 
459 bool
461 {
462  // NewPointee is a no-op
463  const auto & NewPointee = [](PointerObjectIndex)
464  {
465  };
466  return PropagateNewPointees(superset, subset, NewPointee);
467 }
468 
469 bool
471  PointerObjectIndex superset,
472  PointerObjectIndex subset,
473  util::HashSet<PointerObjectIndex> & newPointees)
474 {
475  const auto & NewPointee = [&](PointerObjectIndex pointee)
476  {
477  newPointees.insert(pointee);
478  };
479  return PropagateNewPointees(superset, subset, NewPointee);
480 }
481 
482 void
484 {
485  auto root = GetUnificationRoot(index);
487  PointsToSets_[root].Clear();
488 }
489 
490 bool
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 
508 std::unique_ptr<PointerObjectSet>
510 {
511  return std::make_unique<PointerObjectSet>(*this);
512 }
513 
514 bool
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
528  if (IsPointingToExternal(i) != other.IsPointingToExternal(i))
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 
546 size_t
548 {
550 }
551 
552 size_t
554 {
556 }
557 
558 // Makes P(superset) a superset of P(subset)
559 bool
561 {
563 }
564 
565 // for all x in P(pointer1), make P(x) a superset of P(pointer2)
566 bool
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)
582 bool
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 
604 template<typename MarkAsPointeesEscaping, typename MarkAsPointsToExternal>
605 void
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 
642 template<typename MarkAsPointeesEscaping, typename MarkAsPointsToExternal>
643 static 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 
666 template<typename MakeSupersetFunctor>
667 static 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 
695 template<typename MakeSupersetFunctor>
696 static 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 
731 template<typename MakeSupersetFunctor>
732 static 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
753 bool
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 
795 bool
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 
867 template<typename MarkAsPointeesEscapingFunctor, typename MarkAsPointsToExternalFunctor>
868 static 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 
912 bool
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 
936 bool
938 {
939  return ConstraintSetFrozen_;
940 }
941 
942 void
944  PointerObjectIndex pointer,
945  PointerObjectIndex pointee)
946 {
947  JLM_ASSERT(!IsFrozen());
948  // All set constraints are additive, so simple constraints like this can be directly applied.
949  Set_.AddToPointsToSet(pointer, pointee);
950 }
951 
952 void
954 {
955  JLM_ASSERT(!IsFrozen());
956  // Flags are never removed, so adding the flag now ensures it will be included.
958 }
959 
960 void
962 {
963  JLM_ASSERT(!IsFrozen());
964  JLM_ASSERT(Set_.IsPointerObjectRegister(registerIndex));
965  Set_.MarkAsPointeesEscaping(registerIndex);
966 }
967 
968 void
970 {
971  JLM_ASSERT(!IsFrozen());
972  Constraints_.push_back(c);
973 }
974 
975 const std::vector<PointerObjectConstraintSet::ConstraintVariant> &
977 {
978  return Constraints_;
979 }
980 
981 size_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 
993 std::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 
1006  if (Set_.IsPointingToExternal(i))
1007  numOtherFlagConstraints++;
1008  if (Set_.HasPointeesEscaping(i))
1009  numOtherFlagConstraints++;
1010  if (Set_.IsStoredAsScalar(i))
1011  numScalarFlagConstraints++;
1012  if (Set_.IsLoadedAsScalar(i))
1013  numScalarFlagConstraints++;
1014  }
1015  return { numScalarFlagConstraints, numOtherFlagConstraints };
1016 }
1017 
1024 static std::string
1026 {
1027  std::ostringstream label;
1028  label << index;
1029 
1030  auto kind = set.GetPointerObjectKind(index);
1032  label << " A";
1033  else if (kind == PointerObjectKind::MallocMemoryObject)
1034  label << " M";
1035  else if (kind == PointerObjectKind::FunctionMemoryObject)
1036  label << " F";
1037  else if (kind == PointerObjectKind::GlobalMemoryObject)
1038  label << " G";
1039  else if (kind == PointerObjectKind::ImportMemoryObject)
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 
1081 static 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();
1091  node.SetLabel(CreateSubsetGraphNodeLabel(set, i));
1092 
1093  if (set.IsPointerObjectRegister(i))
1094  node.SetShape(util::graph::Node::Shape::Oval);
1095  else
1096  node.SetShape(util::graph::Node::Shape::Rectangle);
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 
1134 static 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 
1206 static 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 
1244  CreateSubsetGraphNodes(Set_, graph);
1247 
1248  return graph;
1249 }
1250 
1251 // Helper function for DoOfflineVariableSubstitution()
1252 std::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 
1329 static 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 
1391 size_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 
1487 size_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 
1547 template<
1548  typename Worklist,
1549  bool EnableOnlineCycleDetection,
1550  bool EnableHybridCycleDetection,
1551  bool EnableLazyCycleDetection,
1552  bool EnableDifferencePropagation,
1553  bool EnablePreferImplicitPointees>
1554 void
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);
2067  else if (kind == PointerObjectKind::FunctionMemoryObject)
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 
2298 const 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 
2318 size_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 
2348 std::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 rvsdg::Input * Argument(const rvsdg::Node &node, const size_t n)
Definition: call.hpp:291
static size_t NumArguments(const rvsdg::Node &node) noexcept
Definition: call.hpp:279
bool AddToPointsToSet(PointerObjectIndex pointer, PointerObjectIndex pointee)
bool PointeesEscapeIsNew(PointerObjectIndex index)
void OnPointerObjectsUnified(PointerObjectIndex root, PointerObjectIndex nonRoot)
bool PointsToExternalIsNew(PointerObjectIndex index)
void MarkPointsToExternalAsHandled(PointerObjectIndex index)
const util::HashSet< PointerObjectIndex > & GetNewPointees(PointerObjectIndex index) const
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)
Delta node.
Definition: delta.hpp:129
Output * origin() const noexcept
Definition: node.hpp:58
Lambda node.
Definition: lambda.hpp:83
std::vector< rvsdg::Output * > GetFunctionArguments() const
Definition: lambda.cpp:57
std::vector< rvsdg::Input * > GetFunctionResults() const
Definition: lambda.cpp:69
size_t noutputs() const noexcept
Definition: node.hpp:644
const SimpleOperation & GetOperation() const noexcept override
Definition: simple-node.cpp:48
NodeOutput * output(size_t index) const noexcept
Definition: simple-node.hpp:88
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)
size_t NumNodes() const noexcept
Edge & CreateDirectedEdge(Port &from, Port &to)
#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, [[maybe_unused]] PointerObjectIndex imported, MarkAsPointeesEscaping &markAsPointeesEscaping, MarkAsPointsToExternal &markAsPointsToExternal)
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 CreateSubsetGraphEdges(const PointerObjectSet &set, const std::vector< PointerObjectConstraintSet::ConstraintVariant > &constraints, util::graph::Graph &graph)
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