Jlm
LocalAliasAnalysis.cpp
Go to the documentation of this file.
1 /*
2  * Copyright 2025 HÃ¥vard Krogstie <krogstie.havard@gmail.com>
3  * See COPYING for terms of redistribution.
4  */
5 
14 #include <jlm/llvm/ir/Trace.hpp>
15 #include <jlm/llvm/ir/types.hpp>
17 #include <jlm/rvsdg/gamma.hpp>
18 #include <jlm/rvsdg/lambda.hpp>
19 #include <jlm/rvsdg/MatchType.hpp>
20 #include <jlm/rvsdg/theta.hpp>
21 
22 #include <queue>
23 
24 namespace jlm::llvm::aa
25 {
26 
28 
29 LocalAliasAnalysis::~LocalAliasAnalysis() noexcept = default;
30 
31 std::string
33 {
34  return "LocalAA";
35 }
36 
37 size_t
39 {
41 }
42 
43 void
44 LocalAliasAnalysis::setMaxTraceCollectionSize(size_t maxTraceCollectionSize)
45 {
46  maxTraceCollectionSize_ = maxTraceCollectionSize;
47 }
48 
60 {
62  std::optional<int64_t> Offset;
63 };
64 
69 {
75  std::unordered_map<const rvsdg::Output *, std::optional<int64_t>> AllTracedOutputs;
76 
82  std::unordered_map<const rvsdg::Output *, std::optional<int64_t>> TopOrigins;
83 };
84 
86 LocalAliasAnalysis::Query(const rvsdg::Output & p1, size_t s1, const rvsdg::Output & p2, size_t s2)
87 {
88  const auto & p1Norm = llvm::traceOutput(p1);
89  const auto & p2Norm = llvm::traceOutput(p2);
90 
91  // If the two pointers are the same value, they must alias
92  if (&p1Norm == &p2Norm)
93  return MustAlias;
94 
95  // Trace through GEP operations to get closer to the origins of the pointers
96  // Only trace through GEPs where the offset is known at compile time,
97  // to avoid giving up on MustAlias prematurely
98  const auto p1Traced = TracePointerOriginPrecise(p1Norm);
99  const auto p2Traced = TracePointerOriginPrecise(p2Norm);
100  JLM_ASSERT(p1Traced.Offset.has_value() && p2Traced.Offset.has_value());
101 
102  if (p1Traced.BasePointer == p2Traced.BasePointer)
103  {
104  // The pointers share base, but may have different offsets
105  // p1 = base + p1Offset
106  // p2 = base + p2Offset
107 
108  return QueryOffsets(p1Traced.Offset, s1, p2Traced.Offset, s2);
109  }
110 
111  // If the max trace collection size is set to 1,
112  // give up before tracing through multiple origins or unknown offsets.
113  if (maxTraceCollectionSize_ <= 1)
114  {
115  // If both pointers are original origins, and not the same pointer, we have NoAlias
116  if (IsOriginalOrigin(*p1Traced.BasePointer) && IsOriginalOrigin(*p2Traced.BasePointer))
117  return NoAlias;
118 
119  return MayAlias;
120  }
121 
122  // Keep tracing back to all sources
123  TraceCollection p1TraceCollection;
124  TraceCollection p2TraceCollection;
125 
126  // If tracing reaches too many possible outputs, it may give up
127  if (!TraceAllPointerOrigins(p1Traced, p1TraceCollection))
128  return MayAlias;
129 
130  if (!TraceAllPointerOrigins(p2Traced, p2TraceCollection))
131  return MayAlias;
132 
133  // Removes top origins that can not possibly be valid targets due to being too small.
134  // If p1 + s1 is outside the range of a top origin, then p1 can not target it
135  RemoveTopOriginsWithRemainingSizeBelow(p1TraceCollection, s1);
136  RemoveTopOriginsWithRemainingSizeBelow(p2TraceCollection, s2);
137 
138  // If each trace collection has only one top origin, check if they have the same base pointer
139  if (p1TraceCollection.TopOrigins.size() == 1 && p2TraceCollection.TopOrigins.size() == 1)
140  {
141  const auto & [p1Base, p1Offset] = *p1TraceCollection.TopOrigins.begin();
142  const auto & [p2Base, p2Offset] = *p2TraceCollection.TopOrigins.begin();
143  if (p1Base == p2Base)
144  return QueryOffsets(p1Offset, s1, p2Offset, s2);
145  }
146 
147  // From this point on we give up on MustAlias
148 
149  // Since we only have inbound GEPs, a pointer p = b + 12 must point at least 12 bytes into
150  // the memory region it points to
151  auto minimumP1OffsetFromStart = GetMinimumOffsetFromStart(p1TraceCollection);
152  auto minimumP2OffsetFromStart = GetMinimumOffsetFromStart(p2TraceCollection);
153 
154  // In case the trace collections contain unknown offsets, also try using the
155  // precise p1Traced and p2Traced, which always have a known offset
156  if (*p1Traced.Offset > 0)
157  minimumP1OffsetFromStart =
158  std::max(minimumP1OffsetFromStart, static_cast<size_t>(*p1Traced.Offset));
159  if (*p2Traced.Offset > 0)
160  minimumP2OffsetFromStart =
161  std::max(minimumP2OffsetFromStart, static_cast<size_t>(*p2Traced.Offset));
162 
163  // Since we have given up on MustAlias, we can remove some targets even if they are valid.
164  // Even if p1 might point to an 4-byte int foo,
165  // if p2 is an 8-byte operation, or p2 is at least 4 bytes into its target,
166  // we can safely discard that p1 might target foo.
167  RemoveTopOriginsSmallerThanSize(p2TraceCollection, minimumP1OffsetFromStart + s1);
168  RemoveTopOriginsSmallerThanSize(p1TraceCollection, minimumP2OffsetFromStart + s2);
169 
170  // If we know that p2 is at least 12 bytes into the memory region it targets,
171  // then any use of p1 where p1 + s1 is within the first 12 bytes of its memory region can be
172  // ignored.
173  RemoveTopOriginsWithinTheFirstNBytes(p1TraceCollection, s1, minimumP2OffsetFromStart);
174  RemoveTopOriginsWithinTheFirstNBytes(p2TraceCollection, s2, minimumP1OffsetFromStart);
175 
176  // Any direct overlap in the collections' top sets means there is a possibility of aliasing
177  if (DoTraceCollectionsOverlap(p1TraceCollection, s1, p2TraceCollection, s2))
178  return MayAlias;
179 
180  // Even if there is no direct overlap in the trace collections, the pointers may still alias
181  // Take for example the top sets { ALLOCA[a]+40, ALLOCA[b] } and { ALLOCA[c], o4+20 }
182  // o4 is some output that can not be traced further, but it is also not original.
183  // It is possible for o4 to be a pointer to ALLOCA[a]+20, in which case there is aliasing.
184 
185  // We already know that there is no direct overlap in the top origin sets,
186  // so if both trace collections only contain original pointers, there is NoAlias.
187  const bool p1AllTopsOriginal = HasOnlyOriginalTopOrigins(p1TraceCollection);
188  const bool p2AllTopsOriginal = HasOnlyOriginalTopOrigins(p2TraceCollection);
189 
190  if (p1AllTopsOriginal && p2AllTopsOriginal)
191  return NoAlias;
192 
193  // If one of the pointers has a top origin set containing only fully traceable ALLOCAs,
194  // it is not possible for the other pointer to target any of them,
195  // as they would already be explicitly included in its top origin set.
196  const bool p1OnlyTraceable = HasOnlyFullyTraceableTopOrigins(p1TraceCollection);
197  const bool p2OnlyTraceable = HasOnlyFullyTraceableTopOrigins(p2TraceCollection);
198 
199  if (p1OnlyTraceable || p2OnlyTraceable)
200  return NoAlias;
201 
202  return MayAlias;
203 }
204 
214 static std::optional<int64_t>
216  const rvsdg::SimpleNode & gepNode,
217  size_t inputIndex,
218  const rvsdg::Type & type)
219 {
220  // If we have no more input index values, we are not offsetting into the type
221  if (inputIndex >= gepNode.ninputs())
222  return 0;
223 
224  // GEP input 0 is the pointer being offset
225  // GEP input 1 is the number of whole types
226  // Intra-type offsets start at input 2 and beyond
227  JLM_ASSERT(inputIndex >= 2);
228 
229  auto & gepInput = *gepNode.input(inputIndex)->origin();
230  auto indexingValue = tryGetConstantSignedInteger(gepInput);
231 
232  // Any unknown indexing value means the GEP offset is unknown overall
233  if (!indexingValue.has_value())
234  return std::nullopt;
235 
236  if (auto array = dynamic_cast<const ArrayType *>(&type))
237  {
238  const auto & elementType = array->GetElementType();
239  int64_t offset = *indexingValue * GetTypeAllocSize(*elementType);
240 
241  // Get the offset into the element type as well, if any
242  const auto subOffset = CalculateIntraTypeGepOffset(gepNode, inputIndex + 1, *elementType);
243  if (subOffset.has_value())
244  return offset + *subOffset;
245 
246  return std::nullopt;
247  }
248  if (auto strct = dynamic_cast<const StructType *>(&type))
249  {
250  if (*indexingValue < 0 || static_cast<size_t>(*indexingValue) >= strct->numElements())
251  throw std::logic_error("Struct type has fewer fields than requested by GEP");
252 
253  const auto & fieldType = strct->getElementType(*indexingValue);
254  int64_t offset = strct->GetFieldOffset(*indexingValue);
255 
256  const auto subOffset = CalculateIntraTypeGepOffset(gepNode, inputIndex + 1, *fieldType);
257  if (subOffset.has_value())
258  return offset + *subOffset;
259 
260  return std::nullopt;
261  }
262 
263  JLM_UNREACHABLE("Unknown GEP type");
264 }
265 
266 std::optional<int64_t>
268 {
269  const auto gep = util::assertedCast<const GetElementPtrOperation>(&gepNode.GetOperation());
270 
271  // The pointee type. Gets updated by the loop below if the GEP has multiple levels of offsets
272  const auto & pointeeType = gep->getPointeeType();
273 
274  const auto & wholeTypeIndexingOrigin = *gepNode.input(1)->origin();
275  const auto wholeTypeIndexing = tryGetConstantSignedInteger(wholeTypeIndexingOrigin);
276 
277  if (!wholeTypeIndexing.has_value())
278  return std::nullopt;
279 
280  int64_t offset = *wholeTypeIndexing * GetTypeAllocSize(pointeeType);
281 
282  // In addition to offsetting by whole types, a GEP can also offset within a type
283  const auto subOffset = CalculateIntraTypeGepOffset(gepNode, 2, pointeeType);
284  if (!subOffset.has_value())
285  return std::nullopt;
286 
287  return offset + *subOffset;
288 }
289 
292 {
293  // The original pointer p is always equal to base + byte offset
294  const rvsdg::Output * base = &p;
295  int64_t offset = 0;
296 
297  while (true)
298  {
299  // Use normalization function to get past all trivially invariant operations
300  base = &llvm::traceOutput(*base);
301 
302  if (const auto [node, gep] =
303  rvsdg::TryGetSimpleNodeAndOptionalOp<GetElementPtrOperation>(*base);
304  gep)
305  {
306  auto calculatedOffset = CalculateGepOffset(*node);
307 
308  // Only trace through GEPs with statically known offsets
309  if (!calculatedOffset.has_value())
310  break;
311 
312  base = node->input(0)->origin();
313  offset += *calculatedOffset;
314 
315  continue;
316  }
317 
318  // We were not able to trace further
319  break;
320  }
321 
322  return TracedPointerOrigin{ base, offset };
323 }
324 
327  std::optional<int64_t> offset1,
328  size_t s1,
329  std::optional<int64_t> offset2,
330  size_t s2)
331 {
332  // If either offset is unknown, return MayAlias
333  if (!offset1.has_value() || !offset2.has_value())
334  return MayAlias;
335 
336  auto difference = *offset2 - *offset1;
337  if (difference == 0)
338  return MustAlias;
339 
340  // p2 starts at or after p1+s1
341  if (difference >= 0 && static_cast<size_t>(difference) >= s1)
342  return NoAlias;
343 
344  // p1 starts at or after p2+s2
345  if (difference <= 0 && static_cast<size_t>(-difference) >= s2)
346  return NoAlias;
347 
348  // We have a partial alias
349  return MayAlias;
350 }
351 
352 bool
354 {
355  if (traceCollection.AllTracedOutputs.size() >= maxTraceCollectionSize_)
356  return false;
357 
358  // Normalize the pointer first, to avoid tracing trivial temporary outputs
360 
361  auto it = traceCollection.AllTracedOutputs.find(p.BasePointer);
362  if (it != traceCollection.AllTracedOutputs.end())
363  {
364  // If the base pointer has already been traced with an unknown offset, we have nothing to add
365  if (!it->second.has_value())
366  return true;
367 
368  // The offset used for the base pointer the last time it was traced
369  const auto prevOffset = *it->second;
370 
371  // If we are visiting the same base pointer again with the same offset, we have nothing to add
372  if (p.Offset.has_value() && *p.Offset == prevOffset)
373  return true;
374 
375  // We have different offsets to last time, collapse to unknown offset
376  p.Offset = std::nullopt;
377  }
378 
379  traceCollection.AllTracedOutputs[p.BasePointer] = p.Offset;
380 
381  // If it is a GEP, we can trace through it, but possibly lose precise offset information
382  if (const auto [node, gep] =
383  rvsdg::TryGetSimpleNodeAndOptionalOp<GetElementPtrOperation>(*p.BasePointer);
384  gep)
385  {
386  // Update the base pointer and offset to represent the other side of the GEP
387  p.BasePointer = node->input(0)->origin();
388 
389  // If we have precisely tracked the offset so far, try updating it with the GEPs offset
390  if (p.Offset.has_value())
391  {
392  const auto gepOffset = CalculateGepOffset(*node);
393  if (gepOffset.has_value())
394  p.Offset = *p.Offset + *gepOffset;
395  else
396  p.Offset = std::nullopt;
397  }
398 
399  return TraceAllPointerOrigins(p, traceCollection);
400  }
401 
402  // If the node is a \ref SelectOperation, trace through both possible inputs
403  if (const auto [node, select] =
404  rvsdg::TryGetSimpleNodeAndOptionalOp<SelectOperation>(*p.BasePointer);
405  select)
406  {
407  auto leftTrace = p;
408  leftTrace.BasePointer = node->input(1)->origin();
409  auto rightTrace = p;
410  rightTrace.BasePointer = node->input(2)->origin();
411 
412  return TraceAllPointerOrigins(leftTrace, traceCollection)
413  && TraceAllPointerOrigins(rightTrace, traceCollection);
414  }
415 
416  // If we reach undef nodes, do not include them in the TopOrigins
417  if (rvsdg::IsOwnerNodeOperation<UndefValueOperation>(*p.BasePointer))
418  {
419  return true;
420  }
421 
422  // Trace into gamma nodes
423  if (auto gamma = rvsdg::TryGetOwnerNode<rvsdg::GammaNode>(*p.BasePointer))
424  {
425  auto exitVar = gamma->MapOutputExitVar(*p.BasePointer);
426  for (auto result : exitVar.branchResult)
427  {
428  TracedPointerOrigin inside = { result->origin(), p.Offset };
429 
430  // If tracing gives up, we give up
431  if (!TraceAllPointerOrigins(inside, traceCollection))
432  return false;
433  }
434 
435  return true;
436  }
437 
438  // Trace into theta nodes
439  if (auto theta = rvsdg::TryGetOwnerNode<rvsdg::ThetaNode>(*p.BasePointer))
440  {
441  auto loopVar = theta->MapOutputLoopVar(*p.BasePointer);
442 
443  // Invariant loop variables should already have been handled by normalization
445 
446  TracedPointerOrigin inside = { loopVar.post->origin(), p.Offset };
447  return TraceAllPointerOrigins(inside, traceCollection);
448  }
449 
450  // We could not trace further, add p as a TopOrigin
451  traceCollection.TopOrigins[p.BasePointer] = p.Offset;
452  return true;
453 }
454 
455 bool
457 {
458  // Each GraphImport represents a unique object
459  if (dynamic_cast<const LlvmGraphImport *>(&pointer))
460  return true;
461 
462  if (rvsdg::TryGetOwnerNode<rvsdg::DeltaNode>(pointer))
463  return true;
464 
465  if (rvsdg::TryGetOwnerNode<rvsdg::LambdaNode>(pointer))
466  return true;
467 
468  // Is pointer the output of one of the nodes
469  if (const auto node = rvsdg::TryGetOwnerNode<rvsdg::SimpleNode>(pointer))
470  {
471  if (is<AllocaOperation>(node->GetOperation()))
472  return true;
473 
474  if (is<MallocOperation>(node->GetOperation()))
475  return true;
476  }
477 
478  return false;
479 }
480 
481 bool
483 {
484  for (auto [output, offset] : traces.TopOrigins)
485  {
486  if (!IsOriginalOrigin(*output))
487  return false;
488  }
489  return true;
490 }
491 
492 std::optional<size_t>
494 {
495  if (auto delta = rvsdg::TryGetOwnerNode<rvsdg::DeltaNode>(pointer))
496  return GetTypeAllocSize(*delta->GetOperation().Type());
497  if (auto import = dynamic_cast<const LlvmGraphImport *>(&pointer))
498  {
499  auto size = GetTypeAllocSize(*import->ValueType());
500  // Workaround for imported incomplete types appearing to have size 0 in the LLVM IR
501  if (size == 0)
502  return std::nullopt;
503 
504  return size;
505  }
506  if (const auto [node, allocaOp] = rvsdg::TryGetSimpleNodeAndOptionalOp<AllocaOperation>(pointer);
507  allocaOp)
508  {
509  const auto elementCount = tryGetConstantSignedInteger(*node->input(0)->origin());
510  if (elementCount.has_value())
511  return *elementCount * GetTypeAllocSize(*allocaOp->allocatedType());
512  }
513  if (const auto [node, mallocOp] = rvsdg::TryGetSimpleNodeAndOptionalOp<MallocOperation>(pointer);
514  mallocOp)
515  {
516  const auto mallocSize =
518  if (mallocSize.has_value())
519  return *mallocSize;
520  }
521 
522  return std::nullopt;
523 }
524 
525 std::optional<size_t>
527 {
528  const auto totalSize = GetOriginalOriginSize(*trace.BasePointer);
529  if (!totalSize.has_value())
530  return std::nullopt;
531 
532  if (!trace.Offset.has_value())
533  return *totalSize;
534 
535  // Avoid wrap-around by truncating remaining size to min 0
536  if (*trace.Offset > 0 && static_cast<size_t>(*trace.Offset) > *totalSize)
537  return 0;
538 
539  return *totalSize - *trace.Offset;
540 }
541 
542 void
544 {
545  auto it = traces.TopOrigins.begin();
546  while (it != traces.TopOrigins.end())
547  {
548  const auto remainingSize = GetRemainingSize({ it->first, it->second });
549  if (remainingSize.has_value())
550  {
551  // This top origin leaves too little room, and can be fully removed
552  if (*remainingSize < s)
553  {
554  it = traces.TopOrigins.erase(it);
555  continue;
556  }
557 
558  // If a top origin is exactly large enough for s, any unknown offset must be 0
559  if (*remainingSize == s && !it->second.has_value())
560  it->second = 0;
561  }
562  it++;
563  }
564 }
565 
566 size_t
568 {
569  std::optional<size_t> minimumOffset;
570  for (auto [output, offset] : traces.TopOrigins)
571  {
572  // If one of the possible targets has an unknown offset, just use the access size
573  if (!offset.has_value())
574  return 0;
575 
576  if (*offset < 0)
577  return 0;
578 
579  if (minimumOffset.has_value())
580  minimumOffset = std::min(*minimumOffset, static_cast<size_t>(*offset));
581  else
582  minimumOffset = offset;
583  }
584 
585  if (minimumOffset.has_value())
586  return *minimumOffset;
587 
588  // We only get here if the top origins is empty, in which case the return value
589  // does not matter, as the query will return NoAlias anyway.
590  return 0;
591 }
592 
593 void
595 {
596  auto it = traces.TopOrigins.begin();
597  while (it != traces.TopOrigins.end())
598  {
599  auto originSize = GetOriginalOriginSize(*it->first);
600  if (originSize.has_value() && *originSize < s)
601  it = traces.TopOrigins.erase(it);
602  else
603  it++;
604  }
605 }
606 
607 void
609  TraceCollection & traces,
610  size_t s,
611  size_t N)
612 {
613  auto it = traces.TopOrigins.begin();
614  while (it != traces.TopOrigins.end())
615  {
616  const auto offset = it->second;
617 
618  // If the pointer is original, it is also pointing to the beginning of the memory region.
619  // The offset thus tells us exactly which bytes within the memory region we touch.
620  if (IsOriginalOrigin(*it->first) && offset.has_value() && *offset + s <= N)
621  it = traces.TopOrigins.erase(it);
622  else
623  it++;
624  }
625 }
626 
627 bool
629  TraceCollection & tc1,
630  size_t s1,
631  TraceCollection & tc2,
632  size_t s2)
633 {
634  for (auto [p1Origin, p1Offset] : tc1.TopOrigins)
635  {
636  auto p2Find = tc2.TopOrigins.find(p1Origin);
637  if (p2Find == tc2.TopOrigins.end())
638  continue;
639 
640  auto p2Offset = p2Find->second;
641  if (QueryOffsets(p1Offset, s1, p2Offset, s2) != NoAlias)
642  return true;
643  }
644 
645  return false;
646 }
647 
648 bool
650 {
651  // The only original origins that can be fully traced for escaping are ALLOCAs
652  if (!rvsdg::IsOwnerNodeOperation<AllocaOperation>(pointer))
653  return false;
654 
655  // Check if the result for this ALLOCA is already memoized
656  auto it = IsFullyTraceable_.find(&pointer);
657  if (it != IsFullyTraceable_.end())
658  return it->second;
659 
660  // Use a queue to find all users of the ALLOCA's address
661  std::queue<const rvsdg::Output *> qu;
662  std::unordered_set<const rvsdg::Output *> added;
663 
664  const auto Enqueue = [&](const rvsdg::Output & p)
665  {
666  // Only enqueue new outputs
667  auto [_, inserted] = added.insert(&p);
668  if (inserted)
669  qu.push(&p);
670  };
671 
672  Enqueue(pointer);
673  while (!qu.empty())
674  {
675  auto & p = *qu.front();
676  qu.pop();
677 
678  // Handle all inputs that are users of p
679  for (auto & user : p.Users())
680  {
681  if (auto gamma = rvsdg::TryGetOwnerNode<rvsdg::GammaNode>(user))
682  {
683  auto input = gamma->MapInput(user);
684 
685  // A pointer must always be an EntryVar, as the MatchVar has a ControlType
686  auto entry = std::get_if<rvsdg::GammaNode::EntryVar>(&input);
687  JLM_ASSERT(entry);
688 
689  for (auto output : entry->branchArgument)
690  Enqueue(*output);
691 
692  continue;
693  }
694  if (auto gamma = rvsdg::TryGetRegionParentNode<rvsdg::GammaNode>(user))
695  {
696  // user is a gamma result, find the corresponding gamma output
697  auto exitVar = gamma->MapBranchResultExitVar(user);
698  Enqueue(*exitVar.output);
699 
700  continue;
701  }
702 
703  if (auto theta = rvsdg::TryGetOwnerNode<rvsdg::ThetaNode>(user))
704  {
705  auto loopVar = theta->MapInputLoopVar(user);
706 
707  // The loop always runs at least once, so map it to the inside
708  Enqueue(*loopVar.pre);
709 
710  continue;
711  }
712  if (auto theta = rvsdg::TryGetRegionParentNode<rvsdg::ThetaNode>(user))
713  {
714  // user is a theta result, find the corresponding loop variable
715  auto loopVar = theta->MapPostLoopVar(user);
716  Enqueue(*loopVar.pre);
717  Enqueue(*loopVar.output);
718 
719  continue;
720  }
721 
722  if (auto node = rvsdg::TryGetOwnerNode<rvsdg::SimpleNode>(user))
723  {
724  bool do_continue = MatchTypeWithDefault(
725  node->GetOperation(),
726  [&](const IOBarrierOperation &)
727  {
728  // The pointer input must be the node's first input
729  JLM_ASSERT(user.index() == 0);
730  Enqueue(*node->output(0));
731  return true;
732  },
733  [&](const GetElementPtrOperation &)
734  {
735  // The pointer input must be the node's first input
736  JLM_ASSERT(user.index() == 0);
737  Enqueue(*node->output(0));
738  return true;
739  },
740  [&](const SelectOperation &)
741  {
742  // Select operations are fine, if the output is still fully traceable
743  Enqueue(*node->output(0));
744  return true;
745  },
746  [&](const LoadOperation &)
747  {
748  // Loads are always fine
749  return true;
750  },
751  [&](const StoreOperation &)
752  {
753  // Stores are only fine if the pointer itself is not being stored somewhere
754  if (&user == &StoreOperation::AddressInput(*node))
755  return true;
756  else
757  return false;
758  },
759  []()
760  {
761  return false;
762  });
763  if (do_continue)
764  continue;
765  }
766 
767  // We were unable to handle this user, so the original pointer escapes tracing
768  IsFullyTraceable_[&pointer] = false;
769  return false;
770  }
771  }
772 
773  // The entire queue was processed without reaching a single untraceable user of the pointer
774  IsFullyTraceable_[&pointer] = true;
775  return true;
776 }
777 
778 bool
780 {
781  for (auto [topOrigin, _] : traces.TopOrigins)
782  {
783  if (!IsOriginalOriginFullyTraceable(*topOrigin))
784  return false;
785  }
786 
787  return true;
788 }
789 
790 }
static rvsdg::Input & sizeInput(const rvsdg::Node &node)
Definition: operators.hpp:2440
StructType class.
Definition: types.hpp:184
static void RemoveTopOriginsWithinTheFirstNBytes(TraceCollection &traces, size_t s, size_t N)
static bool IsOriginalOrigin(const rvsdg::Output &pointer)
~LocalAliasAnalysis() noexcept override
bool HasOnlyFullyTraceableTopOrigins(TraceCollection &traces)
AliasQueryResponse Query(const rvsdg::Output &p1, size_t s1, const rvsdg::Output &p2, size_t s2) override
std::unordered_map< const rvsdg::Output *, bool > IsFullyTraceable_
static TracedPointerOrigin TracePointerOriginPrecise(const rvsdg::Output &p)
static bool DoTraceCollectionsOverlap(TraceCollection &tc1, size_t s1, TraceCollection &tc2, size_t s2)
static void RemoveTopOriginsSmallerThanSize(TraceCollection &traces, size_t s)
void setMaxTraceCollectionSize(size_t maxTraceCollectionSize)
static std::optional< int64_t > CalculateGepOffset(const rvsdg::SimpleNode &gepNode)
static bool HasOnlyOriginalTopOrigins(TraceCollection &traces)
static size_t GetMinimumOffsetFromStart(TraceCollection &traces)
bool IsOriginalOriginFullyTraceable(const rvsdg::Output &pointer)
static void RemoveTopOriginsWithRemainingSizeBelow(TraceCollection &traces, size_t s)
static AliasQueryResponse QueryOffsets(std::optional< int64_t > offset1, size_t s1, std::optional< int64_t > offset2, size_t s2)
bool TraceAllPointerOrigins(TracedPointerOrigin p, TraceCollection &traceCollection)
static std::optional< size_t > GetOriginalOriginSize(const rvsdg::Output &pointer)
static std::optional< size_t > GetRemainingSize(TracedPointerOrigin trace)
Output * origin() const noexcept
Definition: node.hpp:58
size_t ninputs() const noexcept
Definition: node.hpp:609
const SimpleOperation & GetOperation() const noexcept override
Definition: simple-node.cpp:48
NodeInput * input(size_t index) const noexcept
Definition: simple-node.hpp:82
#define JLM_ASSERT(x)
Definition: common.hpp:16
#define JLM_UNREACHABLE(msg)
Definition: common.hpp:43
static std::optional< int64_t > CalculateIntraTypeGepOffset(const rvsdg::SimpleNode &gepNode, size_t inputIndex, const rvsdg::Type &type)
size_t GetTypeAllocSize(const rvsdg::Type &type)
Definition: types.cpp:473
rvsdg::Output & traceOutput(rvsdg::Output &output, const rvsdg::Region *withinRegion)
Definition: Trace.cpp:54
static std::string ToString(const std::vector< MemoryNodeId > &memoryNodeIds)
std::optional< int64_t > tryGetConstantSignedInteger(const rvsdg::Output &output)
Definition: Trace.cpp:62
void MatchTypeWithDefault(T &obj, const Fns &... fns)
Pattern match over subclass type of given object with default handler.
static bool ThetaLoopVarIsInvariant(const ThetaNode::LoopVar &loopVar) noexcept
Definition: theta.hpp:227
std::unordered_map< const rvsdg::Output *, std::optional< int64_t > > TopOrigins
std::unordered_map< const rvsdg::Output *, std::optional< int64_t > > AllTracedOutputs