blob: 91a9f0bff86fed983cb498d7919f06e6735dab6b [file] [log] [blame]
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// A utility function to fix convergence control tokens in the presence of
// irreducible control flow.
//
// When generating convergence control tokens for iteration statements, CodeGen
// ignores the presence of any jumps that may cause the resulting CFG to be
// irreducible. CodeGen optimistically emits a `loop` intrinsic in the header of
// every iteration statement, which is invalid if control can jump into that
// statement. We fix this in the LLVM IR after the whole function is fully
// generated. For each irreducible cycle discovered in the CFG, we replace the
// `loop` intrinsic in the header with suitable invocations of `anchor` instead.
//
// Separately, a "backwards goto" can create a cycle that encapsulates an
// iteration statement. As a result, the `loop` intrinsic in that iteration
// statement no longer uses a token from its immediate parent cycle in the CFG.
// This is also fixed by replacing the `loop` intrinsic with an `anchor`
// intrinsic.
//
// The overall result is to produce in implementation-defined convergence as a
// result of unstructured jumps as defined in the Clang spec for convergence.
//
// ===----------------------------------------------------------------------===//
#include "llvm/Transforms/Utils/FixConvergenceControl.h"
#include "llvm/IR/CycleInfo.h"
#include "llvm/IR/InstIterator.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/IntrinsicInst.h"
#include <map>
#define DEBUG_TYPE "convergence-fixup"
using namespace llvm;
static void
initializeTokenSources(SmallVectorImpl<ConvergenceControlInst *> &Worklist,
Function *F) {
for (Instruction &II : instructions(F)) {
if (auto *CB = dyn_cast<ConvergenceControlInst>(&II)) {
if (CB->isEntry() || CB->isAnchor())
Worklist.push_back(CB);
}
}
}
namespace llvm {
void fixConvergenceControl(Function *F) {
CycleInfo CI;
CI.compute(*F);
// F->dump();
// CI.dump();
enum DecisionTy { Delete, Replace };
// Can't use a DenseMap because we later insert while iterating.
std::map<CallBase *, DecisionTy> Decision;
SmallPtrSet<CallBase *, 4> NonIntrinsicUsers;
SmallVector<ConvergenceControlInst *> Worklist;
initializeTokenSources(Worklist, F);
while (!Worklist.empty()) {
ConvergenceControlInst *CB = Worklist.pop_back_val();
LLVM_DEBUG(llvm::dbgs() << "Visiting: " << *CB << "\n");
Cycle *CurrentCycle = CI.getCycle(CB->getParent());
for (Use &U : CB->uses()) {
auto *UserCB = cast<CallBase>(U.getUser());
if (auto *C = dyn_cast<ConvergenceControlInst>(UserCB)) {
Worklist.push_back(C);
continue;
}
Cycle *UserCycle = CI.getCycle(UserCB->getParent());
// A non-intrinsic user cannot use a token defined outside its own cycle.
if (UserCycle && !UserCycle->contains(CurrentCycle))
NonIntrinsicUsers.insert(UserCB);
}
if (!CurrentCycle)
continue;
// A loop intrinsic is no longer useful in two cases:
// 1. Its cycle became irreducible, or,
// 2. The cycle appears `rotated` in the CFG and the call is not in the
// header. This happens when a loop statement is unreachable via
// sequential control flow, but is jumped into by a goto or switch.
//
// We will be visiting its users later.
if (!CurrentCycle->isReducible() ||
CurrentCycle->getHeader() != CB->getParent()) {
LLVM_DEBUG(llvm::dbgs() << " Delete.\n");
Decision[CB] = Delete;
continue;
}
// A token use is valid only if the def is with the immediate parent. It's
// okay if the def is with a sibling, as long as the common parent is the
// immediate parent.
//
// The def can end up outside the parent when a goto forms a reducible cycle
// around a loop statement. Such a new reducible cycle does not itself have
// a heart.
ConvergenceControlInst *TokenUsed = CB->getConvergenceControlToken();
Cycle *DefCycle = CI.getCycle(TokenUsed->getParent());
assert(CurrentCycle == DefCycle || !CurrentCycle->contains(DefCycle));
Cycle *Parent = CurrentCycle->getParentCycle();
if (DefCycle != Parent && Parent && !Parent->contains(DefCycle)) {
// Don't overwrite if previous decision was to delete.
Decision.try_emplace(CB, Replace);
}
}
SmallVector<CallBase *> ToDelete;
// For deletion candidates, decide how to process each of the uses.
for (auto [CB, D] : Decision) {
if (D != Delete)
continue;
ToDelete.push_back(CB);
for (Use &U : CB->uses()) {
auto *ConvOp = cast<CallBase>(U.getUser());
// Users that are calls to the loop intrinsic can no longer use this as
// the parent token, so replace them with anchors.
if (auto *Child = dyn_cast<ConvergenceControlInst>(ConvOp)) {
// Don't overwrite if previous decision was to delete. Note that we are
// inserting while iterating over the std::map. It is possible that the
// newly inserted node is not visited, which is okay because we are only
// iterating over candidates mapped to ``Delete``.
Decision.try_emplace(Child, Replace);
continue;
}
// Other convergent users should be made non-converent.
NonIntrinsicUsers.insert(ConvOp);
}
}
for (auto [CB, D] : Decision) {
if (D != Replace)
continue;
Cycle *CurrentCycle = CI.getCycle(CB->getParent());
assert(CurrentCycle && CurrentCycle->isReducible());
LLVM_DEBUG(llvm::dbgs() << " Replace with anchor: " << *CB << "\n");
auto *Anchor = ConvergenceControlInst::CreateAnchor(*CB->getParent());
CB->replaceAllUsesWith(Anchor);
CB->eraseFromParent();
}
// Make all non-intrinsic users non-convergent. It would have been convenient
// to just strip the token and the ``convergent`` attribute, but attributes
// get checked on the callee too if they don't exist on the call. We could
// have set the ``noconvergent`` attribute if it existed. For now,
// equivalently, we replace the token with an anchor.
for (CallBase *CB : NonIntrinsicUsers) {
auto *Token = ConvergenceControlInst::CreateAnchor(*CB->getParent());
CB = setConvergenceControlToken(CB, Token);
LLVM_DEBUG(llvm::dbgs() << " Make non-convergent: " << *CB << "\n");
}
bool Changed = true;
while (Changed) {
Changed = false;
for (unsigned I = 0, E = ToDelete.size(); I != E; ++I) {
CallBase *CB = ToDelete[I];
if (CB) {
LLVM_DEBUG(llvm::dbgs() << "Try delete:\n" << *CB << "\n");
if (!CB->use_empty()) {
LLVM_DEBUG(llvm::dbgs() << " ... has pending use.\n");
continue;
}
LLVM_DEBUG(llvm::dbgs() << " ... deleted.\n");
CB->eraseFromParent();
ToDelete[I] = nullptr;
Changed = true;
}
}
}
LLVM_DEBUG(for (CallBase *CB : ToDelete) assert(!CB););
// F->dump();
}
} // end namespace llvm