//===------ AccessEnforcementDom.cpp - dominated access removal opt -------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
///
/// This function pass removes dominated accesses in two ways:
///
/// 1) Remove accesses dominated by an existing access
///
/// General case:
/// begin_access A (may or may not have no_nested_conflict)
/// load/store
/// end_access
/// ...
/// begin_access A [no_nested_conflict] // dominated by the first access
/// load/store
/// end_access A
///
/// The second access scope does not need to be emitted.
///
/// 2) Add a new dominating accesses to loop's preheader
///
/// General case:
/// <loop preheader>
/// A = ref_element_addr
/// <loop>
/// begin_access A [dynamic] [no_nested_conflict]
///
/// Adding an empty begin_access A in the preheader would allow us to
/// turn the loop's access to [static]
///
/// Warning: This optimization requires that all points within this function
/// that begin an access can be identified. Failure to recognize the beginning
/// of an access scope could weaken dynamic enforcement.
///
/// FIXME: This pass currently only runs in the last-chance pipeline, with a
/// guarantee that no access marker removal is done after it. This happens to
/// work but is dangerous and violates SIL semantics. We should instead add a
/// flag for accesses to give them the semantics that they may guard memory
/// operations other than those enclosed by the access scope.
//===----------------------------------------------------------------------===//

#define DEBUG_TYPE "access-enforcement-dom"

#include "swift/Basic/Assertions.h"
#include "swift/SIL/DebugUtils.h"
#include "swift/SIL/MemAccessUtils.h"
#include "swift/SIL/SILBuilder.h"
#include "swift/SIL/SILFunction.h"
#include "swift/SILOptimizer/Analysis/DominanceAnalysis.h"
#include "swift/SILOptimizer/Analysis/LoopAnalysis.h"
#include "swift/SILOptimizer/PassManager/Transforms.h"
#include "swift/SILOptimizer/Utils/InstOptUtils.h"
#include "llvm/ADT/DepthFirstIterator.h"

using namespace swift;

// =============================================================================
// DominatedAccessAnalysis

namespace swift {
/// Information about each dynamic access with valid storage.
///
/// This is a pass-specific subclass of AccessStorage with identical layout.
/// An instance is created for each BeginAccess in the current function. In
/// additional to identifying the access' storage location, it associates that
/// access with pass-specific data in reserved bits. The reserved bits do not
/// participate in equality or hash lookup.
class DomAccessStorage : public AccessStorage {
public:
  DomAccessStorage() {}

  explicit DomAccessStorage(const AccessStorage &storage)
      : AccessStorage(storage) {
    Bits.DomAccessStorage.isInner = false;
    Bits.DomAccessStorage.containsRead = false;
  }

  // Is this dynamic, identifiable access scope potentially contained within any
  // kind of outer scope. The outer scope may be static and/or have unidentified
  // storage. For example:
  //
  // A1: [static] [read]
  // A2: [dynamic] [read]
  // A3: [dynamic] [modify]
  //
  // A2 cannot be promoted to modify if its scope overlaps with A1.
  bool isInner() const { return Bits.DomAccessStorage.isInner; }

  void setIsInner() { Bits.DomAccessStorage.isInner = true; }

  /// Is this dynamic, identifiable access scope a [read], and does it
  /// potentially contain another non-distinct [read] access of any kind?
  bool containsRead() const { return Bits.DomAccessStorage.containsRead; }

  void setContainsRead() { Bits.DomAccessStorage.containsRead = true; }

  void dump() const {
    AccessStorage::dump();
    llvm::dbgs() << "<" << (isInner() ? "" : "inner")
                 << (containsRead() ? "" : "containsRead") << ">\n";
  }
};
} // namespace swift

