Jlm
LoopStrengthReduction.cpp
Go to the documentation of this file.
1 /*
2  * Copyright 2026 Andreas Lilleby Hjulstad <andreas.lilleby.hjulstad@gmail.com>
3  * See COPYING for terms of redistribution.
4  */
5 
9 #include <jlm/llvm/ir/Trace.hpp>
12 #include <jlm/rvsdg/gamma.hpp>
14 #include <jlm/util/Statistics.hpp>
15 
16 #include <algorithm>
17 
18 namespace jlm::llvm
19 {
21 {
22 public:
23  ~Context() noexcept = default;
24 
25  Context() = default;
26 
27  Context(const Context &) = delete;
28 
29  Context(Context &&) = delete;
30 
31  Context &
32  operator=(const Context &) = delete;
33 
34  Context &
35  operator=(Context &&) = delete;
36 
37  static std::unique_ptr<Context>
39  {
40  return std::make_unique<Context>();
41  }
42 
43  void
45  {
46  OperationsReducedMap_[&theta]++;
47  }
48 
49  const std::unordered_map<const rvsdg::ThetaNode *, size_t> &
51  {
52  return OperationsReducedMap_;
53  }
54 
56  size_t NumGEPCandidates = 0;
59 
60 private:
61  std::unordered_map<const rvsdg::ThetaNode *, size_t> OperationsReducedMap_;
62 };
63 
65 {
66 
67 public:
68  ~Statistics() noexcept override = default;
69 
70  explicit Statistics(const util::FilePath & sourceFile)
71  : util::Statistics(Id::LoopStrengthReduction, sourceFile)
72  {}
73 
74  void
75  Start() noexcept
76  {
78  }
79 
80  void
81  Stop(const Context & context) noexcept
82  {
84 
87  context.NumArithmeticCandidates + context.NumGEPCandidates);
88  AddMeasurement(Label::NumArithmeticLSRCandidates, context.NumArithmeticCandidates);
89  AddMeasurement(Label::NumGepLSRCandidates, context.NumGEPCandidates);
90  AddMeasurement(Label::NumArithmeticOperationsReduced, context.NumArithmeticOperationsReduced);
91  AddMeasurement(Label::NumGepOperationsReduced, context.NumGEPOperationsReduced);
94  GetStatisticsString(context.GetOperationsReducedMap()));
95  }
96 
97  static std::string
99  const std::unordered_map<const rvsdg::ThetaNode *, size_t> & operationsReducedMap)
100  {
101  std::string s = "";
102  size_t totalCount = 0;
103  for (auto & [thetaNode, operationsReduced] : operationsReducedMap)
104  {
105  totalCount += operationsReduced;
106 
107  s += "ID(" + std::to_string(thetaNode->subregion()->getRegionId())
108  + ")=" + std::to_string(operationsReduced);
109  s += ",";
110  }
111  s += "Total=" + std::to_string(totalCount);
112  return s;
113  }
114 
115  static std::unique_ptr<Statistics>
116  Create(const util::FilePath & sourceFile)
117  {
118  return std::make_unique<Statistics>(sourceFile);
119  }
120 };
121 
123  : Transformation("LoopStrengthReduction")
124 {}
125 
127 
128 void
130  rvsdg::RvsdgModule & rvsdgModule,
131  util::StatisticsCollector & statisticsCollector)
132 {
133  auto statistics = Statistics::Create(rvsdgModule.SourceFilePath().value());
134  statistics->Start();
135 
136  ScalarEvolution scalarEvolution;
137  scalarEvolution.Run(rvsdgModule, statisticsCollector);
138 
139  Context_ = Context::Create();
140 
141  ChrecMap_ = scalarEvolution.GetChrecMap();
142  SCEVMap_ = scalarEvolution.GetSCEVMap();
143 
144  ProcessRegion(rvsdgModule.Rvsdg().GetRootRegion());
145 
146  statistics->Stop(*Context_);
147  statisticsCollector.CollectDemandedStatistics(std::move(statistics));
148 }
149 
150 void
152 {
153  for (auto & node : region.Nodes())
154  {
155  if (const auto structuralNode = dynamic_cast<rvsdg::StructuralNode *>(&node))
156  {
157  for (auto & subregion : structuralNode->Subregions())
158  {
159  ProcessRegion(subregion);
160  }
161  if (const auto thetaNode = dynamic_cast<rvsdg::ThetaNode *>(structuralNode))
162  {
163  ReduceStrength(*thetaNode);
164  thetaNode->subregion()->prune(false);
165  }
166  }
167  }
168 }
169 
170 void
172 {
173  // General algorithm from prof. Pingali's notes:
174  // https://www.cs.utexas.edu/~pingali/CS380C/2019/lectures/strengthReduction.pdf
175  // Adapted to work with RVSDG and to make use of the computed recurrences from the scalar
176  // evolution analysis.
177  //
178  // In short, we look for candidate operations and replace them with a new induction variable.
179  //
180  // A candidate operation must satisfy four conditions:
181  // 1. The operation is either arithmetic (+, * or -) or a GEP operation
182  // 2. The SCEV tree of the operation is a linear combination of loop variables and constants
183  // 3. For arithmetic operations, the subtree must contain at least one multiplication node
184  // (IntegerMulOperation), ensuring we only reduce operations that actually benefit from
185  // strength reduction.
186  // 4. Its SCEV evaluates to a chain recurrence of the form {a,+,b}, meaning the value
187  // can be expressed as a start value plus a constant step per iteration. This is what makes
188  // it a valid induction variable candidate. For GEP operations, a, is the base
189  // address of the array we are indexing into.
190  //
191  // Modifying the RVSDG is done as follows:
192  //
193  // For an arithmetic candidate operation j with recurrence {a,+,b}:
194  // 1. Introduce a new loop variable initialized to the base value a
195  // 2. Replace all uses of j with the new loop variable
196  // If b is not 0:
197  // 3. Update the new loop variable each iteration by incrementing it by b
198  //
199  // For a GEP which has the recurrence {Init(a),+,b}, where Init(a) is the SCEV for the base
200  // address loop variable:
201  // 1. Introduce a new loop variable initialized to the base address
202  // 2. Replace all uses of the original GEP with the new loop variable
203  // 3. Update the new loop variable each iteration by advancing it by offset b using a new GEP
204  // In the case where b is invariant, the GEP can be eliminated entirely.
205 
206  // We traverse the nodes in the theta node in a bottom-up manner starting at the origin output of
207  // the post values for the loop variables. We look for candidate operations and add them to the
208  // stack of operations to be reduced.
209  util::HashSet<rvsdg::Output *> candidateOperations;
211  DependsOnIVMemo_.clear();
212  ContainsMulMemo_.clear();
213  for (const auto loopVar : thetaNode.GetLoopVars())
214  {
215  ProcessOutput(*loopVar.post->origin(), thetaNode, candidateOperations, visited);
216  }
217 
218  for (auto & output : candidateOperations.Items())
219  {
220  ReplaceCandidateOperation(*output, thetaNode);
221  }
222 }
223 
224 void
226  rvsdg::Output & output,
227  rvsdg::ThetaNode & thetaNode,
228  util::HashSet<rvsdg::Output *> & candidateOperations,
230 {
231  if (!visited.insert(&output))
232  return;
233 
234  const auto & simpleNode = rvsdg::TryGetOwnerNode<rvsdg::SimpleNode>(output);
235 
236  if (!simpleNode)
237  return;
238 
239  // Multiplication, addition, subtraction, shl and GEP operations are candidates for strength
240  // reduction
241  if (const auto & operation = simpleNode->GetOperation();
242  rvsdg::is<IntegerMulOperation>(operation) || rvsdg::is<IntegerAddOperation>(operation)
243  || rvsdg::is<IntegerSubOperation>(operation) || rvsdg::is<IntegerShlOperation>(operation)
244  || rvsdg::is<GetElementPtrOperation>(operation))
245  {
246  if (SCEVMap_.find(&output) == SCEVMap_.end())
247  return;
248 
249  if (IsValidCandidateOperation(output, operation))
250  {
251  candidateOperations.insert(&output);
252  return; // Return early to not create unnecessary induction variables for nested operations
253  }
254  }
255 
256  // For non-candidate operations, we traverse through to the node's inputs
257  for (auto & input : simpleNode->Inputs())
258  ProcessOutput(*input.origin(), thetaNode, candidateOperations, visited);
259 }
260 
261 void
263  rvsdg::Output & output,
264  rvsdg::ThetaNode & thetaNode)
265 {
266  JLM_ASSERT(ChrecMap_.find(&output) != ChrecMap_.end());
267  auto & chrec = ChrecMap_.at(&output);
268 
269  JLM_ASSERT(chrec->NumOperands() <= 2);
270 
271  const rvsdg::ThetaNode::LoopVar * newIV = nullptr;
272  if (const auto & bitType = std::dynamic_pointer_cast<const rvsdg::BitType>(output.Type()))
273  {
274  const auto numBits = bitType->nbits();
275  const auto newIVOpt = CreateNewArithmeticInductionVariable(*chrec, thetaNode, numBits);
276  if (!newIVOpt.has_value())
277  return;
278 
279  newIV = &newIVOpt.value();
280 
281  Context_->NumArithmeticOperationsReduced++;
282  }
283  else if (std::dynamic_pointer_cast<const PointerType>(output.Type()))
284  {
285  const auto newIVOpt = CreateNewGEPInductionVariable(*chrec, thetaNode);
286  if (!newIVOpt.has_value())
287  return;
288 
289  newIV = &newIVOpt.value();
290 
291  Context_->NumGEPOperationsReduced++;
292  }
293  else
294  {
295  throw std::logic_error("Invalid output type in ReplaceCandidateOperation!");
296  }
297 
298  JLM_ASSERT(newIV != nullptr);
299 
300  output.divert_users(newIV->pre);
301  // Insert the chrec for the new induction variable
302  // NOTE: This only updates the *copy* of the original chrec map from the scalar evolution
303  // analysis. In the future, we would want to insert this into the "global" chrec map so other
304  // analyses and transformations can use it as well.
305  ChrecMap_[newIV->pre] = std::move(chrec);
306  Context_->IncrementOperationsReducedCount(thetaNode);
307 }
308 
309 std::optional<rvsdg::Output *>
311  const SCEVChainRecurrence & chrec,
312  const rvsdg::ThetaNode & thetaNode,
313  const size_t numBits)
314 {
315  if (!SCEVChainRecurrence::IsInvariantInLoop(chrec, thetaNode))
316  return std::nullopt;
317 
318  auto & targetLoop = chrec.GetLoop();
319 
320  rvsdg::Output * chrecOutput = nullptr;
321  if (ScalarEvolution::StructurallyEqual(*ChrecMap_.at(&chrec.GetOutput()), chrec))
322  {
323  // The chrec stored in the chrec map corresponds with the chrec we are processing (it is an
324  // induction variable). This means that the chrec has a corresponding output which we can use.
325  chrecOutput = &chrec.GetOutput();
326  }
327  else
328  {
329  // The chrec doesn't have a corresponding output. This can happen in cases where we have folded
330  // some constant into the chrec of an induction variable, either with addition or
331  // multiplication. In this case, we need to create a new induction variable for the chrec.
332  const auto newIV = CreateNewArithmeticInductionVariable(chrec, targetLoop, numBits);
333  if (!newIV.has_value())
334  return std::nullopt;
335 
336  chrecOutput = newIV->pre;
337  }
338 
339  if (targetLoop.subregion() == thetaNode.region())
340  {
341  return chrecOutput;
342  }
343 
344  if (rvsdg::Region::isAncestor(*targetLoop.subregion(), *thetaNode.subregion()))
345  {
346  auto & traced = llvm::traceOutput(*chrecOutput, thetaNode.subregion());
347  if (traced.region() != thetaNode.region())
348  return std::nullopt;
349 
350  return &traced;
351  }
352 
353  // This is fine to do and wont throw since we know, due to the cases above and the invariance
354  // check at the start, that the region of the chrec output is guaranteed to be an ancestor of the
355  // theta region
356  auto & routed = rvsdg::RouteToRegion(*chrecOutput, *thetaNode.region());
357  return &routed;
358 }
359 
360 std::optional<rvsdg::Output *>
362  const SCEV & scev,
363  rvsdg::ThetaNode & thetaNode,
364  const size_t numBits)
365 {
366  if (const auto constant = dynamic_cast<const SCEVConstant *>(&scev))
367  {
368  const auto value = constant->GetValue();
369  const auto & constantNode =
370  IntegerConstantOperation::Create(*thetaNode.region(), numBits, value);
371 
372  return constantNode.output(0);
373  }
374  if (const auto init = dynamic_cast<const SCEVInit *>(&scev))
375  {
376  const auto & prePointer = init->GetPrePointer();
377  const auto targetLoop = rvsdg::TryGetRegionParentNode<rvsdg::ThetaNode>(prePointer);
378  if (targetLoop == nullptr)
379  return std::nullopt;
380 
381  const auto initLoopVar = targetLoop->MapPreLoopVar(prePointer);
382 
383  if (targetLoop->subregion() == thetaNode.subregion())
384  {
385  return initLoopVar.input->origin();
386  }
387 
388  if (rvsdg::Region::isAncestor(*targetLoop->subregion(), *thetaNode.subregion()))
389  {
390  auto & traced = llvm::traceOutput(*initLoopVar.pre, thetaNode.region());
391 
392  if (traced.region() != thetaNode.region())
393  return std::nullopt;
394 
395  return &traced;
396  }
397 
398  auto & routed = rvsdg::RouteToRegion(*initLoopVar.pre, *thetaNode.region());
399  return &routed;
400  }
401  if (const auto addExpr = dynamic_cast<const SCEVNAryAddExpr *>(&scev))
402  {
403  const auto rightMost = addExpr->GetOperand(addExpr->NumOperands() - 1);
404  const auto rightSideOpt = HoistSCEVExpresssion(*rightMost, thetaNode, numBits);
405  if (!rightSideOpt.has_value())
406  return std::nullopt;
407 
408  const auto newAddExpr = SCEV::CloneAs<SCEVNAryAddExpr>(*addExpr);
409  newAddExpr->RemoveOperand(addExpr->NumOperands() - 1);
410 
411  std::optional<rvsdg::Output *> leftSideOpt;
412  if (newAddExpr->NumOperands() == 1)
413  {
414  // Only a constant
415  const auto constantElement = newAddExpr->GetOperand(0);
416  leftSideOpt = HoistSCEVExpresssion(*constantElement, thetaNode, numBits);
417  }
418  else
419  {
420  leftSideOpt = HoistSCEVExpresssion(*newAddExpr, thetaNode, numBits);
421  }
422 
423  if (!leftSideOpt.has_value())
424  return std::nullopt;
425 
426  rvsdg::Output * leftSide = *leftSideOpt;
427  rvsdg::Output * rightSide = *rightSideOpt;
428  // If either side is a pointer, we use a GEP instead of IntegerAddOperation
429  if (const auto rightIsPtr = rvsdg::is<PointerType>(rightSide->Type()),
430  leftIsPtr = rvsdg::is<PointerType>(leftSide->Type());
431  leftIsPtr || rightIsPtr)
432  {
433  const auto ptrSide = leftIsPtr ? leftSide : rightSide;
434  auto offsetSide = leftIsPtr ? rightSide : leftSide;
435 
436  const auto ptrType = std::dynamic_pointer_cast<const PointerType>(ptrSide->Type());
437  JLM_ASSERT(ptrType);
438  auto newGep = GetElementPtrOperation::create(
439  ptrSide,
440  { offsetSide },
441  rvsdg::BitType::Create(8)); // Byte
442 
443  return newGep;
444  }
445 
446  if (const auto leftBitType = std::dynamic_pointer_cast<const rvsdg::BitType>(leftSide->Type());
447  leftBitType->nbits() != numBits)
448  {
449  leftSide = SExtOperation::create(numBits, leftSide);
450  }
451  if (const auto rightBitType =
452  std::dynamic_pointer_cast<const rvsdg::BitType>(rightSide->Type());
453  rightBitType->nbits() != numBits)
454  {
455  rightSide = SExtOperation::create(numBits, rightSide);
456  }
457 
458  auto & newAddNode =
459  jlm::rvsdg::CreateOpNode<IntegerAddOperation>({ leftSide, rightSide }, numBits);
460 
461  return newAddNode.output(0);
462  }
463  if (const auto mulExpr = dynamic_cast<const SCEVNAryMulExpr *>(&scev))
464  {
465  const auto rightMost = mulExpr->GetOperand(mulExpr->NumOperands() - 1);
466  const auto rightSideOpt = HoistSCEVExpresssion(*rightMost, thetaNode, numBits);
467  if (!rightSideOpt.has_value())
468  return std::nullopt;
469 
470  const auto newMulExpr = SCEV::CloneAs<SCEVNAryMulExpr>(*mulExpr);
471  newMulExpr->RemoveOperand(mulExpr->NumOperands() - 1);
472 
473  std::optional<rvsdg::Output *> leftSideOpt;
474  if (newMulExpr->NumOperands() == 1)
475  {
476  const auto constantElement = newMulExpr->GetOperand(0);
477  leftSideOpt = HoistSCEVExpresssion(*constantElement, thetaNode, numBits);
478  }
479  else
480  {
481  leftSideOpt = HoistSCEVExpresssion(*newMulExpr, thetaNode, numBits);
482  }
483 
484  if (!leftSideOpt.has_value())
485  return std::nullopt;
486 
487  rvsdg::Output * leftSide = *leftSideOpt;
488  rvsdg::Output * rightSide = *rightSideOpt;
489  // Sign extend in cases where the input does not match the expected number of bits
490  if (const auto leftBitType = std::dynamic_pointer_cast<const rvsdg::BitType>(leftSide->Type());
491  leftBitType->nbits() != numBits)
492  {
493  leftSide = SExtOperation::create(numBits, leftSide);
494  }
495  if (const auto rightBitType =
496  std::dynamic_pointer_cast<const rvsdg::BitType>(rightSide->Type());
497  rightBitType->nbits() != numBits)
498  {
499  rightSide = SExtOperation::create(numBits, rightSide);
500  }
501 
502  auto & newMulNode =
503  jlm::rvsdg::CreateOpNode<IntegerMulOperation>({ leftSide, rightSide }, numBits);
504 
505  return newMulNode.output(0);
506  }
507  if (const auto chrec = dynamic_cast<const SCEVChainRecurrence *>(&scev))
508  {
509  if (!SCEVChainRecurrence::IsAffine(*chrec))
510  return std::nullopt;
511 
512  return HoistChrec(*chrec, thetaNode, numBits);
513  }
514  throw std::logic_error("Unknown SCEV type in HoistSCEVExpression\n");
515 }
516 
517 std::optional<rvsdg::ThetaNode::LoopVar>
519  const SCEVChainRecurrence & chrec,
520  rvsdg::ThetaNode & thetaNode,
521  const size_t numBits)
522 {
523  const auto & startSCEV = chrec.GetStartValue();
524 
526  {
527  // Chrec has the form {a}, which indicates a loop-invariant (trivial induction variable).
528  // We can hoist this out of the loop by creating a new loop variable.
529  const auto hoistedConstant = HoistSCEVExpresssion(*startSCEV, thetaNode, numBits);
530  if (!hoistedConstant.has_value())
531  return std::nullopt;
532 
533  return thetaNode.AddLoopVar(*hoistedConstant);
534  }
536  {
537  // Chrec has the form {a,+,b} which is a basic induction variable
538  const auto & stepPtr = chrec.GetStep();
539 
540  JLM_ASSERT(stepPtr.has_value());
541 
542  const auto & stepSCEV = *stepPtr;
543 
544  rvsdg::Output * stepOutput = nullptr;
545  if (const auto stepConstant = dynamic_cast<const SCEVConstant *>(stepSCEV.get()))
546  {
547  const auto stepValue = stepConstant->GetValue();
548  const auto & stepValueNode =
549  IntegerConstantOperation::Create(*thetaNode.subregion(), numBits, stepValue);
550  stepOutput = stepValueNode.output(0);
551  }
552  else if (const auto stepInit = dynamic_cast<const SCEVInit *>(stepSCEV.get()))
553  {
554  stepOutput = &stepInit->GetPrePointer();
555  }
556  else if (const auto stepNAryExpr = dynamic_cast<const SCEVNAryExpr *>(stepSCEV.get()))
557  {
558  const auto hoistedStep = HoistSCEVExpresssion(*stepNAryExpr, thetaNode, numBits);
559  if (!hoistedStep.has_value())
560  return std::nullopt;
561 
562  const auto newStepIV = thetaNode.AddLoopVar(*hoistedStep);
563  stepOutput = newStepIV.pre;
564  }
565  else
566  {
567  return std::nullopt;
568  }
569 
570  const auto hoistedStart = HoistSCEVExpresssion(*startSCEV, thetaNode, numBits);
571  if (!hoistedStart.has_value())
572  return std::nullopt;
573 
574  auto newIV = thetaNode.AddLoopVar(*hoistedStart);
575 
576  auto & newAddNode =
577  jlm::rvsdg::CreateOpNode<IntegerAddOperation>({ newIV.pre, stepOutput }, numBits);
578  newIV.post->divert_to(newAddNode.output(0));
579 
580  return newIV;
581  }
582  throw std::logic_error("Invalid chrec size in CreateNewArithmeticInductionVariable!");
583 }
584 
585 std::optional<rvsdg::ThetaNode::LoopVar>
587  const SCEVChainRecurrence & chrec,
588  rvsdg::ThetaNode & thetaNode)
589 {
590  const auto & baseAddressSCEV = chrec.GetStartValue();
592  {
593  const auto hoistedStart = HoistSCEVExpresssion(*baseAddressSCEV, thetaNode, 64);
594 
595  if (!hoistedStart.has_value())
596  return std::nullopt;
597 
598  return thetaNode.AddLoopVar(*hoistedStart);
599  }
601  {
602  // Chrec has the form {Init(a),+,b} where Init(a) is the (initial) value of the base address
603  // and b is the offset (in bytes).
604  const auto & stepPtr = chrec.GetStep();
605 
606  JLM_ASSERT(stepPtr.has_value());
607 
608  const auto & stepSCEV = *stepPtr;
609 
610  rvsdg::Output * stepOutput = nullptr;
611  if (const auto stepConstant = dynamic_cast<const SCEVConstant *>(stepSCEV.get()))
612  {
613  const auto stepValue = stepConstant->GetValue();
614  const auto & stepValueNode =
615  IntegerConstantOperation::Create(*thetaNode.subregion(), 64, stepValue);
616  stepOutput = stepValueNode.output(0);
617  }
618  else if (const auto stepInit = dynamic_cast<const SCEVInit *>(stepSCEV.get()))
619  {
620  stepOutput = &stepInit->GetPrePointer();
621  }
622  else if (const auto stepNAryExpr = dynamic_cast<const SCEVNAryExpr *>(stepSCEV.get()))
623  {
624  const auto hoistedStep = HoistSCEVExpresssion(*stepNAryExpr, thetaNode, 64);
625  if (!hoistedStep.has_value())
626  return std::nullopt;
627 
628  const auto newStepIV = thetaNode.AddLoopVar(*hoistedStep);
629  stepOutput = newStepIV.pre;
630  }
631  else
632  {
633  return std::nullopt;
634  }
635 
636  const auto hoistedStart = HoistSCEVExpresssion(*baseAddressSCEV, thetaNode, 64);
637 
638  if (!hoistedStart.has_value())
639  return std::nullopt;
640 
641  const auto newIV = thetaNode.AddLoopVar(*hoistedStart);
642 
643  if (const auto stepBitType =
644  std::dynamic_pointer_cast<const rvsdg::BitType>(stepOutput->Type());
645  stepBitType->nbits() != 64)
646  {
647  stepOutput = SExtOperation::create(64, stepOutput);
648  }
649 
650  auto newGep = GetElementPtrOperation::create(
651  newIV.pre,
652  { stepOutput },
653  rvsdg::BitType::Create(8)); // Byte
654 
655  newIV.post->divert_to(newGep);
656 
657  return newIV;
658  }
659 
660  throw std::logic_error("Invalid chrec size in CreateNewGEPInductionVariable!");
661 }
662 
663 bool
665  const rvsdg::Output & output,
666  const rvsdg::SimpleOperation & operation)
667 {
668  if (rvsdg::is<GetElementPtrOperation>(operation))
669  Context_->NumGEPCandidates++;
670  else
671  Context_->NumArithmeticCandidates++;
672 
673  if (!DependsOnInductionVariable(output))
674  return false;
675 
676  // We only reduce arithmetic operations if they contain a multiplication somewhere
677  if (rvsdg::is<IntegerBinaryOperation>(operation) && !ContainsMul(output))
678  return false;
679 
680  // We only support invariant and affine recurrences (1-2 operands) that are not unknown
681  if (const auto & chrec = ChrecMap_.at(&output);
682  chrec->NumOperands() > 2 || ScalarEvolution::IsUnknown(*chrec))
683  return false;
684 
685  return true;
686 }
687 
688 bool
690 {
691  if (const auto it = DependsOnIVMemo_.find(&output); it != DependsOnIVMemo_.end())
692  return it->second;
693 
694  // Check if the current output is an induction variable (loop variable with predictable
695  // evolution)
696  if (rvsdg::TryGetRegionParentNode<rvsdg::ThetaNode>(output))
697  {
698  const auto it = ChrecMap_.find(&output);
699  if (it == ChrecMap_.end())
700  return DependsOnIVMemo_[&output] = false;
701 
702  if (const auto & chrec = it->second; ScalarEvolution::IsUnknown(*chrec))
703  return DependsOnIVMemo_[&output] = false;
704 
705  return DependsOnIVMemo_[&output] = true;
706  }
707 
708  const auto & simpleNode = rvsdg::TryGetOwnerNode<rvsdg::SimpleNode>(output);
709 
710  if (!simpleNode)
711  return DependsOnIVMemo_[&output] = false;
712 
713  for (const auto & input : simpleNode->Inputs())
714  {
715  if (DependsOnInductionVariable(*input.origin()))
716  return DependsOnIVMemo_[&output] = true;
717  }
718 
719  return DependsOnIVMemo_[&output] = false;
720 }
721 
722 bool
724 {
725  if (const auto it = ContainsMulMemo_.find(&output); it != ContainsMulMemo_.end())
726  return it->second;
727 
728  const auto & simpleNode = rvsdg::TryGetOwnerNode<rvsdg::SimpleNode>(output);
729 
730  if (!simpleNode)
731  return ContainsMulMemo_[&output] = false;
732 
733  if (const auto & simpleOperation = simpleNode->GetOperation();
734  rvsdg::is<IntegerMulOperation>(simpleOperation)
735  || rvsdg::is<IntegerShlOperation>(simpleOperation))
736  return ContainsMulMemo_[&output] = true;
737 
738  for (const auto & input : simpleNode->Inputs())
739  {
740  if (ContainsMul(*input.origin()))
741  return ContainsMulMemo_[&output] = true;
742  }
743 
744  return ContainsMulMemo_[&output] = false;
745 }
746 }
static jlm::util::StatisticsCollector statisticsCollector
static rvsdg::Output * create(rvsdg::Output *baseAddress, const std::vector< rvsdg::Output * > &indices, std::shared_ptr< const rvsdg::Type > pointeeType)
static rvsdg::Node & Create(rvsdg::Region &region, IntegerValueRepresentation representation)
static std::unique_ptr< Context > Create()
std::unordered_map< const rvsdg::ThetaNode *, size_t > OperationsReducedMap_
const std::unordered_map< const rvsdg::ThetaNode *, size_t > & GetOperationsReducedMap() const
void IncrementOperationsReducedCount(const rvsdg::ThetaNode &theta)
~Statistics() noexcept override=default
static std::string GetStatisticsString(const std::unordered_map< const rvsdg::ThetaNode *, size_t > &operationsReducedMap)
static std::unique_ptr< Statistics > Create(const util::FilePath &sourceFile)
void Stop(const Context &context) noexcept
bool IsValidCandidateOperation(const rvsdg::Output &output, const rvsdg::SimpleOperation &operation)
~LoopStrengthReduction() noexcept override
std::optional< rvsdg::ThetaNode::LoopVar > CreateNewArithmeticInductionVariable(const SCEVChainRecurrence &chrec, rvsdg::ThetaNode &thetaNode, size_t numBits)
std::optional< rvsdg::Output * > HoistChrec(const SCEVChainRecurrence &chrec, const rvsdg::ThetaNode &thetaNode, size_t numBits)
void ReplaceCandidateOperation(rvsdg::Output &output, rvsdg::ThetaNode &thetaNode)
std::optional< rvsdg::Output * > HoistSCEVExpresssion(const SCEV &scev, rvsdg::ThetaNode &thetaNode, size_t numBits)
void ProcessOutput(rvsdg::Output &output, rvsdg::ThetaNode &thetaNode, util::HashSet< rvsdg::Output * > &candidateOperations, util::HashSet< rvsdg::Output * > &visited)
bool DependsOnInductionVariable(const rvsdg::Output &output)
std::unordered_map< const rvsdg::Output *, bool > ContainsMulMemo_
std::unordered_map< const rvsdg::Output *, bool > DependsOnIVMemo_
void ReduceStrength(rvsdg::ThetaNode &thetaNode)
std::unordered_map< const rvsdg::Output *, std::unique_ptr< SCEVChainRecurrence > > ChrecMap_
bool ContainsMul(const rvsdg::Output &output)
void ProcessRegion(rvsdg::Region &region)
std::unordered_map< const rvsdg::Output *, std::unique_ptr< SCEV > > SCEVMap_
std::optional< rvsdg::ThetaNode::LoopVar > CreateNewGEPInductionVariable(const SCEVChainRecurrence &chrec, rvsdg::ThetaNode &thetaNode)
rvsdg::ThetaNode & GetLoop() const noexcept
static bool IsConstant(const SCEVChainRecurrence &chrec)
rvsdg::Output & GetOutput() const noexcept
std::optional< std::unique_ptr< SCEV > > GetStep() const
static bool IsInvariantInLoop(const SCEVChainRecurrence &chrec, const rvsdg::ThetaNode &theta)
static bool IsAffine(const SCEVChainRecurrence &chrec)
static std::unique_ptr< llvm::ThreeAddressCode > create(const Variable *operand, const std::shared_ptr< const rvsdg::Type > &type)
Definition: sext.hpp:75
std::unordered_map< const rvsdg::Output *, std::unique_ptr< SCEVChainRecurrence > > GetChrecMap() const
void Run(rvsdg::RvsdgModule &rvsdgModule, util::StatisticsCollector &statisticsCollector) override
Perform RVSDG transformation.
static bool StructurallyEqual(const SCEV &a, const SCEV &b)
static bool IsUnknown(const SCEV &scev)
std::unordered_map< const rvsdg::Output *, std::unique_ptr< SCEV > > GetSCEVMap() const
static std::shared_ptr< const BitType > Create(std::size_t nbits)
Creates bit type of specified width.
Definition: type.cpp:45
void divert_to(Output *new_origin)
Definition: node.cpp:64
rvsdg::Region * region() const noexcept
Definition: node.hpp:761
const std::shared_ptr< const rvsdg::Type > & Type() const noexcept
Definition: node.hpp:366
void divert_users(jlm::rvsdg::Output *new_origin)
Definition: node.hpp:301
Represent acyclic RVSDG subgraphs.
Definition: region.hpp:213
static bool isAncestor(const rvsdg::Region &region, const rvsdg::Region &ancestor) noexcept
Definition: region.cpp:471
NodeRange Nodes() noexcept
Definition: region.hpp:328
std::vector< LoopVar > GetLoopVars() const
Returns all loop variables.
Definition: theta.cpp:176
rvsdg::Region * subregion() const noexcept
Definition: theta.hpp:79
LoopVar AddLoopVar(rvsdg::Output *origin)
Creates a new loop-carried variable.
Definition: theta.cpp:49
bool insert(ItemType item)
Definition: HashSet.hpp:210
IteratorRange< ItemConstIterator > Items() const noexcept
Definition: HashSet.hpp:223
void CollectDemandedStatistics(std::unique_ptr< Statistics > statistics)
Definition: Statistics.hpp:574
Statistics Interface.
Definition: Statistics.hpp:31
util::Timer & GetTimer(const std::string &name)
Definition: Statistics.cpp:137
util::Timer & AddTimer(std::string name)
Definition: Statistics.cpp:158
void AddMeasurement(std::string name, T value)
Definition: Statistics.hpp:177
void start() noexcept
Definition: time.hpp:54
void stop() noexcept
Definition: time.hpp:67
#define JLM_ASSERT(x)
Definition: common.hpp:16
Global memory state passed between functions.
rvsdg::Output & traceOutput(rvsdg::Output &output, const rvsdg::Region *withinRegion)
Definition: Trace.cpp:54
Output & RouteToRegion(Output &output, Region &region)
Definition: node.cpp:381
Description of a loop-carried variable.
Definition: theta.hpp:50
rvsdg::Output * pre
Variable before iteration (input argument to subregion).
Definition: theta.hpp:58
rvsdg::Input * post
Variable after iteration (output result from subregion).
Definition: theta.hpp:62
static const char * NumGepOperationsReduced
Definition: Statistics.hpp:249
static const char * NumGepLSRCandidates
Definition: Statistics.hpp:246
static const char * NumOperationsReduced
Definition: Statistics.hpp:247
static const char * Timer
Definition: Statistics.hpp:251
static const char * NumArithmeticOperationsReduced
Definition: Statistics.hpp:248
static const char * NumLSRCandidates
Definition: Statistics.hpp:244
static const char * NumArithmeticLSRCandidates
Definition: Statistics.hpp:245