[PATCH] D76132: [LoopUnrollAndJam] Changed safety checks to consider more than 2-levels loop nest.

Michael Kruse via Phabricator via llvm-commits llvm-commits at lists.llvm.org
Wed Mar 25 21:39:25 PDT 2020


Meinersbur added a comment.

Here is my suggested dependency checker:

  // Check whether it is semantically safe Src and Dst considering any potential
  // dependency between them.
  //
  // @param UnrollLevel The level of the loop being unrolled
  // @param JamLevel    The level of the loop being jammed; if Src and Dst are on
  // different levels, the outermost common loop counts as jammed level
  //
  // @return true if is safe and false if there is a dependency violation.
  static bool checkDependency(Instruction *Src, Instruction *Dst,
                              unsigned UnrollLevel, unsigned JamLevel,
                              bool Sequentialized, DependenceInfo &DI) {
    assert(UnrollLevel <= JamLevel);
  
    if (Src == Dst)
      return true;
    if (isa<LoadInst>(Src) && isa<LoadInst>(Dst))
      return true;
  
    // Check whether unroll-and-jam may violate a dependency.
    // By construction, every dependency will be lexicographically non-negative
    // (if it was, it would violate the current execution order), such as
    //   (0,0,>,*,*)
    // Unroll-and-jam changes the GT execution of two executions to the same
    // iteration of the chosen unroll level. That is, a GT dependence becomes a GE
    // dependence (or EQ, if we fully unrolled the loop) at the loop's position:
    //   (0,0,>=,*,*)
    // Now, the dependency is not necessarily non-negative anymore, i.e.
    // unroll-and-jam may violate correctness.
    std::unique_ptr<Dependence> D = DI.depends(Src, Dst, true);
    if (!D)
      return true;
    assert(D->isOrdered() && "Expected an output, flow or anti dep.");
  
    // Quick bail-out.
    if (D->isConfused())
      return false;
  
    for (unsigned d = 1; d < UnrollLevel; ++d) {
      // Check if dependence is carried by an outer loop.
      // That is, changing
      //   (0,>,>,*,*)
      // to
      //   (0,>,>=,*,*)
      // will still not violate the dependency.
      if (D->getDirection(d) == Dependence::DVEntry::GT)
        return true;
    }
  
    if (!(D->getDirection(UnrollLevel) & Dependence::DVEntry::GT)) {
      // If the unrolled loop did not carry the dependency in the first place
      // (i.e. being <, <= or =), it will also not need after unroll-and-jam.
      // Note: The standard case is the dependence vector
      //   (0,0,=,>,*)
      // where the unrolled level is already EQ. Changing LT and LE should also
      // not affect the semantics, since these didn't help to fulfill the
      // dependence in the first place.
      return true;
    }
  
    // Check if unrolled level becomes an EQ dependence at the unroll level,
    // whether one of the inner loops would carry the dependence.
    for (unsigned d = UnrollLevel + 1; d <= JamLevel; ++d) {
      unsigned Dir = D->getDirection(d);
  
      // Check whether the jammed level will carry the dependency.
      if (Dir == Dependence::DVEntry::GT)
        return true;
  
      // A possible backwards direction will violate the dependency
      if (Dir & Dependence::DVEntry::LT)
        return false;
    }
  
    // We previously already already checked whether the instructions are in the
    // same region. Reaching this means that they are not, hence we have a
    // dependency violation.
    return Sequentialized;
  }
  
  static bool
  checkDependencies(Loop &Root, const BasicBlockSet &SubLoopBlocks,
                    const DenseMap<Loop *, BasicBlockSet> &ForeBlocksMap,
                    const DenseMap<Loop *, BasicBlockSet> &AftBlocksMap,
                    DependenceInfo &DI, LoopInfo &LI) {
    SmallVector<BasicBlockSet, 8> AllBlocks;
    for (Loop *L : Root.getLoopsInPreorder())
      if (ForeBlocksMap.find(L) != ForeBlocksMap.end())
        AllBlocks.push_back(ForeBlocksMap.lookup(L));
    AllBlocks.push_back(SubLoopBlocks);
    for (Loop *L : Root.getLoopsInPreorder())
      if (AftBlocksMap.find(L) != AftBlocksMap.end())
        AllBlocks.push_back(AftBlocksMap.lookup(L));
  
    unsigned LoopDepth = Root.getLoopDepth();
    SmallVector<Instruction *, 4> EarlierLoadsAndStores;
    SmallVector<Instruction *, 4> CurrentLoadsAndStores;
    for (BasicBlockSet &Blocks : AllBlocks) {
      CurrentLoadsAndStores.clear();
      if (!getLoadsAndStores(Blocks, CurrentLoadsAndStores))
        return false;
  
      Loop *CurLoop = LI.getLoopFor((*Blocks.begin())->front().getParent());
      unsigned CurLoopDepth = CurLoop->getLoopDepth();
  
      for (auto Earlier : EarlierLoadsAndStores) {
        Loop *EarlierLoop = LI.getLoopFor(Earlier->getParent());
        unsigned EarlierDepth = EarlierLoop->getLoopDepth();
        unsigned CommonLoopDepth = std::min(EarlierDepth, CurLoopDepth);
        for (auto Later : CurrentLoadsAndStores) {
          if (!checkDependency(Earlier, Later, LoopDepth, CommonLoopDepth, false,
                               DI))
            return false;
        }
      }
  
      size_t NumInsts = CurrentLoadsAndStores.size();
      for (size_t i = 0; i < NumInsts; ++i) {
        for (size_t j = i + 1; j < NumInsts; ++j) {
          if (!checkDependency(CurrentLoadsAndStores[i], CurrentLoadsAndStores[j],
                               LoopDepth, CurLoopDepth, true, DI))
            return false;
        }
      }
  
      EarlierLoadsAndStores.append(CurrentLoadsAndStores.begin(),
                                   CurrentLoadsAndStores.end());
    }
    return true;
  }

I think I discovered a bug in the current dependency checker. LoopUnrollAndJam transforms `sub_sub_more` in the check `dependencies.ll` which I dint think it is allowed to do.



================
Comment at: llvm/test/Transforms/LoopUnrollAndJam/dependencies.ll:439
-; CHECK-NOT: %j.1 = phi
-define void @sub_sub_more(i32* noalias nocapture %A, i32 %N, i32* noalias nocapture readonly %B) {
 entry:
----------------
Whitney wrote:
> This test was orignally testing
> ```
> for i
>   for j
>     A[i]
>     A[i+1]
> ```
> which should be **safe** to unroll and jam. 
> 
> I think it actually want to test the code for sub with sub with the dependence distance of the inner loop is more.
> ```
> for i
>   for j
>     A[i][j]
>     A[i+1][j+1]
> ```
I think this is NOT safe to unroll-and jam. The unrolled equivalent is:
```
for i += 2
  for j
    S1:  A[i]
    S2:  A[i+1]
    S1': A[i+1]
    S2': A[i+2]
```

At S1', the lasst access of A[i+1] is S2 from the same itertation. instead if S1 from the previous j-iteration is in the original loop.


Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D76132/new/

https://reviews.llvm.org/D76132





More information about the llvm-commits mailing list