namespace {
// An analysis that maps each valid dynamic BeginAccess to
// DomAccessStorage. Performs a trivial data flow analysis to populate the map
// with information about nested accesses. Data flow is needed to track open
// access scopes, but only flow-insensitive information is recorded in the
// result.
//
// Note that all access scopes are tracked during data flow, but only valid
// dynamic are mapped to results.
//
// TODO: Separate this into a shared analysis and factor it with
// AccessEnforcementOpts. The optimization that merges accesses would also
// benefit.
//
// TODO: This could be made more precise by querying AccessStorageAnalysis.
class DominatedAccessAnalysis {
public:
  // The result records information for all dynamic accesses in this
  // function. If an UnpairedAccess exists, then the result will be
  // conservatively empty.
  struct Result {
    llvm::SmallDenseMap<BeginAccessInst *, DomAccessStorage, 32> accessMap;
  };

private:
  // The data flow state for each block. It is only valid between the time at
  // least one predecesor block has been finished and before its own block
  // has been finished.
  //
  // The isBottom flag allows the analysis to avoid quadratic behavior where
  // each open access must be updated at each conservatively handled
  // instruction. Instead, the accesses are only conservatively updated once
  // until the next scope is entered. So the complexity is (#OpenScopes)^2
  // instead of (#OpenScopes)*(#Applies).
  struct BBState {
    using DenseAccessSet = llvm::SmallDenseSet<BeginAccessInst *, 4>;
    using DenseCoroutineSet = llvm::SmallDenseSet<BeginApplyInst *, 4>;

    DenseAccessSet inScopeAccesses;
    DenseCoroutineSet inScopeCoroutines;
    bool isBottom = false;
  };
  using BlockStateMap = llvm::DenseMap<SILBasicBlock *, BBState>;

  PostOrderFunctionInfo *PO;

  Result result; // Flow-insensitive analysis result.

  BlockStateMap blockStateMap; // Data flow state.

public:
  DominatedAccessAnalysis(PostOrderFunctionInfo *PO) : PO(PO) {}

  Result analyze() &&;

protected:
  void setBottom(BBState &state);
  void analyzeAccess(BeginAccessInst *BAI, BBState &state);
};
} // namespace

// Set information for all in-scope accesses to the worst case "bottom".
// The isInner flag is not affected because it only applies to scopes inside the
// currently open scopes, not the currently open scopes themselves.
void DominatedAccessAnalysis::setBottom(BBState &state) {
  if (state.isBottom)
    return;

  // Unordered iteration over the in-access scopes.
  llvm::for_each(state.inScopeAccesses, [this](BeginAccessInst *BAI) {
    if (auto &domStorage = result.accessMap[BAI])
      domStorage.setContainsRead();
  });
}

// Perform the analysis and return the Result, relinquishing internal state.
DominatedAccessAnalysis::Result DominatedAccessAnalysis::analyze() && {
  // A single RPO traversal is sufficient to visit access scopes in order. The
  // set of open accesses entering a block cannot grow as a result of loops, and
  // within an open scope the results are flow-insensitive.
  for (auto *BB : PO->getReversePostOrder()) {
    BBState state = blockStateMap[BB];
    for (auto &I : *BB) {
      if (auto *BAI = dyn_cast<BeginAccessInst>(&I)) {
        analyzeAccess(BAI, state);
        continue;
      }
      if (auto *EAI = dyn_cast<EndAccessInst>(&I)) {
        bool erased = state.inScopeAccesses.erase(EAI->getBeginAccess());
        (void)erased;
        assert(erased);
        continue;
      }
      // Check for BeginApply before checking FullApplySite below.
      if (auto *beginApply = dyn_cast<BeginApplyInst>(&I)) {
        auto iterAndInserted = state.inScopeCoroutines.insert(beginApply);
        (void)iterAndInserted;
        assert(iterAndInserted.second);
        continue;
      }
      if (auto *endApply = dyn_cast<EndApplyInst>(&I)) {
        bool erased = state.inScopeCoroutines.erase(endApply->getBeginApply());
        (void)erased;
        assert(erased);
        continue;
      }
      if (FullApplySite::isa(&I)) {
        setBottom(state);
        continue;
      }
      if (isa<BeginUnpairedAccessInst>(&I)) {
        // Unpaired accesses could be tracked, but are ignored because they are
        // mostly irrelevant and hard to test. Completely bail on this function.
        result.accessMap.clear();
        return std::move(result);
      }
    }
    auto successors = BB->getTerminator()->getSuccessors();
    unsigned numSucc = successors.size();
    for (unsigned succIdx : indices(successors)) {
      SILBasicBlock *succBB = successors[succIdx].getBB();
      if (succBB == BB)
        continue;

      if (succIdx != numSucc - 1)
        blockStateMap.try_emplace(succBB, state);
      else
        // Move the state into the last successor to avoid copying sets.
        blockStateMap.try_emplace(succBB, std::move(state));
    }
  }
  return std::move(result);
}

// The data flow transfer function for BeginAccess. Creates the
// DomAccessStorage for this access and inserts it in the result.
void DominatedAccessAnalysis::analyzeAccess(BeginAccessInst *BAI,
                                            BBState &state) {
  DomAccessStorage domStorage;
  // Only track dynamic access in the result. Static accesses still need to be
  // tracked by data flow, but they can't be optimized as "dominating".
  if (BAI->getEnforcement() == SILAccessEnforcement::Dynamic) {
    auto storage = AccessStorage::compute(BAI->getSource());
    // Copy the AccessStorage into DomAccessStorage. All pass-specific bits
    // are initialized to zero.
    domStorage = DomAccessStorage(storage);
  }
  // Continue to handle both untracked access and invalid domStorage
  // conservatively below...

  // unordered set iteration...
  llvm::for_each(state.inScopeAccesses, [&](BeginAccessInst *outerBegin) {
    auto &outerInfo = result.accessMap[outerBegin];
    // If the current access is mapped, set its isInner flag.
    if (domStorage && !domStorage.isInner()) {
      if (domStorage.isDistinctFrom(outerInfo))
        return;

      domStorage.setIsInner();
    }
    // The results for tracked in-scope accesses still need to be
    // updated even if the current access is not be tracked.
    if (outerInfo && BAI->getAccessKind() == SILAccessKind::Read
        && outerBegin->getAccessKind() == SILAccessKind::Read) {
      outerInfo.setContainsRead();
    }
  });
  // Track this access even if it is invalid or unmapped.
  {
    auto iterAndInserted = state.inScopeAccesses.insert(BAI);
    (void)iterAndInserted;
    assert(iterAndInserted.second);
  }
  // Update the results if this access will be mapped.
  if (!domStorage)
    return;

  // Set the current access isInner flag if it's inside a coroutine scope.
  if (!state.inScopeCoroutines.empty())
    domStorage.setIsInner();

  // Map the current access.
  {
    auto iterAndInserted = result.accessMap.try_emplace(BAI, domStorage);
    (void)iterAndInserted;
    assert(iterAndInserted.second);
  }
  state.isBottom = false;
}

// =============================================================================
// DominatedAccessRemoval optimization.

namespace {
using DomTreeNode = llvm::DomTreeNodeBase<SILBasicBlock>;

// Visit the dominator tree top down, tracking the current set of dominating
// dynamic accesses. Dominated dynamic accesses with identical storage are
// marked static during traversal. If a dynamic access inside a loop has no
// dominating access, insert a new access in the preheader.
class DominatedAccessRemoval {
  // Record the first access of a given storage location and the dominator node
  // in which the access occurred.
  struct DominatingAccess {
    BeginAccessInst *beginAccess;
    DomTreeNode *domNode;

    DominatingAccess(BeginAccessInst *beginAccess, DomTreeNode *domNode)
        : beginAccess(beginAccess), domNode(domNode) {}
  };
  using StorageToDomMap = llvm::DenseMap<AccessStorage, DominatingAccess>;

  SILFunction &func;
  DominanceInfo *domInfo;
  SILLoopInfo *loopInfo;
  DominatedAccessAnalysis::Result &DAA;

  // Hash map from each storage location to the dominating access.
  StorageToDomMap storageToDomMap;

  DomTreeNode *currDomNode = nullptr;

  bool hasChanged = false;

public:
  DominatedAccessRemoval(SILFunction &func, DominanceInfo *domInfo,
                         SILLoopInfo *loopInfo,
                         DominatedAccessAnalysis::Result &DAA)
      : func(func), domInfo(domInfo), loopInfo(loopInfo), DAA(DAA) {}

  bool optimize();

protected:
  void visitBeginAccess(BeginAccessInst *BAI);
  bool checkDominatedAccess(BeginAccessInst *BAI,
                            DomAccessStorage currDomStorage);
  bool optimizeDominatedAccess(BeginAccessInst *currBegin,
                               DomAccessStorage currDomStorage,
                               const DominatingAccess &domAccess);
  void tryInsertLoopPreheaderAccess(BeginAccessInst *BAI,
                                    DomAccessStorage currDomStorage);
};
} // namespace

// Optimize the current function, and return true if any optimization was
// performed.
bool DominatedAccessRemoval::optimize() {
  DomTreeNode *entryNode = domInfo->getNode(func.getEntryBlock());
  for (DomTreeNode *domNode : llvm::depth_first(entryNode)) {
    currDomNode = domNode;

    // Optimize dominated accesses in this block.
    for (auto &instr : *domNode->getBlock()) {
      if (auto *BAI = dyn_cast<BeginAccessInst>(&instr))
        visitBeginAccess(BAI);
    }
  }
  return hasChanged;
}

// Visit a BeginAccessInst once-and-only-once in domtree order.
// Attempt to find a dominating access with identical storage.
// If that fails, attempt to insert a new dominating access in the preheader.
void DominatedAccessRemoval::visitBeginAccess(BeginAccessInst *BAI) {
  if (BAI->getEnforcement() != SILAccessEnforcement::Dynamic)
    return;

  DomAccessStorage currDomStorage = DAA.accessMap.lookup(BAI);
  if (!currDomStorage)
    return;

  // Only track "identifiable" storage.
  if (currDomStorage.isFormalAccessBase()) {
    if (checkDominatedAccess(BAI, currDomStorage))
      return;
  }
  tryInsertLoopPreheaderAccess(BAI, currDomStorage);
}

// Track this identifiable dynamic access in storageToDomMap, and optimize it if
// possible. Return true if the optimization succeeds.
bool DominatedAccessRemoval::checkDominatedAccess(
    BeginAccessInst *BAI, DomAccessStorage currDomStorage) {
  // Attempt to add this access to storageToDomMap using its base storage
  // location as the key.
  //
  // Cast this DomAccessStorage back to a plain storage location. The
  // pass-specific bits will be ignored, but reset them anyway for soundness.
  AccessStorage storage = static_cast<AccessStorage>(currDomStorage);
  storage.resetSubclassData();
  auto iterAndInserted =
      storageToDomMap.try_emplace(storage, DominatingAccess(BAI, currDomNode));
  if (iterAndInserted.second)
    return false;

  // An access has already been recorded for this storage.
  // If the previous domNode does not dominate currDomNode, then replace it.
  DominatingAccess &domAccess = iterAndInserted.first->second;
  if (!domInfo->dominates(domAccess.domNode, currDomNode)) {
    domAccess = DominatingAccess(BAI, currDomNode);
    return false;
  }
  // The previously mapped access still dominates this block, so the current
  // access can potentially be optimized.
  return optimizeDominatedAccess(BAI, currDomStorage, domAccess);
}

// If possible, optimize the current access by converting it to [static]. Return
// true if the optimization succeeds.
//
// This function is not allowed to add or erase instructions, only change
// instruction flags.
//
// The four required conditions for converting this access to static are:
//
// 1. A closed dominating access has identical storage.
//
// The caller looked up this access' storage in storageToDomMap and checked
// that the previously seen access dominates this block. As long as this access'
// isInner flag from DominatedAccessAnalysis is not set, the dominating access
// must be closed.
//
// 2. There is no open (overlapping) access with nondistinct storage (that isn't
// also open at the dominating access).
//
// The isInner flag from DominatedAccessAnalysis indicates no enclosing
// nondistinct scopes within this function. Any outer scope would enclose the
// whole function, thereby also enclosing the dominating scope.
//
// 3. This access has no_nested_conflict.
//
// This is a direct check on this BeginAccessInst flag.
//
// 4. The current and dominating access kinds are compatible:
//
// read -> read: OK
//
// modify -> read: OK
//
// modify -> modify: OK
//
// read -> modify: Requires promoting the dominating access to a modify. This
// can be done as long as the dominating access does not contain another
// non-distinct read and isn't contained by another non-distinct read. The
// containsRead and isInner flags from in DominatedAccessAnalysis answer this
// conservatively.
//
// Note: Promoting an earlier access to a modify could cause a program to
// trap when optimized even if the unoptimized program does not trap; the
// original modify access may be on an unreachable code path. This is acceptable
// because:
//
// (a) in theory, exclusivity violations do not need to be executed to be
// considered program violations. Promoting the access does not introduce any
// new conflict where one didn't already exist statically. Catching these
// violations at runtime is only an implementation compromise, and the more true
// violations are caught, the better.
//
// (b) in practice, this situation is so exceedingly unlikely that it won't
// cause any pervasive usability problem where programs have stronger
// enforcement only when optimized.
bool DominatedAccessRemoval::optimizeDominatedAccess(
    BeginAccessInst *BAI, DomAccessStorage currAccessInfo,
    const DominatingAccess &domAccess) {
  // 1. and 2. If any nondistinct scopes are open, it must remain dynamic.
  if (currAccessInfo.isInner())
    return false;

  // 3. If BAI may have a nested conflict, it must remain dynamic.
  if (!BAI->hasNoNestedConflict())
    return false;

  // 4. Promoting a read to a modify is only safe with no nested reads.
  if (domAccess.beginAccess->getAccessKind() == SILAccessKind::Read
      && BAI->getAccessKind() == SILAccessKind::Modify) {
    DomAccessStorage domStorage = DAA.accessMap[domAccess.beginAccess];
    if (domStorage.containsRead() || domStorage.isInner())
      return false;

    LLVM_DEBUG(llvm::dbgs()
               << "Promoting to modify: " << *domAccess.beginAccess << "\n");
    domAccess.beginAccess->setAccessKind(SILAccessKind::Modify);
  }
  LLVM_DEBUG(llvm::dbgs() << "Setting static enforcement: " << *BAI << "\n");
  LLVM_DEBUG(llvm::dbgs() << "Dominated by: " << *domAccess.beginAccess
                          << "\n");
  BAI->setEnforcement(SILAccessEnforcement::Static);

  hasChanged = true;
  return true;
}

// Attempt to insert a new access in the loop preheader. If successful, insert
// the new access in DominatedAccessAnalysis so it can be used to dominate other
// accesses. Also convert the current access to static and update the current
// storageToDomMap since the access may already have been recorded (when it was
// still dynamic).
//
// This function cannot add or remove instructions in the current block, but
// may add instructions to the current loop's preheader.
//
// The required conditions for inserting a new dominating access are:
//
// 1. The new preheader access is not enclosed in another scope that doesn't
// also enclose the current scope.
//
// This is inferred from the loop structure; any scope that encloses the
// preheader must also enclose the entire loop.
//
// 2. The current access is not enclosed in another scope that doesn't also
// enclose the preheader.
//
// As before, it is sufficient to check this access' isInner flags in
// DominatedAccessAnalysis; if this access isn't enclosed by any scope within
// the function, then it can't be enclosed within a scope inside the loop.
//
// 3. The current header has no nested conflict within its scope.
//
// 4. The access' source operand is available in the loop preheader.
void DominatedAccessRemoval::tryInsertLoopPreheaderAccess(
    BeginAccessInst *BAI, DomAccessStorage currAccessInfo) {
  // 2. the current access may be enclosed.
  if (currAccessInfo.isInner())
    return;

  // 3. the current access must be instantaneous.
  if (!BAI->hasNoNestedConflict())
    return;

  SILLoop *currLoop = loopInfo->getLoopFor(BAI->getParent());
  if (!currLoop)
    return;
  SILBasicBlock *preheader = currLoop->getLoopPreheader();
  if (!preheader)
    return;

  // 4. The source operand must be available in the preheader.
  auto sourceOperand = BAI->getOperand();
  auto *sourceBB = sourceOperand->getParentBlock();
  if (!domInfo->dominates(sourceBB, preheader))
    return;

  // Insert a new access scope immediately before the
  // preheader's terminator.
  TermInst *preheaderTerm = preheader->getTerminator();
  SILBuilderWithScope scopeBuilder(preheaderTerm);
  BeginAccessInst *newBegin = scopeBuilder.createBeginAccess(
      preheaderTerm->getLoc(), sourceOperand, BAI->getAccessKind(),
      SILAccessEnforcement::Dynamic, true /*no nested conflict*/,
      BAI->isFromBuiltin());
  scopeBuilder.createEndAccess(preheaderTerm->getLoc(), newBegin, false);
  LLVM_DEBUG(llvm::dbgs() << "Created loop preheader access: " << *newBegin
                          << "\n"
                          << "dominating: " << *BAI << "\n");
  BAI->setEnforcement(SILAccessEnforcement::Static);

  hasChanged = true;

  // Insert the new dominating instruction in both DominatedAccessAnalysis and
  // storageToDomMap if it has uniquely identifiable storage.
  if (!currAccessInfo.isFormalAccessBase())
    return;

  AccessStorage storage = static_cast<AccessStorage>(currAccessInfo);
  storage.resetSubclassData();

  // Create a DomAccessStorage for the new access with no flags set.
  DAA.accessMap.try_emplace(newBegin, DomAccessStorage(storage));

  // Track the new access as long as no other accesses from the same storage are
  // already tracked. This also necessarily replaces the current access, which
  // was just made static.
  DominatingAccess newDomAccess(newBegin, domInfo->getNode(preheader));
  auto iterAndInserted = storageToDomMap.try_emplace(storage, newDomAccess);
  if (!iterAndInserted.second) {
    DominatingAccess &curDomAccess = iterAndInserted.first->second;
    if (curDomAccess.beginAccess == BAI)
      curDomAccess = newDomAccess;
  }
}

namespace {
struct AccessEnforcementDom : public SILFunctionTransform {
  void run() override;
};
} // namespace

void AccessEnforcementDom::run() {
  SILFunction *func = getFunction();
  if (func->empty())
    return;

  PostOrderFunctionInfo *PO = getAnalysis<PostOrderAnalysis>()->get(func);
  auto DAA = DominatedAccessAnalysis(PO).analyze();

  DominanceAnalysis *domAnalysis = getAnalysis<DominanceAnalysis>();
  DominanceInfo *domInfo = domAnalysis->get(func);
  SILLoopAnalysis *loopAnalysis = PM->getAnalysis<SILLoopAnalysis>();
  SILLoopInfo *loopInfo = loopAnalysis->get(func);

  DominatedAccessRemoval eliminationPass(*func, domInfo, loopInfo, DAA);
  if (eliminationPass.optimize())
    invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions);
}

SILTransform *swift::createAccessEnforcementDom() {
  return new AccessEnforcementDom();
}